integral 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (761) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +105 -0
  4. data/Rakefile +22 -0
  5. data/app/assets/images/integral/backend/data-unavailable.png +0 -0
  6. data/app/assets/images/integral/backend/logo.png +0 -0
  7. data/app/assets/images/integral/defaults/no_image_available.jpg +0 -0
  8. data/app/assets/images/integral/defaults/user_avatar.jpg +0 -0
  9. data/app/assets/images/integral/image-not-set.png +0 -0
  10. data/app/assets/images/integral/posts-hero-banner.jpg +0 -0
  11. data/app/assets/javascripts/ckeditor/loader.js.erb +6 -0
  12. data/app/assets/javascripts/ckeditor/my_config.js.erb +154 -0
  13. data/app/assets/javascripts/ckeditor/my_contents.sass +22 -0
  14. data/app/assets/javascripts/ckeditor/my_javascript.js +12 -0
  15. data/app/assets/javascripts/ckeditor/my_styles.js +5 -0
  16. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/az.js +8 -0
  17. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/bg.js +8 -0
  18. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ca.js +8 -0
  19. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/cs.js +8 -0
  20. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/da.js +8 -0
  21. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/de-ch.js +8 -0
  22. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/de.js +8 -0
  23. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/el.js +8 -0
  24. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/en-au.js +8 -0
  25. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/en.js +8 -0
  26. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/eo.js +8 -0
  27. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/es-mx.js +8 -0
  28. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/es.js +8 -0
  29. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/eu.js +8 -0
  30. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/fr.js +8 -0
  31. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/gl.js +8 -0
  32. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/hr.js +8 -0
  33. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/hu.js +8 -0
  34. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/it.js +8 -0
  35. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ja.js +8 -0
  36. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/km.js +8 -0
  37. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ko.js +8 -0
  38. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ku.js +8 -0
  39. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/mk.js +8 -0
  40. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/nb.js +8 -0
  41. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/nl.js +8 -0
  42. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/oc.js +8 -0
  43. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/pl.js +8 -0
  44. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/pt-br.js +8 -0
  45. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/pt.js +8 -0
  46. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ro.js +8 -0
  47. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ru.js +8 -0
  48. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/sk.js +8 -0
  49. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/sq.js +8 -0
  50. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/sv.js +8 -0
  51. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/tr.js +8 -0
  52. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/ug.js +8 -0
  53. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/uk.js +8 -0
  54. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/vi.js +8 -0
  55. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/zh-cn.js +8 -0
  56. data/app/assets/javascripts/ckeditor/plugins/autoembed/lang/zh.js +8 -0
  57. data/app/assets/javascripts/ckeditor/plugins/autoembed/plugin.js +218 -0
  58. data/app/assets/javascripts/ckeditor/plugins/autogrow/plugin.js +232 -0
  59. data/app/assets/javascripts/ckeditor/plugins/autogrow/samples/autogrow.html +102 -0
  60. data/app/assets/javascripts/ckeditor/plugins/autolink/plugin.js +69 -0
  61. data/app/assets/javascripts/ckeditor/plugins/codemirror/css/codemirror.min.css +1 -0
  62. data/app/assets/javascripts/ckeditor/plugins/codemirror/icons/autocomplete.png +0 -0
  63. data/app/assets/javascripts/ckeditor/plugins/codemirror/icons/autoformat.png +0 -0
  64. data/app/assets/javascripts/ckeditor/plugins/codemirror/icons/commentselectedrange.png +0 -0
  65. data/app/assets/javascripts/ckeditor/plugins/codemirror/icons/searchcode.png +0 -0
  66. data/app/assets/javascripts/ckeditor/plugins/codemirror/icons/uncommentselectedrange.png +0 -0
  67. data/app/assets/javascripts/ckeditor/plugins/codemirror/js/beautify.min.js +2 -0
  68. data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.addons.min.js +2 -0
  69. data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.addons.search.min.js +1 -0
  70. data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.min.js +6 -0
  71. data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.mode.bbcode.min.js +1 -0
  72. data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.mode.bbcodemixed.min.js +1 -0
  73. data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.mode.htmlmixed.min.js +2 -0
  74. data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.mode.javascript.min.js +1 -0
  75. data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.mode.php.min.js +3 -0
  76. data/app/assets/javascripts/ckeditor/plugins/codemirror/js/codemirror.mode.twig.min.js +1 -0
  77. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/af.js +12 -0
  78. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ar.js +12 -0
  79. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/bg.js +12 -0
  80. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/bn.js +12 -0
  81. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/bs.js +12 -0
  82. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ca.js +12 -0
  83. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/cs.js +12 -0
  84. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/cy.js +12 -0
  85. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/da.js +12 -0
  86. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/de.js +12 -0
  87. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/el.js +12 -0
  88. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/en-au.js +12 -0
  89. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/en-ca.js +12 -0
  90. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/en-gb.js +12 -0
  91. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/en.js +12 -0
  92. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/eo.js +12 -0
  93. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/es.js +12 -0
  94. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/et.js +12 -0
  95. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/eu.js +12 -0
  96. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/fa.js +12 -0
  97. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/fi.js +12 -0
  98. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/fo.js +12 -0
  99. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/fr-ca.js +12 -0
  100. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/fr.js +12 -0
  101. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/gl.js +12 -0
  102. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/gu.js +12 -0
  103. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/he.js +12 -0
  104. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/hi.js +12 -0
  105. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/hr.js +12 -0
  106. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/hu.js +12 -0
  107. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/is.js +12 -0
  108. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/it.js +12 -0
  109. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ja.js +12 -0
  110. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ka.js +12 -0
  111. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/km.js +12 -0
  112. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ko.js +12 -0
  113. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ku.js +12 -0
  114. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/lt.js +12 -0
  115. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/lv.js +12 -0
  116. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/mk.js +12 -0
  117. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/mn.js +12 -0
  118. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ms.js +12 -0
  119. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/nb.js +12 -0
  120. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/nl.js +12 -0
  121. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/no.js +12 -0
  122. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/pl.js +12 -0
  123. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/pt-br.js +12 -0
  124. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/pt.js +12 -0
  125. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ro.js +12 -0
  126. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ru.js +12 -0
  127. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/sk.js +12 -0
  128. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/sl.js +12 -0
  129. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/sr-latn.js +12 -0
  130. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/sr.js +12 -0
  131. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/sv.js +12 -0
  132. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/th.js +12 -0
  133. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/tr.js +12 -0
  134. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/ug.js +12 -0
  135. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/uk.js +12 -0
  136. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/vi.js +12 -0
  137. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/zh-cn.js +12 -0
  138. data/app/assets/javascripts/ckeditor/plugins/codemirror/lang/zh.js +12 -0
  139. data/app/assets/javascripts/ckeditor/plugins/codemirror/plugin.js +1311 -0
  140. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/3024-day.css +41 -0
  141. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/3024-night.css +39 -0
  142. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/abcdef.css +32 -0
  143. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/ambiance-mobile.css +5 -0
  144. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/ambiance.css +74 -0
  145. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/base16-dark.css +38 -0
  146. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/base16-light.css +38 -0
  147. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/bespin.css +34 -0
  148. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/blackboard.css +32 -0
  149. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/cobalt.css +25 -0
  150. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/colorforth.css +33 -0
  151. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/dracula.css +40 -0
  152. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/duotone-dark.css +35 -0
  153. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/duotone-light.css +36 -0
  154. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/eclipse.css +23 -0
  155. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/elegant.css +13 -0
  156. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/erlang-dark.css +34 -0
  157. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/hopscotch.css +34 -0
  158. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/icecoder.css +43 -0
  159. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/isotope.css +34 -0
  160. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/lesser-dark.css +47 -0
  161. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/liquibyte.css +95 -0
  162. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/material.css +53 -0
  163. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/mbo.css +37 -0
  164. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/mdn-like.css +46 -0
  165. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/midnight.css +43 -0
  166. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/monokai.css +36 -0
  167. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/neat.css +12 -0
  168. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/neo.css +43 -0
  169. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/night.css +27 -0
  170. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/panda-syntax.css +85 -0
  171. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/paraiso-dark.css +38 -0
  172. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/paraiso-light.css +38 -0
  173. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/pastel-on-dark.css +52 -0
  174. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/railscasts.css +34 -0
  175. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/rubyblue.css +25 -0
  176. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/seti.css +44 -0
  177. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/solarized.css +168 -0
  178. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/the-matrix.css +30 -0
  179. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/tomorrow-night-bright.css +35 -0
  180. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/tomorrow-night-eighties.css +38 -0
  181. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/ttcn.css +64 -0
  182. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/twilight.css +32 -0
  183. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/vibrant-ink.css +34 -0
  184. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/xq-dark.css +53 -0
  185. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/xq-light.css +43 -0
  186. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/yeti.css +44 -0
  187. data/app/assets/javascripts/ckeditor/plugins/codemirror/theme/zenburn.css +37 -0
  188. data/app/assets/javascripts/ckeditor/plugins/embed/icons/embed.png +0 -0
  189. data/app/assets/javascripts/ckeditor/plugins/embed/icons/hidpi/embed.png +0 -0
  190. data/app/assets/javascripts/ckeditor/plugins/embed/plugin.js +105 -0
  191. data/app/assets/javascripts/ckeditor/plugins/embedbase/dialogs/embedbase.js +99 -0
  192. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/az.js +15 -0
  193. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/bg.js +15 -0
  194. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ca.js +15 -0
  195. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/cs.js +15 -0
  196. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/da.js +15 -0
  197. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/de-ch.js +15 -0
  198. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/de.js +15 -0
  199. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/en-au.js +15 -0
  200. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/en.js +15 -0
  201. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/eo.js +15 -0
  202. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/es-mx.js +15 -0
  203. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/es.js +15 -0
  204. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/eu.js +15 -0
  205. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/fr.js +15 -0
  206. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/gl.js +15 -0
  207. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/hr.js +15 -0
  208. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/hu.js +15 -0
  209. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/id.js +15 -0
  210. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/it.js +15 -0
  211. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ja.js +15 -0
  212. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ko.js +15 -0
  213. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ku.js +15 -0
  214. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/nb.js +15 -0
  215. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/nl.js +15 -0
  216. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/oc.js +15 -0
  217. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/pl.js +15 -0
  218. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/pt-br.js +15 -0
  219. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/pt.js +15 -0
  220. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ro.js +15 -0
  221. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ru.js +15 -0
  222. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/sk.js +15 -0
  223. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/sq.js +15 -0
  224. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/sv.js +15 -0
  225. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/tr.js +15 -0
  226. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/ug.js +15 -0
  227. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/uk.js +15 -0
  228. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/zh-cn.js +15 -0
  229. data/app/assets/javascripts/ckeditor/plugins/embedbase/lang/zh.js +15 -0
  230. data/app/assets/javascripts/ckeditor/plugins/embedbase/plugin.js +652 -0
  231. data/app/assets/javascripts/ckeditor/plugins/embedjs/plugin.js +110 -0
  232. data/app/assets/javascripts/ckeditor/plugins/lineutils/dev/dnd.html +172 -0
  233. data/app/assets/javascripts/ckeditor/plugins/lineutils/dev/magicfinger.html +285 -0
  234. data/app/assets/javascripts/ckeditor/plugins/lineutils/plugin.js +1018 -0
  235. data/app/assets/javascripts/ckeditor/plugins/notification/lang/cs.js +7 -0
  236. data/app/assets/javascripts/ckeditor/plugins/notification/lang/da.js +7 -0
  237. data/app/assets/javascripts/ckeditor/plugins/notification/lang/de-ch.js +7 -0
  238. data/app/assets/javascripts/ckeditor/plugins/notification/lang/de.js +7 -0
  239. data/app/assets/javascripts/ckeditor/plugins/notification/lang/en.js +7 -0
  240. data/app/assets/javascripts/ckeditor/plugins/notification/lang/eo.js +7 -0
  241. data/app/assets/javascripts/ckeditor/plugins/notification/lang/eu.js +7 -0
  242. data/app/assets/javascripts/ckeditor/plugins/notification/lang/fr.js +7 -0
  243. data/app/assets/javascripts/ckeditor/plugins/notification/lang/gl.js +7 -0
  244. data/app/assets/javascripts/ckeditor/plugins/notification/lang/id.js +7 -0
  245. data/app/assets/javascripts/ckeditor/plugins/notification/lang/it.js +7 -0
  246. data/app/assets/javascripts/ckeditor/plugins/notification/lang/km.js +7 -0
  247. data/app/assets/javascripts/ckeditor/plugins/notification/lang/ko.js +7 -0
  248. data/app/assets/javascripts/ckeditor/plugins/notification/lang/ku.js +7 -0
  249. data/app/assets/javascripts/ckeditor/plugins/notification/lang/nb.js +7 -0
  250. data/app/assets/javascripts/ckeditor/plugins/notification/lang/nl.js +7 -0
  251. data/app/assets/javascripts/ckeditor/plugins/notification/lang/pl.js +7 -0
  252. data/app/assets/javascripts/ckeditor/plugins/notification/lang/pt-br.js +7 -0
  253. data/app/assets/javascripts/ckeditor/plugins/notification/lang/pt.js +7 -0
  254. data/app/assets/javascripts/ckeditor/plugins/notification/lang/ru.js +7 -0
  255. data/app/assets/javascripts/ckeditor/plugins/notification/lang/sv.js +7 -0
  256. data/app/assets/javascripts/ckeditor/plugins/notification/lang/tr.js +7 -0
  257. data/app/assets/javascripts/ckeditor/plugins/notification/lang/ug.js +7 -0
  258. data/app/assets/javascripts/ckeditor/plugins/notification/lang/uk.js +7 -0
  259. data/app/assets/javascripts/ckeditor/plugins/notification/lang/zh-cn.js +7 -0
  260. data/app/assets/javascripts/ckeditor/plugins/notification/lang/zh.js +7 -0
  261. data/app/assets/javascripts/ckeditor/plugins/notification/plugin.js +923 -0
  262. data/app/assets/javascripts/ckeditor/plugins/notificationaggregator/plugin.js +548 -0
  263. data/app/assets/javascripts/ckeditor/plugins/placeholder/dev/placeholder.html +60 -0
  264. data/app/assets/javascripts/ckeditor/plugins/placeholder/dialogs/placeholder.js +49 -0
  265. data/app/assets/javascripts/ckeditor/plugins/placeholder/icons/hidpi/placeholder.png +0 -0
  266. data/app/assets/javascripts/ckeditor/plugins/placeholder/icons/placeholder.png +0 -0
  267. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/af.js +12 -0
  268. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ar.js +12 -0
  269. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/az.js +12 -0
  270. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/bg.js +12 -0
  271. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ca.js +12 -0
  272. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/cs.js +12 -0
  273. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/cy.js +12 -0
  274. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/da.js +12 -0
  275. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/de-ch.js +12 -0
  276. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/de.js +12 -0
  277. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/el.js +12 -0
  278. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/en-gb.js +12 -0
  279. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/en.js +12 -0
  280. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/eo.js +12 -0
  281. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/es-mx.js +12 -0
  282. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/es.js +12 -0
  283. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/et.js +12 -0
  284. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/eu.js +12 -0
  285. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/fa.js +12 -0
  286. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/fi.js +12 -0
  287. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/fr-ca.js +12 -0
  288. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/fr.js +12 -0
  289. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/gl.js +12 -0
  290. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/he.js +12 -0
  291. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/hr.js +12 -0
  292. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/hu.js +12 -0
  293. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/id.js +12 -0
  294. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/it.js +12 -0
  295. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ja.js +12 -0
  296. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/km.js +12 -0
  297. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ko.js +12 -0
  298. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ku.js +12 -0
  299. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/lv.js +12 -0
  300. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/nb.js +12 -0
  301. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/nl.js +12 -0
  302. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/no.js +12 -0
  303. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/oc.js +12 -0
  304. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/pl.js +12 -0
  305. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/pt-br.js +12 -0
  306. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/pt.js +12 -0
  307. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ru.js +12 -0
  308. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/si.js +12 -0
  309. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/sk.js +12 -0
  310. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/sl.js +12 -0
  311. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/sq.js +12 -0
  312. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/sv.js +12 -0
  313. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/th.js +12 -0
  314. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/tr.js +12 -0
  315. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/tt.js +12 -0
  316. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/ug.js +12 -0
  317. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/uk.js +12 -0
  318. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/vi.js +12 -0
  319. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/zh-cn.js +12 -0
  320. data/app/assets/javascripts/ckeditor/plugins/placeholder/lang/zh.js +12 -0
  321. data/app/assets/javascripts/ckeditor/plugins/placeholder/plugin.js +99 -0
  322. data/app/assets/javascripts/ckeditor/plugins/placeholder/samples/placeholder.html +75 -0
  323. data/app/assets/javascripts/ckeditor/plugins/strinsert/README.md +76 -0
  324. data/app/assets/javascripts/ckeditor/plugins/strinsert/bower.json +16 -0
  325. data/app/assets/javascripts/ckeditor/plugins/strinsert/plugin.js +107 -0
  326. data/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/contents.css +23 -0
  327. data/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/sample.jpg +0 -0
  328. data/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/contents.css +36 -0
  329. data/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/dialogs/simplebox.js +51 -0
  330. data/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/icons/simplebox.png +0 -0
  331. data/app/assets/javascripts/ckeditor/plugins/widget/dev/assets/simplebox/plugin.js +114 -0
  332. data/app/assets/javascripts/ckeditor/plugins/widget/dev/console.js +131 -0
  333. data/app/assets/javascripts/ckeditor/plugins/widget/dev/nestedwidgets.html +134 -0
  334. data/app/assets/javascripts/ckeditor/plugins/widget/dev/widgetstyles.html +144 -0
  335. data/app/assets/javascripts/ckeditor/plugins/widget/images/handle.png +0 -0
  336. data/app/assets/javascripts/ckeditor/plugins/widget/lang/af.js +8 -0
  337. data/app/assets/javascripts/ckeditor/plugins/widget/lang/ar.js +8 -0
  338. data/app/assets/javascripts/ckeditor/plugins/widget/lang/az.js +8 -0
  339. data/app/assets/javascripts/ckeditor/plugins/widget/lang/bg.js +8 -0
  340. data/app/assets/javascripts/ckeditor/plugins/widget/lang/ca.js +8 -0
  341. data/app/assets/javascripts/ckeditor/plugins/widget/lang/cs.js +8 -0
  342. data/app/assets/javascripts/ckeditor/plugins/widget/lang/cy.js +8 -0
  343. data/app/assets/javascripts/ckeditor/plugins/widget/lang/da.js +8 -0
  344. data/app/assets/javascripts/ckeditor/plugins/widget/lang/de-ch.js +8 -0
  345. data/app/assets/javascripts/ckeditor/plugins/widget/lang/de.js +8 -0
  346. data/app/assets/javascripts/ckeditor/plugins/widget/lang/el.js +8 -0
  347. data/app/assets/javascripts/ckeditor/plugins/widget/lang/en-gb.js +8 -0
  348. data/app/assets/javascripts/ckeditor/plugins/widget/lang/en.js +8 -0
  349. data/app/assets/javascripts/ckeditor/plugins/widget/lang/eo.js +8 -0
  350. data/app/assets/javascripts/ckeditor/plugins/widget/lang/es-mx.js +8 -0
  351. data/app/assets/javascripts/ckeditor/plugins/widget/lang/es.js +8 -0
  352. data/app/assets/javascripts/ckeditor/plugins/widget/lang/eu.js +8 -0
  353. data/app/assets/javascripts/ckeditor/plugins/widget/lang/fa.js +8 -0
  354. data/app/assets/javascripts/ckeditor/plugins/widget/lang/fi.js +8 -0
  355. data/app/assets/javascripts/ckeditor/plugins/widget/lang/fr.js +8 -0
  356. data/app/assets/javascripts/ckeditor/plugins/widget/lang/gl.js +8 -0
  357. data/app/assets/javascripts/ckeditor/plugins/widget/lang/he.js +8 -0
  358. data/app/assets/javascripts/ckeditor/plugins/widget/lang/hr.js +8 -0
  359. data/app/assets/javascripts/ckeditor/plugins/widget/lang/hu.js +8 -0
  360. data/app/assets/javascripts/ckeditor/plugins/widget/lang/id.js +8 -0
  361. data/app/assets/javascripts/ckeditor/plugins/widget/lang/it.js +8 -0
  362. data/app/assets/javascripts/ckeditor/plugins/widget/lang/ja.js +8 -0
  363. data/app/assets/javascripts/ckeditor/plugins/widget/lang/km.js +8 -0
  364. data/app/assets/javascripts/ckeditor/plugins/widget/lang/ko.js +8 -0
  365. data/app/assets/javascripts/ckeditor/plugins/widget/lang/ku.js +8 -0
  366. data/app/assets/javascripts/ckeditor/plugins/widget/lang/lv.js +8 -0
  367. data/app/assets/javascripts/ckeditor/plugins/widget/lang/nb.js +8 -0
  368. data/app/assets/javascripts/ckeditor/plugins/widget/lang/nl.js +8 -0
  369. data/app/assets/javascripts/ckeditor/plugins/widget/lang/no.js +8 -0
  370. data/app/assets/javascripts/ckeditor/plugins/widget/lang/oc.js +8 -0
  371. data/app/assets/javascripts/ckeditor/plugins/widget/lang/pl.js +8 -0
  372. data/app/assets/javascripts/ckeditor/plugins/widget/lang/pt-br.js +8 -0
  373. data/app/assets/javascripts/ckeditor/plugins/widget/lang/pt.js +8 -0
  374. data/app/assets/javascripts/ckeditor/plugins/widget/lang/ru.js +8 -0
  375. data/app/assets/javascripts/ckeditor/plugins/widget/lang/sk.js +8 -0
  376. data/app/assets/javascripts/ckeditor/plugins/widget/lang/sl.js +8 -0
  377. data/app/assets/javascripts/ckeditor/plugins/widget/lang/sq.js +8 -0
  378. data/app/assets/javascripts/ckeditor/plugins/widget/lang/sv.js +8 -0
  379. data/app/assets/javascripts/ckeditor/plugins/widget/lang/tr.js +8 -0
  380. data/app/assets/javascripts/ckeditor/plugins/widget/lang/tt.js +8 -0
  381. data/app/assets/javascripts/ckeditor/plugins/widget/lang/ug.js +8 -0
  382. data/app/assets/javascripts/ckeditor/plugins/widget/lang/uk.js +8 -0
  383. data/app/assets/javascripts/ckeditor/plugins/widget/lang/vi.js +8 -0
  384. data/app/assets/javascripts/ckeditor/plugins/widget/lang/zh-cn.js +8 -0
  385. data/app/assets/javascripts/ckeditor/plugins/widget/lang/zh.js +8 -0
  386. data/app/assets/javascripts/ckeditor/plugins/widget/plugin.js +4147 -0
  387. data/app/assets/javascripts/ckeditor/plugins/widgetselection/plugin.js +366 -0
  388. data/app/assets/javascripts/ckeditor/plugins/wordcount/css/wordcount.css +3 -0
  389. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/ar.js +11 -0
  390. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/ca.js +13 -0
  391. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/da.js +13 -0
  392. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/de.js +13 -0
  393. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/el.js +13 -0
  394. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/en.js +13 -0
  395. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/es.js +13 -0
  396. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/fi.js +14 -0
  397. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/fr.js +11 -0
  398. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/he.js +13 -0
  399. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/hr.js +13 -0
  400. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/it.js +14 -0
  401. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/ja.js +14 -0
  402. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/jp.js +13 -0
  403. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/nl.js +13 -0
  404. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/no.js +10 -0
  405. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/pl.js +13 -0
  406. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/pt-br.js +13 -0
  407. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/pt.js +9 -0
  408. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/ru.js +13 -0
  409. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/sv.js +13 -0
  410. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/tr.js +13 -0
  411. data/app/assets/javascripts/ckeditor/plugins/wordcount/lang/zh-cn.js +13 -0
  412. data/app/assets/javascripts/ckeditor/plugins/wordcount/plugin.js +407 -0
  413. data/app/assets/javascripts/ckeditor/plugins/wordcount/samples/wordcount.html +61 -0
  414. data/app/assets/javascripts/ckeditor/plugins/wordcount/samples/wordcountWithMaxCount.html +110 -0
  415. data/app/assets/javascripts/integral/backend.js +268 -0
  416. data/app/assets/javascripts/integral/backend/extensions.js +1 -0
  417. data/app/assets/javascripts/integral/backend/support/Chart.bundle.min.js +10 -0
  418. data/app/assets/javascripts/integral/frontend.js +60 -0
  419. data/app/assets/javascripts/integral/support/character_counter.js +67 -0
  420. data/app/assets/javascripts/integral/support/chart_manager.coffee +145 -0
  421. data/app/assets/javascripts/integral/support/click_to_copy.coffee +28 -0
  422. data/app/assets/javascripts/integral/support/confirm_modal.coffee +58 -0
  423. data/app/assets/javascripts/integral/support/date_picker.coffee +83 -0
  424. data/app/assets/javascripts/integral/support/date_picker/picker.date.js +1435 -0
  425. data/app/assets/javascripts/integral/support/date_picker/picker.js +1132 -0
  426. data/app/assets/javascripts/integral/support/gallery.coffee +82 -0
  427. data/app/assets/javascripts/integral/support/google_analytics.coffee +23 -0
  428. data/app/assets/javascripts/integral/support/grid.coffee +92 -0
  429. data/app/assets/javascripts/integral/support/header_anchors.coffee +22 -0
  430. data/app/assets/javascripts/integral/support/image-preview.coffee +14 -0
  431. data/app/assets/javascripts/integral/support/image_selector.coffee +43 -0
  432. data/app/assets/javascripts/integral/support/image_uploader.coffee +59 -0
  433. data/app/assets/javascripts/integral/support/lib/html.sortable.js +691 -0
  434. data/app/assets/javascripts/integral/support/lib/jquery.are-you-sure.js +231 -0
  435. data/app/assets/javascripts/integral/support/lib/jquery_form.js +11 -0
  436. data/app/assets/javascripts/integral/support/lib/js.cookie.js +165 -0
  437. data/app/assets/javascripts/integral/support/lib/materialize-tags.js +758 -0
  438. data/app/assets/javascripts/integral/support/lib/materialize.clockpicker.js +699 -0
  439. data/app/assets/javascripts/integral/support/lib/swiper.js +7719 -0
  440. data/app/assets/javascripts/integral/support/lib/typeahead.js +2451 -0
  441. data/app/assets/javascripts/integral/support/lib/underscore.js +6 -0
  442. data/app/assets/javascripts/integral/support/list.coffee +71 -0
  443. data/app/assets/javascripts/integral/support/list_item.coffee +249 -0
  444. data/app/assets/javascripts/integral/support/notification_manager.coffee +17 -0
  445. data/app/assets/javascripts/integral/support/record_selector.coffee +161 -0
  446. data/app/assets/javascripts/integral/support/remote_form.coffee +87 -0
  447. data/app/assets/javascripts/integral/support/responsive_swiper.coffee +26 -0
  448. data/app/assets/javascripts/integral/support/scroll_to_top.coffee +17 -0
  449. data/app/assets/javascripts/integral/support/slug_generator.coffee +49 -0
  450. data/app/assets/stylesheets/errors.css +1834 -0
  451. data/app/assets/stylesheets/integral/backend.sass +66 -0
  452. data/app/assets/stylesheets/integral/backend/_foundation_settings.scss +867 -0
  453. data/app/assets/stylesheets/integral/backend/dashboard-layout.scss +491 -0
  454. data/app/assets/stylesheets/integral/backend/devise.sass +40 -0
  455. data/app/assets/stylesheets/integral/backend/foundation_and_overrides.scss +56 -0
  456. data/app/assets/stylesheets/integral/backend/materialize-tags.sass +70 -0
  457. data/app/assets/stylesheets/integral/backend/notifications.sass +43 -0
  458. data/app/assets/stylesheets/integral/backend/overrides.sass +1 -0
  459. data/app/assets/stylesheets/integral/backend/shared.sass +770 -0
  460. data/app/assets/stylesheets/integral/emails.scss +19 -0
  461. data/app/assets/stylesheets/integral/emails/colors.sass +3 -0
  462. data/app/assets/stylesheets/integral/frontend.sass +26 -0
  463. data/app/assets/stylesheets/integral/frontend/_foundation_settings.scss +863 -0
  464. data/app/assets/stylesheets/integral/frontend/blog.sass +263 -0
  465. data/app/assets/stylesheets/integral/frontend/demo.sass +158 -0
  466. data/app/assets/stylesheets/integral/frontend/foundation_and_overrides.scss +56 -0
  467. data/app/assets/stylesheets/integral/frontend/layout.sass +66 -0
  468. data/app/assets/stylesheets/integral/frontend/overrides.sass +1 -0
  469. data/app/assets/stylesheets/integral/frontend/share_modal.sass +25 -0
  470. data/app/assets/stylesheets/integral/frontend/shared.sass +73 -0
  471. data/app/assets/stylesheets/integral/support/date-picker.scss +16 -0
  472. data/app/assets/stylesheets/integral/support/date_picker/_default.date.scss +452 -0
  473. data/app/assets/stylesheets/integral/support/date_picker/_default.scss +201 -0
  474. data/app/assets/stylesheets/integral/support/date_picker/_default.time.scss +125 -0
  475. data/app/assets/stylesheets/integral/support/date_picker/materialize_clockpicker.sass +220 -0
  476. data/app/assets/stylesheets/integral/support/gallery.sass +54 -0
  477. data/app/assets/stylesheets/integral/support/media-query-indicator.sass +25 -0
  478. data/app/assets/stylesheets/integral/support/mixins.sass +29 -0
  479. data/app/assets/stylesheets/integral/support/scroll-to-top.sass +19 -0
  480. data/app/assets/stylesheets/integral/support/swiper.css +618 -0
  481. data/app/assets/stylesheets/integral/wysiwyg.sass +13 -0
  482. data/app/controllers/integral/application_controller.rb +130 -0
  483. data/app/controllers/integral/backend/activities_controller.rb +55 -0
  484. data/app/controllers/integral/backend/base_controller.rb +123 -0
  485. data/app/controllers/integral/backend/images_controller.rb +120 -0
  486. data/app/controllers/integral/backend/lists_controller.rb +138 -0
  487. data/app/controllers/integral/backend/pages_controller.rb +142 -0
  488. data/app/controllers/integral/backend/posts_controller.rb +132 -0
  489. data/app/controllers/integral/backend/settings_controller.rb +53 -0
  490. data/app/controllers/integral/backend/static_pages_controller.rb +10 -0
  491. data/app/controllers/integral/backend/users_controller.rb +130 -0
  492. data/app/controllers/integral/blog_controller.rb +19 -0
  493. data/app/controllers/integral/contact_controller.rb +36 -0
  494. data/app/controllers/integral/pages_controller.rb +45 -0
  495. data/app/controllers/integral/posts_controller.rb +71 -0
  496. data/app/controllers/integral/tags_controller.rb +38 -0
  497. data/app/decorators/integral/image_decorator.rb +16 -0
  498. data/app/decorators/integral/list_decorator.rb +21 -0
  499. data/app/decorators/integral/page_decorator.rb +26 -0
  500. data/app/decorators/integral/post_decorator.rb +63 -0
  501. data/app/decorators/integral/user_decorator.rb +21 -0
  502. data/app/decorators/integral/version_decorator.rb +57 -0
  503. data/app/helpers/integral/application_helper.rb +52 -0
  504. data/app/helpers/integral/backend/base_helper.rb +72 -0
  505. data/app/helpers/integral/blog_helper.rb +26 -0
  506. data/app/helpers/integral/gallery_helper.rb +36 -0
  507. data/app/helpers/integral/mail_helper.rb +16 -0
  508. data/app/helpers/integral/social_helper.rb +123 -0
  509. data/app/helpers/integral/support_helper.rb +60 -0
  510. data/app/jobs/integral/application_job.rb +10 -0
  511. data/app/jobs/integral/newsletter_signup_job.rb +28 -0
  512. data/app/mailers/integral/contact_mailer.rb +56 -0
  513. data/app/models/ckeditor/asset.rb +8 -0
  514. data/app/models/ckeditor/attachment_file.rb +9 -0
  515. data/app/models/ckeditor/picture.rb +21 -0
  516. data/app/models/integral/application_record.rb +6 -0
  517. data/app/models/integral/basic.rb +11 -0
  518. data/app/models/integral/enquiry.rb +46 -0
  519. data/app/models/integral/image.rb +67 -0
  520. data/app/models/integral/image_version.rb +8 -0
  521. data/app/models/integral/link.rb +11 -0
  522. data/app/models/integral/list.rb +69 -0
  523. data/app/models/integral/list_item.rb +89 -0
  524. data/app/models/integral/list_version.rb +8 -0
  525. data/app/models/integral/newsletter_signup.rb +22 -0
  526. data/app/models/integral/object.rb +30 -0
  527. data/app/models/integral/page.rb +153 -0
  528. data/app/models/integral/page_version.rb +8 -0
  529. data/app/models/integral/post.rb +149 -0
  530. data/app/models/integral/post_version.rb +8 -0
  531. data/app/models/integral/post_viewing.rb +19 -0
  532. data/app/models/integral/role.rb +14 -0
  533. data/app/models/integral/role_assignment.rb +7 -0
  534. data/app/models/integral/settings.rb +5 -0
  535. data/app/models/integral/user.rb +59 -0
  536. data/app/models/integral/user_version.rb +8 -0
  537. data/app/models/integral/version.rb +31 -0
  538. data/app/models/paper_trail/version.rb +9 -0
  539. data/app/policies/integral/base_policy.rb +50 -0
  540. data/app/policies/integral/image_policy.rb +14 -0
  541. data/app/policies/integral/list_policy.rb +9 -0
  542. data/app/policies/integral/page_policy.rb +45 -0
  543. data/app/policies/integral/post_policy.rb +9 -0
  544. data/app/policies/integral/settings_policy.rb +17 -0
  545. data/app/policies/integral/user_policy.rb +17 -0
  546. data/app/policies/integral/version_policy.rb +19 -0
  547. data/app/uploaders/ckeditor_attachment_file_uploader.rb +34 -0
  548. data/app/uploaders/ckeditor_picture_uploader.rb +55 -0
  549. data/app/uploaders/integral/avatar_uploader.rb +9 -0
  550. data/app/uploaders/integral/image_uploader.rb +114 -0
  551. data/app/views/devise/invitations/edit.haml +17 -0
  552. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  553. data/app/views/devise/mailer/invitation_instructions.html.erb +13 -0
  554. data/app/views/devise/mailer/invitation_instructions.text.erb +12 -0
  555. data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
  556. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  557. data/app/views/devise/passwords/edit.haml +17 -0
  558. data/app/views/devise/passwords/new.haml +12 -0
  559. data/app/views/devise/sessions/new.haml +18 -0
  560. data/app/views/devise/shared/_links.html.erb +25 -0
  561. data/app/views/integral/backend/activities/_grid.haml +23 -0
  562. data/app/views/integral/backend/activities/index.haml +15 -0
  563. data/app/views/integral/backend/activities/shared/_grid.haml +20 -0
  564. data/app/views/integral/backend/activities/shared/_listing.haml +14 -0
  565. data/app/views/integral/backend/activities/shared/_log.haml +56 -0
  566. data/app/views/integral/backend/activities/show.haml +18 -0
  567. data/app/views/integral/backend/images/_create_modal.haml +17 -0
  568. data/app/views/integral/backend/images/_form.haml +41 -0
  569. data/app/views/integral/backend/images/_grid.haml +16 -0
  570. data/app/views/integral/backend/images/edit.haml +3 -0
  571. data/app/views/integral/backend/images/index.haml +18 -0
  572. data/app/views/integral/backend/images/new.haml +1 -0
  573. data/app/views/integral/backend/lists/_child_fields.haml +26 -0
  574. data/app/views/integral/backend/lists/_form.haml +25 -0
  575. data/app/views/integral/backend/lists/_grid.haml +16 -0
  576. data/app/views/integral/backend/lists/_item_container.haml +26 -0
  577. data/app/views/integral/backend/lists/_item_modal.haml +46 -0
  578. data/app/views/integral/backend/lists/_list_item_fields.haml +26 -0
  579. data/app/views/integral/backend/lists/_manager.haml +14 -0
  580. data/app/views/integral/backend/lists/edit.haml +2 -0
  581. data/app/views/integral/backend/lists/index.haml +18 -0
  582. data/app/views/integral/backend/lists/new.haml +1 -0
  583. data/app/views/integral/backend/lists/show.haml +26 -0
  584. data/app/views/integral/backend/pages/_form.haml +52 -0
  585. data/app/views/integral/backend/pages/_grid.haml +21 -0
  586. data/app/views/integral/backend/pages/activities.haml +2 -0
  587. data/app/views/integral/backend/pages/activity.haml +1 -0
  588. data/app/views/integral/backend/pages/edit.html.haml +15 -0
  589. data/app/views/integral/backend/pages/index.haml +22 -0
  590. data/app/views/integral/backend/pages/new.haml +2 -0
  591. data/app/views/integral/backend/posts/_form.haml +57 -0
  592. data/app/views/integral/backend/posts/_grid.haml +25 -0
  593. data/app/views/integral/backend/posts/activities.haml +3 -0
  594. data/app/views/integral/backend/posts/activity.haml +1 -0
  595. data/app/views/integral/backend/posts/edit.haml +16 -0
  596. data/app/views/integral/backend/posts/index.haml +20 -0
  597. data/app/views/integral/backend/posts/new.haml +2 -0
  598. data/app/views/integral/backend/settings/_input.haml +4 -0
  599. data/app/views/integral/backend/settings/_section.haml +11 -0
  600. data/app/views/integral/backend/settings/index.haml +10 -0
  601. data/app/views/integral/backend/settings/sections/_general.haml +12 -0
  602. data/app/views/integral/backend/settings/sections/_lists.haml +4 -0
  603. data/app/views/integral/backend/settings/sections/_seo.haml +8 -0
  604. data/app/views/integral/backend/settings/sections/_social.haml +16 -0
  605. data/app/views/integral/backend/shared/_breadcrumbs.haml +9 -0
  606. data/app/views/integral/backend/shared/_empty_grid.haml +7 -0
  607. data/app/views/integral/backend/shared/_grid.haml +18 -0
  608. data/app/views/integral/backend/shared/_image_preview.haml +23 -0
  609. data/app/views/integral/backend/shared/_image_selector.haml +4 -0
  610. data/app/views/integral/backend/shared/_pagination.haml +1 -0
  611. data/app/views/integral/backend/shared/cards/_at_a_glance.haml +5 -0
  612. data/app/views/integral/backend/shared/cards/_welcome.haml +29 -0
  613. data/app/views/integral/backend/shared/graphs/_donut.haml +5 -0
  614. data/app/views/integral/backend/shared/graphs/_line.haml +5 -0
  615. data/app/views/integral/backend/shared/graphs/_no_data_available.haml +2 -0
  616. data/app/views/integral/backend/shared/record_selector/_collection.haml +13 -0
  617. data/app/views/integral/backend/shared/record_selector/_modal.haml +42 -0
  618. data/app/views/integral/backend/shared/record_selector/_record.haml +6 -0
  619. data/app/views/integral/backend/static_pages/_card.haml +24 -0
  620. data/app/views/integral/backend/static_pages/dashboard.haml +20 -0
  621. data/app/views/integral/backend/users/_form.haml +58 -0
  622. data/app/views/integral/backend/users/_grid.haml +19 -0
  623. data/app/views/integral/backend/users/edit.haml +3 -0
  624. data/app/views/integral/backend/users/index.haml +18 -0
  625. data/app/views/integral/backend/users/new.haml +2 -0
  626. data/app/views/integral/backend/users/show.haml +36 -0
  627. data/app/views/integral/contact_mailer/auto_reply.html.haml +16 -0
  628. data/app/views/integral/contact_mailer/auto_reply.text.erb +15 -0
  629. data/app/views/integral/contact_mailer/forward_enquiry.haml +12 -0
  630. data/app/views/integral/contact_mailer/forward_enquiry.text.erb +9 -0
  631. data/app/views/integral/pages/_demo.haml +188 -0
  632. data/app/views/integral/pages/templates/default.haml +6 -0
  633. data/app/views/integral/posts/_item.haml +16 -0
  634. data/app/views/integral/posts/index.haml +6 -0
  635. data/app/views/integral/posts/templates/default.haml +38 -0
  636. data/app/views/integral/shared/_blog_layout.haml +15 -0
  637. data/app/views/integral/shared/_blog_sidebar.haml +49 -0
  638. data/app/views/integral/shared/_breadcrumbs.haml +5 -0
  639. data/app/views/integral/shared/_media_query_indicator.haml +2 -0
  640. data/app/views/integral/shared/_scroll_to_top.haml +3 -0
  641. data/app/views/integral/shared/_share_modal.haml +20 -0
  642. data/app/views/integral/shared/_social_list_items.haml +41 -0
  643. data/app/views/integral/shared/_swiper_layout.haml +20 -0
  644. data/app/views/integral/shared/gallery/_placeholder.haml +7 -0
  645. data/app/views/integral/shared/gallery/_slide.haml +5 -0
  646. data/app/views/integral/shared/gallery/_thumb_slide.haml +2 -0
  647. data/app/views/integral/shared/gallery/gallery.haml +7 -0
  648. data/app/views/integral/tags/index.haml +6 -0
  649. data/app/views/integral/tags/show.haml +6 -0
  650. data/app/views/layouts/error.haml +12 -0
  651. data/app/views/layouts/integral/backend.html.haml +93 -0
  652. data/app/views/layouts/integral/backend/_create_dropdown.haml +30 -0
  653. data/app/views/layouts/integral/backend/_head.html.haml +17 -0
  654. data/app/views/layouts/integral/backend/_main_menu_items.haml +91 -0
  655. data/app/views/layouts/integral/frontend.html.haml +36 -0
  656. data/app/views/layouts/integral/frontend/_admin_bar.haml +25 -0
  657. data/app/views/layouts/integral/frontend/_footer.haml +23 -0
  658. data/app/views/layouts/integral/frontend/_header.haml +12 -0
  659. data/app/views/layouts/integral/login.haml +8 -0
  660. data/app/views/layouts/integral/mailer.html.inky-haml +17 -0
  661. data/app/views/layouts/integral/mailer/_footer.html.inky-haml +11 -0
  662. data/app/views/layouts/integral/mailer/_header.html.inky-haml +7 -0
  663. data/config/initializers/acts_as_taggable_on.rb +6 -0
  664. data/config/initializers/ckeditor.rb +51 -0
  665. data/config/initializers/client_side_validations.rb +20 -0
  666. data/config/initializers/compression.rb +46 -0
  667. data/config/initializers/devise.rb +308 -0
  668. data/config/initializers/extensions/ckeditor.rb +31 -0
  669. data/config/initializers/friendly_id.rb +88 -0
  670. data/config/initializers/gaffe.rb +3 -0
  671. data/config/initializers/simple_form.rb +169 -0
  672. data/config/initializers/simple_form/length_component.rb +36 -0
  673. data/config/locales/devise.en.yml +60 -0
  674. data/config/locales/devise_invitable.en.yml +31 -0
  675. data/config/locales/en.yml +477 -0
  676. data/config/locales/simple_form.en.yml +31 -0
  677. data/config/routes.rb +3 -0
  678. data/db/migrate/20160129021044_devise_create_integral_users.rb +42 -0
  679. data/db/migrate/20160130051535_add_name_and_avatar_to_users.rb +6 -0
  680. data/db/migrate/20160201003936_add_roles.rb +13 -0
  681. data/db/migrate/20160302071241_create_integral_images.rb +13 -0
  682. data/db/migrate/20160311092506_create_ckeditor_assets.rb +26 -0
  683. data/db/migrate/20160318004136_create_integral_pages.rb +12 -0
  684. data/db/migrate/20160513033619_create_integral_posts.rb +12 -0
  685. data/db/migrate/20160514063048_add_slug_column_to_post.rb +6 -0
  686. data/db/migrate/20160516084702_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb +31 -0
  687. data/db/migrate/20160516084703_add_missing_unique_indices.acts_as_taggable_on_engine.rb +20 -0
  688. data/db/migrate/20160516084704_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb +15 -0
  689. data/db/migrate/20160516084705_add_missing_taggable_index.acts_as_taggable_on_engine.rb +10 -0
  690. data/db/migrate/20160516084706_change_collation_for_tag_names.acts_as_taggable_on_engine.rb +10 -0
  691. data/db/migrate/20160519005736_add_image_to_posts.rb +5 -0
  692. data/db/migrate/20160519053329_create_friendly_id_slugs.rb +15 -0
  693. data/db/migrate/20160610021858_add_view_count_to_posts.rb +5 -0
  694. data/db/migrate/20160613035508_add_drafting_to_posts.rb +6 -0
  695. data/db/migrate/20160628050006_create_integral_post_viewings.rb +10 -0
  696. data/db/migrate/20160718011218_add_locale_to_integral_users.rb +5 -0
  697. data/db/migrate/20160722100113_devise_invitable_add_to_integral_users.rb +23 -0
  698. data/db/migrate/20160809115906_add_drafting_to_pages.rb +5 -0
  699. data/db/migrate/20160909124940_add_deleted_at_to_objects.rb +15 -0
  700. data/db/migrate/20161014175823_create_lists.rb +31 -0
  701. data/db/migrate/20161102094859_create_settings.rb +18 -0
  702. data/db/migrate/20170215012231_add_templates_to_pages.rb +5 -0
  703. data/db/migrate/20170425091312_add_image_processing_fields.rb +7 -0
  704. data/db/migrate/20170516033523_add_timestamps_to_lists.rb +6 -0
  705. data/db/migrate/20170608131624_add_file_size_to_images.rb +5 -0
  706. data/db/migrate/20170801182434_add_parent_to_pages.rb +5 -0
  707. data/db/migrate/20170922201940_add_image_id_to_posts.rb +9 -0
  708. data/db/migrate/20171008193414_create_post_versions.rb +21 -0
  709. data/db/migrate/20171030215146_create_page_versions.rb +20 -0
  710. data/db/migrate/20171030215154_create_user_versions.rb +20 -0
  711. data/db/migrate/20171030215202_create_list_versions.rb +20 -0
  712. data/db/migrate/20171123155010_create_integral_enquiries.rb +13 -0
  713. data/db/migrate/20171127141132_add_deleted_at_to_lists.rb +6 -0
  714. data/db/migrate/20171230213848_add_image_to_pages.rb +6 -0
  715. data/db/migrate/20180202010010_add_lock_version_to_integral_objects.rb +9 -0
  716. data/db/migrate/20180202010020_create_image_versions.rb +20 -0
  717. data/db/migrate/20180207174914_add_fields_to_lists.rb +7 -0
  718. data/db/migrate/20180223232512_create_integral_newsletter_signups.rb +11 -0
  719. data/db/migrate/20180306204912_add_admin_to_integral_users.rb +5 -0
  720. data/db/migrate/20180326224500_add_processed_to_enquiries_and_newsletters.rb +6 -0
  721. data/db/migrate/20180509101917_add_context_to_enquiries.rb +6 -0
  722. data/db/migrate/20180920030236_update_posts_and_pages_null.rb +6 -0
  723. data/db/migrate/20181011234446_add_preview_image_to_posts.rb +6 -0
  724. data/db/seeds.rb +53 -0
  725. data/lib/generators/integral/install_generator.rb +19 -0
  726. data/lib/generators/templates/app.yml +24 -0
  727. data/lib/generators/templates/carrierwave.rb +35 -0
  728. data/lib/generators/templates/carrierwave_backgrounder.rb +11 -0
  729. data/lib/generators/templates/integral.rb +129 -0
  730. data/lib/generators/templates/sitemap.rb +41 -0
  731. data/lib/integral.rb +155 -0
  732. data/lib/integral/acts_as_listable.rb +45 -0
  733. data/lib/integral/button_link_renderer.rb +54 -0
  734. data/lib/integral/chart_renderer/base.rb +80 -0
  735. data/lib/integral/chart_renderer/donut.rb +21 -0
  736. data/lib/integral/chart_renderer/line.rb +24 -0
  737. data/lib/integral/content_renderer.rb +78 -0
  738. data/lib/integral/engine.rb +124 -0
  739. data/lib/integral/foundation_builder.rb +33 -0
  740. data/lib/integral/google_tag_manager.rb +33 -0
  741. data/lib/integral/grids/activities_grid.rb +37 -0
  742. data/lib/integral/grids/images_grid.rb +21 -0
  743. data/lib/integral/grids/lists_grid.rb +21 -0
  744. data/lib/integral/grids/pages_grid.rb +27 -0
  745. data/lib/integral/grids/posts_grid.rb +32 -0
  746. data/lib/integral/grids/users_grid.rb +22 -0
  747. data/lib/integral/list_item_renderer.rb +204 -0
  748. data/lib/integral/list_renderer.rb +91 -0
  749. data/lib/integral/middleware/page_router.rb +84 -0
  750. data/lib/integral/partial_list_item_renderer.rb +40 -0
  751. data/lib/integral/router.rb +106 -0
  752. data/lib/integral/slack_bot.rb +45 -0
  753. data/lib/integral/swiper_list_renderer.rb +30 -0
  754. data/lib/integral/version.rb +5 -0
  755. data/lib/integral/widgets/recent_posts.rb +41 -0
  756. data/lib/integral/widgets/swiper_list.rb +40 -0
  757. data/lib/tasks/integral_tasks.rake +57 -0
  758. data/lib/templates/erb/scaffold/_form.html.erb +13 -0
  759. data/spec/factories.rb +114 -0
  760. data/spec/support/image.jpg +0 -0
  761. metadata +1734 -0
@@ -0,0 +1,4147 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3
+ * For licensing, see LICENSE.md or http://ckeditor.com/license
4
+ */
5
+
6
+ /**
7
+ * @fileOverview [Widget](http://ckeditor.com/addon/widget) plugin.
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ ( function() {
13
+ var DRAG_HANDLER_SIZE = 15;
14
+
15
+ CKEDITOR.plugins.add( 'widget', {
16
+ // jscs:disable maximumLineLength
17
+ lang: 'af,ar,az,bg,ca,cs,cy,da,de,de-ch,el,en,en-gb,eo,es,es-mx,eu,fa,fi,fr,gl,he,hr,hu,id,it,ja,km,ko,ku,lv,nb,nl,no,oc,pl,pt,pt-br,ru,sk,sl,sq,sv,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
18
+ // jscs:enable maximumLineLength
19
+ requires: 'lineutils,clipboard,widgetselection',
20
+ onLoad: function() {
21
+ CKEDITOR.addCss(
22
+ '.cke_widget_wrapper{' +
23
+ 'position:relative;' +
24
+ 'outline:none' +
25
+ '}' +
26
+ '.cke_widget_inline{' +
27
+ 'display:inline-block' +
28
+ '}' +
29
+ '.cke_widget_wrapper:hover>.cke_widget_element{' +
30
+ 'outline:2px solid yellow;' +
31
+ 'cursor:default' +
32
+ '}' +
33
+ '.cke_widget_wrapper:hover .cke_widget_editable{' +
34
+ 'outline:2px solid yellow' +
35
+ '}' +
36
+ '.cke_widget_wrapper.cke_widget_focused>.cke_widget_element,' +
37
+ // We need higher specificity than hover style.
38
+ '.cke_widget_wrapper .cke_widget_editable.cke_widget_editable_focused{' +
39
+ 'outline:2px solid #ace' +
40
+ '}' +
41
+ '.cke_widget_editable{' +
42
+ 'cursor:text' +
43
+ '}' +
44
+ '.cke_widget_drag_handler_container{' +
45
+ 'position:absolute;' +
46
+ 'width:' + DRAG_HANDLER_SIZE + 'px;' +
47
+ 'height:0;' +
48
+ // Initially drag handler should not be visible, until its position will be
49
+ // calculated (http://dev.ckeditor.com/ticket/11177).
50
+ // We need to hide unpositined handlers, so they don't extend
51
+ // widget's outline far to the left (http://dev.ckeditor.com/ticket/12024).
52
+ 'display:none;' +
53
+ 'opacity:0.75;' +
54
+ 'transition:height 0s 0.2s;' + // Delay hiding drag handler.
55
+ // Prevent drag handler from being misplaced (http://dev.ckeditor.com/ticket/11198).
56
+ 'line-height:0' +
57
+ '}' +
58
+ '.cke_widget_wrapper:hover>.cke_widget_drag_handler_container{' +
59
+ 'height:' + DRAG_HANDLER_SIZE + 'px;' +
60
+ 'transition:none' +
61
+ '}' +
62
+ '.cke_widget_drag_handler_container:hover{' +
63
+ 'opacity:1' +
64
+ '}' +
65
+ 'img.cke_widget_drag_handler{' +
66
+ 'cursor:move;' +
67
+ 'width:' + DRAG_HANDLER_SIZE + 'px;' +
68
+ 'height:' + DRAG_HANDLER_SIZE + 'px;' +
69
+ 'display:inline-block' +
70
+ '}' +
71
+ '.cke_widget_mask{' +
72
+ 'position:absolute;' +
73
+ 'top:0;' +
74
+ 'left:0;' +
75
+ 'width:100%;' +
76
+ 'height:100%;' +
77
+ 'display:block' +
78
+ '}' +
79
+ '.cke_editable.cke_widget_dragging, .cke_editable.cke_widget_dragging *{' +
80
+ 'cursor:move !important' +
81
+ '}'
82
+ );
83
+ },
84
+
85
+ beforeInit: function( editor ) {
86
+ /**
87
+ * An instance of widget repository. It contains all
88
+ * {@link CKEDITOR.plugins.widget.repository#registered registered widget definitions} and
89
+ * {@link CKEDITOR.plugins.widget.repository#instances initialized instances}.
90
+ *
91
+ * editor.widgets.add( 'someName', {
92
+ * // Widget definition...
93
+ * } );
94
+ *
95
+ * editor.widgets.registered.someName; // -> Widget definition
96
+ *
97
+ * @since 4.3
98
+ * @readonly
99
+ * @property {CKEDITOR.plugins.widget.repository} widgets
100
+ * @member CKEDITOR.editor
101
+ */
102
+ editor.widgets = new Repository( editor );
103
+ },
104
+
105
+ afterInit: function( editor ) {
106
+ addWidgetButtons( editor );
107
+ setupContextMenu( editor );
108
+ }
109
+ } );
110
+
111
+ /**
112
+ * Widget repository. It keeps track of all {@link #registered registered widget definitions} and
113
+ * {@link #instances initialized instances}. An instance of the repository is available under
114
+ * the {@link CKEDITOR.editor#widgets} property.
115
+ *
116
+ * @class CKEDITOR.plugins.widget.repository
117
+ * @mixins CKEDITOR.event
118
+ * @constructor Creates a widget repository instance. Note that the widget plugin automatically
119
+ * creates a repository instance which is available under the {@link CKEDITOR.editor#widgets} property.
120
+ * @param {CKEDITOR.editor} editor The editor instance for which the repository will be created.
121
+ */
122
+ function Repository( editor ) {
123
+ /**
124
+ * The editor instance for which this repository was created.
125
+ *
126
+ * @readonly
127
+ * @property {CKEDITOR.editor} editor
128
+ */
129
+ this.editor = editor;
130
+
131
+ /**
132
+ * A hash of registered widget definitions (definition name => {@link CKEDITOR.plugins.widget.definition}).
133
+ *
134
+ * To register a definition use the {@link #add} method.
135
+ *
136
+ * @readonly
137
+ */
138
+ this.registered = {};
139
+
140
+ /**
141
+ * An object containing initialized widget instances (widget id => {@link CKEDITOR.plugins.widget}).
142
+ *
143
+ * @readonly
144
+ */
145
+ this.instances = {};
146
+
147
+ /**
148
+ * An array of selected widget instances.
149
+ *
150
+ * @readonly
151
+ * @property {CKEDITOR.plugins.widget[]} selected
152
+ */
153
+ this.selected = [];
154
+
155
+ /**
156
+ * The focused widget instance. See also {@link CKEDITOR.plugins.widget#event-focus}
157
+ * and {@link CKEDITOR.plugins.widget#event-blur} events.
158
+ *
159
+ * editor.on( 'selectionChange', function() {
160
+ * if ( editor.widgets.focused ) {
161
+ * // Do something when a widget is focused...
162
+ * }
163
+ * } );
164
+ *
165
+ * @readonly
166
+ * @property {CKEDITOR.plugins.widget} focused
167
+ */
168
+ this.focused = null;
169
+
170
+ /**
171
+ * The widget instance that contains the nested editable which is currently focused.
172
+ *
173
+ * @readonly
174
+ * @property {CKEDITOR.plugins.widget} widgetHoldingFocusedEditable
175
+ */
176
+ this.widgetHoldingFocusedEditable = null;
177
+
178
+ this._ = {
179
+ nextId: 0,
180
+ upcasts: [],
181
+ upcastCallbacks: [],
182
+ filters: {}
183
+ };
184
+
185
+ setupWidgetsLifecycle( this );
186
+ setupSelectionObserver( this );
187
+ setupMouseObserver( this );
188
+ setupKeyboardObserver( this );
189
+ setupDragAndDrop( this );
190
+ setupNativeCutAndCopy( this );
191
+ }
192
+
193
+ Repository.prototype = {
194
+ /**
195
+ * Minimum interval between selection checks.
196
+ *
197
+ * @private
198
+ */
199
+ MIN_SELECTION_CHECK_INTERVAL: 500,
200
+
201
+ /**
202
+ * Adds a widget definition to the repository. Fires the {@link CKEDITOR.editor#widgetDefinition} event
203
+ * which allows to modify the widget definition which is going to be registered.
204
+ *
205
+ * @param {String} name The name of the widget definition.
206
+ * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget definition.
207
+ * @returns {CKEDITOR.plugins.widget.definition}
208
+ */
209
+ add: function( name, widgetDef ) {
210
+ // Create prototyped copy of original widget definition, so we won't modify it.
211
+ widgetDef = CKEDITOR.tools.prototypedCopy( widgetDef );
212
+ widgetDef.name = name;
213
+
214
+ widgetDef._ = widgetDef._ || {};
215
+
216
+ this.editor.fire( 'widgetDefinition', widgetDef );
217
+
218
+ if ( widgetDef.template )
219
+ widgetDef.template = new CKEDITOR.template( widgetDef.template );
220
+
221
+ addWidgetCommand( this.editor, widgetDef );
222
+ addWidgetProcessors( this, widgetDef );
223
+
224
+ this.registered[ name ] = widgetDef;
225
+
226
+ return widgetDef;
227
+ },
228
+
229
+ /**
230
+ * Adds a callback for element upcasting. Each callback will be executed
231
+ * for every element which is later tested by upcast methods. If a callback
232
+ * returns `false`, the element will not be upcasted.
233
+ *
234
+ * // Images with the "banner" class will not be upcasted (e.g. to the image widget).
235
+ * editor.widgets.addUpcastCallback( function( element ) {
236
+ * if ( element.name == 'img' && element.hasClass( 'banner' ) )
237
+ * return false;
238
+ * } );
239
+ *
240
+ * @param {Function} callback
241
+ * @param {CKEDITOR.htmlParser.element} callback.element
242
+ */
243
+ addUpcastCallback: function( callback ) {
244
+ this._.upcastCallbacks.push( callback );
245
+ },
246
+
247
+ /**
248
+ * Checks the selection to update widget states (selection and focus).
249
+ *
250
+ * This method is triggered by the {@link #event-checkSelection} event.
251
+ */
252
+ checkSelection: function() {
253
+ var sel = this.editor.getSelection(),
254
+ selectedElement = sel.getSelectedElement(),
255
+ updater = stateUpdater( this ),
256
+ widget;
257
+
258
+ // Widget is focused so commit and finish checking.
259
+ if ( selectedElement && ( widget = this.getByElement( selectedElement, true ) ) )
260
+ return updater.focus( widget ).select( widget ).commit();
261
+
262
+ var range = sel.getRanges()[ 0 ];
263
+
264
+ // No ranges or collapsed range mean that nothing is selected, so commit and finish checking.
265
+ if ( !range || range.collapsed )
266
+ return updater.commit();
267
+
268
+ // Range is not empty, so create walker checking for wrappers.
269
+ var walker = new CKEDITOR.dom.walker( range ),
270
+ wrapper;
271
+
272
+ walker.evaluator = Widget.isDomWidgetWrapper;
273
+
274
+ while ( ( wrapper = walker.next() ) )
275
+ updater.select( this.getByElement( wrapper ) );
276
+
277
+ updater.commit();
278
+ },
279
+
280
+ /**
281
+ * Checks if all widget instances are still present in the DOM.
282
+ * Destroys those instances that are not present.
283
+ * Reinitializes widgets on widget wrappers for which widget instances
284
+ * cannot be found. Takes nested widgets into account, too.
285
+ *
286
+ * This method triggers the {@link #event-checkWidgets} event whose listeners
287
+ * can cancel the method's execution or modify its options.
288
+ *
289
+ * @param [options] The options object.
290
+ * @param {Boolean} [options.initOnlyNew] Initializes widgets only on newly wrapped
291
+ * widget elements (those which still have the `cke_widget_new` class). When this option is
292
+ * set to `true`, widgets which were invalidated (e.g. by replacing with a cloned DOM structure)
293
+ * will not be reinitialized. This makes the check faster.
294
+ * @param {Boolean} [options.focusInited] If only one widget is initialized by
295
+ * the method, it will be focused.
296
+ */
297
+ checkWidgets: function( options ) {
298
+ this.fire( 'checkWidgets', CKEDITOR.tools.copy( options || {} ) );
299
+ },
300
+
301
+ /**
302
+ * Removes the widget from the editor and moves the selection to the closest
303
+ * editable position if the widget was focused before.
304
+ *
305
+ * @param {CKEDITOR.plugins.widget} widget The widget instance to be deleted.
306
+ */
307
+ del: function( widget ) {
308
+ if ( this.focused === widget ) {
309
+ var editor = widget.editor,
310
+ range = editor.createRange(),
311
+ found;
312
+
313
+ // If haven't found place for caret on the default side,
314
+ // try to find it on the other side.
315
+ if ( !( found = range.moveToClosestEditablePosition( widget.wrapper, true ) ) )
316
+ found = range.moveToClosestEditablePosition( widget.wrapper, false );
317
+
318
+ if ( found )
319
+ editor.getSelection().selectRanges( [ range ] );
320
+ }
321
+
322
+ widget.wrapper.remove();
323
+ this.destroy( widget, true );
324
+ },
325
+
326
+ /**
327
+ * Destroys the widget instance and all its nested widgets (widgets inside its nested editables).
328
+ *
329
+ * @param {CKEDITOR.plugins.widget} widget The widget instance to be destroyed.
330
+ * @param {Boolean} [offline] Whether the widget is offline (detached from the DOM tree) —
331
+ * in this case the DOM (attributes, classes, etc.) will not be cleaned up.
332
+ */
333
+ destroy: function( widget, offline ) {
334
+ if ( this.widgetHoldingFocusedEditable === widget )
335
+ setFocusedEditable( this, widget, null, offline );
336
+
337
+ widget.destroy( offline );
338
+ delete this.instances[ widget.id ];
339
+ this.fire( 'instanceDestroyed', widget );
340
+ },
341
+
342
+ /**
343
+ * Destroys all widget instances.
344
+ *
345
+ * @param {Boolean} [offline] Whether the widgets are offline (detached from the DOM tree) —
346
+ * in this case the DOM (attributes, classes, etc.) will not be cleaned up.
347
+ * @param {CKEDITOR.dom.element} [container] The container within widgets will be destroyed.
348
+ * This option will be ignored if the `offline` flag was set to `true`, because in such case
349
+ * it is not possible to find widgets within the passed block.
350
+ */
351
+ destroyAll: function( offline, container ) {
352
+ var widget,
353
+ id,
354
+ instances = this.instances;
355
+
356
+ if ( container && !offline ) {
357
+ var wrappers = container.find( '.cke_widget_wrapper' ),
358
+ l = wrappers.count(),
359
+ i = 0;
360
+
361
+ // Length is constant, because this is not a live node list.
362
+ // Note: since querySelectorAll returns nodes in document order,
363
+ // outer widgets are always placed before their nested widgets and therefore
364
+ // are destroyed before them.
365
+ for ( ; i < l; ++i ) {
366
+ widget = this.getByElement( wrappers.getItem( i ), true );
367
+ // Widget might not be found, because it could be a nested widget,
368
+ // which would be destroyed when destroying its parent.
369
+ if ( widget )
370
+ this.destroy( widget );
371
+ }
372
+
373
+ return;
374
+ }
375
+
376
+ for ( id in instances ) {
377
+ widget = instances[ id ];
378
+ this.destroy( widget, offline );
379
+ }
380
+ },
381
+
382
+ /**
383
+ * Finalizes a process of widget creation. This includes:
384
+ *
385
+ * * inserting widget element into editor,
386
+ * * marking widget instance as ready (see {@link CKEDITOR.plugins.widget#event-ready}),
387
+ * * focusing widget instance.
388
+ *
389
+ * This method is used by the default widget's command and is called
390
+ * after widget's dialog (if set) is closed. It may also be used in a
391
+ * customized process of widget creation and insertion.
392
+ *
393
+ * widget.once( 'edit', function() {
394
+ * // Finalize creation only of not ready widgets.
395
+ * if ( widget.isReady() )
396
+ * return;
397
+ *
398
+ * // Cancel edit event to prevent automatic widget insertion.
399
+ * evt.cancel();
400
+ *
401
+ * CustomDialog.open( widget.data, function saveCallback( savedData ) {
402
+ * // Cache the container, because widget may be destroyed while saving data,
403
+ * // if this process will require some deep transformations.
404
+ * var container = widget.wrapper.getParent();
405
+ *
406
+ * widget.setData( savedData );
407
+ *
408
+ * // Widget will be retrieved from container and inserted into editor.
409
+ * editor.widgets.finalizeCreation( container );
410
+ * } );
411
+ * } );
412
+ *
413
+ * @param {CKEDITOR.dom.element/CKEDITOR.dom.documentFragment} container The element
414
+ * or document fragment which contains widget wrapper. The container is used, so before
415
+ * finalizing creation the widget can be freely transformed (even destroyed and reinitialized).
416
+ */
417
+ finalizeCreation: function( container ) {
418
+ var wrapper = container.getFirst();
419
+ if ( wrapper && Widget.isDomWidgetWrapper( wrapper ) ) {
420
+ this.editor.insertElement( wrapper );
421
+
422
+ var widget = this.getByElement( wrapper );
423
+ // Fire postponed #ready event.
424
+ widget.ready = true;
425
+ widget.fire( 'ready' );
426
+ widget.focus();
427
+ }
428
+ },
429
+
430
+ /**
431
+ * Finds a widget instance which contains a given element. The element will be the {@link CKEDITOR.plugins.widget#wrapper wrapper}
432
+ * of the returned widget or a descendant of this {@link CKEDITOR.plugins.widget#wrapper wrapper}.
433
+ *
434
+ * editor.widgets.getByElement( someWidget.wrapper ); // -> someWidget
435
+ * editor.widgets.getByElement( someWidget.parts.caption ); // -> someWidget
436
+ *
437
+ * // Check wrapper only:
438
+ * editor.widgets.getByElement( someWidget.wrapper, true ); // -> someWidget
439
+ * editor.widgets.getByElement( someWidget.parts.caption, true ); // -> null
440
+ *
441
+ * @param {CKEDITOR.dom.element} element The element to be checked.
442
+ * @param {Boolean} [checkWrapperOnly] If set to `true`, the method will not check wrappers' descendants.
443
+ * @returns {CKEDITOR.plugins.widget} The widget instance or `null`.
444
+ */
445
+ getByElement: ( function() {
446
+ var validWrapperElements = { div: 1, span: 1 };
447
+ function getWidgetId( element ) {
448
+ return element.is( validWrapperElements ) && element.data( 'cke-widget-id' );
449
+ }
450
+
451
+ return function( element, checkWrapperOnly ) {
452
+ if ( !element )
453
+ return null;
454
+
455
+ var id = getWidgetId( element );
456
+
457
+ // There's no need to check element parents if element is a wrapper.
458
+ if ( !checkWrapperOnly && !id ) {
459
+ var limit = this.editor.editable();
460
+
461
+ // Try to find a closest ascendant which is a widget wrapper.
462
+ do {
463
+ element = element.getParent();
464
+ } while ( element && !element.equals( limit ) && !( id = getWidgetId( element ) ) );
465
+ }
466
+
467
+ return this.instances[ id ] || null;
468
+ };
469
+ } )(),
470
+
471
+ /**
472
+ * Initializes a widget on a given element if the widget has not been initialized on it yet.
473
+ *
474
+ * @param {CKEDITOR.dom.element} element The future widget element.
475
+ * @param {String/CKEDITOR.plugins.widget.definition} [widgetDef] Name of a widget or a widget definition.
476
+ * The widget definition should be previously registered by using the
477
+ * {@link CKEDITOR.plugins.widget.repository#add} method.
478
+ * @param [startupData] Widget startup data (has precedence over default one).
479
+ * @returns {CKEDITOR.plugins.widget} The widget instance or `null` if a widget could not be initialized on
480
+ * a given element.
481
+ */
482
+ initOn: function( element, widgetDef, startupData ) {
483
+ if ( !widgetDef )
484
+ widgetDef = this.registered[ element.data( 'widget' ) ];
485
+ else if ( typeof widgetDef == 'string' )
486
+ widgetDef = this.registered[ widgetDef ];
487
+
488
+ if ( !widgetDef )
489
+ return null;
490
+
491
+ // Wrap element if still wasn't wrapped (was added during runtime by method that skips dataProcessor).
492
+ var wrapper = this.wrapElement( element, widgetDef.name );
493
+
494
+ if ( wrapper ) {
495
+ // Check if widget wrapper is new (widget hasn't been initialized on it yet).
496
+ // This class will be removed by widget constructor to avoid locking snapshot twice.
497
+ if ( wrapper.hasClass( 'cke_widget_new' ) ) {
498
+ var widget = new Widget( this, this._.nextId++, element, widgetDef, startupData );
499
+
500
+ // Widget could be destroyed when initializing it.
501
+ if ( widget.isInited() ) {
502
+ this.instances[ widget.id ] = widget;
503
+
504
+ return widget;
505
+ } else {
506
+ return null;
507
+ }
508
+ }
509
+
510
+ // Widget already has been initialized, so try to get widget by element.
511
+ // Note - it may happen that other instance will returned than the one created above,
512
+ // if for example widget was destroyed and reinitialized.
513
+ return this.getByElement( element );
514
+ }
515
+
516
+ // No wrapper means that there's no widget for this element.
517
+ return null;
518
+ },
519
+
520
+ /**
521
+ * Initializes widgets on all elements which were wrapped by {@link #wrapElement} and
522
+ * have not been initialized yet.
523
+ *
524
+ * @param {CKEDITOR.dom.element} [container=editor.editable()] The container which will be checked for not
525
+ * initialized widgets. Defaults to editor's {@link CKEDITOR.editor#editable editable} element.
526
+ * @returns {CKEDITOR.plugins.widget[]} Array of widget instances which have been initialized.
527
+ * Note: Only first-level widgets are returned &mdash; without nested widgets.
528
+ */
529
+ initOnAll: function( container ) {
530
+ var newWidgets = ( container || this.editor.editable() ).find( '.cke_widget_new' ),
531
+ newInstances = [],
532
+ instance;
533
+
534
+ for ( var i = newWidgets.count(); i--; ) {
535
+ instance = this.initOn( newWidgets.getItem( i ).getFirst( Widget.isDomWidgetElement ) );
536
+ if ( instance )
537
+ newInstances.push( instance );
538
+ }
539
+
540
+ return newInstances;
541
+ },
542
+
543
+ /**
544
+ * Allows to listen to events on specific types of widgets, even if they are not created yet.
545
+ *
546
+ * Please note that this method inherits parameters from the {@link CKEDITOR.event#method-on} method with one
547
+ * extra parameter at the beginning which is the widget name.
548
+ *
549
+ * editor.widgets.onWidget( 'image', 'action', function( evt ) {
550
+ * // Event `action` occurs on `image` widget.
551
+ * } );
552
+ *
553
+ * @since 4.5
554
+ * @param {String} widgetName
555
+ * @param {String} eventName
556
+ * @param {Function} listenerFunction
557
+ * @param {Object} [scopeObj]
558
+ * @param {Object} [listenerData]
559
+ * @param {Number} [priority=10]
560
+ */
561
+ onWidget: function( widgetName ) {
562
+ var args = Array.prototype.slice.call( arguments );
563
+
564
+ args.shift();
565
+
566
+ for ( var i in this.instances ) {
567
+ var instance = this.instances[ i ];
568
+
569
+ if ( instance.name == widgetName ) {
570
+ instance.on.apply( instance, args );
571
+ }
572
+ }
573
+
574
+ this.on( 'instanceCreated', function( evt ) {
575
+ var widget = evt.data;
576
+
577
+ if ( widget.name == widgetName ) {
578
+ widget.on.apply( widget, args );
579
+ }
580
+ } );
581
+ },
582
+
583
+ /**
584
+ * Parses element classes string and returns an object
585
+ * whose keys contain class names. Skips all `cke_*` classes.
586
+ *
587
+ * This method is used by the {@link CKEDITOR.plugins.widget#getClasses} method and
588
+ * may be used when overriding that method.
589
+ *
590
+ * @since 4.4
591
+ * @param {String} classes String (value of `class` attribute).
592
+ * @returns {Object} Object containing classes or `null` if no classes found.
593
+ */
594
+ parseElementClasses: function( classes ) {
595
+ if ( !classes )
596
+ return null;
597
+
598
+ classes = CKEDITOR.tools.trim( classes ).split( /\s+/ );
599
+
600
+ var cl,
601
+ obj = {},
602
+ hasClasses = 0;
603
+
604
+ while ( ( cl = classes.pop() ) ) {
605
+ if ( cl.indexOf( 'cke_' ) == -1 )
606
+ obj[ cl ] = hasClasses = 1;
607
+ }
608
+
609
+ return hasClasses ? obj : null;
610
+ },
611
+
612
+ /**
613
+ * Wraps an element with a widget's non-editable container.
614
+ *
615
+ * If this method is called on an {@link CKEDITOR.htmlParser.element}, then it will
616
+ * also take care of fixing the DOM after wrapping (the wrapper may not be allowed in element's parent).
617
+ *
618
+ * @param {CKEDITOR.dom.element/CKEDITOR.htmlParser.element} element The widget element to be wrapped.
619
+ * @param {String} [widgetName] The name of the widget definition. Defaults to element's `data-widget`
620
+ * attribute value.
621
+ * @returns {CKEDITOR.dom.element/CKEDITOR.htmlParser.element} The wrapper element or `null` if
622
+ * the widget definition of this name is not registered.
623
+ */
624
+ wrapElement: function( element, widgetName ) {
625
+ var wrapper = null,
626
+ widgetDef,
627
+ isInline;
628
+
629
+ if ( element instanceof CKEDITOR.dom.element ) {
630
+ widgetName = widgetName || element.data( 'widget' );
631
+ widgetDef = this.registered[ widgetName ];
632
+
633
+ if ( !widgetDef )
634
+ return null;
635
+
636
+ // Do not wrap already wrapped element.
637
+ wrapper = element.getParent();
638
+ if ( wrapper && wrapper.type == CKEDITOR.NODE_ELEMENT && wrapper.data( 'cke-widget-wrapper' ) )
639
+ return wrapper;
640
+
641
+ // If attribute isn't already set (e.g. for pasted widget), set it.
642
+ if ( !element.hasAttribute( 'data-cke-widget-keep-attr' ) )
643
+ element.data( 'cke-widget-keep-attr', element.data( 'widget' ) ? 1 : 0 );
644
+
645
+ element.data( 'widget', widgetName );
646
+
647
+ isInline = isWidgetInline( widgetDef, element.getName() );
648
+
649
+ wrapper = new CKEDITOR.dom.element( isInline ? 'span' : 'div' );
650
+ wrapper.setAttributes( getWrapperAttributes( isInline, widgetName ) );
651
+
652
+ wrapper.data( 'cke-display-name', widgetDef.pathName ? widgetDef.pathName : element.getName() );
653
+
654
+ // Replace element unless it is a detached one.
655
+ if ( element.getParent( true ) )
656
+ wrapper.replace( element );
657
+ element.appendTo( wrapper );
658
+ }
659
+ else if ( element instanceof CKEDITOR.htmlParser.element ) {
660
+ widgetName = widgetName || element.attributes[ 'data-widget' ];
661
+ widgetDef = this.registered[ widgetName ];
662
+
663
+ if ( !widgetDef )
664
+ return null;
665
+
666
+ wrapper = element.parent;
667
+ if ( wrapper && wrapper.type == CKEDITOR.NODE_ELEMENT && wrapper.attributes[ 'data-cke-widget-wrapper' ] )
668
+ return wrapper;
669
+
670
+ // If attribute isn't already set (e.g. for pasted widget), set it.
671
+ if ( !( 'data-cke-widget-keep-attr' in element.attributes ) )
672
+ element.attributes[ 'data-cke-widget-keep-attr' ] = element.attributes[ 'data-widget' ] ? 1 : 0;
673
+ if ( widgetName )
674
+ element.attributes[ 'data-widget' ] = widgetName;
675
+
676
+ isInline = isWidgetInline( widgetDef, element.name );
677
+
678
+ wrapper = new CKEDITOR.htmlParser.element( isInline ? 'span' : 'div', getWrapperAttributes( isInline, widgetName ) );
679
+ wrapper.attributes[ 'data-cke-display-name' ] = widgetDef.pathName ? widgetDef.pathName : element.name;
680
+
681
+ var parent = element.parent,
682
+ index;
683
+
684
+ // Don't detach already detached element.
685
+ if ( parent ) {
686
+ index = element.getIndex();
687
+ element.remove();
688
+ }
689
+
690
+ wrapper.add( element );
691
+
692
+ // Insert wrapper fixing DOM (splitting parents if wrapper is not allowed inside them).
693
+ parent && insertElement( parent, index, wrapper );
694
+ }
695
+
696
+ return wrapper;
697
+ },
698
+
699
+ // Expose for tests.
700
+ _tests_createEditableFilter: createEditableFilter
701
+ };
702
+
703
+ CKEDITOR.event.implementOn( Repository.prototype );
704
+
705
+ /**
706
+ * An event fired when a widget instance is created, but before it is fully initialized.
707
+ *
708
+ * @event instanceCreated
709
+ * @param {CKEDITOR.plugins.widget} data The widget instance.
710
+ */
711
+
712
+ /**
713
+ * An event fired when a widget instance was destroyed.
714
+ *
715
+ * See also {@link CKEDITOR.plugins.widget#event-destroy}.
716
+ *
717
+ * @event instanceDestroyed
718
+ * @param {CKEDITOR.plugins.widget} data The widget instance.
719
+ */
720
+
721
+ /**
722
+ * An event fired to trigger the selection check.
723
+ *
724
+ * See the {@link #method-checkSelection} method.
725
+ *
726
+ * @event checkSelection
727
+ */
728
+
729
+ /**
730
+ * An event fired by the the {@link #method-checkWidgets} method.
731
+ *
732
+ * It can be canceled in order to stop the {@link #method-checkWidgets}
733
+ * method execution or the event listener can modify the method's options.
734
+ *
735
+ * @event checkWidgets
736
+ * @param [data]
737
+ * @param {Boolean} [data.initOnlyNew] Initialize widgets only on newly wrapped
738
+ * widget elements (those which still have the `cke_widget_new` class). When this option is
739
+ * set to `true`, widgets which were invalidated (e.g. by replacing with a cloned DOM structure)
740
+ * will not be reinitialized. This makes the check faster.
741
+ * @param {Boolean} [data.focusInited] If only one widget is initialized by
742
+ * the method, it will be focused.
743
+ */
744
+
745
+
746
+ /**
747
+ * An instance of a widget. Together with {@link CKEDITOR.plugins.widget.repository} these
748
+ * two classes constitute the core of the Widget System.
749
+ *
750
+ * Note that neither the repository nor the widget instances can be created by using their constructors.
751
+ * A repository instance is automatically set up by the Widget plugin and is accessible under
752
+ * {@link CKEDITOR.editor#widgets}, while widget instances are created and destroyed by the repository.
753
+ *
754
+ * To create a widget, first you need to {@link CKEDITOR.plugins.widget.repository#add register} its
755
+ * {@link CKEDITOR.plugins.widget.definition definition}:
756
+ *
757
+ * editor.widgets.add( 'simplebox', {
758
+ * upcast: function( element ) {
759
+ * // Defines which elements will become widgets.
760
+ * if ( element.hasClass( 'simplebox' ) )
761
+ * return true;
762
+ * },
763
+ * init: function() {
764
+ * // ...
765
+ * }
766
+ * } );
767
+ *
768
+ * Once the widget definition is registered, widgets will be automatically
769
+ * created when loading data:
770
+ *
771
+ * editor.setData( '<div class="simplebox">foo</div>', function() {
772
+ * console.log( editor.widgets.instances ); // -> An object containing one instance.
773
+ * } );
774
+ *
775
+ * It is also possible to create instances during runtime by using a command
776
+ * (if a {@link CKEDITOR.plugins.widget.definition#template} property was defined):
777
+ *
778
+ * // You can execute an automatically defined command to
779
+ * // insert a new simplebox widget or edit the one currently focused.
780
+ * editor.execCommand( 'simplebox' );
781
+ *
782
+ * Note: Since CKEditor 4.5 widget's `startupData` can be passed as the command argument:
783
+ *
784
+ * editor.execCommand( 'simplebox', {
785
+ * startupData: {
786
+ * align: 'left'
787
+ * }
788
+ * } );
789
+ *
790
+ * A widget can also be created in a completely custom way:
791
+ *
792
+ * var element = editor.document.createElement( 'div' );
793
+ * editor.insertElement( element );
794
+ * var widget = editor.widgets.initOn( element, 'simplebox' );
795
+ *
796
+ * @since 4.3
797
+ * @class CKEDITOR.plugins.widget
798
+ * @mixins CKEDITOR.event
799
+ * @extends CKEDITOR.plugins.widget.definition
800
+ * @constructor Creates an instance of the widget class. Do not use it directly, but instead initialize widgets
801
+ * by using the {@link CKEDITOR.plugins.widget.repository#initOn} method or by the upcasting system.
802
+ * @param {CKEDITOR.plugins.widget.repository} widgetsRepo
803
+ * @param {Number} id Unique ID of this widget instance.
804
+ * @param {CKEDITOR.dom.element} element The widget element.
805
+ * @param {CKEDITOR.plugins.widget.definition} widgetDef Widget's registered definition.
806
+ * @param [startupData] Initial widget data. This data object will overwrite the default data and
807
+ * the data loaded from the DOM.
808
+ */
809
+ function Widget( widgetsRepo, id, element, widgetDef, startupData ) {
810
+ var editor = widgetsRepo.editor;
811
+
812
+ // Extend this widget with widgetDef-specific methods and properties.
813
+ CKEDITOR.tools.extend( this, widgetDef, {
814
+ /**
815
+ * The editor instance.
816
+ *
817
+ * @readonly
818
+ * @property {CKEDITOR.editor}
819
+ */
820
+ editor: editor,
821
+
822
+ /**
823
+ * This widget's unique (per editor instance) ID.
824
+ *
825
+ * @readonly
826
+ * @property {Number}
827
+ */
828
+ id: id,
829
+
830
+ /**
831
+ * Whether this widget is an inline widget (based on an inline element unless
832
+ * forced otherwise by {@link CKEDITOR.plugins.widget.definition#inline}).
833
+ *
834
+ * **Note:** This option does not allow to turn a block element into an inline widget.
835
+ * However, it makes it possible to turn an inline element into a block widget or to
836
+ * force a correct type in case when automatic recognition fails.
837
+ *
838
+ * @readonly
839
+ * @property {Boolean}
840
+ */
841
+ inline: element.getParent().getName() == 'span',
842
+
843
+ /**
844
+ * The widget element &mdash; the element on which the widget was initialized.
845
+ *
846
+ * @readonly
847
+ * @property {CKEDITOR.dom.element} element
848
+ */
849
+ element: element,
850
+
851
+ /**
852
+ * Widget's data object.
853
+ *
854
+ * The data can only be set by using the {@link #setData} method.
855
+ * Changes made to the data fire the {@link #event-data} event.
856
+ *
857
+ * @readonly
858
+ */
859
+ data: CKEDITOR.tools.extend( {}, typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults ),
860
+
861
+ /**
862
+ * Indicates if a widget is data-ready. Set to `true` when data from all sources
863
+ * ({@link CKEDITOR.plugins.widget.definition#defaults}, set in the
864
+ * {@link #init} method, loaded from the widget's element and startup data coming from the constructor)
865
+ * are finally loaded. This is immediately followed by the first {@link #event-data}.
866
+ *
867
+ * @readonly
868
+ */
869
+ dataReady: false,
870
+
871
+ /**
872
+ * Whether a widget instance was initialized. This means that:
873
+ *
874
+ * * An instance was created,
875
+ * * Its properties were set,
876
+ * * The `init` method was executed.
877
+ *
878
+ * **Note**: The first {@link #event-data} event could not be fired yet which
879
+ * means that the widget's DOM has not been set up yet. Wait for the {@link #event-ready}
880
+ * event to be notified when a widget is fully initialized and ready.
881
+ *
882
+ * **Note**: Use the {@link #isInited} method to check whether a widget is initialized and
883
+ * has not been destroyed.
884
+ *
885
+ * @readonly
886
+ */
887
+ inited: false,
888
+
889
+ /**
890
+ * Whether a widget instance is ready. This means that the widget is {@link #inited} and
891
+ * that its DOM was finally set up.
892
+ *
893
+ * **Note:** Use the {@link #isReady} method to check whether a widget is ready and
894
+ * has not been destroyed.
895
+ *
896
+ * @readonly
897
+ */
898
+ ready: false,
899
+
900
+ // Revert what widgetDef could override (automatic #edit listener).
901
+ edit: Widget.prototype.edit,
902
+
903
+ /**
904
+ * The nested editable element which is currently focused.
905
+ *
906
+ * @readonly
907
+ * @property {CKEDITOR.plugins.widget.nestedEditable}
908
+ */
909
+ focusedEditable: null,
910
+
911
+ /**
912
+ * The widget definition from which this instance was created.
913
+ *
914
+ * @readonly
915
+ * @property {CKEDITOR.plugins.widget.definition} definition
916
+ */
917
+ definition: widgetDef,
918
+
919
+ /**
920
+ * Link to the widget repository which created this instance.
921
+ *
922
+ * @readonly
923
+ * @property {CKEDITOR.plugins.widget.repository} repository
924
+ */
925
+ repository: widgetsRepo,
926
+
927
+ draggable: widgetDef.draggable !== false,
928
+
929
+ // WAAARNING: Overwrite widgetDef's priv object, because otherwise violent unicorn's gonna visit you.
930
+ _: {
931
+ downcastFn: ( widgetDef.downcast && typeof widgetDef.downcast == 'string' ) ?
932
+ widgetDef.downcasts[ widgetDef.downcast ] : widgetDef.downcast
933
+ }
934
+ }, true );
935
+
936
+ /**
937
+ * An object of widget component elements.
938
+ *
939
+ * For every `partName => selector` pair in {@link CKEDITOR.plugins.widget.definition#parts},
940
+ * one `partName => element` pair is added to this object during the widget initialization.
941
+ *
942
+ * @readonly
943
+ * @property {Object} parts
944
+ */
945
+
946
+ /**
947
+ * The template which will be used to create a new widget element (when the widget's command is executed).
948
+ * It will be populated with {@link #defaults default values}.
949
+ *
950
+ * @readonly
951
+ * @property {CKEDITOR.template} template
952
+ */
953
+
954
+ /**
955
+ * The widget wrapper &mdash; a non-editable `div` or `span` element (depending on {@link #inline})
956
+ * which is a parent of the {@link #element} and widget compontents like the drag handler and the {@link #mask}.
957
+ * It is the outermost widget element.
958
+ *
959
+ * @readonly
960
+ * @property {CKEDITOR.dom.element} wrapper
961
+ */
962
+
963
+ widgetsRepo.fire( 'instanceCreated', this );
964
+
965
+ setupWidget( this, widgetDef );
966
+
967
+ this.init && this.init();
968
+
969
+ // Finally mark widget as inited.
970
+ this.inited = true;
971
+
972
+ setupWidgetData( this, startupData );
973
+
974
+ // If at some point (e.g. in #data listener) widget hasn't been destroyed
975
+ // and widget is already attached to document then fire #ready.
976
+ if ( this.isInited() && editor.editable().contains( this.wrapper ) ) {
977
+ this.ready = true;
978
+ this.fire( 'ready' );
979
+ }
980
+ }
981
+
982
+ Widget.prototype = {
983
+ /**
984
+ * Adds a class to the widget element. This method is used by
985
+ * the {@link #applyStyle} method and should be overridden by widgets
986
+ * which should handle classes differently (e.g. add them to other elements).
987
+ *
988
+ * Since 4.6.0 this method also adds a corresponding class prefixed with {@link #WRAPPER_CLASS_PREFIX}
989
+ * to the widget wrapper element.
990
+ *
991
+ * **Note**: This method should not be used directly. Use the {@link #setData} method to
992
+ * set the `classes` property. Read more in the {@link #setData} documentation.
993
+ *
994
+ * See also: {@link #removeClass}, {@link #hasClass}, {@link #getClasses}.
995
+ *
996
+ * @since 4.4
997
+ * @param {String} className The class name to be added.
998
+ */
999
+ addClass: function( className ) {
1000
+ this.element.addClass( className );
1001
+ this.wrapper.addClass( Widget.WRAPPER_CLASS_PREFIX + className );
1002
+ },
1003
+
1004
+ /**
1005
+ * Applies the specified style to the widget. It is highly recommended to use the
1006
+ * {@link CKEDITOR.editor#applyStyle} or {@link CKEDITOR.style#apply} methods instead of
1007
+ * using this method directly, because unlike editor's and style's methods, this one
1008
+ * does not perform any checks.
1009
+ *
1010
+ * By default this method handles only classes defined in the style. It clones existing
1011
+ * classes which are stored in the {@link #property-data widget data}'s `classes` property,
1012
+ * adds new classes, and calls the {@link #setData} method if at least one new class was added.
1013
+ * Then, using the {@link #event-data} event listener widget applies modifications passing
1014
+ * new classes to the {@link #addClass} method.
1015
+ *
1016
+ * If you need to handle classes differently than in the default way, you can override the
1017
+ * {@link #addClass} and related methods. You can also handle other style properties than `classes`
1018
+ * by overriding this method.
1019
+ *
1020
+ * See also: {@link #checkStyleActive}, {@link #removeStyle}.
1021
+ *
1022
+ * @since 4.4
1023
+ * @param {CKEDITOR.style} style The custom widget style to be applied.
1024
+ */
1025
+ applyStyle: function( style ) {
1026
+ applyRemoveStyle( this, style, 1 );
1027
+ },
1028
+
1029
+ /**
1030
+ * Checks if the specified style is applied to this widget. It is highly recommended to use the
1031
+ * {@link CKEDITOR.style#checkActive} method instead of using this method directly,
1032
+ * because unlike style's method, this one does not perform any checks.
1033
+ *
1034
+ * By default this method handles only classes defined in the style and passes
1035
+ * them to the {@link #hasClass} method. You can override these methods to handle classes
1036
+ * differently or to handle more of the style properties.
1037
+ *
1038
+ * See also: {@link #applyStyle}, {@link #removeStyle}.
1039
+ *
1040
+ * @since 4.4
1041
+ * @param {CKEDITOR.style} style The custom widget style to be checked.
1042
+ * @returns {Boolean} Whether the style is applied to this widget.
1043
+ */
1044
+ checkStyleActive: function( style ) {
1045
+ var classes = getStyleClasses( style ),
1046
+ cl;
1047
+
1048
+ if ( !classes )
1049
+ return false;
1050
+
1051
+ while ( ( cl = classes.pop() ) ) {
1052
+ if ( !this.hasClass( cl ) )
1053
+ return false;
1054
+ }
1055
+ return true;
1056
+ },
1057
+
1058
+ /**
1059
+ * Destroys this widget instance.
1060
+ *
1061
+ * Use {@link CKEDITOR.plugins.widget.repository#destroy} when possible instead of this method.
1062
+ *
1063
+ * This method fires the {#event-destroy} event.
1064
+ *
1065
+ * @param {Boolean} [offline] Whether a widget is offline (detached from the DOM tree) &mdash;
1066
+ * in this case the DOM (attributes, classes, etc.) will not be cleaned up.
1067
+ */
1068
+ destroy: function( offline ) {
1069
+ this.fire( 'destroy' );
1070
+
1071
+ if ( this.editables ) {
1072
+ for ( var name in this.editables )
1073
+ this.destroyEditable( name, offline );
1074
+ }
1075
+
1076
+ if ( !offline ) {
1077
+ if ( this.element.data( 'cke-widget-keep-attr' ) == '0' )
1078
+ this.element.removeAttribute( 'data-widget' );
1079
+ this.element.removeAttributes( [ 'data-cke-widget-data', 'data-cke-widget-keep-attr' ] );
1080
+ this.element.removeClass( 'cke_widget_element' );
1081
+ this.element.replace( this.wrapper );
1082
+ }
1083
+
1084
+ this.wrapper = null;
1085
+ },
1086
+
1087
+ /**
1088
+ * Destroys a nested editable and all nested widgets.
1089
+ *
1090
+ * @param {String} editableName Nested editable name.
1091
+ * @param {Boolean} [offline] See {@link #method-destroy} method.
1092
+ */
1093
+ destroyEditable: function( editableName, offline ) {
1094
+ var editable = this.editables[ editableName ];
1095
+
1096
+ editable.removeListener( 'focus', onEditableFocus );
1097
+ editable.removeListener( 'blur', onEditableBlur );
1098
+ this.editor.focusManager.remove( editable );
1099
+
1100
+ if ( !offline ) {
1101
+ this.repository.destroyAll( false, editable );
1102
+ editable.removeClass( 'cke_widget_editable' );
1103
+ editable.removeClass( 'cke_widget_editable_focused' );
1104
+ editable.removeAttributes( [ 'contenteditable', 'data-cke-widget-editable', 'data-cke-enter-mode' ] );
1105
+ }
1106
+
1107
+ delete this.editables[ editableName ];
1108
+ },
1109
+
1110
+ /**
1111
+ * Starts widget editing.
1112
+ *
1113
+ * This method fires the {@link CKEDITOR.plugins.widget#event-edit} event
1114
+ * which may be canceled in order to prevent it from opening a dialog window.
1115
+ *
1116
+ * The dialog window name is obtained from the event's data `dialog` property or
1117
+ * from {@link CKEDITOR.plugins.widget.definition#dialog}.
1118
+ *
1119
+ * @returns {Boolean} Returns `true` if a dialog window was opened.
1120
+ */
1121
+ edit: function() {
1122
+ var evtData = { dialog: this.dialog },
1123
+ that = this;
1124
+
1125
+ // Edit event was blocked or there's no dialog to be automatically opened.
1126
+ if ( this.fire( 'edit', evtData ) === false || !evtData.dialog )
1127
+ return false;
1128
+
1129
+ this.editor.openDialog( evtData.dialog, function( dialog ) {
1130
+ var showListener,
1131
+ okListener;
1132
+
1133
+ // Allow to add a custom dialog handler.
1134
+ if ( that.fire( 'dialog', dialog ) === false )
1135
+ return;
1136
+
1137
+ showListener = dialog.on( 'show', function() {
1138
+ dialog.setupContent( that );
1139
+ } );
1140
+
1141
+ okListener = dialog.on( 'ok', function() {
1142
+ // Commit dialog's fields, but prevent from
1143
+ // firing data event for every field. Fire only one,
1144
+ // bulk event at the end.
1145
+ var dataChanged,
1146
+ dataListener = that.on( 'data', function( evt ) {
1147
+ dataChanged = 1;
1148
+ evt.cancel();
1149
+ }, null, null, 0 );
1150
+
1151
+ // Create snapshot preceeding snapshot with changed widget...
1152
+ // TODO it should not be required, but it is and I found similar
1153
+ // code in dialog#ok listener in dialog/plugin.js.
1154
+ that.editor.fire( 'saveSnapshot' );
1155
+ dialog.commitContent( that );
1156
+
1157
+ dataListener.removeListener();
1158
+ if ( dataChanged ) {
1159
+ that.fire( 'data', that.data );
1160
+ that.editor.fire( 'saveSnapshot' );
1161
+ }
1162
+ } );
1163
+
1164
+ dialog.once( 'hide', function() {
1165
+ showListener.removeListener();
1166
+ okListener.removeListener();
1167
+ } );
1168
+ } );
1169
+
1170
+ return true;
1171
+ },
1172
+
1173
+ /**
1174
+ * Returns widget element classes parsed to an object. This method
1175
+ * is used to populate the `classes` property of widget's {@link #property-data}.
1176
+ *
1177
+ * This method reuses {@link CKEDITOR.plugins.widget.repository#parseElementClasses}.
1178
+ * It should be overriden if a widget should handle classes differently (e.g. on other elements).
1179
+ *
1180
+ * See also: {@link #removeClass}, {@link #addClass}, {@link #hasClass}.
1181
+ *
1182
+ * @since 4.4
1183
+ * @returns {Object}
1184
+ */
1185
+ getClasses: function() {
1186
+ return this.repository.parseElementClasses( this.element.getAttribute( 'class' ) );
1187
+ },
1188
+
1189
+ /**
1190
+ * Checks if the widget element has specified class. This method is used by
1191
+ * the {@link #checkStyleActive} method and should be overriden by widgets
1192
+ * which should handle classes differently (e.g. on other elements).
1193
+ *
1194
+ * See also: {@link #removeClass}, {@link #addClass}, {@link #getClasses}.
1195
+ *
1196
+ * @since 4.4
1197
+ * @param {String} className The class to be checked.
1198
+ * @param {Boolean} Whether a widget has specified class.
1199
+ */
1200
+ hasClass: function( className ) {
1201
+ return this.element.hasClass( className );
1202
+ },
1203
+
1204
+ /**
1205
+ * Initializes a nested editable.
1206
+ *
1207
+ * **Note**: Only elements from {@link CKEDITOR.dtd#$editable} may become editables.
1208
+ *
1209
+ * @param {String} editableName The nested editable name.
1210
+ * @param {CKEDITOR.plugins.widget.nestedEditable.definition} definition The definition of the nested editable.
1211
+ * @returns {Boolean} Whether an editable was successfully initialized.
1212
+ */
1213
+ initEditable: function( editableName, definition ) {
1214
+ // Don't fetch just first element which matched selector but look for a correct one. (http://dev.ckeditor.com/ticket/13334)
1215
+ var editable = this._findOneNotNested( definition.selector );
1216
+
1217
+ if ( editable && editable.is( CKEDITOR.dtd.$editable ) ) {
1218
+ editable = new NestedEditable( this.editor, editable, {
1219
+ filter: createEditableFilter.call( this.repository, this.name, editableName, definition )
1220
+ } );
1221
+ this.editables[ editableName ] = editable;
1222
+
1223
+ editable.setAttributes( {
1224
+ contenteditable: 'true',
1225
+ 'data-cke-widget-editable': editableName,
1226
+ 'data-cke-enter-mode': editable.enterMode
1227
+ } );
1228
+
1229
+ if ( editable.filter )
1230
+ editable.data( 'cke-filter', editable.filter.id );
1231
+
1232
+ editable.addClass( 'cke_widget_editable' );
1233
+ // This class may be left when d&ding widget which
1234
+ // had focused editable. Clean this class here, not in
1235
+ // cleanUpWidgetElement for performance and code size reasons.
1236
+ editable.removeClass( 'cke_widget_editable_focused' );
1237
+
1238
+ if ( definition.pathName )
1239
+ editable.data( 'cke-display-name', definition.pathName );
1240
+
1241
+ this.editor.focusManager.add( editable );
1242
+ editable.on( 'focus', onEditableFocus, this );
1243
+ CKEDITOR.env.ie && editable.on( 'blur', onEditableBlur, this );
1244
+
1245
+ // Finally, process editable's data. This data wasn't processed when loading
1246
+ // editor's data, becuase they need to be processed separately, with its own filters and settings.
1247
+ editable._.initialSetData = true;
1248
+ editable.setData( editable.getHtml() );
1249
+
1250
+ return true;
1251
+ }
1252
+
1253
+ return false;
1254
+ },
1255
+
1256
+ /**
1257
+ * Looks inside wrapper element to find a node that
1258
+ * matches given selector and is not nested in other widget. (http://dev.ckeditor.com/ticket/13334)
1259
+ *
1260
+ * @since 4.5
1261
+ * @private
1262
+ * @param {String} selector Selector to match.
1263
+ * @returns {CKEDITOR.dom.element} Matched element or `null` if a node has not been found.
1264
+ */
1265
+ _findOneNotNested: function( selector ) {
1266
+ var matchedElements = this.wrapper.find( selector ),
1267
+ match,
1268
+ closestWrapper;
1269
+
1270
+ for ( var i = 0; i < matchedElements.count(); i++ ) {
1271
+ match = matchedElements.getItem( i );
1272
+ closestWrapper = match.getAscendant( Widget.isDomWidgetWrapper );
1273
+
1274
+ // The closest ascendant-wrapper of this match defines to which widget
1275
+ // this match belongs. If the ascendant is this widget's wrapper
1276
+ // it means that the match is not nested in other widget.
1277
+ if ( this.wrapper.equals( closestWrapper ) ) {
1278
+ return match;
1279
+ }
1280
+ }
1281
+
1282
+ return null;
1283
+ },
1284
+
1285
+ /**
1286
+ * Checks if a widget has already been initialized and has not been destroyed yet.
1287
+ *
1288
+ * See {@link #inited} for more details.
1289
+ *
1290
+ * @returns {Boolean}
1291
+ */
1292
+ isInited: function() {
1293
+ return !!( this.wrapper && this.inited );
1294
+ },
1295
+
1296
+ /**
1297
+ * Checks if a widget is ready and has not been destroyed yet.
1298
+ *
1299
+ * See {@link #property-ready} for more details.
1300
+ *
1301
+ * @returns {Boolean}
1302
+ */
1303
+ isReady: function() {
1304
+ return this.isInited() && this.ready;
1305
+ },
1306
+
1307
+ /**
1308
+ * Focuses a widget by selecting it.
1309
+ */
1310
+ focus: function() {
1311
+ var sel = this.editor.getSelection();
1312
+
1313
+ // Fake the selection before focusing editor, to avoid unpreventable viewports scrolling
1314
+ // on Webkit/Blink/IE which is done because there's no selection or selection was somewhere else than widget.
1315
+ if ( sel ) {
1316
+ var isDirty = this.editor.checkDirty();
1317
+
1318
+ sel.fake( this.wrapper );
1319
+
1320
+ !isDirty && this.editor.resetDirty();
1321
+ }
1322
+
1323
+ // Always focus editor (not only when focusManger.hasFocus is false) (because of http://dev.ckeditor.com/ticket/10483).
1324
+ this.editor.focus();
1325
+ },
1326
+
1327
+ /**
1328
+ * Removes a class from the widget element. This method is used by
1329
+ * the {@link #removeStyle} method and should be overriden by widgets
1330
+ * which should handle classes differently (e.g. on other elements).
1331
+ *
1332
+ * **Note**: This method should not be used directly. Use the {@link #setData} method to
1333
+ * set the `classes` property. Read more in the {@link #setData} documentation.
1334
+ *
1335
+ * See also: {@link #hasClass}, {@link #addClass}.
1336
+ *
1337
+ * @since 4.4
1338
+ * @param {String} className The class to be removed.
1339
+ */
1340
+ removeClass: function( className ) {
1341
+ this.element.removeClass( className );
1342
+ this.wrapper.removeClass( Widget.WRAPPER_CLASS_PREFIX + className );
1343
+ },
1344
+
1345
+ /**
1346
+ * Removes the specified style from the widget. It is highly recommended to use the
1347
+ * {@link CKEDITOR.editor#removeStyle} or {@link CKEDITOR.style#remove} methods instead of
1348
+ * using this method directly, because unlike editor's and style's methods, this one
1349
+ * does not perform any checks.
1350
+ *
1351
+ * Read more about how applying/removing styles works in the {@link #applyStyle} method documentation.
1352
+ *
1353
+ * See also {@link #checkStyleActive}, {@link #applyStyle}, {@link #getClasses}.
1354
+ *
1355
+ * @since 4.4
1356
+ * @param {CKEDITOR.style} style The custom widget style to be removed.
1357
+ */
1358
+ removeStyle: function( style ) {
1359
+ applyRemoveStyle( this, style, 0 );
1360
+ },
1361
+
1362
+ /**
1363
+ * Sets widget value(s) in the {@link #property-data} object.
1364
+ * If the given value(s) modifies current ones, the {@link #event-data} event is fired.
1365
+ *
1366
+ * this.setData( 'align', 'left' );
1367
+ * this.data.align; // -> 'left'
1368
+ *
1369
+ * this.setData( { align: 'right', opened: false } );
1370
+ * this.data.align; // -> 'right'
1371
+ * this.data.opened; // -> false
1372
+ *
1373
+ * Set values are stored in {@link #element}'s attribute (`data-cke-widget-data`),
1374
+ * in a JSON string, therefore {@link #property-data} should contain
1375
+ * only serializable data.
1376
+ *
1377
+ * **Note:** A special data property, `classes`, exists. It contains an object with
1378
+ * classes which were returned by the {@link #getClasses} method during the widget initialization.
1379
+ * This property is then used by the {@link #applyStyle} and {@link #removeStyle} methods.
1380
+ * When it is changed (the reference to object must be changed!), the widget updates its classes by
1381
+ * using the {@link #addClass} and {@link #removeClass} methods.
1382
+ *
1383
+ * // Adding a new class.
1384
+ * var classes = CKEDITOR.tools.clone( widget.data.classes );
1385
+ * classes.newClass = 1;
1386
+ * widget.setData( 'classes', classes );
1387
+ *
1388
+ * // Removing a class.
1389
+ * var classes = CKEDITOR.tools.clone( widget.data.classes );
1390
+ * delete classes.newClass;
1391
+ * widget.setData( 'classes', classes );
1392
+ *
1393
+ * @param {String/Object} keyOrData
1394
+ * @param {Object} value
1395
+ * @chainable
1396
+ */
1397
+ setData: function( key, value ) {
1398
+ var data = this.data,
1399
+ modified = 0;
1400
+
1401
+ if ( typeof key == 'string' ) {
1402
+ if ( data[ key ] !== value ) {
1403
+ data[ key ] = value;
1404
+ modified = 1;
1405
+ }
1406
+ }
1407
+ else {
1408
+ var newData = key;
1409
+
1410
+ for ( key in newData ) {
1411
+ if ( data[ key ] !== newData[ key ] ) {
1412
+ modified = 1;
1413
+ data[ key ] = newData[ key ];
1414
+ }
1415
+ }
1416
+ }
1417
+
1418
+ // Block firing data event and overwriting data element before setupWidgetData is executed.
1419
+ if ( modified && this.dataReady ) {
1420
+ writeDataToElement( this );
1421
+ this.fire( 'data', data );
1422
+ }
1423
+
1424
+ return this;
1425
+ },
1426
+
1427
+ /**
1428
+ * Changes the widget's focus state. This method is executed automatically after
1429
+ * a widget was focused by the {@link #method-focus} method or the selection was moved
1430
+ * out of the widget.
1431
+ *
1432
+ * This is a low-level method which is not integrated with e.g. the undo manager.
1433
+ * Use the {@link #method-focus} method instead.
1434
+ *
1435
+ * @param {Boolean} selected Whether to select or deselect this widget.
1436
+ * @chainable
1437
+ */
1438
+ setFocused: function( focused ) {
1439
+ this.wrapper[ focused ? 'addClass' : 'removeClass' ]( 'cke_widget_focused' );
1440
+ this.fire( focused ? 'focus' : 'blur' );
1441
+ return this;
1442
+ },
1443
+
1444
+ /**
1445
+ * Changes the widget's select state. This method is executed automatically after
1446
+ * a widget was selected by the {@link #method-focus} method or the selection
1447
+ * was moved out of the widget.
1448
+ *
1449
+ * This is a low-level method which is not integrated with e.g. the undo manager.
1450
+ * Use the {@link #method-focus} method instead or simply change the selection.
1451
+ *
1452
+ * @param {Boolean} selected Whether to select or deselect this widget.
1453
+ * @chainable
1454
+ */
1455
+ setSelected: function( selected ) {
1456
+ this.wrapper[ selected ? 'addClass' : 'removeClass' ]( 'cke_widget_selected' );
1457
+ this.fire( selected ? 'select' : 'deselect' );
1458
+ return this;
1459
+ },
1460
+
1461
+ /**
1462
+ * Repositions drag handler according to the widget's element position. Should be called from events, like mouseover.
1463
+ */
1464
+ updateDragHandlerPosition: function() {
1465
+ var editor = this.editor,
1466
+ domElement = this.element.$,
1467
+ oldPos = this._.dragHandlerOffset,
1468
+ newPos = {
1469
+ x: domElement.offsetLeft,
1470
+ y: domElement.offsetTop - DRAG_HANDLER_SIZE
1471
+ };
1472
+
1473
+ if ( oldPos && newPos.x == oldPos.x && newPos.y == oldPos.y )
1474
+ return;
1475
+
1476
+ // We need to make sure that dirty state is not changed (http://dev.ckeditor.com/ticket/11487).
1477
+ var initialDirty = editor.checkDirty();
1478
+
1479
+ editor.fire( 'lockSnapshot' );
1480
+ this.dragHandlerContainer.setStyles( {
1481
+ top: newPos.y + 'px',
1482
+ left: newPos.x + 'px',
1483
+ display: 'block'
1484
+ } );
1485
+ editor.fire( 'unlockSnapshot' );
1486
+ !initialDirty && editor.resetDirty();
1487
+
1488
+ this._.dragHandlerOffset = newPos;
1489
+ }
1490
+ };
1491
+
1492
+ CKEDITOR.event.implementOn( Widget.prototype );
1493
+
1494
+ /**
1495
+ * Gets the {@link #isDomNestedEditable nested editable}
1496
+ * (returned as a {@link CKEDITOR.dom.element}, not as a {@link CKEDITOR.plugins.widget.nestedEditable})
1497
+ * closest to the `node` or the `node` if it is a nested editable itself.
1498
+ *
1499
+ * @since 4.5
1500
+ * @static
1501
+ * @param {CKEDITOR.dom.element} guard Stop ancestor search on this node (usually editor's editable).
1502
+ * @param {CKEDITOR.dom.node} node Start the search from this node.
1503
+ * @returns {CKEDITOR.dom.element/null} Element or `null` if not found.
1504
+ */
1505
+ Widget.getNestedEditable = function( guard, node ) {
1506
+ if ( !node || node.equals( guard ) )
1507
+ return null;
1508
+
1509
+ if ( Widget.isDomNestedEditable( node ) )
1510
+ return node;
1511
+
1512
+ return Widget.getNestedEditable( guard, node.getParent() );
1513
+ };
1514
+
1515
+ /**
1516
+ * Checks whether the `node` is a widget's drag handle element.
1517
+ *
1518
+ * @since 4.5
1519
+ * @static
1520
+ * @param {CKEDITOR.dom.node} node
1521
+ * @returns {Boolean}
1522
+ */
1523
+ Widget.isDomDragHandler = function( node ) {
1524
+ return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-drag-handler' );
1525
+ };
1526
+
1527
+ /**
1528
+ * Checks whether the `node` is a container of the widget's drag handle element.
1529
+ *
1530
+ * @since 4.5
1531
+ * @static
1532
+ * @param {CKEDITOR.dom.node} node
1533
+ * @returns {Boolean}
1534
+ */
1535
+ Widget.isDomDragHandlerContainer = function( node ) {
1536
+ return node.type == CKEDITOR.NODE_ELEMENT && node.hasClass( 'cke_widget_drag_handler_container' );
1537
+ };
1538
+
1539
+ /**
1540
+ * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#editables nested editable}.
1541
+ * Note that this function only checks whether it is the right element, not whether
1542
+ * the passed `node` is an instance of {@link CKEDITOR.plugins.widget.nestedEditable}.
1543
+ *
1544
+ * @since 4.5
1545
+ * @static
1546
+ * @param {CKEDITOR.dom.node} node
1547
+ * @returns {Boolean}
1548
+ */
1549
+ Widget.isDomNestedEditable = function( node ) {
1550
+ return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-editable' );
1551
+ };
1552
+
1553
+ /**
1554
+ * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#element widget element}.
1555
+ *
1556
+ * @since 4.5
1557
+ * @static
1558
+ * @param {CKEDITOR.dom.node} node
1559
+ * @returns {Boolean}
1560
+ */
1561
+ Widget.isDomWidgetElement = function( node ) {
1562
+ return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-widget' );
1563
+ };
1564
+
1565
+ /**
1566
+ * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#wrapper widget wrapper}.
1567
+ *
1568
+ * @since 4.5
1569
+ * @static
1570
+ * @param {CKEDITOR.dom.element} node
1571
+ * @returns {Boolean}
1572
+ */
1573
+ Widget.isDomWidgetWrapper = function( node ) {
1574
+ return node.type == CKEDITOR.NODE_ELEMENT && node.hasAttribute( 'data-cke-widget-wrapper' );
1575
+ };
1576
+
1577
+ /**
1578
+ * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#element widget element}.
1579
+ *
1580
+ * @since 4.5
1581
+ * @static
1582
+ * @param {CKEDITOR.htmlParser.node} node
1583
+ * @returns {Boolean}
1584
+ */
1585
+ Widget.isParserWidgetElement = function( node ) {
1586
+ return node.type == CKEDITOR.NODE_ELEMENT && !!node.attributes[ 'data-widget' ];
1587
+ };
1588
+
1589
+ /**
1590
+ * Checks whether the `node` is a {@link CKEDITOR.plugins.widget#wrapper widget wrapper}.
1591
+ *
1592
+ * @since 4.5
1593
+ * @static
1594
+ * @param {CKEDITOR.htmlParser.element} node
1595
+ * @returns {Boolean}
1596
+ */
1597
+ Widget.isParserWidgetWrapper = function( node ) {
1598
+ return node.type == CKEDITOR.NODE_ELEMENT && !!node.attributes[ 'data-cke-widget-wrapper' ];
1599
+ };
1600
+
1601
+ /**
1602
+ * Prefix added to wrapper classes. Each class added to the widget element by the {@link #addClass}
1603
+ * method will also be added to the wrapper prefixed with it.
1604
+ *
1605
+ * @since 4.6.0
1606
+ * @static
1607
+ * @readonly
1608
+ * @property {String} [='cke_widget_wrapper_']
1609
+ */
1610
+ Widget.WRAPPER_CLASS_PREFIX = 'cke_widget_wrapper_';
1611
+
1612
+ /**
1613
+ * An event fired when a widget is ready (fully initialized). This event is fired after:
1614
+ *
1615
+ * * {@link #init} is called,
1616
+ * * The first {@link #event-data} event is fired,
1617
+ * * A widget is attached to the document.
1618
+ *
1619
+ * Therefore, in case of widget creation with a command which opens a dialog window, this event
1620
+ * will be delayed after the dialog window is closed and the widget is finally inserted into the document.
1621
+ *
1622
+ * **Note**: If your widget does not use automatic dialog window binding (i.e. you open the dialog window manually)
1623
+ * or another situation in which the widget wrapper is not attached to document at the time when it is
1624
+ * initialized occurs, you need to take care of firing {@link #event-ready} yourself.
1625
+ *
1626
+ * See also {@link #property-ready} and {@link #property-inited} properties, and
1627
+ * {@link #isReady} and {@link #isInited} methods.
1628
+ *
1629
+ * @event ready
1630
+ */
1631
+
1632
+ /**
1633
+ * An event fired when a widget is about to be destroyed, but before it is
1634
+ * fully torn down.
1635
+ *
1636
+ * @event destroy
1637
+ */
1638
+
1639
+ /**
1640
+ * An event fired when a widget is focused.
1641
+ *
1642
+ * Widget can be focused by executing {@link #method-focus}.
1643
+ *
1644
+ * @event focus
1645
+ */
1646
+
1647
+ /**
1648
+ * An event fired when a widget is blurred.
1649
+ *
1650
+ * @event blur
1651
+ */
1652
+
1653
+ /**
1654
+ * An event fired when a widget is selected.
1655
+ *
1656
+ * @event select
1657
+ */
1658
+
1659
+ /**
1660
+ * An event fired when a widget is deselected.
1661
+ *
1662
+ * @event deselect
1663
+ */
1664
+
1665
+ /**
1666
+ * An event fired by the {@link #method-edit} method. It can be canceled
1667
+ * in order to stop the default action (opening a dialog window and/or
1668
+ * {@link CKEDITOR.plugins.widget.repository#finalizeCreation finalizing widget creation}).
1669
+ *
1670
+ * @event edit
1671
+ * @param data
1672
+ * @param {String} data.dialog Defaults to {@link CKEDITOR.plugins.widget.definition#dialog}
1673
+ * and can be changed or set by the listener.
1674
+ */
1675
+
1676
+ /**
1677
+ * An event fired when a dialog window for widget editing is opened.
1678
+ * This event can be canceled in order to handle the editing dialog in a custom manner.
1679
+ *
1680
+ * @event dialog
1681
+ * @param {CKEDITOR.dialog} data The opened dialog window instance.
1682
+ */
1683
+
1684
+ /**
1685
+ * An event fired when a key is pressed on a focused widget.
1686
+ * This event is forwarded from the {@link CKEDITOR.editor#key} event and
1687
+ * has the ability to block editor keystrokes if it is canceled.
1688
+ *
1689
+ * @event key
1690
+ * @param data
1691
+ * @param {Number} data.keyCode A number representing the key code (or combination).
1692
+ */
1693
+
1694
+ /**
1695
+ * An event fired when a widget is double clicked.
1696
+ *
1697
+ * **Note:** If a default editing action is executed on double click (i.e. a widget has a
1698
+ * {@link CKEDITOR.plugins.widget.definition#dialog dialog} defined and the {@link #event-doubleclick} event was not
1699
+ * canceled), this event will be automatically canceled, so a listener added with the default priority (10)
1700
+ * will not be executed. Use a listener with low priority (e.g. 5) to be sure that it will be executed.
1701
+ *
1702
+ * widget.on( 'doubleclick', function( evt ) {
1703
+ * console.log( 'widget#doubleclick' );
1704
+ * }, null, null, 5 );
1705
+ *
1706
+ * If your widget handles double click in a special way (so the default editing action is not executed),
1707
+ * make sure you cancel this event, because otherwise it will be propagated to {@link CKEDITOR.editor#doubleclick}
1708
+ * and another feature may step in (e.g. a Link dialog window may be opened if your widget was inside a link).
1709
+ *
1710
+ * @event doubleclick
1711
+ * @param data
1712
+ * @param {CKEDITOR.dom.element} data.element The double-clicked element.
1713
+ */
1714
+
1715
+ /**
1716
+ * An event fired when the context menu is opened for a widget.
1717
+ *
1718
+ * @event contextMenu
1719
+ * @param data The object containing context menu options to be added
1720
+ * for this widget. See {@link CKEDITOR.plugins.contextMenu#addListener}.
1721
+ */
1722
+
1723
+ /**
1724
+ * An event fired when the widget data changed. See the {@link #setData} method and the {@link #property-data} property.
1725
+ *
1726
+ * @event data
1727
+ */
1728
+
1729
+
1730
+
1731
+ /**
1732
+ * The wrapper class for editable elements inside widgets.
1733
+ *
1734
+ * Do not use directly. Use {@link CKEDITOR.plugins.widget.definition#editables} or
1735
+ * {@link CKEDITOR.plugins.widget#initEditable}.
1736
+ *
1737
+ * @class CKEDITOR.plugins.widget.nestedEditable
1738
+ * @extends CKEDITOR.dom.element
1739
+ * @constructor
1740
+ * @param {CKEDITOR.editor} editor
1741
+ * @param {CKEDITOR.dom.element} element
1742
+ * @param config
1743
+ * @param {CKEDITOR.filter} [config.filter]
1744
+ */
1745
+ function NestedEditable( editor, element, config ) {
1746
+ // Call the base constructor.
1747
+ CKEDITOR.dom.element.call( this, element.$ );
1748
+ this.editor = editor;
1749
+ this._ = {};
1750
+ var filter = this.filter = config.filter;
1751
+
1752
+ // If blockless editable - always use BR mode.
1753
+ if ( !CKEDITOR.dtd[ this.getName() ].p )
1754
+ this.enterMode = this.shiftEnterMode = CKEDITOR.ENTER_BR;
1755
+ else {
1756
+ this.enterMode = filter ? filter.getAllowedEnterMode( editor.enterMode ) : editor.enterMode;
1757
+ this.shiftEnterMode = filter ? filter.getAllowedEnterMode( editor.shiftEnterMode, true ) : editor.shiftEnterMode;
1758
+ }
1759
+ }
1760
+
1761
+ NestedEditable.prototype = CKEDITOR.tools.extend( CKEDITOR.tools.prototypedCopy( CKEDITOR.dom.element.prototype ), {
1762
+ /**
1763
+ * Sets the editable data. The data will be passed through the {@link CKEDITOR.editor#dataProcessor}
1764
+ * and the {@link CKEDITOR.editor#filter}. This ensures that the data was filtered and prepared to be
1765
+ * edited like the {@link CKEDITOR.editor#method-setData editor data}.
1766
+ *
1767
+ * Before content is changed, all nested widgets are destroyed. Afterwards, after new content is loaded,
1768
+ * all nested widgets are initialized.
1769
+ *
1770
+ * @param {String} data
1771
+ */
1772
+ setData: function( data ) {
1773
+ // For performance reasons don't call destroyAll when initializing a nested editable,
1774
+ // because there are no widgets inside.
1775
+ if ( !this._.initialSetData ) {
1776
+ // Destroy all nested widgets before setting data.
1777
+ this.editor.widgets.destroyAll( false, this );
1778
+ }
1779
+ this._.initialSetData = false;
1780
+
1781
+ data = this.editor.dataProcessor.toHtml( data, {
1782
+ context: this.getName(),
1783
+ filter: this.filter,
1784
+ enterMode: this.enterMode
1785
+ } );
1786
+ this.setHtml( data );
1787
+
1788
+ this.editor.widgets.initOnAll( this );
1789
+ },
1790
+
1791
+ /**
1792
+ * Gets the editable data. Like {@link #setData}, this method will process and filter the data.
1793
+ *
1794
+ * @returns {String}
1795
+ */
1796
+ getData: function() {
1797
+ return this.editor.dataProcessor.toDataFormat( this.getHtml(), {
1798
+ context: this.getName(),
1799
+ filter: this.filter,
1800
+ enterMode: this.enterMode
1801
+ } );
1802
+ }
1803
+ } );
1804
+
1805
+ /**
1806
+ * The editor instance.
1807
+ *
1808
+ * @readonly
1809
+ * @property {CKEDITOR.editor} editor
1810
+ */
1811
+
1812
+ /**
1813
+ * The filter instance if allowed content rules were defined.
1814
+ *
1815
+ * @readonly
1816
+ * @property {CKEDITOR.filter} filter
1817
+ */
1818
+
1819
+ /**
1820
+ * The enter mode active in this editable.
1821
+ * It is determined from editable's name (whether it is a blockless editable),
1822
+ * its allowed content rules (if defined) and the default editor's mode.
1823
+ *
1824
+ * @readonly
1825
+ * @property {Number} enterMode
1826
+ */
1827
+
1828
+ /**
1829
+ * The shift enter move active in this editable.
1830
+ *
1831
+ * @readonly
1832
+ * @property {Number} shiftEnterMode
1833
+ */
1834
+
1835
+
1836
+ //
1837
+ // REPOSITORY helpers -----------------------------------------------------
1838
+ //
1839
+
1840
+ function addWidgetButtons( editor ) {
1841
+ var widgets = editor.widgets.registered,
1842
+ widget,
1843
+ widgetName,
1844
+ widgetButton;
1845
+
1846
+ for ( widgetName in widgets ) {
1847
+ widget = widgets[ widgetName ];
1848
+
1849
+ // Create button if defined.
1850
+ widgetButton = widget.button;
1851
+ if ( widgetButton && editor.ui.addButton ) {
1852
+ editor.ui.addButton( CKEDITOR.tools.capitalize( widget.name, true ), {
1853
+ label: widgetButton,
1854
+ command: widget.name,
1855
+ toolbar: 'insert,10'
1856
+ } );
1857
+ }
1858
+ }
1859
+ }
1860
+
1861
+ // Create a command creating and editing widget.
1862
+ //
1863
+ // @param editor
1864
+ // @param {CKEDITOR.plugins.widget.definition} widgetDef
1865
+ function addWidgetCommand( editor, widgetDef ) {
1866
+ editor.addCommand( widgetDef.name, {
1867
+ exec: function( editor, commandData ) {
1868
+ var focused = editor.widgets.focused;
1869
+ // If a widget of the same type is focused, start editing.
1870
+ if ( focused && focused.name == widgetDef.name )
1871
+ focused.edit();
1872
+ // Otherwise...
1873
+ // ... use insert method is was defined.
1874
+ else if ( widgetDef.insert )
1875
+ widgetDef.insert();
1876
+ // ... or create a brand-new widget from template.
1877
+ else if ( widgetDef.template ) {
1878
+ var defaults = typeof widgetDef.defaults == 'function' ? widgetDef.defaults() : widgetDef.defaults,
1879
+ element = CKEDITOR.dom.element.createFromHtml( widgetDef.template.output( defaults ) ),
1880
+ instance,
1881
+ wrapper = editor.widgets.wrapElement( element, widgetDef.name ),
1882
+ temp = new CKEDITOR.dom.documentFragment( wrapper.getDocument() );
1883
+
1884
+ // Append wrapper to a temporary document. This will unify the environment
1885
+ // in which #data listeners work when creating and editing widget.
1886
+ temp.append( wrapper );
1887
+ instance = editor.widgets.initOn( element, widgetDef, commandData && commandData.startupData );
1888
+
1889
+ // Instance could be destroyed during initialization.
1890
+ // In this case finalize creation if some new widget
1891
+ // was left in temporary document fragment.
1892
+ if ( !instance ) {
1893
+ finalizeCreation();
1894
+ return;
1895
+ }
1896
+
1897
+ // Listen on edit to finalize widget insertion.
1898
+ //
1899
+ // * If dialog was set, then insert widget after dialog was successfully saved or destroy this
1900
+ // temporary instance.
1901
+ // * If dialog wasn't set and edit wasn't canceled, insert widget.
1902
+ var editListener = instance.once( 'edit', function( evt ) {
1903
+ if ( evt.data.dialog ) {
1904
+ instance.once( 'dialog', function( evt ) {
1905
+ var dialog = evt.data,
1906
+ okListener,
1907
+ cancelListener;
1908
+
1909
+ // Finalize creation AFTER (20) new data was set.
1910
+ okListener = dialog.once( 'ok', finalizeCreation, null, null, 20 );
1911
+
1912
+ cancelListener = dialog.once( 'cancel', function( evt ) {
1913
+ if ( !( evt.data && evt.data.hide === false ) ) {
1914
+ editor.widgets.destroy( instance, true );
1915
+ }
1916
+ } );
1917
+
1918
+ dialog.once( 'hide', function() {
1919
+ okListener.removeListener();
1920
+ cancelListener.removeListener();
1921
+ } );
1922
+ } );
1923
+ } else {
1924
+ // Dialog hasn't been set, so insert widget now.
1925
+ finalizeCreation();
1926
+ }
1927
+ }, null, null, 999 );
1928
+
1929
+ instance.edit();
1930
+
1931
+ // Remove listener in case someone canceled it before this
1932
+ // listener was executed.
1933
+ editListener.removeListener();
1934
+ }
1935
+
1936
+ function finalizeCreation() {
1937
+ editor.widgets.finalizeCreation( temp );
1938
+ }
1939
+ },
1940
+
1941
+ allowedContent: widgetDef.allowedContent,
1942
+ requiredContent: widgetDef.requiredContent,
1943
+ contentForms: widgetDef.contentForms,
1944
+ contentTransformations: widgetDef.contentTransformations
1945
+ } );
1946
+ }
1947
+
1948
+ function addWidgetProcessors( widgetsRepo, widgetDef ) {
1949
+ var upcast = widgetDef.upcast,
1950
+ upcasts,
1951
+ priority = widgetDef.upcastPriority || 10;
1952
+
1953
+ if ( !upcast )
1954
+ return;
1955
+
1956
+ // Multiple upcasts defined in string.
1957
+ if ( typeof upcast == 'string' ) {
1958
+ upcasts = upcast.split( ',' );
1959
+ while ( upcasts.length ) {
1960
+ addUpcast( widgetDef.upcasts[ upcasts.pop() ], widgetDef.name, priority );
1961
+ }
1962
+ }
1963
+ // Single rule which is automatically activated.
1964
+ else {
1965
+ addUpcast( upcast, widgetDef.name, priority );
1966
+ }
1967
+
1968
+ function addUpcast( upcast, name, priority ) {
1969
+ // Find index of the first higher (in terms of value) priority upcast.
1970
+ var index = CKEDITOR.tools.getIndex( widgetsRepo._.upcasts, function( element ) {
1971
+ return element[ 2 ] > priority;
1972
+ } );
1973
+ // Add at the end if it is the highest priority so far.
1974
+ if ( index < 0 ) {
1975
+ index = widgetsRepo._.upcasts.length;
1976
+ }
1977
+
1978
+ widgetsRepo._.upcasts.splice( index, 0, [ upcast, name, priority ] );
1979
+ }
1980
+ }
1981
+
1982
+ function blurWidget( widgetsRepo, widget ) {
1983
+ widgetsRepo.focused = null;
1984
+
1985
+ if ( widget.isInited() ) {
1986
+ var isDirty = widget.editor.checkDirty();
1987
+
1988
+ // Widget could be destroyed in the meantime - e.g. data could be set.
1989
+ widgetsRepo.fire( 'widgetBlurred', { widget: widget } );
1990
+ widget.setFocused( false );
1991
+
1992
+ !isDirty && widget.editor.resetDirty();
1993
+ }
1994
+ }
1995
+
1996
+ function checkWidgets( evt ) {
1997
+ var options = evt.data;
1998
+
1999
+ if ( this.editor.mode != 'wysiwyg' )
2000
+ return;
2001
+
2002
+ var editable = this.editor.editable(),
2003
+ instances = this.instances,
2004
+ newInstances, i, count, wrapper, notYetInitialized;
2005
+
2006
+ if ( !editable )
2007
+ return;
2008
+
2009
+ // Remove widgets which have no corresponding elements in DOM.
2010
+ for ( i in instances ) {
2011
+ // http://dev.ckeditor.com/ticket/13410 Remove widgets that are ready. This prevents from destroying widgets that are during loading process.
2012
+ if ( instances[ i ].isReady() && !editable.contains( instances[ i ].wrapper ) )
2013
+ this.destroy( instances[ i ], true );
2014
+ }
2015
+
2016
+ // Init on all (new) if initOnlyNew option was passed.
2017
+ if ( options && options.initOnlyNew )
2018
+ newInstances = this.initOnAll();
2019
+ else {
2020
+ var wrappers = editable.find( '.cke_widget_wrapper' );
2021
+ newInstances = [];
2022
+
2023
+ // Create widgets on existing wrappers if they do not exists.
2024
+ for ( i = 0, count = wrappers.count(); i < count; i++ ) {
2025
+ wrapper = wrappers.getItem( i );
2026
+ notYetInitialized = !this.getByElement( wrapper, true );
2027
+
2028
+ // Check if:
2029
+ // * there's no instance for this widget
2030
+ // * wrapper is not inside some temporary element like copybin (http://dev.ckeditor.com/ticket/11088)
2031
+ // * it was a nested widget's wrapper which has been detached from DOM,
2032
+ // when nested editable has been initialized (it overwrites its innerHTML
2033
+ // and initializes nested widgets).
2034
+ if ( notYetInitialized && !findParent( wrapper, isDomTemp ) && editable.contains( wrapper ) ) {
2035
+ // Add cke_widget_new class because otherwise
2036
+ // widget will not be created on such wrapper.
2037
+ wrapper.addClass( 'cke_widget_new' );
2038
+ newInstances.push( this.initOn( wrapper.getFirst( Widget.isDomWidgetElement ) ) );
2039
+ }
2040
+ }
2041
+ }
2042
+
2043
+ // If only single widget was initialized and focusInited was passed, focus it.
2044
+ if ( options && options.focusInited && newInstances.length == 1 )
2045
+ newInstances[ 0 ].focus();
2046
+ }
2047
+
2048
+ // Unwraps widget element and clean up element.
2049
+ //
2050
+ // This function is used to clean up pasted widgets.
2051
+ // It should have similar result to widget#destroy plus
2052
+ // some additional adjustments, specific for pasting.
2053
+ //
2054
+ // @param {CKEDITOR.htmlParser.element} el
2055
+ function cleanUpWidgetElement( el ) {
2056
+ var parent = el.parent;
2057
+ if ( parent.type == CKEDITOR.NODE_ELEMENT && parent.attributes[ 'data-cke-widget-wrapper' ] )
2058
+ parent.replaceWith( el );
2059
+ }
2060
+
2061
+ // Similar to cleanUpWidgetElement, but works on DOM and finds
2062
+ // widget elements by its own.
2063
+ //
2064
+ // Unlike cleanUpWidgetElement it will wrap element back.
2065
+ //
2066
+ // @param {CKEDITOR.dom.element} container
2067
+ function cleanUpAllWidgetElements( widgetsRepo, container ) {
2068
+ var wrappers = container.find( '.cke_widget_wrapper' ),
2069
+ wrapper, element,
2070
+ i = 0,
2071
+ l = wrappers.count();
2072
+
2073
+ for ( ; i < l; ++i ) {
2074
+ wrapper = wrappers.getItem( i );
2075
+ element = wrapper.getFirst( Widget.isDomWidgetElement );
2076
+ // If wrapper contains widget element - unwrap it and wrap again.
2077
+ if ( element.type == CKEDITOR.NODE_ELEMENT && element.data( 'widget' ) ) {
2078
+ element.replace( wrapper );
2079
+ widgetsRepo.wrapElement( element );
2080
+ } else {
2081
+ // Otherwise - something is wrong... clean this up.
2082
+ wrapper.remove();
2083
+ }
2084
+ }
2085
+ }
2086
+
2087
+ // Creates {@link CKEDITOR.filter} instance for given widget, editable and rules.
2088
+ //
2089
+ // Once filter for widget-editable pair is created it is cached, so the same instance
2090
+ // will be returned when method is executed again.
2091
+ //
2092
+ // @param {String} widgetName
2093
+ // @param {String} editableName
2094
+ // @param {CKEDITOR.plugins.widget.nestedEditableDefinition} editableDefinition The nested editable definition.
2095
+ // @returns {CKEDITOR.filter} Filter instance or `null` if rules are not defined.
2096
+ // @context CKEDITOR.plugins.widget.repository
2097
+ function createEditableFilter( widgetName, editableName, editableDefinition ) {
2098
+ if ( !editableDefinition.allowedContent && !editableDefinition.disallowedContent )
2099
+ return null;
2100
+
2101
+ var editables = this._.filters[ widgetName ];
2102
+
2103
+ if ( !editables )
2104
+ this._.filters[ widgetName ] = editables = {};
2105
+
2106
+ var filter = editables[ editableName ];
2107
+
2108
+ if ( !filter ) {
2109
+ filter = editableDefinition.allowedContent ? new CKEDITOR.filter( editableDefinition.allowedContent ) : this.editor.filter.clone();
2110
+
2111
+ editables[ editableName ] = filter;
2112
+
2113
+ if ( editableDefinition.disallowedContent ) {
2114
+ filter.disallow( editableDefinition.disallowedContent );
2115
+ }
2116
+ }
2117
+
2118
+ return filter;
2119
+ }
2120
+
2121
+ // Creates an iterator function which when executed on all
2122
+ // elements in DOM tree will gather elements that should be wrapped
2123
+ // and initialized as widgets.
2124
+ function createUpcastIterator( widgetsRepo ) {
2125
+ var toBeWrapped = [],
2126
+ upcasts = widgetsRepo._.upcasts,
2127
+ upcastCallbacks = widgetsRepo._.upcastCallbacks;
2128
+
2129
+ return {
2130
+ toBeWrapped: toBeWrapped,
2131
+
2132
+ iterator: function( element ) {
2133
+ var upcast, upcasted,
2134
+ data,
2135
+ i,
2136
+ upcastsLength,
2137
+ upcastCallbacksLength;
2138
+
2139
+ // Wrapper found - find widget element, add it to be
2140
+ // cleaned up (unwrapped) and wrapped and stop iterating in this branch.
2141
+ if ( 'data-cke-widget-wrapper' in element.attributes ) {
2142
+ element = element.getFirst( Widget.isParserWidgetElement );
2143
+
2144
+ if ( element )
2145
+ toBeWrapped.push( [ element ] );
2146
+
2147
+ // Do not iterate over descendants.
2148
+ return false;
2149
+ }
2150
+ // Widget element found - add it to be cleaned up (just in case)
2151
+ // and wrapped and stop iterating in this branch.
2152
+ else if ( 'data-widget' in element.attributes ) {
2153
+ toBeWrapped.push( [ element ] );
2154
+
2155
+ // Do not iterate over descendants.
2156
+ return false;
2157
+ }
2158
+ else if ( ( upcastsLength = upcasts.length ) ) {
2159
+ // Ignore elements with data-cke-widget-upcasted to avoid multiple upcasts (http://dev.ckeditor.com/ticket/11533).
2160
+ // Do not iterate over descendants.
2161
+ if ( element.attributes[ 'data-cke-widget-upcasted' ] )
2162
+ return false;
2163
+
2164
+ // Check element with upcast callbacks first.
2165
+ // If any of them return false abort upcasting.
2166
+ for ( i = 0, upcastCallbacksLength = upcastCallbacks.length; i < upcastCallbacksLength; ++i ) {
2167
+ if ( upcastCallbacks[ i ]( element ) === false )
2168
+ return;
2169
+ // Return nothing in order to continue iterating over ascendants.
2170
+ // See http://dev.ckeditor.com/ticket/11186#comment:6
2171
+ }
2172
+
2173
+ for ( i = 0; i < upcastsLength; ++i ) {
2174
+ upcast = upcasts[ i ];
2175
+ data = {};
2176
+
2177
+ if ( ( upcasted = upcast[ 0 ]( element, data ) ) ) {
2178
+ // If upcast function returned element, upcast this one.
2179
+ // It can be e.g. a new element wrapping the original one.
2180
+ if ( upcasted instanceof CKEDITOR.htmlParser.element )
2181
+ element = upcasted;
2182
+
2183
+ // Set initial data attr with data from upcast method.
2184
+ element.attributes[ 'data-cke-widget-data' ] = encodeURIComponent( JSON.stringify( data ) );
2185
+ element.attributes[ 'data-cke-widget-upcasted' ] = 1;
2186
+
2187
+ toBeWrapped.push( [ element, upcast[ 1 ] ] );
2188
+
2189
+ // Do not iterate over descendants.
2190
+ return false;
2191
+ }
2192
+ }
2193
+ }
2194
+ }
2195
+ };
2196
+ }
2197
+
2198
+ // Finds a first parent that matches query.
2199
+ //
2200
+ // @param {CKEDITOR.dom.element} element
2201
+ // @param {Function} query
2202
+ function findParent( element, query ) {
2203
+ var parent = element;
2204
+
2205
+ while ( ( parent = parent.getParent() ) ) {
2206
+ if ( query( parent ) )
2207
+ return true;
2208
+ }
2209
+ return false;
2210
+ }
2211
+
2212
+ function getWrapperAttributes( inlineWidget, name ) {
2213
+ return {
2214
+ // tabindex="-1" means that it can receive focus by code.
2215
+ tabindex: -1,
2216
+ contenteditable: 'false',
2217
+ 'data-cke-widget-wrapper': 1,
2218
+ 'data-cke-filter': 'off',
2219
+ // Class cke_widget_new marks widgets which haven't been initialized yet.
2220
+ 'class': 'cke_widget_wrapper cke_widget_new cke_widget_' +
2221
+ ( inlineWidget ? 'inline' : 'block' ) +
2222
+ ( name ? ' cke_widget_' + name : '' )
2223
+ };
2224
+ }
2225
+
2226
+ // Inserts element at given index.
2227
+ // It will check DTD and split ancestor elements up to the first
2228
+ // that can contain this element.
2229
+ //
2230
+ // @param {CKEDITOR.htmlParser.element} parent
2231
+ // @param {Number} index
2232
+ // @param {CKEDITOR.htmlParser.element} element
2233
+ function insertElement( parent, index, element ) {
2234
+ // Do not split doc fragment...
2235
+ if ( parent.type == CKEDITOR.NODE_ELEMENT ) {
2236
+ var parentAllows = CKEDITOR.dtd[ parent.name ];
2237
+ // Parent element is known (included in DTD) and cannot contain
2238
+ // this element.
2239
+ if ( parentAllows && !parentAllows[ element.name ] ) {
2240
+ var parent2 = parent.split( index ),
2241
+ parentParent = parent.parent;
2242
+
2243
+ // Element will now be inserted at right parent's index.
2244
+ index = parent2.getIndex();
2245
+
2246
+ // If left part of split is empty - remove it.
2247
+ if ( !parent.children.length ) {
2248
+ index -= 1;
2249
+ parent.remove();
2250
+ }
2251
+
2252
+ // If right part of split is empty - remove it.
2253
+ if ( !parent2.children.length )
2254
+ parent2.remove();
2255
+
2256
+ // Try inserting as grandpas' children.
2257
+ return insertElement( parentParent, index, element );
2258
+ }
2259
+ }
2260
+
2261
+ // Finally we can add this element.
2262
+ parent.add( element, index );
2263
+ }
2264
+
2265
+ // Checks whether for the given widget definition and element widget should be created in inline or block mode.
2266
+ //
2267
+ // See also: {@link CKEDITOR.plugins.widget.definition#inline} and {@link CKEDITOR.plugins.widget#element}.
2268
+ //
2269
+ // @param {CKEDITOR.plugins.widget.definition} widgetDef The widget definition.
2270
+ // @param {String} elementName The name of the widget element.
2271
+ // @returns {Boolean}
2272
+ function isWidgetInline( widgetDef, elementName ) {
2273
+ return typeof widgetDef.inline == 'boolean' ? widgetDef.inline : !!CKEDITOR.dtd.$inline[ elementName ];
2274
+ }
2275
+
2276
+ // @param {CKEDITOR.dom.element}
2277
+ // @returns {Boolean}
2278
+ function isDomTemp( element ) {
2279
+ return element.hasAttribute( 'data-cke-temp' );
2280
+ }
2281
+
2282
+ function onEditableKey( widget, keyCode ) {
2283
+ var focusedEditable = widget.focusedEditable,
2284
+ range;
2285
+
2286
+ // CTRL+A.
2287
+ if ( keyCode == CKEDITOR.CTRL + 65 ) {
2288
+ var bogus = focusedEditable.getBogus();
2289
+
2290
+ range = widget.editor.createRange();
2291
+ range.selectNodeContents( focusedEditable );
2292
+ // Exclude bogus if exists.
2293
+ if ( bogus )
2294
+ range.setEndAt( bogus, CKEDITOR.POSITION_BEFORE_START );
2295
+
2296
+ range.select();
2297
+ // Cancel event - block default.
2298
+ return false;
2299
+ }
2300
+ // DEL or BACKSPACE.
2301
+ else if ( keyCode == 8 || keyCode == 46 ) {
2302
+ var ranges = widget.editor.getSelection().getRanges();
2303
+
2304
+ range = ranges[ 0 ];
2305
+
2306
+ // Block del or backspace if at editable's boundary.
2307
+ return !( ranges.length == 1 && range.collapsed &&
2308
+ range.checkBoundaryOfElement( focusedEditable, CKEDITOR[ keyCode == 8 ? 'START' : 'END' ] ) );
2309
+ }
2310
+ }
2311
+
2312
+ function setFocusedEditable( widgetsRepo, widget, editableElement, offline ) {
2313
+ var editor = widgetsRepo.editor;
2314
+
2315
+ editor.fire( 'lockSnapshot' );
2316
+
2317
+ if ( editableElement ) {
2318
+ var editableName = editableElement.data( 'cke-widget-editable' ),
2319
+ editableInstance = widget.editables[ editableName ];
2320
+
2321
+ widgetsRepo.widgetHoldingFocusedEditable = widget;
2322
+ widget.focusedEditable = editableInstance;
2323
+ editableElement.addClass( 'cke_widget_editable_focused' );
2324
+
2325
+ if ( editableInstance.filter )
2326
+ editor.setActiveFilter( editableInstance.filter );
2327
+ editor.setActiveEnterMode( editableInstance.enterMode, editableInstance.shiftEnterMode );
2328
+ } else {
2329
+ if ( !offline )
2330
+ widget.focusedEditable.removeClass( 'cke_widget_editable_focused' );
2331
+
2332
+ widget.focusedEditable = null;
2333
+ widgetsRepo.widgetHoldingFocusedEditable = null;
2334
+ editor.setActiveFilter( null );
2335
+ editor.setActiveEnterMode( null, null );
2336
+ }
2337
+
2338
+ editor.fire( 'unlockSnapshot' );
2339
+ }
2340
+
2341
+ function setupContextMenu( editor ) {
2342
+ if ( !editor.contextMenu )
2343
+ return;
2344
+
2345
+ editor.contextMenu.addListener( function( element ) {
2346
+ var widget = editor.widgets.getByElement( element, true );
2347
+
2348
+ if ( widget )
2349
+ return widget.fire( 'contextMenu', {} );
2350
+ } );
2351
+ }
2352
+
2353
+ // And now we've got two problems - original problem and RegExp.
2354
+ // Some softeners:
2355
+ // * FF tends to copy all blocks up to the copybin container.
2356
+ // * IE tends to copy only the copybin, without its container.
2357
+ // * We use spans on IE and blockless editors, but divs in other cases.
2358
+ var pasteReplaceRegex = new RegExp(
2359
+ '^' +
2360
+ '(?:<(?:div|span)(?: data-cke-temp="1")?(?: id="cke_copybin")?(?: data-cke-temp="1")?>)?' +
2361
+ '(?:<(?:div|span)(?: style="[^"]+")?>)?' +
2362
+ '<span [^>]*data-cke-copybin-start="1"[^>]*>.?</span>([\\s\\S]+)<span [^>]*data-cke-copybin-end="1"[^>]*>.?</span>' +
2363
+ '(?:</(?:div|span)>)?' +
2364
+ '(?:</(?:div|span)>)?' +
2365
+ '$',
2366
+ // IE8 prefers uppercase when browsers stick to lowercase HTML (http://dev.ckeditor.com/ticket/13460).
2367
+ 'i'
2368
+ );
2369
+
2370
+ function pasteReplaceFn( match, wrapperHtml ) {
2371
+ // Avoid polluting pasted data with any whitspaces,
2372
+ // what's going to break check whether only one widget was pasted.
2373
+ return CKEDITOR.tools.trim( wrapperHtml );
2374
+ }
2375
+
2376
+ function setupDragAndDrop( widgetsRepo ) {
2377
+ var editor = widgetsRepo.editor,
2378
+ lineutils = CKEDITOR.plugins.lineutils;
2379
+
2380
+ // These listeners handle inline and block widgets drag and drop.
2381
+ // The only thing we need to do to make block widgets custom drag and drop functionality
2382
+ // is to fire those events with the right properties (like the target which must be the drag handle).
2383
+ editor.on( 'dragstart', function( evt ) {
2384
+ var target = evt.data.target;
2385
+
2386
+ if ( Widget.isDomDragHandler( target ) ) {
2387
+ var widget = widgetsRepo.getByElement( target );
2388
+
2389
+ evt.data.dataTransfer.setData( 'cke/widget-id', widget.id );
2390
+
2391
+ // IE needs focus.
2392
+ editor.focus();
2393
+
2394
+ // and widget need to be focused on drag start (http://dev.ckeditor.com/ticket/12172#comment:10).
2395
+ widget.focus();
2396
+ }
2397
+ } );
2398
+
2399
+ editor.on( 'drop', function( evt ) {
2400
+ var dataTransfer = evt.data.dataTransfer,
2401
+ id = dataTransfer.getData( 'cke/widget-id' ),
2402
+ transferType = dataTransfer.getTransferType( editor ),
2403
+ dragRange = editor.createRange(),
2404
+ sourceWidget;
2405
+
2406
+ // Disable cross-editor drag & drop for widgets - http://dev.ckeditor.com/ticket/13599.
2407
+ if ( id !== '' && transferType === CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) {
2408
+ evt.cancel();
2409
+ return;
2410
+ }
2411
+
2412
+ if ( id === '' || transferType != CKEDITOR.DATA_TRANSFER_INTERNAL ) {
2413
+ return;
2414
+ }
2415
+
2416
+ sourceWidget = widgetsRepo.instances[ id ];
2417
+ if ( !sourceWidget ) {
2418
+ return;
2419
+ }
2420
+
2421
+ dragRange.setStartBefore( sourceWidget.wrapper );
2422
+ dragRange.setEndAfter( sourceWidget.wrapper );
2423
+ evt.data.dragRange = dragRange;
2424
+
2425
+ // [IE8-9] Reset state of the clipboard#fixSplitNodesAfterDrop fix because by setting evt.data.dragRange
2426
+ // (see above) after drop happened we do not need it. That fix is needed only if dragRange was created
2427
+ // before drop (before text node was split).
2428
+ delete CKEDITOR.plugins.clipboard.dragStartContainerChildCount;
2429
+ delete CKEDITOR.plugins.clipboard.dragEndContainerChildCount;
2430
+
2431
+ evt.data.dataTransfer.setData( 'text/html', editor.editable().getHtmlFromRange( dragRange ).getHtml() );
2432
+ editor.widgets.destroy( sourceWidget, true );
2433
+ } );
2434
+
2435
+ editor.on( 'contentDom', function() {
2436
+ var editable = editor.editable();
2437
+
2438
+ // Register Lineutils's utilities as properties of repo.
2439
+ CKEDITOR.tools.extend( widgetsRepo, {
2440
+ finder: new lineutils.finder( editor, {
2441
+ lookups: {
2442
+ // Element is block but not list item and not in nested editable.
2443
+ 'default': function( el ) {
2444
+ if ( el.is( CKEDITOR.dtd.$listItem ) )
2445
+ return;
2446
+
2447
+ if ( !el.is( CKEDITOR.dtd.$block ) )
2448
+ return;
2449
+
2450
+ // Allow drop line inside, but never before or after nested editable (http://dev.ckeditor.com/ticket/12006).
2451
+ if ( Widget.isDomNestedEditable( el ) )
2452
+ return;
2453
+
2454
+ // Do not allow droping inside the widget being dragged (http://dev.ckeditor.com/ticket/13397).
2455
+ if ( widgetsRepo._.draggedWidget.wrapper.contains( el ) ) {
2456
+ return;
2457
+ }
2458
+
2459
+ // If element is nested editable, make sure widget can be dropped there (http://dev.ckeditor.com/ticket/12006).
2460
+ var nestedEditable = Widget.getNestedEditable( editable, el );
2461
+ if ( nestedEditable ) {
2462
+ var draggedWidget = widgetsRepo._.draggedWidget;
2463
+
2464
+ // Don't let the widget to be dropped into its own nested editable.
2465
+ if ( widgetsRepo.getByElement( nestedEditable ) == draggedWidget )
2466
+ return;
2467
+
2468
+ var filter = CKEDITOR.filter.instances[ nestedEditable.data( 'cke-filter' ) ],
2469
+ draggedRequiredContent = draggedWidget.requiredContent;
2470
+
2471
+ // There will be no relation if the filter of nested editable does not allow
2472
+ // requiredContent of dragged widget.
2473
+ if ( filter && draggedRequiredContent && !filter.check( draggedRequiredContent ) )
2474
+ return;
2475
+ }
2476
+
2477
+ return CKEDITOR.LINEUTILS_BEFORE | CKEDITOR.LINEUTILS_AFTER;
2478
+ }
2479
+ }
2480
+ } ),
2481
+ locator: new lineutils.locator( editor ),
2482
+ liner: new lineutils.liner( editor, {
2483
+ lineStyle: {
2484
+ cursor: 'move !important',
2485
+ 'border-top-color': '#666'
2486
+ },
2487
+ tipLeftStyle: {
2488
+ 'border-left-color': '#666'
2489
+ },
2490
+ tipRightStyle: {
2491
+ 'border-right-color': '#666'
2492
+ }
2493
+ } )
2494
+ }, true );
2495
+ } );
2496
+ }
2497
+
2498
+ // Setup mouse observer which will trigger:
2499
+ // * widget focus on widget click,
2500
+ // * widget#doubleclick forwarded from editor#doubleclick.
2501
+ function setupMouseObserver( widgetsRepo ) {
2502
+ var editor = widgetsRepo.editor;
2503
+
2504
+ editor.on( 'contentDom', function() {
2505
+ var editable = editor.editable(),
2506
+ evtRoot = editable.isInline() ? editable : editor.document,
2507
+ widget,
2508
+ mouseDownOnDragHandler;
2509
+
2510
+ editable.attachListener( evtRoot, 'mousedown', function( evt ) {
2511
+ var target = evt.data.getTarget();
2512
+
2513
+ // Clicking scrollbar in Chrome will invoke event with target object of document type (#663).
2514
+ // In IE8 the target object will be empty (http://dev.ckeditor.com/ticket/10887).
2515
+ // We need to check if target is a proper element.
2516
+ widget = ( target instanceof CKEDITOR.dom.element ) ? widgetsRepo.getByElement( target ) : null;
2517
+
2518
+ mouseDownOnDragHandler = 0; // Reset.
2519
+
2520
+ // Widget was clicked, but not editable nested in it.
2521
+ if ( widget ) {
2522
+ // Ignore mousedown on drag and drop handler if the widget is inline.
2523
+ // Block widgets are handled by Lineutils.
2524
+ if ( widget.inline && target.type == CKEDITOR.NODE_ELEMENT && target.hasAttribute( 'data-cke-widget-drag-handler' ) ) {
2525
+ mouseDownOnDragHandler = 1;
2526
+
2527
+ // When drag handler is pressed we have to clear current selection if it wasn't already on this widget.
2528
+ // Otherwise, the selection may be in a fillingChar, which prevents dragging a widget. (http://dev.ckeditor.com/ticket/13284, see comment 8 and 9.)
2529
+ if ( widgetsRepo.focused != widget )
2530
+ editor.getSelection().removeAllRanges();
2531
+
2532
+ return;
2533
+ }
2534
+
2535
+ if ( !Widget.getNestedEditable( widget.wrapper, target ) ) {
2536
+ evt.data.preventDefault();
2537
+ if ( !CKEDITOR.env.ie )
2538
+ widget.focus();
2539
+ } else {
2540
+ // Reset widget so mouseup listener is not confused.
2541
+ widget = null;
2542
+ }
2543
+ }
2544
+ } );
2545
+
2546
+ // Focus widget on mouseup if mousedown was fired on drag handler.
2547
+ // Note: mouseup won't be fired at all if widget was dragged and dropped, so
2548
+ // this code will be executed only when drag handler was clicked.
2549
+ editable.attachListener( evtRoot, 'mouseup', function() {
2550
+ // Check if widget is not destroyed (if widget is destroyed the wrapper will be null).
2551
+ if ( mouseDownOnDragHandler && widget && widget.wrapper ) {
2552
+ mouseDownOnDragHandler = 0;
2553
+ widget.focus();
2554
+ }
2555
+ } );
2556
+
2557
+ // On IE it is not enough to block mousedown. If widget wrapper (element with
2558
+ // contenteditable=false attribute) is clicked directly (it is a target),
2559
+ // then after mouseup/click IE will select that element.
2560
+ // It is not possible to prevent that default action,
2561
+ // so we force fake selection after everything happened.
2562
+ if ( CKEDITOR.env.ie ) {
2563
+ editable.attachListener( evtRoot, 'mouseup', function() {
2564
+ setTimeout( function() {
2565
+ // Check if widget is not destroyed (if widget is destroyed the wrapper will be null) and
2566
+ // in editable contains widget (it could be dragged and removed).
2567
+ if ( widget && widget.wrapper && editable.contains( widget.wrapper ) ) {
2568
+ widget.focus();
2569
+ widget = null;
2570
+ }
2571
+ } );
2572
+ } );
2573
+ }
2574
+ } );
2575
+
2576
+ editor.on( 'doubleclick', function( evt ) {
2577
+ var widget = widgetsRepo.getByElement( evt.data.element );
2578
+
2579
+ // Not in widget or in nested editable.
2580
+ if ( !widget || Widget.getNestedEditable( widget.wrapper, evt.data.element ) )
2581
+ return;
2582
+
2583
+ return widget.fire( 'doubleclick', { element: evt.data.element } );
2584
+ }, null, null, 1 );
2585
+ }
2586
+
2587
+ // Setup editor#key observer which will forward it
2588
+ // to focused widget.
2589
+ function setupKeyboardObserver( widgetsRepo ) {
2590
+ var editor = widgetsRepo.editor;
2591
+
2592
+ editor.on( 'key', function( evt ) {
2593
+ var focused = widgetsRepo.focused,
2594
+ widgetHoldingFocusedEditable = widgetsRepo.widgetHoldingFocusedEditable,
2595
+ ret;
2596
+
2597
+ if ( focused )
2598
+ ret = focused.fire( 'key', { keyCode: evt.data.keyCode } );
2599
+ else if ( widgetHoldingFocusedEditable )
2600
+ ret = onEditableKey( widgetHoldingFocusedEditable, evt.data.keyCode );
2601
+
2602
+ return ret;
2603
+ }, null, null, 1 );
2604
+ }
2605
+
2606
+ // Setup copybin on native copy and cut events in order to handle copy and cut commands
2607
+ // if user accepted security alert on IEs.
2608
+ // Note: when copying or cutting using keystroke, copySingleWidget will be first executed
2609
+ // by the keydown listener. Conflict between two calls will be resolved by copy_bin existence check.
2610
+ function setupNativeCutAndCopy( widgetsRepo ) {
2611
+ var editor = widgetsRepo.editor;
2612
+
2613
+ editor.on( 'contentDom', function() {
2614
+ var editable = editor.editable();
2615
+
2616
+ editable.attachListener( editable, 'copy', eventListener );
2617
+ editable.attachListener( editable, 'cut', eventListener );
2618
+ } );
2619
+
2620
+ function eventListener( evt ) {
2621
+ if ( widgetsRepo.focused )
2622
+ copySingleWidget( widgetsRepo.focused, evt.name == 'cut' );
2623
+ }
2624
+ }
2625
+
2626
+ // Setup selection observer which will trigger:
2627
+ // * widget select & focus on selection change,
2628
+ // * nested editable focus (related properites and classes) on selection change,
2629
+ // * deselecting and blurring all widgets on data,
2630
+ // * blurring widget on editor blur.
2631
+ function setupSelectionObserver( widgetsRepo ) {
2632
+ var editor = widgetsRepo.editor;
2633
+
2634
+ editor.on( 'selectionCheck', function() {
2635
+ widgetsRepo.fire( 'checkSelection' );
2636
+ } );
2637
+
2638
+ widgetsRepo.on( 'checkSelection', widgetsRepo.checkSelection, widgetsRepo );
2639
+
2640
+ editor.on( 'selectionChange', function( evt ) {
2641
+ var nestedEditable = Widget.getNestedEditable( editor.editable(), evt.data.selection.getStartElement() ),
2642
+ newWidget = nestedEditable && widgetsRepo.getByElement( nestedEditable ),
2643
+ oldWidget = widgetsRepo.widgetHoldingFocusedEditable;
2644
+
2645
+ if ( oldWidget ) {
2646
+ if ( oldWidget !== newWidget || !oldWidget.focusedEditable.equals( nestedEditable ) ) {
2647
+ setFocusedEditable( widgetsRepo, oldWidget, null );
2648
+
2649
+ if ( newWidget && nestedEditable )
2650
+ setFocusedEditable( widgetsRepo, newWidget, nestedEditable );
2651
+ }
2652
+ }
2653
+ // It may happen that there's no widget even if editable was found -
2654
+ // e.g. if selection was automatically set in editable although widget wasn't initialized yet.
2655
+ else if ( newWidget && nestedEditable ) {
2656
+ setFocusedEditable( widgetsRepo, newWidget, nestedEditable );
2657
+ }
2658
+ } );
2659
+
2660
+ // Invalidate old widgets early - immediately on dataReady.
2661
+ editor.on( 'dataReady', function() {
2662
+ // Deselect and blur all widgets.
2663
+ stateUpdater( widgetsRepo ).commit();
2664
+ } );
2665
+
2666
+ editor.on( 'blur', function() {
2667
+ var widget;
2668
+
2669
+ if ( ( widget = widgetsRepo.focused ) )
2670
+ blurWidget( widgetsRepo, widget );
2671
+
2672
+ if ( ( widget = widgetsRepo.widgetHoldingFocusedEditable ) )
2673
+ setFocusedEditable( widgetsRepo, widget, null );
2674
+ } );
2675
+ }
2676
+
2677
+ // Set up actions like:
2678
+ // * processing in toHtml/toDataFormat,
2679
+ // * pasting handling,
2680
+ // * insertion handling,
2681
+ // * editable reload handling (setData, mode switch, undo/redo),
2682
+ // * DOM invalidation handling,
2683
+ // * widgets checks.
2684
+ function setupWidgetsLifecycle( widgetsRepo ) {
2685
+ setupWidgetsLifecycleStart( widgetsRepo );
2686
+ setupWidgetsLifecycleEnd( widgetsRepo );
2687
+
2688
+ widgetsRepo.on( 'checkWidgets', checkWidgets );
2689
+ widgetsRepo.editor.on( 'contentDomInvalidated', widgetsRepo.checkWidgets, widgetsRepo );
2690
+ }
2691
+
2692
+ function setupWidgetsLifecycleEnd( widgetsRepo ) {
2693
+ var editor = widgetsRepo.editor,
2694
+ downcastingSessions = {};
2695
+
2696
+ // Listen before htmlDP#htmlFilter is applied to cache all widgets, because we'll
2697
+ // loose data-cke-* attributes.
2698
+ editor.on( 'toDataFormat', function( evt ) {
2699
+ // To avoid conflicts between htmlDP#toDF calls done at the same time
2700
+ // (e.g. nestedEditable#getData called during downcasting some widget)
2701
+ // mark every toDataFormat event chain with the downcasting session id.
2702
+ var id = CKEDITOR.tools.getNextNumber(),
2703
+ toBeDowncasted = [];
2704
+ evt.data.downcastingSessionId = id;
2705
+ downcastingSessions[ id ] = toBeDowncasted;
2706
+
2707
+ evt.data.dataValue.forEach( function( element ) {
2708
+ var attrs = element.attributes,
2709
+ widget, widgetElement;
2710
+
2711
+ // Wrapper.
2712
+ // Perform first part of downcasting (cleanup) and cache widgets,
2713
+ // because after applying DP's filter all data-cke-* attributes will be gone.
2714
+ if ( 'data-cke-widget-id' in attrs ) {
2715
+ widget = widgetsRepo.instances[ attrs[ 'data-cke-widget-id' ] ];
2716
+ if ( widget ) {
2717
+ widgetElement = element.getFirst( Widget.isParserWidgetElement );
2718
+ toBeDowncasted.push( {
2719
+ wrapper: element,
2720
+ element: widgetElement,
2721
+ widget: widget,
2722
+ editables: {}
2723
+ } );
2724
+
2725
+ // If widget did not have data-cke-widget attribute before upcasting remove it.
2726
+ if ( widgetElement.attributes[ 'data-cke-widget-keep-attr' ] != '1' )
2727
+ delete widgetElement.attributes[ 'data-widget' ];
2728
+ }
2729
+ }
2730
+ // Nested editable.
2731
+ else if ( 'data-cke-widget-editable' in attrs ) {
2732
+ // Save the reference to this nested editable in the closest widget to be downcasted.
2733
+ // Nested editables are downcasted in the successive toDataFormat to create an opportunity
2734
+ // for dataFilter's "excludeNestedEditable" option to do its job (that option relies on
2735
+ // contenteditable="true" attribute) (http://dev.ckeditor.com/ticket/11372).
2736
+ toBeDowncasted[ toBeDowncasted.length - 1 ].editables[ attrs[ 'data-cke-widget-editable' ] ] = element;
2737
+
2738
+ // Don't check children - there won't be next wrapper or nested editable which we
2739
+ // should process in this session.
2740
+ return false;
2741
+ }
2742
+ }, CKEDITOR.NODE_ELEMENT, true );
2743
+ }, null, null, 8 );
2744
+
2745
+ // Listen after dataProcessor.htmlFilter and ACF were applied
2746
+ // so wrappers securing widgets' contents are removed after all filtering was done.
2747
+ editor.on( 'toDataFormat', function( evt ) {
2748
+ // Ignore some unmarked sessions.
2749
+ if ( !evt.data.downcastingSessionId )
2750
+ return;
2751
+
2752
+ var toBeDowncasted = downcastingSessions[ evt.data.downcastingSessionId ],
2753
+ toBe, widget, widgetElement, retElement, editableElement, e;
2754
+
2755
+ while ( ( toBe = toBeDowncasted.shift() ) ) {
2756
+ widget = toBe.widget;
2757
+ widgetElement = toBe.element;
2758
+ retElement = widget._.downcastFn && widget._.downcastFn.call( widget, widgetElement );
2759
+
2760
+ // Replace nested editables' content with their output data.
2761
+ for ( e in toBe.editables ) {
2762
+ editableElement = toBe.editables[ e ];
2763
+
2764
+ delete editableElement.attributes.contenteditable;
2765
+ editableElement.setHtml( widget.editables[ e ].getData() );
2766
+ }
2767
+
2768
+ // Returned element always defaults to widgetElement.
2769
+ if ( !retElement )
2770
+ retElement = widgetElement;
2771
+
2772
+ toBe.wrapper.replaceWith( retElement );
2773
+ }
2774
+ }, null, null, 13 );
2775
+
2776
+
2777
+ editor.on( 'contentDomUnload', function() {
2778
+ widgetsRepo.destroyAll( true );
2779
+ } );
2780
+ }
2781
+
2782
+ function setupWidgetsLifecycleStart( widgetsRepo ) {
2783
+ var editor = widgetsRepo.editor,
2784
+ processedWidgetOnly,
2785
+ snapshotLoaded;
2786
+
2787
+ // Listen after ACF (so data are filtered),
2788
+ // but before dataProcessor.dataFilter was applied (so we can secure widgets' internals).
2789
+ editor.on( 'toHtml', function( evt ) {
2790
+ var upcastIterator = createUpcastIterator( widgetsRepo ),
2791
+ toBeWrapped;
2792
+
2793
+ evt.data.dataValue.forEach( upcastIterator.iterator, CKEDITOR.NODE_ELEMENT, true );
2794
+
2795
+ // Clean up and wrap all queued elements.
2796
+ while ( ( toBeWrapped = upcastIterator.toBeWrapped.pop() ) ) {
2797
+ cleanUpWidgetElement( toBeWrapped[ 0 ] );
2798
+ widgetsRepo.wrapElement( toBeWrapped[ 0 ], toBeWrapped[ 1 ] );
2799
+ }
2800
+
2801
+ // Used to determine whether only widget was pasted.
2802
+ if ( evt.data.protectedWhitespaces ) {
2803
+ // Whitespaces are protected by wrapping content with spans. Take the middle node only.
2804
+ processedWidgetOnly = evt.data.dataValue.children.length == 3 &&
2805
+ Widget.isParserWidgetWrapper( evt.data.dataValue.children[ 1 ] );
2806
+ } else {
2807
+ processedWidgetOnly = evt.data.dataValue.children.length == 1 &&
2808
+ Widget.isParserWidgetWrapper( evt.data.dataValue.children[ 0 ] );
2809
+ }
2810
+ }, null, null, 8 );
2811
+
2812
+ editor.on( 'dataReady', function() {
2813
+ // Clean up all widgets loaded from snapshot.
2814
+ if ( snapshotLoaded )
2815
+ cleanUpAllWidgetElements( widgetsRepo, editor.editable() );
2816
+ snapshotLoaded = 0;
2817
+
2818
+ // Some widgets were destroyed on contentDomUnload,
2819
+ // some on loadSnapshot, but that does not include
2820
+ // e.g. setHtml on inline editor or widgets removed just
2821
+ // before setting data.
2822
+ widgetsRepo.destroyAll( true );
2823
+ widgetsRepo.initOnAll();
2824
+ } );
2825
+
2826
+ // Set flag so dataReady will know that additional
2827
+ // cleanup is needed, because snapshot containing widgets was loaded.
2828
+ editor.on( 'loadSnapshot', function( evt ) {
2829
+ // Primitive but sufficient check which will prevent from executing
2830
+ // heavier cleanUpAllWidgetElements if not needed.
2831
+ if ( ( /data-cke-widget/ ).test( evt.data ) )
2832
+ snapshotLoaded = 1;
2833
+
2834
+ widgetsRepo.destroyAll( true );
2835
+ }, null, null, 9 );
2836
+
2837
+ // Handle pasted single widget.
2838
+ editor.on( 'paste', function( evt ) {
2839
+ var data = evt.data;
2840
+
2841
+ data.dataValue = data.dataValue.replace( pasteReplaceRegex, pasteReplaceFn );
2842
+
2843
+ // If drag'n'drop kind of paste into nested editable (data.range), selection is set AFTER
2844
+ // data is pasted, which means editor has no chance to change activeFilter's context.
2845
+ // As a result, pasted data is filtered with default editor's filter instead of NE's and
2846
+ // funny things get inserted. Changing the filter by analysis of the paste range below (http://dev.ckeditor.com/ticket/13186).
2847
+ if ( data.range ) {
2848
+ // Check if pasting into nested editable.
2849
+ var nestedEditable = Widget.getNestedEditable( editor.editable(), data.range.startContainer );
2850
+
2851
+ if ( nestedEditable ) {
2852
+ // Retrieve the filter from NE's data and set it active before editor.insertHtml is done
2853
+ // in clipboard plugin.
2854
+ var filter = CKEDITOR.filter.instances[ nestedEditable.data( 'cke-filter' ) ];
2855
+
2856
+ if ( filter ) {
2857
+ editor.setActiveFilter( filter );
2858
+ }
2859
+ }
2860
+ }
2861
+ } );
2862
+
2863
+ // Listen with high priority to check widgets after data was inserted.
2864
+ editor.on( 'afterInsertHtml', function( evt ) {
2865
+ if ( evt.data.intoRange ) {
2866
+ widgetsRepo.checkWidgets( { initOnlyNew: true } );
2867
+ } else {
2868
+ editor.fire( 'lockSnapshot' );
2869
+ // Init only new for performance reason.
2870
+ // Focus inited if only widget was processed.
2871
+ widgetsRepo.checkWidgets( { initOnlyNew: true, focusInited: processedWidgetOnly } );
2872
+
2873
+ editor.fire( 'unlockSnapshot' );
2874
+ }
2875
+ } );
2876
+ }
2877
+
2878
+ // Helper for coordinating which widgets should be
2879
+ // selected/deselected and which one should be focused/blurred.
2880
+ function stateUpdater( widgetsRepo ) {
2881
+ var currentlySelected = widgetsRepo.selected,
2882
+ toBeSelected = [],
2883
+ toBeDeselected = currentlySelected.slice( 0 ),
2884
+ focused = null;
2885
+
2886
+ return {
2887
+ select: function( widget ) {
2888
+ if ( CKEDITOR.tools.indexOf( currentlySelected, widget ) < 0 )
2889
+ toBeSelected.push( widget );
2890
+
2891
+ var index = CKEDITOR.tools.indexOf( toBeDeselected, widget );
2892
+ if ( index >= 0 )
2893
+ toBeDeselected.splice( index, 1 );
2894
+
2895
+ return this;
2896
+ },
2897
+
2898
+ focus: function( widget ) {
2899
+ focused = widget;
2900
+ return this;
2901
+ },
2902
+
2903
+ commit: function() {
2904
+ var focusedChanged = widgetsRepo.focused !== focused,
2905
+ widget, isDirty;
2906
+
2907
+ widgetsRepo.editor.fire( 'lockSnapshot' );
2908
+
2909
+ if ( focusedChanged && ( widget = widgetsRepo.focused ) )
2910
+ blurWidget( widgetsRepo, widget );
2911
+
2912
+ while ( ( widget = toBeDeselected.pop() ) ) {
2913
+ currentlySelected.splice( CKEDITOR.tools.indexOf( currentlySelected, widget ), 1 );
2914
+ // Widget could be destroyed in the meantime - e.g. data could be set.
2915
+ if ( widget.isInited() ) {
2916
+ isDirty = widget.editor.checkDirty();
2917
+
2918
+ widget.setSelected( false );
2919
+
2920
+ !isDirty && widget.editor.resetDirty();
2921
+ }
2922
+ }
2923
+
2924
+ if ( focusedChanged && focused ) {
2925
+ isDirty = widgetsRepo.editor.checkDirty();
2926
+
2927
+ widgetsRepo.focused = focused;
2928
+ widgetsRepo.fire( 'widgetFocused', { widget: focused } );
2929
+ focused.setFocused( true );
2930
+
2931
+ !isDirty && widgetsRepo.editor.resetDirty();
2932
+ }
2933
+
2934
+ while ( ( widget = toBeSelected.pop() ) ) {
2935
+ currentlySelected.push( widget );
2936
+ widget.setSelected( true );
2937
+ }
2938
+
2939
+ widgetsRepo.editor.fire( 'unlockSnapshot' );
2940
+ }
2941
+ };
2942
+ }
2943
+
2944
+
2945
+ //
2946
+ // WIDGET helpers ---------------------------------------------------------
2947
+ //
2948
+
2949
+ // LEFT, RIGHT, UP, DOWN, DEL, BACKSPACE - unblock default fake sel handlers.
2950
+ var keystrokesNotBlockedByWidget = { 37: 1, 38: 1, 39: 1, 40: 1, 8: 1, 46: 1 };
2951
+
2952
+ // Applies or removes style's classes from widget.
2953
+ // @param {CKEDITOR.style} style Custom widget style.
2954
+ // @param {Boolean} apply Whether to apply or remove style.
2955
+ function applyRemoveStyle( widget, style, apply ) {
2956
+ var changed = 0,
2957
+ classes = getStyleClasses( style ),
2958
+ updatedClasses = widget.data.classes || {},
2959
+ cl;
2960
+
2961
+ // Ee... Something is wrong with this style.
2962
+ if ( !classes )
2963
+ return;
2964
+
2965
+ // Clone, because we need to break reference.
2966
+ updatedClasses = CKEDITOR.tools.clone( updatedClasses );
2967
+
2968
+ while ( ( cl = classes.pop() ) ) {
2969
+ if ( apply ) {
2970
+ if ( !updatedClasses[ cl ] )
2971
+ changed = updatedClasses[ cl ] = 1;
2972
+ } else {
2973
+ if ( updatedClasses[ cl ] ) {
2974
+ delete updatedClasses[ cl ];
2975
+ changed = 1;
2976
+ }
2977
+ }
2978
+ }
2979
+ if ( changed )
2980
+ widget.setData( 'classes', updatedClasses );
2981
+ }
2982
+
2983
+ function cancel( evt ) {
2984
+ evt.cancel();
2985
+ }
2986
+
2987
+ function copySingleWidget( widget, isCut ) {
2988
+ var editor = widget.editor,
2989
+ doc = editor.document;
2990
+
2991
+ // We're still handling previous copy/cut.
2992
+ // When keystroke is used to copy/cut this will also prevent
2993
+ // conflict with copySingleWidget called again for native copy/cut event.
2994
+ if ( doc.getById( 'cke_copybin' ) )
2995
+ return;
2996
+
2997
+ // [IE] Use span for copybin and its container to avoid bug with expanding editable height by
2998
+ // absolutely positioned element.
2999
+ var copybinName = ( editor.blockless || CKEDITOR.env.ie ) ? 'span' : 'div',
3000
+ copybin = doc.createElement( copybinName ),
3001
+ copybinContainer = doc.createElement( copybinName ),
3002
+ // IE8 always jumps to the end of document.
3003
+ needsScrollHack = CKEDITOR.env.ie && CKEDITOR.env.version < 9;
3004
+
3005
+ copybinContainer.setAttributes( {
3006
+ id: 'cke_copybin',
3007
+ 'data-cke-temp': '1'
3008
+ } );
3009
+
3010
+ // Position copybin element outside current viewport.
3011
+ copybin.setStyles( {
3012
+ position: 'absolute',
3013
+ width: '1px',
3014
+ height: '1px',
3015
+ overflow: 'hidden'
3016
+ } );
3017
+
3018
+ copybin.setStyle( editor.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-5000px' );
3019
+
3020
+ var range = editor.createRange();
3021
+ range.setStartBefore( widget.wrapper );
3022
+ range.setEndAfter( widget.wrapper );
3023
+
3024
+ copybin.setHtml(
3025
+ '<span data-cke-copybin-start="1">\u200b</span>' +
3026
+ editor.editable().getHtmlFromRange( range ).getHtml() +
3027
+ '<span data-cke-copybin-end="1">\u200b</span>' );
3028
+
3029
+ // Save snapshot with the current state.
3030
+ editor.fire( 'saveSnapshot' );
3031
+
3032
+ // Ignore copybin.
3033
+ editor.fire( 'lockSnapshot' );
3034
+
3035
+ copybinContainer.append( copybin );
3036
+ editor.editable().append( copybinContainer );
3037
+
3038
+ var listener1 = editor.on( 'selectionChange', cancel, null, null, 0 ),
3039
+ listener2 = widget.repository.on( 'checkSelection', cancel, null, null, 0 );
3040
+
3041
+ if ( needsScrollHack ) {
3042
+ var docElement = doc.getDocumentElement().$,
3043
+ scrollTop = docElement.scrollTop;
3044
+ }
3045
+
3046
+ // Once the clone of the widget is inside of copybin, select
3047
+ // the entire contents. This selection will be copied by the
3048
+ // native browser's clipboard system.
3049
+ range = editor.createRange();
3050
+ range.selectNodeContents( copybin );
3051
+ range.select();
3052
+
3053
+ if ( needsScrollHack )
3054
+ docElement.scrollTop = scrollTop;
3055
+
3056
+ setTimeout( function() {
3057
+ // [IE] Focus widget before removing copybin to avoid scroll jump.
3058
+ if ( !isCut )
3059
+ widget.focus();
3060
+
3061
+ copybinContainer.remove();
3062
+
3063
+ listener1.removeListener();
3064
+ listener2.removeListener();
3065
+
3066
+ editor.fire( 'unlockSnapshot' );
3067
+
3068
+ if ( isCut ) {
3069
+ widget.repository.del( widget );
3070
+ editor.fire( 'saveSnapshot' );
3071
+ }
3072
+ }, 100 ); // Use 100ms, so Chrome (@Mac) will be able to grab the content.
3073
+ }
3074
+
3075
+ // Extracts classes array from style instance.
3076
+ function getStyleClasses( style ) {
3077
+ var attrs = style.getDefinition().attributes,
3078
+ classes = attrs && attrs[ 'class' ];
3079
+
3080
+ return classes ? classes.split( /\s+/ ) : null;
3081
+ }
3082
+
3083
+ // [IE] Force keeping focus because IE sometimes forgets to fire focus on main editable
3084
+ // when blurring nested editable.
3085
+ // @context widget
3086
+ function onEditableBlur() {
3087
+ var active = CKEDITOR.document.getActive(),
3088
+ editor = this.editor,
3089
+ editable = editor.editable();
3090
+
3091
+ // If focus stays within editor override blur and set currentActive because it should be
3092
+ // automatically changed to editable on editable#focus but it is not fired.
3093
+ if ( ( editable.isInline() ? editable : editor.document.getWindow().getFrame() ).equals( active ) )
3094
+ editor.focusManager.focus( editable );
3095
+ }
3096
+
3097
+ // Force selectionChange when editable was focused.
3098
+ // Similar to hack in selection.js#~620.
3099
+ // @context widget
3100
+ function onEditableFocus() {
3101
+ // Gecko does not support 'DOMFocusIn' event on which we unlock selection
3102
+ // in selection.js to prevent selection locking when entering nested editables.
3103
+ if ( CKEDITOR.env.gecko )
3104
+ this.editor.unlockSelection();
3105
+
3106
+ // We don't need to force selectionCheck on Webkit, because on Webkit
3107
+ // we do that on DOMFocusIn in selection.js.
3108
+ if ( !CKEDITOR.env.webkit ) {
3109
+ this.editor.forceNextSelectionCheck();
3110
+ this.editor.selectionChange( 1 );
3111
+ }
3112
+ }
3113
+
3114
+ // Setup listener on widget#data which will update (remove/add) classes
3115
+ // by comparing newly set classes with the old ones.
3116
+ function setupDataClassesListener( widget ) {
3117
+ // Note: previousClasses and newClasses may be null!
3118
+ // Tip: for ( cl in null ) is correct.
3119
+ var previousClasses = null;
3120
+
3121
+ widget.on( 'data', function() {
3122
+ var newClasses = this.data.classes,
3123
+ cl;
3124
+
3125
+ // When setting new classes one need to remember
3126
+ // that he must break reference.
3127
+ if ( previousClasses == newClasses )
3128
+ return;
3129
+
3130
+ for ( cl in previousClasses ) {
3131
+ // Avoid removing and adding classes again.
3132
+ if ( !( newClasses && newClasses[ cl ] ) )
3133
+ this.removeClass( cl );
3134
+ }
3135
+ for ( cl in newClasses )
3136
+ this.addClass( cl );
3137
+
3138
+ previousClasses = newClasses;
3139
+ } );
3140
+ }
3141
+
3142
+ // Add a listener to data event that will set/change widget's label (http://dev.ckeditor.com/ticket/14539).
3143
+ function setupA11yListener( widget ) {
3144
+ // Note, the function gets executed in a context of widget instance.
3145
+ function getLabelDefault() {
3146
+ return this.editor.lang.widget.label.replace( /%1/, this.pathName || this.element.getName() );
3147
+ }
3148
+
3149
+ // Setting a listener on data is enough, there's no need to perform it on widget initialization, as
3150
+ // setupWidgetData fires this event anyway.
3151
+ widget.on( 'data', function() {
3152
+ // In some cases widget might get destroyed in an earlier data listener. For instance, image2 plugin, does
3153
+ // so when changing its internal state.
3154
+ if ( !widget.wrapper ) {
3155
+ return;
3156
+ }
3157
+
3158
+ var label = this.getLabel ? this.getLabel() : getLabelDefault.call( this );
3159
+
3160
+ widget.wrapper.setAttribute( 'role', 'region' );
3161
+ widget.wrapper.setAttribute( 'aria-label', label );
3162
+ }, null, null, 9999 );
3163
+ }
3164
+
3165
+ function setupDragHandler( widget ) {
3166
+ if ( !widget.draggable )
3167
+ return;
3168
+
3169
+ var editor = widget.editor,
3170
+ // Use getLast to find wrapper's direct descendant (http://dev.ckeditor.com/ticket/12022).
3171
+ container = widget.wrapper.getLast( Widget.isDomDragHandlerContainer ),
3172
+ img;
3173
+
3174
+ // Reuse drag handler if already exists (http://dev.ckeditor.com/ticket/11281).
3175
+ if ( container )
3176
+ img = container.findOne( 'img' );
3177
+ else {
3178
+ container = new CKEDITOR.dom.element( 'span', editor.document );
3179
+ container.setAttributes( {
3180
+ 'class': 'cke_reset cke_widget_drag_handler_container',
3181
+ // Split background and background-image for IE8 which will break on rgba().
3182
+ style: 'background:rgba(220,220,220,0.5);background-image:url(' + editor.plugins.widget.path + 'images/handle.png)'
3183
+ } );
3184
+
3185
+ img = new CKEDITOR.dom.element( 'img', editor.document );
3186
+ img.setAttributes( {
3187
+ 'class': 'cke_reset cke_widget_drag_handler',
3188
+ 'data-cke-widget-drag-handler': '1',
3189
+ src: CKEDITOR.tools.transparentImageData,
3190
+ width: DRAG_HANDLER_SIZE,
3191
+ title: editor.lang.widget.move,
3192
+ height: DRAG_HANDLER_SIZE,
3193
+ role: 'presentation'
3194
+ } );
3195
+ widget.inline && img.setAttribute( 'draggable', 'true' );
3196
+
3197
+ container.append( img );
3198
+ widget.wrapper.append( container );
3199
+ }
3200
+
3201
+ // Preventing page reload when dropped content on widget wrapper (http://dev.ckeditor.com/ticket/13015).
3202
+ // Widget is not editable so by default drop on it isn't allowed what means that
3203
+ // browser handles it (there's no editable#drop event). If there's no drop event we cannot block
3204
+ // the drop, so page is reloaded. This listener enables drop on widget wrappers.
3205
+ widget.wrapper.on( 'dragover', function( evt ) {
3206
+ evt.data.preventDefault();
3207
+ } );
3208
+
3209
+ widget.wrapper.on( 'mouseenter', widget.updateDragHandlerPosition, widget );
3210
+ setTimeout( function() {
3211
+ widget.on( 'data', widget.updateDragHandlerPosition, widget );
3212
+ }, 50 );
3213
+
3214
+ if ( !widget.inline ) {
3215
+ img.on( 'mousedown', onBlockWidgetDrag, widget );
3216
+
3217
+ // On IE8 'dragstart' is propagated to editable, so editor#dragstart is fired twice on block widgets.
3218
+ if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
3219
+ img.on( 'dragstart', function( evt ) {
3220
+ evt.data.preventDefault( true );
3221
+ } );
3222
+ }
3223
+ }
3224
+
3225
+ widget.dragHandlerContainer = container;
3226
+ }
3227
+
3228
+ function onBlockWidgetDrag( evt ) {
3229
+ var finder = this.repository.finder,
3230
+ locator = this.repository.locator,
3231
+ liner = this.repository.liner,
3232
+ editor = this.editor,
3233
+ editable = editor.editable(),
3234
+ listeners = [],
3235
+ sorted = [],
3236
+ locations,
3237
+ y;
3238
+
3239
+ // Mark dragged widget for repository#finder.
3240
+ this.repository._.draggedWidget = this;
3241
+
3242
+ // Harvest all possible relations and display some closest.
3243
+ var relations = finder.greedySearch(),
3244
+
3245
+ buffer = CKEDITOR.tools.eventsBuffer( 50, function() {
3246
+ locations = locator.locate( relations );
3247
+
3248
+ // There's only a single line displayed for D&D.
3249
+ sorted = locator.sort( y, 1 );
3250
+
3251
+ if ( sorted.length ) {
3252
+ liner.prepare( relations, locations );
3253
+ liner.placeLine( sorted[ 0 ] );
3254
+ liner.cleanup();
3255
+ }
3256
+ } );
3257
+
3258
+ // Let's have the "dragging cursor" over entire editable.
3259
+ editable.addClass( 'cke_widget_dragging' );
3260
+
3261
+ // Cache mouse position so it is re-used in events buffer.
3262
+ listeners.push( editable.on( 'mousemove', function( evt ) {
3263
+ y = evt.data.$.clientY;
3264
+ buffer.input();
3265
+ } ) );
3266
+
3267
+ // Fire drag start as it happens during the native D&D.
3268
+ editor.fire( 'dragstart', { target: evt.sender } );
3269
+
3270
+ function onMouseUp() {
3271
+ var l;
3272
+
3273
+ buffer.reset();
3274
+
3275
+ // Stop observing events.
3276
+ while ( ( l = listeners.pop() ) )
3277
+ l.removeListener();
3278
+
3279
+ onBlockWidgetDrop.call( this, sorted, evt.sender );
3280
+ }
3281
+
3282
+ // Mouseup means "drop". This is when the widget is being detached
3283
+ // from DOM and placed at range determined by the line (location).
3284
+ listeners.push( editor.document.once( 'mouseup', onMouseUp, this ) );
3285
+
3286
+ // Prevent calling 'onBlockWidgetDrop' twice in the inline editor.
3287
+ // `removeListener` does not work if it is called at the same time event is fired.
3288
+ if ( !editable.isInline() ) {
3289
+ // Mouseup may occur when user hovers the line, which belongs to
3290
+ // the outer document. This is, of course, a valid listener too.
3291
+ listeners.push( CKEDITOR.document.once( 'mouseup', onMouseUp, this ) );
3292
+ }
3293
+ }
3294
+
3295
+ function onBlockWidgetDrop( sorted, dragTarget ) {
3296
+ var finder = this.repository.finder,
3297
+ liner = this.repository.liner,
3298
+ editor = this.editor,
3299
+ editable = this.editor.editable();
3300
+
3301
+ if ( !CKEDITOR.tools.isEmpty( liner.visible ) ) {
3302
+ // Retrieve range for the closest location.
3303
+ var dropRange = finder.getRange( sorted[ 0 ] );
3304
+
3305
+ // Focus widget (it could lost focus after mousedown+mouseup)
3306
+ // and save this state as the one where we want to be taken back when undoing.
3307
+ this.focus();
3308
+
3309
+ // Drag range will be set in the drop listener.
3310
+ editor.fire( 'drop', {
3311
+ dropRange: dropRange,
3312
+ target: dropRange.startContainer
3313
+ } );
3314
+ }
3315
+
3316
+ // Clean-up custom cursor for editable.
3317
+ editable.removeClass( 'cke_widget_dragging' );
3318
+
3319
+ // Clean-up all remaining lines.
3320
+ liner.hideVisible();
3321
+
3322
+ // Clean-up drag & drop.
3323
+ editor.fire( 'dragend', { target: dragTarget } );
3324
+ }
3325
+
3326
+ function setupEditables( widget ) {
3327
+ var editableName,
3328
+ editableDef,
3329
+ definedEditables = widget.editables;
3330
+
3331
+ widget.editables = {};
3332
+
3333
+ if ( !widget.editables )
3334
+ return;
3335
+
3336
+ for ( editableName in definedEditables ) {
3337
+ editableDef = definedEditables[ editableName ];
3338
+ widget.initEditable( editableName, typeof editableDef == 'string' ? { selector: editableDef } : editableDef );
3339
+ }
3340
+ }
3341
+
3342
+ function setupMask( widget ) {
3343
+ if ( !widget.mask )
3344
+ return;
3345
+
3346
+ // Reuse mask if already exists (http://dev.ckeditor.com/ticket/11281).
3347
+ var img = widget.wrapper.findOne( '.cke_widget_mask' );
3348
+
3349
+ if ( !img ) {
3350
+ img = new CKEDITOR.dom.element( 'img', widget.editor.document );
3351
+ img.setAttributes( {
3352
+ src: CKEDITOR.tools.transparentImageData,
3353
+ 'class': 'cke_reset cke_widget_mask'
3354
+ } );
3355
+ widget.wrapper.append( img );
3356
+ }
3357
+
3358
+ widget.mask = img;
3359
+ }
3360
+
3361
+ // Replace parts object containing:
3362
+ // partName => selector pairs
3363
+ // with:
3364
+ // partName => element pairs
3365
+ function setupParts( widget ) {
3366
+ if ( widget.parts ) {
3367
+ var parts = {},
3368
+ el, partName;
3369
+
3370
+ for ( partName in widget.parts ) {
3371
+ el = widget.wrapper.findOne( widget.parts[ partName ] );
3372
+ parts[ partName ] = el;
3373
+ }
3374
+ widget.parts = parts;
3375
+ }
3376
+ }
3377
+
3378
+ function setupWidget( widget, widgetDef ) {
3379
+ setupWrapper( widget );
3380
+ setupParts( widget );
3381
+ setupEditables( widget );
3382
+ setupMask( widget );
3383
+ setupDragHandler( widget );
3384
+ setupDataClassesListener( widget );
3385
+ setupA11yListener( widget );
3386
+
3387
+ // http://dev.ckeditor.com/ticket/11145: [IE8] Non-editable content of widget is draggable.
3388
+ if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
3389
+ widget.wrapper.on( 'dragstart', function( evt ) {
3390
+ var target = evt.data.getTarget();
3391
+
3392
+ // Allow text dragging inside nested editables or dragging inline widget's drag handler.
3393
+ if ( !Widget.getNestedEditable( widget, target ) && !( widget.inline && Widget.isDomDragHandler( target ) ) )
3394
+ evt.data.preventDefault();
3395
+ } );
3396
+ }
3397
+
3398
+ widget.wrapper.removeClass( 'cke_widget_new' );
3399
+ widget.element.addClass( 'cke_widget_element' );
3400
+
3401
+ widget.on( 'key', function( evt ) {
3402
+ var keyCode = evt.data.keyCode;
3403
+
3404
+ // ENTER.
3405
+ if ( keyCode == 13 ) {
3406
+ widget.edit();
3407
+ // CTRL+C or CTRL+X.
3408
+ } else if ( keyCode == CKEDITOR.CTRL + 67 || keyCode == CKEDITOR.CTRL + 88 ) {
3409
+ copySingleWidget( widget, keyCode == CKEDITOR.CTRL + 88 );
3410
+ return; // Do not preventDefault.
3411
+ } else if ( keyCode in keystrokesNotBlockedByWidget || ( CKEDITOR.CTRL & keyCode ) || ( CKEDITOR.ALT & keyCode ) ) {
3412
+ // Pass chosen keystrokes to other plugins or default fake sel handlers.
3413
+ // Pass all CTRL/ALT keystrokes.
3414
+ return;
3415
+ }
3416
+
3417
+ return false;
3418
+ }, null, null, 999 );
3419
+ // Listen with high priority so it's possible
3420
+ // to overwrite this callback.
3421
+
3422
+ widget.on( 'doubleclick', function( evt ) {
3423
+ if ( widget.edit() ) {
3424
+ // We have to cancel event if edit method opens a dialog, otherwise
3425
+ // link plugin may open extra dialog (http://dev.ckeditor.com/ticket/12140).
3426
+ evt.cancel();
3427
+ }
3428
+ } );
3429
+
3430
+ if ( widgetDef.data )
3431
+ widget.on( 'data', widgetDef.data );
3432
+
3433
+ if ( widgetDef.edit )
3434
+ widget.on( 'edit', widgetDef.edit );
3435
+ }
3436
+
3437
+ function setupWidgetData( widget, startupData ) {
3438
+ var widgetDataAttr = widget.element.data( 'cke-widget-data' );
3439
+
3440
+ if ( widgetDataAttr )
3441
+ widget.setData( JSON.parse( decodeURIComponent( widgetDataAttr ) ) );
3442
+ if ( startupData )
3443
+ widget.setData( startupData );
3444
+
3445
+ // Populate classes if they are not preset.
3446
+ if ( !widget.data.classes )
3447
+ widget.setData( 'classes', widget.getClasses() );
3448
+
3449
+ // Unblock data and...
3450
+ widget.dataReady = true;
3451
+
3452
+ // Write data to element because this was blocked when data wasn't ready.
3453
+ writeDataToElement( widget );
3454
+
3455
+ // Fire data event first time, because this was blocked when data wasn't ready.
3456
+ widget.fire( 'data', widget.data );
3457
+ }
3458
+
3459
+ function setupWrapper( widget ) {
3460
+ // Retrieve widget wrapper. Assign an id to it.
3461
+ var wrapper = widget.wrapper = widget.element.getParent();
3462
+ wrapper.setAttribute( 'data-cke-widget-id', widget.id );
3463
+ }
3464
+
3465
+ function writeDataToElement( widget ) {
3466
+ widget.element.data( 'cke-widget-data', encodeURIComponent( JSON.stringify( widget.data ) ) );
3467
+ }
3468
+
3469
+ //
3470
+ // WIDGET STYLE HANDLER ---------------------------------------------------
3471
+ //
3472
+
3473
+ ( function() {
3474
+ // Styles categorized by group. It is used to prevent applying styles for the same group being used together.
3475
+ var styleGroups = {};
3476
+
3477
+ /**
3478
+ * The class representing a widget style. It is an {@link CKEDITOR#STYLE_OBJECT object} like
3479
+ * the styles handler for widgets.
3480
+ *
3481
+ * **Note:** This custom style handler does not support all methods of the {@link CKEDITOR.style} class.
3482
+ * Not supported methods: {@link #applyToRange}, {@link #removeFromRange}, {@link #applyToObject}.
3483
+ *
3484
+ * @since 4.4
3485
+ * @class CKEDITOR.style.customHandlers.widget
3486
+ * @extends CKEDITOR.style
3487
+ */
3488
+ CKEDITOR.style.addCustomHandler( {
3489
+ type: 'widget',
3490
+
3491
+ setup: function( styleDefinition ) {
3492
+ /**
3493
+ * The name of widget to which this style can be applied.
3494
+ * It is extracted from style definition's `widget` property.
3495
+ *
3496
+ * @property {String} widget
3497
+ */
3498
+ this.widget = styleDefinition.widget;
3499
+
3500
+ /**
3501
+ * An array of groups that this style belongs to.
3502
+ * Styles assigned to the same group cannot be combined.
3503
+ *
3504
+ * @since 4.6.2
3505
+ * @property {Array} group
3506
+ */
3507
+ this.group = typeof styleDefinition.group == 'string' ? [ styleDefinition.group ] : styleDefinition.group;
3508
+
3509
+ // Store style categorized by its group.
3510
+ // It is used to prevent enabling two styles from same group.
3511
+ if ( this.group ) {
3512
+ saveStyleGroup( this );
3513
+ }
3514
+ },
3515
+
3516
+ apply: function( editor ) {
3517
+ var widget;
3518
+
3519
+ // Before CKEditor 4.4 wasn't a required argument, so we need to
3520
+ // handle a case when it wasn't provided.
3521
+ if ( !( editor instanceof CKEDITOR.editor ) )
3522
+ return;
3523
+
3524
+ // Theoretically we could bypass checkApplicable, get widget from
3525
+ // widgets.focused and check its name, what would be faster, but then
3526
+ // this custom style would work differently than the default style
3527
+ // which checks if it's applicable before applying or removing itself.
3528
+ if ( this.checkApplicable( editor.elementPath(), editor ) ) {
3529
+ widget = editor.widgets.focused;
3530
+
3531
+ // Remove other styles from the same group.
3532
+ if ( this.group ) {
3533
+ this.removeStylesFromSameGroup( editor );
3534
+ }
3535
+
3536
+ widget.applyStyle( this );
3537
+ }
3538
+ },
3539
+
3540
+ remove: function( editor ) {
3541
+ // Before CKEditor 4.4 wasn't a required argument, so we need to
3542
+ // handle a case when it wasn't provided.
3543
+ if ( !( editor instanceof CKEDITOR.editor ) )
3544
+ return;
3545
+
3546
+ if ( this.checkApplicable( editor.elementPath(), editor ) )
3547
+ editor.widgets.focused.removeStyle( this );
3548
+ },
3549
+
3550
+ /**
3551
+ * Removes all styles that belong to the same group as this style. This method will neither add nor remove
3552
+ * the current style.
3553
+ * Returns `true` if any style was removed, otherwise returns `false`.
3554
+ *
3555
+ * @since 4.6.2
3556
+ * @param {CKEDITOR.editor} editor
3557
+ * @returns {Boolean}
3558
+ */
3559
+ removeStylesFromSameGroup: function( editor ) {
3560
+ var stylesFromSameGroup,
3561
+ path,
3562
+ removed = false;
3563
+
3564
+ // Before CKEditor 4.4 wasn't a required argument, so we need to
3565
+ // handle a case when it wasn't provided.
3566
+ if ( !( editor instanceof CKEDITOR.editor ) )
3567
+ return false;
3568
+
3569
+ path = editor.elementPath();
3570
+ if ( this.checkApplicable( path, editor ) ) {
3571
+ // Iterate over each group.
3572
+ for ( var i = 0, l = this.group.length; i < l; i++ ) {
3573
+ stylesFromSameGroup = styleGroups[ this.widget ][ this.group[ i ] ];
3574
+ // Iterate over each style from group.
3575
+ for ( var j = 0; j < stylesFromSameGroup.length; j++ ) {
3576
+ if ( stylesFromSameGroup[ j ] !== this && stylesFromSameGroup[ j ].checkActive( path, editor ) ) {
3577
+ editor.widgets.focused.removeStyle( stylesFromSameGroup[ j ] );
3578
+ removed = true;
3579
+ }
3580
+ }
3581
+ }
3582
+ }
3583
+
3584
+ return removed;
3585
+ },
3586
+
3587
+ checkActive: function( elementPath, editor ) {
3588
+ return this.checkElementMatch( elementPath.lastElement, 0, editor );
3589
+ },
3590
+
3591
+ checkApplicable: function( elementPath, editor ) {
3592
+ // Before CKEditor 4.4 wasn't a required argument, so we need to
3593
+ // handle a case when it wasn't provided.
3594
+ if ( !( editor instanceof CKEDITOR.editor ) )
3595
+ return false;
3596
+
3597
+ return this.checkElement( elementPath.lastElement );
3598
+ },
3599
+
3600
+ checkElementMatch: checkElementMatch,
3601
+
3602
+ checkElementRemovable: checkElementMatch,
3603
+
3604
+ /**
3605
+ * Checks if an element is a {@link CKEDITOR.plugins.widget#wrapper wrapper} of a
3606
+ * widget whose name matches the {@link #widget widget name} specified in the style definition.
3607
+ *
3608
+ * @param {CKEDITOR.dom.element} element
3609
+ * @returns {Boolean}
3610
+ */
3611
+ checkElement: function( element ) {
3612
+ if ( !Widget.isDomWidgetWrapper( element ) )
3613
+ return false;
3614
+
3615
+ var widgetElement = element.getFirst( Widget.isDomWidgetElement );
3616
+ return widgetElement && widgetElement.data( 'widget' ) == this.widget;
3617
+ },
3618
+
3619
+ buildPreview: function( label ) {
3620
+ return label || this._.definition.name;
3621
+ },
3622
+
3623
+ /**
3624
+ * Returns allowed content rules which should be registered for this style.
3625
+ * Uses widget's {@link CKEDITOR.plugins.widget.definition#styleableElements} to make a rule
3626
+ * allowing classes on specified elements or use widget's
3627
+ * {@link CKEDITOR.plugins.widget.definition#styleToAllowedContentRules} method to transform a style
3628
+ * into allowed content rules.
3629
+ *
3630
+ * @param {CKEDITOR.editor} The editor instance.
3631
+ * @returns {CKEDITOR.filter.allowedContentRules}
3632
+ */
3633
+ toAllowedContentRules: function( editor ) {
3634
+ if ( !editor )
3635
+ return null;
3636
+
3637
+ var widgetDef = editor.widgets.registered[ this.widget ],
3638
+ classes,
3639
+ rule = {};
3640
+
3641
+ if ( !widgetDef )
3642
+ return null;
3643
+
3644
+ if ( widgetDef.styleableElements ) {
3645
+ classes = this.getClassesArray();
3646
+ if ( !classes )
3647
+ return null;
3648
+
3649
+ rule[ widgetDef.styleableElements ] = {
3650
+ classes: classes,
3651
+ propertiesOnly: true
3652
+ };
3653
+ return rule;
3654
+ }
3655
+ if ( widgetDef.styleToAllowedContentRules )
3656
+ return widgetDef.styleToAllowedContentRules( this );
3657
+ return null;
3658
+ },
3659
+
3660
+ /**
3661
+ * Returns classes defined in the style in form of an array.
3662
+ *
3663
+ * @returns {String[]}
3664
+ */
3665
+ getClassesArray: function() {
3666
+ var classes = this._.definition.attributes && this._.definition.attributes[ 'class' ];
3667
+
3668
+ return classes ? CKEDITOR.tools.trim( classes ).split( /\s+/ ) : null;
3669
+ },
3670
+
3671
+ /**
3672
+ * Not implemented.
3673
+ *
3674
+ * @method applyToRange
3675
+ */
3676
+ applyToRange: notImplemented,
3677
+
3678
+ /**
3679
+ * Not implemented.
3680
+ *
3681
+ * @method removeFromRange
3682
+ */
3683
+ removeFromRange: notImplemented,
3684
+
3685
+ /**
3686
+ * Not implemented.
3687
+ *
3688
+ * @method applyToObject
3689
+ */
3690
+ applyToObject: notImplemented
3691
+ } );
3692
+
3693
+ function notImplemented() {}
3694
+
3695
+ // @context style
3696
+ function checkElementMatch( element, fullMatch, editor ) {
3697
+ // Before CKEditor 4.4 wasn't a required argument, so we need to
3698
+ // handle a case when it wasn't provided.
3699
+ if ( !editor )
3700
+ return false;
3701
+
3702
+ if ( !this.checkElement( element ) )
3703
+ return false;
3704
+
3705
+ var widget = editor.widgets.getByElement( element, true );
3706
+ return widget && widget.checkStyleActive( this );
3707
+ }
3708
+
3709
+ // Save and categorize style by its group.
3710
+ function saveStyleGroup( style ) {
3711
+ var widgetName = style.widget,
3712
+ group;
3713
+
3714
+ if ( !styleGroups[ widgetName ] ) {
3715
+ styleGroups[ widgetName ] = {};
3716
+ }
3717
+
3718
+ for ( var i = 0, l = style.group.length; i < l; i++ ) {
3719
+ group = style.group[ i ];
3720
+ if ( !styleGroups[ widgetName ][ group ] ) {
3721
+ styleGroups[ widgetName ][ group ] = [];
3722
+ }
3723
+
3724
+ styleGroups[ widgetName ][ group ].push( style );
3725
+ }
3726
+ }
3727
+
3728
+ } )();
3729
+
3730
+ //
3731
+ // EXPOSE PUBLIC API ------------------------------------------------------
3732
+ //
3733
+
3734
+ CKEDITOR.plugins.widget = Widget;
3735
+ Widget.repository = Repository;
3736
+ Widget.nestedEditable = NestedEditable;
3737
+ } )();
3738
+
3739
+ /**
3740
+ * An event fired when a widget definition is registered by the {@link CKEDITOR.plugins.widget.repository#add} method.
3741
+ * It is possible to modify the definition being registered.
3742
+ *
3743
+ * @event widgetDefinition
3744
+ * @member CKEDITOR.editor
3745
+ * @param {CKEDITOR.plugins.widget.definition} data Widget definition.
3746
+ */
3747
+
3748
+ /**
3749
+ * This is an abstract class that describes the definition of a widget.
3750
+ * It is a type of {@link CKEDITOR.plugins.widget.repository#add} method's second argument.
3751
+ *
3752
+ * Widget instances inherit from registered widget definitions, although not in a prototypal way.
3753
+ * They are simply extended with corresponding widget definitions. Note that not all properties of
3754
+ * the widget definition become properties of a widget. Some, like {@link #data} or {@link #edit}, become
3755
+ * widget's events listeners.
3756
+ *
3757
+ * @class CKEDITOR.plugins.widget.definition
3758
+ * @abstract
3759
+ * @mixins CKEDITOR.feature
3760
+ */
3761
+
3762
+ /**
3763
+ * Widget definition name. It is automatically set when the definition is
3764
+ * {@link CKEDITOR.plugins.widget.repository#add registered}.
3765
+ *
3766
+ * @property {String} name
3767
+ */
3768
+
3769
+ /**
3770
+ * The method executed while initializing a widget, after a widget instance
3771
+ * is created, but before it is ready. It is executed before the first
3772
+ * {@link CKEDITOR.plugins.widget#event-data} is fired so it is common to
3773
+ * use the `init` method to populate widget data with information loaded from
3774
+ * the DOM, like for exmaple:
3775
+ *
3776
+ * init: function() {
3777
+ * this.setData( 'width', this.element.getStyle( 'width' ) );
3778
+ *
3779
+ * if ( this.parts.caption.getStyle( 'display' ) != 'none' )
3780
+ * this.setData( 'showCaption', true );
3781
+ * }
3782
+ *
3783
+ * @property {Function} init
3784
+ */
3785
+
3786
+ /**
3787
+ * The function to be used to upcast an element to this widget or a
3788
+ * comma-separated list of upcast methods from the {@link #upcasts} object.
3789
+ *
3790
+ * The upcast function **is not** executed in the widget context (because the widget
3791
+ * does not exist yet) and two arguments are passed:
3792
+ *
3793
+ * * `element` ({@link CKEDITOR.htmlParser.element}) &ndash; The element to be checked.
3794
+ * * `data` (`Object`) &ndash; The object which can be extended with data which will then be passed to the widget.
3795
+ *
3796
+ * An element will be upcasted if a function returned `true` or an instance of
3797
+ * a {@link CKEDITOR.htmlParser.element} if upcasting meant DOM structure changes
3798
+ * (in this case the widget will be initialized on the returned element).
3799
+ *
3800
+ * @property {String/Function} upcast
3801
+ */
3802
+
3803
+ /**
3804
+ * The object containing functions which can be used to upcast this widget.
3805
+ * Only those pointed by the {@link #upcast} property will be used.
3806
+ *
3807
+ * In most cases it is appropriate to use {@link #upcast} directly,
3808
+ * because majority of widgets need just one method.
3809
+ * However, in some cases the widget author may want to expose more than one variant
3810
+ * and then this property may be used.
3811
+ *
3812
+ * upcasts: {
3813
+ * // This function may upcast only figure elements.
3814
+ * figure: function() {
3815
+ * // ...
3816
+ * },
3817
+ * // This function may upcast only image elements.
3818
+ * image: function() {
3819
+ * // ...
3820
+ * },
3821
+ * // More variants...
3822
+ * }
3823
+ *
3824
+ * // Then, widget user may choose which upcast methods will be enabled.
3825
+ * editor.on( 'widgetDefinition', function( evt ) {
3826
+ * if ( evt.data.name == 'image' )
3827
+ * evt.data.upcast = 'figure,image'; // Use both methods.
3828
+ * } );
3829
+ *
3830
+ * @property {Object} upcasts
3831
+ */
3832
+
3833
+ /**
3834
+ * The {@link #upcast} method(s) priority. The upcast with a lower priority number will be called before
3835
+ * the one with a higher number. The default priority is `10`.
3836
+ *
3837
+ * @since 4.5
3838
+ * @property {Number} [upcastPriority=10]
3839
+ */
3840
+
3841
+ /**
3842
+ * The function to be used to downcast this widget or
3843
+ * a name of the downcast option from the {@link #downcasts} object.
3844
+ *
3845
+ * The downcast funciton will be executed in the {@link CKEDITOR.plugins.widget} context
3846
+ * and with `widgetElement` ({@link CKEDITOR.htmlParser.element}) argument which is
3847
+ * the widget's main element.
3848
+ *
3849
+ * The function may return an instance of the {@link CKEDITOR.htmlParser.node} class if the widget
3850
+ * needs to be downcasted to a different node than the widget's main element.
3851
+ *
3852
+ * @property {String/Function} downcast
3853
+ */
3854
+
3855
+ /**
3856
+ * The object containing functions which can be used to downcast this widget.
3857
+ * Only the one pointed by the {@link #downcast} property will be used.
3858
+ *
3859
+ * In most cases it is appropriate to use {@link #downcast} directly,
3860
+ * because majority of widgets have just one variant of downcasting (or none at all).
3861
+ * However, in some cases the widget author may want to expose more than one variant
3862
+ * and then this property may be used.
3863
+ *
3864
+ * downcasts: {
3865
+ * // This downcast may transform the widget into the figure element.
3866
+ * figure: function() {
3867
+ * // ...
3868
+ * },
3869
+ * // This downcast may transform the widget into the image element with data-* attributes.
3870
+ * image: function() {
3871
+ * // ...
3872
+ * }
3873
+ * }
3874
+ *
3875
+ * // Then, the widget user may choose one of the downcast options when setting up his editor.
3876
+ * editor.on( 'widgetDefinition', function( evt ) {
3877
+ * if ( evt.data.name == 'image' )
3878
+ * evt.data.downcast = 'figure';
3879
+ * } );
3880
+ *
3881
+ * @property downcasts
3882
+ */
3883
+
3884
+ /**
3885
+ * If set, it will be added as the {@link CKEDITOR.plugins.widget#event-edit} event listener.
3886
+ * This means that it will be executed when a widget is being edited.
3887
+ * See the {@link CKEDITOR.plugins.widget#method-edit} method.
3888
+ *
3889
+ * @property {Function} edit
3890
+ */
3891
+
3892
+ /**
3893
+ * If set, it will be added as the {@link CKEDITOR.plugins.widget#event-data} event listener.
3894
+ * This means that it will be executed every time the {@link CKEDITOR.plugins.widget#property-data widget data} changes.
3895
+ *
3896
+ * @property {Function} data
3897
+ */
3898
+
3899
+ /**
3900
+ * The method to be executed when the widget's command is executed in order to insert a new widget
3901
+ * (widget of this type is not focused). If not defined, then the default action will be
3902
+ * performed which means that:
3903
+ *
3904
+ * * An instance of the widget will be created in a detached {@link CKEDITOR.dom.documentFragment document fragment},
3905
+ * * The {@link CKEDITOR.plugins.widget#method-edit} method will be called to trigger widget editing,
3906
+ * * The widget element will be inserted into DOM.
3907
+ *
3908
+ * @property {Function} insert
3909
+ */
3910
+
3911
+ /**
3912
+ * The name of a dialog window which will be opened on {@link CKEDITOR.plugins.widget#method-edit}.
3913
+ * If not defined, then the {@link CKEDITOR.plugins.widget#method-edit} method will not perform any action and
3914
+ * widget's command will insert a new widget without opening a dialog window first.
3915
+ *
3916
+ * @property {String} dialog
3917
+ */
3918
+
3919
+ /**
3920
+ * The template which will be used to create a new widget element (when the widget's command is executed).
3921
+ * This string is populated with {@link #defaults default values} by using the {@link CKEDITOR.template} format.
3922
+ * Therefore it has to be a valid {@link CKEDITOR.template} argument.
3923
+ *
3924
+ * @property {String} template
3925
+ */
3926
+
3927
+ /**
3928
+ * The data object which will be used to populate the data of a newly created widget.
3929
+ * See {@link CKEDITOR.plugins.widget#property-data}.
3930
+ *
3931
+ * defaults: {
3932
+ * showCaption: true,
3933
+ * align: 'none'
3934
+ * }
3935
+ *
3936
+ * @property defaults
3937
+ */
3938
+
3939
+ /**
3940
+ * An object containing definitions of widget components (part name => CSS selector).
3941
+ *
3942
+ * parts: {
3943
+ * image: 'img',
3944
+ * caption: 'div.caption'
3945
+ * }
3946
+ *
3947
+ * @property parts
3948
+ */
3949
+
3950
+ /**
3951
+ * An object containing definitions of nested editables (editable name => {@link CKEDITOR.plugins.widget.nestedEditable.definition}).
3952
+ * Note that editables *have to* be defined in the same order as they are in DOM / {@link CKEDITOR.plugins.widget.definition#template template}.
3953
+ * Otherwise errors will occur when nesting widgets inside each other.
3954
+ *
3955
+ * editables: {
3956
+ * header: 'h1',
3957
+ * content: {
3958
+ * selector: 'div.content',
3959
+ * allowedContent: 'p strong em; a[!href]'
3960
+ * }
3961
+ * }
3962
+ *
3963
+ * @property editables
3964
+ */
3965
+
3966
+ /**
3967
+ * The function used to obtain an accessibility label for the widget. It might be used to make
3968
+ * the widget labels as precise as possible, since it has access to the widget instance.
3969
+ *
3970
+ * If not specified, the default implementation will use the {@link #pathName} or the main
3971
+ * {@link CKEDITOR.plugins.widget#element element} tag name.
3972
+ *
3973
+ * @property {Function} getLabel
3974
+ */
3975
+
3976
+ /**
3977
+ * The widget name displayed in the elements path.
3978
+ *
3979
+ * @property {String} pathName
3980
+ */
3981
+
3982
+ /**
3983
+ * If set to `true`, the widget's element will be covered with a transparent mask.
3984
+ * This will prevent its content from being clickable, which matters in case
3985
+ * of special elements like embedded Flash or iframes that generate a separate "context".
3986
+ *
3987
+ * @property {Boolean} mask
3988
+ */
3989
+
3990
+ /**
3991
+ * If set to `true/false`, it will force the widget to be either an inline or a block widget.
3992
+ * If not set, the widget type will be determined from the widget element.
3993
+ *
3994
+ * Widget type influences whether a block (`div`) or an inline (`span`) element is used
3995
+ * for the wrapper.
3996
+ *
3997
+ * @property {Boolean} inline
3998
+ */
3999
+
4000
+ /**
4001
+ * The label for the widget toolbar button.
4002
+ *
4003
+ * editor.widgets.add( 'simplebox', {
4004
+ * button: 'Create a simple box'
4005
+ * } );
4006
+ *
4007
+ * editor.widgets.add( 'simplebox', {
4008
+ * button: editor.lang.simplebox.title
4009
+ * } );
4010
+ *
4011
+ * @property {String} button
4012
+ */
4013
+
4014
+ /**
4015
+ * Whether widget should be draggable. Defaults to `true`.
4016
+ * If set to `false` drag handler will not be displayed when hovering widget.
4017
+ *
4018
+ * @property {Boolean} draggable
4019
+ */
4020
+
4021
+ /**
4022
+ * Names of element(s) (separated by spaces) for which the {@link CKEDITOR.filter} should allow classes
4023
+ * defined in the widget styles. For example if your widget is upcasted from a simple `<div>`
4024
+ * element, then in order to make it styleable you can set:
4025
+ *
4026
+ * editor.widgets.add( 'customWidget', {
4027
+ * upcast: function( element ) {
4028
+ * return element.name == 'div';
4029
+ * },
4030
+ *
4031
+ * // ...
4032
+ *
4033
+ * styleableElements: 'div'
4034
+ * } );
4035
+ *
4036
+ * Then, when the following style is defined:
4037
+ *
4038
+ * {
4039
+ * name: 'Thick border', type: 'widget', widget: 'customWidget',
4040
+ * attributes: { 'class': 'thickBorder' }
4041
+ * }
4042
+ *
4043
+ * a rule allowing the `thickBorder` class for `div` elements will be registered in the {@link CKEDITOR.filter}.
4044
+ *
4045
+ * If you need to have more freedom when transforming widget style to allowed content rules,
4046
+ * you can use the {@link #styleToAllowedContentRules} callback.
4047
+ *
4048
+ * @since 4.4
4049
+ * @property {String} styleableElements
4050
+ */
4051
+
4052
+ /**
4053
+ * Function transforming custom widget's {@link CKEDITOR.style} instance into
4054
+ * {@link CKEDITOR.filter.allowedContentRules}. It may be used when a static
4055
+ * {@link #styleableElements} property is not enough to inform the {@link CKEDITOR.filter}
4056
+ * what HTML features should be enabled when allowing the given style.
4057
+ *
4058
+ * In most cases, when style's classes just have to be added to element name(s) used by
4059
+ * the widget element, it is recommended to use simpler {@link #styleableElements} property.
4060
+ *
4061
+ * In order to get parsed classes from the style definition you can use
4062
+ * {@link CKEDITOR.style.customHandlers.widget#getClassesArray}.
4063
+ *
4064
+ * For example, if you want to use the [object format of allowed content rules](#!/guide/dev_allowed_content_rules-section-object-format),
4065
+ * to specify `match` validator, your implementation could look like this:
4066
+ *
4067
+ * editor.widgets.add( 'customWidget', {
4068
+ * // ...
4069
+ *
4070
+ * styleToAllowedContentRules: funciton( style ) {
4071
+ * // Retrieve classes defined in the style.
4072
+ * var classes = style.getClassesArray();
4073
+ *
4074
+ * // Do something crazy - for example return allowed content rules in object format,
4075
+ * // with custom match property and propertiesOnly flag.
4076
+ * return {
4077
+ * h1: {
4078
+ * match: isWidgetElement,
4079
+ * propertiesOnly: true,
4080
+ * classes: classes
4081
+ * }
4082
+ * };
4083
+ * }
4084
+ * } );
4085
+ *
4086
+ * @since 4.4
4087
+ * @property {Function} styleToAllowedContentRules
4088
+ * @param {CKEDITOR.style.customHandlers.widget} style The style to be transformed.
4089
+ * @returns {CKEDITOR.filter.allowedContentRules}
4090
+ */
4091
+
4092
+ /**
4093
+ * This is an abstract class that describes the definition of a widget's nested editable.
4094
+ * It is a type of values in the {@link CKEDITOR.plugins.widget.definition#editables} object.
4095
+ *
4096
+ * In the simplest case the definition is a string which is a CSS selector used to
4097
+ * find an element that will become a nested editable inside the widget. Note that
4098
+ * the widget element can be a nested editable, too.
4099
+ *
4100
+ * In the more advanced case a definition is an object with a required `selector` property.
4101
+ *
4102
+ * editables: {
4103
+ * header: 'h1',
4104
+ * content: {
4105
+ * selector: 'div.content',
4106
+ * allowedContent: 'p strong em; a[!href]'
4107
+ * }
4108
+ * }
4109
+ *
4110
+ * @class CKEDITOR.plugins.widget.nestedEditable.definition
4111
+ * @abstract
4112
+ */
4113
+
4114
+ /**
4115
+ * The CSS selector used to find an element which will become a nested editable.
4116
+ *
4117
+ * @property {String} selector
4118
+ */
4119
+
4120
+ /**
4121
+ * The [Advanced Content Filter](#!/guide/dev_advanced_content_filter) rules
4122
+ * which will be used to limit the content allowed in this nested editable.
4123
+ * This option is similar to {@link CKEDITOR.config#allowedContent} and one can
4124
+ * use it to limit the editor features available in the nested editable.
4125
+ *
4126
+ * If no `allowedContent` is specified, the editable will use the editor default
4127
+ * {@link CKEDITOR.editor#filter}.
4128
+ *
4129
+ * @property {CKEDITOR.filter.allowedContentRules} allowedContent
4130
+ */
4131
+
4132
+ /**
4133
+ * The [Advanced Content Filter](#!/guide/dev_advanced_content_filter) rules
4134
+ * which will be used to blacklist elements within this nested editable.
4135
+ * This option is similar to {@link CKEDITOR.config#disallowedContent}.
4136
+ *
4137
+ * Note that `disallowedContent` work on top of the definition's {@link #allowedContent}.
4138
+ *
4139
+ * @since 4.7.3
4140
+ * @property {CKEDITOR.filter.disallowedContentRules} disallowedContent
4141
+ */
4142
+
4143
+ /**
4144
+ * Nested editable name displayed in the elements path.
4145
+ *
4146
+ * @property {String} pathName
4147
+ */