xmt_froala 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (203) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +2 -0
  5. data/app/assets/javascripts/xmt_froala.js +31 -0
  6. data/app/assets/stylesheets/xmt_froala.css +17 -0
  7. data/app/controllers/xmt_froala/application_controller.rb +5 -0
  8. data/app/controllers/xmt_froala/assets_controller.rb +94 -0
  9. data/app/models/xmt_froala/asset_uploader.rb +107 -0
  10. data/app/models/xmt_froala/file_uploader.rb +10 -0
  11. data/app/models/xmt_froala/flash_uploader.rb +10 -0
  12. data/app/models/xmt_froala/image_uploader.rb +10 -0
  13. data/app/models/xmt_froala/media_uploader.rb +10 -0
  14. data/config/routes.rb +6 -0
  15. data/lib/generators/xmt_froala/install/USAGE +10 -0
  16. data/lib/generators/xmt_froala/install/install_generator.rb +23 -0
  17. data/lib/generators/xmt_froala/install/templates/application.js +17 -0
  18. data/lib/generators/xmt_froala/install/templates/xmt_froala.rb +29 -0
  19. data/lib/generators/xmt_froala/migration/USAGE +14 -0
  20. data/lib/generators/xmt_froala/migration/migration_generator.rb +36 -0
  21. data/lib/generators/xmt_froala/migration/templates/migration/migration.rb +18 -0
  22. data/lib/generators/xmt_froala/migration/templates/models/active_record/xmt_froala/asset.rb +14 -0
  23. data/lib/generators/xmt_froala/migration/templates/models/active_record/xmt_froala/file.rb +3 -0
  24. data/lib/generators/xmt_froala/migration/templates/models/active_record/xmt_froala/flash.rb +3 -0
  25. data/lib/generators/xmt_froala/migration/templates/models/active_record/xmt_froala/image.rb +3 -0
  26. data/lib/generators/xmt_froala/migration/templates/models/active_record/xmt_froala/media.rb +3 -0
  27. data/lib/generators/xmt_froala/migration/templates/models/mongoid/xmt_froala/asset.rb +27 -0
  28. data/lib/generators/xmt_froala/migration/templates/models/mongoid/xmt_froala/file.rb +3 -0
  29. data/lib/generators/xmt_froala/migration/templates/models/mongoid/xmt_froala/flash.rb +3 -0
  30. data/lib/generators/xmt_froala/migration/templates/models/mongoid/xmt_froala/image.rb +3 -0
  31. data/lib/generators/xmt_froala/migration/templates/models/mongoid/xmt_froala/media.rb +3 -0
  32. data/lib/tasks/xmt_froala_tasks.rake +9 -0
  33. data/lib/xmt_froala.rb +65 -0
  34. data/lib/xmt_froala/active_record.rb +14 -0
  35. data/lib/xmt_froala/engine.rb +32 -0
  36. data/lib/xmt_froala/formtastic.rb +12 -0
  37. data/lib/xmt_froala/helper.rb +105 -0
  38. data/lib/xmt_froala/simple_form.rb +11 -0
  39. data/lib/xmt_froala/version.rb +3 -0
  40. data/vendor/assets/javascripts/xmt_froala/froala_editor.js +0 -0
  41. data/vendor/assets/javascripts/xmt_froala/languages/ar.js +318 -0
  42. data/vendor/assets/javascripts/xmt_froala/languages/bs.js +318 -0
  43. data/vendor/assets/javascripts/xmt_froala/languages/cs.js +318 -0
  44. data/vendor/assets/javascripts/xmt_froala/languages/da.js +318 -0
  45. data/vendor/assets/javascripts/xmt_froala/languages/de.js +318 -0
  46. data/vendor/assets/javascripts/xmt_froala/languages/en_ca.js +262 -0
  47. data/vendor/assets/javascripts/xmt_froala/languages/en_gb.js +262 -0
  48. data/vendor/assets/javascripts/xmt_froala/languages/es.js +318 -0
  49. data/vendor/assets/javascripts/xmt_froala/languages/et.js +318 -0
  50. data/vendor/assets/javascripts/xmt_froala/languages/fa.js +318 -0
  51. data/vendor/assets/javascripts/xmt_froala/languages/fi.js +318 -0
  52. data/vendor/assets/javascripts/xmt_froala/languages/fr.js +318 -0
  53. data/vendor/assets/javascripts/xmt_froala/languages/he.js +318 -0
  54. data/vendor/assets/javascripts/xmt_froala/languages/hr.js +318 -0
  55. data/vendor/assets/javascripts/xmt_froala/languages/hu.js +318 -0
  56. data/vendor/assets/javascripts/xmt_froala/languages/id.js +319 -0
  57. data/vendor/assets/javascripts/xmt_froala/languages/it.js +318 -0
  58. data/vendor/assets/javascripts/xmt_froala/languages/ja.js +318 -0
  59. data/vendor/assets/javascripts/xmt_froala/languages/ko.js +318 -0
  60. data/vendor/assets/javascripts/xmt_froala/languages/me.js +318 -0
  61. data/vendor/assets/javascripts/xmt_froala/languages/nb.js +318 -0
  62. data/vendor/assets/javascripts/xmt_froala/languages/nl.js +318 -0
  63. data/vendor/assets/javascripts/xmt_froala/languages/pl.js +318 -0
  64. data/vendor/assets/javascripts/xmt_froala/languages/pt_br.js +318 -0
  65. data/vendor/assets/javascripts/xmt_froala/languages/pt_pt.js +318 -0
  66. data/vendor/assets/javascripts/xmt_froala/languages/ro.js +319 -0
  67. data/vendor/assets/javascripts/xmt_froala/languages/ru.js +318 -0
  68. data/vendor/assets/javascripts/xmt_froala/languages/sk.js +318 -0
  69. data/vendor/assets/javascripts/xmt_froala/languages/sr.js +318 -0
  70. data/vendor/assets/javascripts/xmt_froala/languages/sv.js +318 -0
  71. data/vendor/assets/javascripts/xmt_froala/languages/th.js +318 -0
  72. data/vendor/assets/javascripts/xmt_froala/languages/tr.js +318 -0
  73. data/vendor/assets/javascripts/xmt_froala/languages/uk.js +318 -0
  74. data/vendor/assets/javascripts/xmt_froala/languages/vi.js +258 -0
  75. data/vendor/assets/javascripts/xmt_froala/languages/zh_cn.js +320 -0
  76. data/vendor/assets/javascripts/xmt_froala/languages/zh_tw.js +318 -0
  77. data/vendor/assets/javascripts/xmt_froala/plugins/align.js +139 -0
  78. data/vendor/assets/javascripts/xmt_froala/plugins/align.min.js +7 -0
  79. data/vendor/assets/javascripts/xmt_froala/plugins/char_counter.js +154 -0
  80. data/vendor/assets/javascripts/xmt_froala/plugins/char_counter.min.js +7 -0
  81. data/vendor/assets/javascripts/xmt_froala/plugins/code_beautifier.js +3270 -0
  82. data/vendor/assets/javascripts/xmt_froala/plugins/code_beautifier.min.js +7 -0
  83. data/vendor/assets/javascripts/xmt_froala/plugins/code_view.js +393 -0
  84. data/vendor/assets/javascripts/xmt_froala/plugins/code_view.min.js +7 -0
  85. data/vendor/assets/javascripts/xmt_froala/plugins/colors.js +492 -0
  86. data/vendor/assets/javascripts/xmt_froala/plugins/colors.min.js +7 -0
  87. data/vendor/assets/javascripts/xmt_froala/plugins/draggable.js +459 -0
  88. data/vendor/assets/javascripts/xmt_froala/plugins/draggable.min.js +7 -0
  89. data/vendor/assets/javascripts/xmt_froala/plugins/emoticons.js +509 -0
  90. data/vendor/assets/javascripts/xmt_froala/plugins/emoticons.min.js +7 -0
  91. data/vendor/assets/javascripts/xmt_froala/plugins/entities.js +121 -0
  92. data/vendor/assets/javascripts/xmt_froala/plugins/entities.min.js +7 -0
  93. data/vendor/assets/javascripts/xmt_froala/plugins/file.js +736 -0
  94. data/vendor/assets/javascripts/xmt_froala/plugins/file.min.js +239 -0
  95. data/vendor/assets/javascripts/xmt_froala/plugins/font_family.js +182 -0
  96. data/vendor/assets/javascripts/xmt_froala/plugins/font_family.min.js +7 -0
  97. data/vendor/assets/javascripts/xmt_froala/plugins/font_size.js +118 -0
  98. data/vendor/assets/javascripts/xmt_froala/plugins/font_size.min.js +7 -0
  99. data/vendor/assets/javascripts/xmt_froala/plugins/forms.js +430 -0
  100. data/vendor/assets/javascripts/xmt_froala/plugins/forms.min.js +7 -0
  101. data/vendor/assets/javascripts/xmt_froala/plugins/fullscreen.js +274 -0
  102. data/vendor/assets/javascripts/xmt_froala/plugins/fullscreen.min.js +7 -0
  103. data/vendor/assets/javascripts/xmt_froala/plugins/help.js +216 -0
  104. data/vendor/assets/javascripts/xmt_froala/plugins/help.min.js +7 -0
  105. data/vendor/assets/javascripts/xmt_froala/plugins/image.js +3323 -0
  106. data/vendor/assets/javascripts/xmt_froala/plugins/image.min.js +7 -0
  107. data/vendor/assets/javascripts/xmt_froala/plugins/image_manager.js +1056 -0
  108. data/vendor/assets/javascripts/xmt_froala/plugins/image_manager.min.js +7 -0
  109. data/vendor/assets/javascripts/xmt_froala/plugins/inline_style.js +94 -0
  110. data/vendor/assets/javascripts/xmt_froala/plugins/inline_style.min.js +7 -0
  111. data/vendor/assets/javascripts/xmt_froala/plugins/line_breaker.js +537 -0
  112. data/vendor/assets/javascripts/xmt_froala/plugins/line_breaker.min.js +7 -0
  113. data/vendor/assets/javascripts/xmt_froala/plugins/link.js +1157 -0
  114. data/vendor/assets/javascripts/xmt_froala/plugins/link.min.js +7 -0
  115. data/vendor/assets/javascripts/xmt_froala/plugins/lists.js +462 -0
  116. data/vendor/assets/javascripts/xmt_froala/plugins/lists.min.js +7 -0
  117. data/vendor/assets/javascripts/xmt_froala/plugins/paragraph_format.js +290 -0
  118. data/vendor/assets/javascripts/xmt_froala/plugins/paragraph_format.min.js +7 -0
  119. data/vendor/assets/javascripts/xmt_froala/plugins/paragraph_style.js +144 -0
  120. data/vendor/assets/javascripts/xmt_froala/plugins/paragraph_style.min.js +7 -0
  121. data/vendor/assets/javascripts/xmt_froala/plugins/plain_paste.js +96 -0
  122. data/vendor/assets/javascripts/xmt_froala/plugins/print.js +137 -0
  123. data/vendor/assets/javascripts/xmt_froala/plugins/print.min.js +7 -0
  124. data/vendor/assets/javascripts/xmt_froala/plugins/quick_format.js +89 -0
  125. data/vendor/assets/javascripts/xmt_froala/plugins/quick_insert.js +478 -0
  126. data/vendor/assets/javascripts/xmt_froala/plugins/quick_insert.min.js +7 -0
  127. data/vendor/assets/javascripts/xmt_froala/plugins/quote.js +141 -0
  128. data/vendor/assets/javascripts/xmt_froala/plugins/quote.min.js +7 -0
  129. data/vendor/assets/javascripts/xmt_froala/plugins/save.js +189 -0
  130. data/vendor/assets/javascripts/xmt_froala/plugins/save.min.js +7 -0
  131. data/vendor/assets/javascripts/xmt_froala/plugins/special_characters.js +781 -0
  132. data/vendor/assets/javascripts/xmt_froala/plugins/special_characters.min.js +7 -0
  133. data/vendor/assets/javascripts/xmt_froala/plugins/table.js +4194 -0
  134. data/vendor/assets/javascripts/xmt_froala/plugins/table.min.js +7 -0
  135. data/vendor/assets/javascripts/xmt_froala/plugins/url.js +194 -0
  136. data/vendor/assets/javascripts/xmt_froala/plugins/url.min.js +7 -0
  137. data/vendor/assets/javascripts/xmt_froala/plugins/video.js +2342 -0
  138. data/vendor/assets/javascripts/xmt_froala/plugins/video.min.js +7 -0
  139. data/vendor/assets/javascripts/xmt_froala/plugins/word_paste.js +1403 -0
  140. data/vendor/assets/javascripts/xmt_froala/plugins/word_paste.min.js +7 -0
  141. data/vendor/assets/javascripts/xmt_froala/third_party/embedly.js +543 -0
  142. data/vendor/assets/javascripts/xmt_froala/third_party/embedly.min.js +7 -0
  143. data/vendor/assets/javascripts/xmt_froala/third_party/image_aviary.js +163 -0
  144. data/vendor/assets/javascripts/xmt_froala/third_party/image_aviary.min.js +7 -0
  145. data/vendor/assets/javascripts/xmt_froala/third_party/spell_checker.js +222 -0
  146. data/vendor/assets/javascripts/xmt_froala/third_party/spell_checker.min.js +7 -0
  147. data/vendor/assets/javascripts/xmt_froala/xmt_froala.js +15172 -0
  148. data/vendor/assets/stylesheets/xmt_froala/css/font-awesome.css +2546 -0
  149. data/vendor/assets/stylesheets/xmt_froala/css/froala_editor.min.css +7 -0
  150. data/vendor/assets/stylesheets/xmt_froala/css/froala_editor.pkgd.css +2902 -0
  151. data/vendor/assets/stylesheets/xmt_froala/css/froala_editor.pkgd.min.css +7 -0
  152. data/vendor/assets/stylesheets/xmt_froala/css/froala_style.css +413 -0
  153. data/vendor/assets/stylesheets/xmt_froala/css/froala_style.min.css +7 -0
  154. data/vendor/assets/stylesheets/xmt_froala/css/plugins/char_counter.css +57 -0
  155. data/vendor/assets/stylesheets/xmt_froala/css/plugins/char_counter.min.css +7 -0
  156. data/vendor/assets/stylesheets/xmt_froala/css/plugins/code_view.css +112 -0
  157. data/vendor/assets/stylesheets/xmt_froala/css/plugins/code_view.min.css +7 -0
  158. data/vendor/assets/stylesheets/xmt_froala/css/plugins/colors.css +155 -0
  159. data/vendor/assets/stylesheets/xmt_froala/css/plugins/colors.min.css +7 -0
  160. data/vendor/assets/stylesheets/xmt_froala/css/plugins/draggable.css +43 -0
  161. data/vendor/assets/stylesheets/xmt_froala/css/plugins/draggable.min.css +7 -0
  162. data/vendor/assets/stylesheets/xmt_froala/css/plugins/emoticons.css +42 -0
  163. data/vendor/assets/stylesheets/xmt_froala/css/plugins/emoticons.min.css +7 -0
  164. data/vendor/assets/stylesheets/xmt_froala/css/plugins/file.css +146 -0
  165. data/vendor/assets/stylesheets/xmt_froala/css/plugins/file.min.css +7 -0
  166. data/vendor/assets/stylesheets/xmt_froala/css/plugins/fullscreen.css +28 -0
  167. data/vendor/assets/stylesheets/xmt_froala/css/plugins/fullscreen.min.css +7 -0
  168. data/vendor/assets/stylesheets/xmt_froala/css/plugins/help.css +52 -0
  169. data/vendor/assets/stylesheets/xmt_froala/css/plugins/help.min.css +7 -0
  170. data/vendor/assets/stylesheets/xmt_froala/css/plugins/image.css +244 -0
  171. data/vendor/assets/stylesheets/xmt_froala/css/plugins/image.min.css +7 -0
  172. data/vendor/assets/stylesheets/xmt_froala/css/plugins/image_manager.css +266 -0
  173. data/vendor/assets/stylesheets/xmt_froala/css/plugins/image_manager.min.css +7 -0
  174. data/vendor/assets/stylesheets/xmt_froala/css/plugins/line_breaker.css +37 -0
  175. data/vendor/assets/stylesheets/xmt_froala/css/plugins/line_breaker.min.css +7 -0
  176. data/vendor/assets/stylesheets/xmt_froala/css/plugins/quick_insert.css +70 -0
  177. data/vendor/assets/stylesheets/xmt_froala/css/plugins/quick_insert.min.css +7 -0
  178. data/vendor/assets/stylesheets/xmt_froala/css/plugins/special_characters.css +51 -0
  179. data/vendor/assets/stylesheets/xmt_froala/css/plugins/special_characters.min.css +7 -0
  180. data/vendor/assets/stylesheets/xmt_froala/css/plugins/table.css +181 -0
  181. data/vendor/assets/stylesheets/xmt_froala/css/plugins/table.min.css +7 -0
  182. data/vendor/assets/stylesheets/xmt_froala/css/plugins/video.css +231 -0
  183. data/vendor/assets/stylesheets/xmt_froala/css/plugins/video.min.css +7 -0
  184. data/vendor/assets/stylesheets/xmt_froala/css/themes/dark.css +1281 -0
  185. data/vendor/assets/stylesheets/xmt_froala/css/themes/dark.min.css +7 -0
  186. data/vendor/assets/stylesheets/xmt_froala/css/themes/gray.css +1281 -0
  187. data/vendor/assets/stylesheets/xmt_froala/css/themes/gray.min.css +7 -0
  188. data/vendor/assets/stylesheets/xmt_froala/css/themes/red.css +1281 -0
  189. data/vendor/assets/stylesheets/xmt_froala/css/themes/red.min.css +7 -0
  190. data/vendor/assets/stylesheets/xmt_froala/css/themes/royal.css +1281 -0
  191. data/vendor/assets/stylesheets/xmt_froala/css/themes/royal.min.css +7 -0
  192. data/vendor/assets/stylesheets/xmt_froala/css/third_party/embedly.css +64 -0
  193. data/vendor/assets/stylesheets/xmt_froala/css/third_party/embedly.min.css +7 -0
  194. data/vendor/assets/stylesheets/xmt_froala/css/third_party/spell_checker.css +72 -0
  195. data/vendor/assets/stylesheets/xmt_froala/css/third_party/spell_checker.min.css +7 -0
  196. data/vendor/assets/stylesheets/xmt_froala/css/xmt_froala.css +1423 -0
  197. data/vendor/assets/stylesheets/xmt_froala/fonts/FontAwesome.otf +0 -0
  198. data/vendor/assets/stylesheets/xmt_froala/fonts/fontawesome-webfont.eot +0 -0
  199. data/vendor/assets/stylesheets/xmt_froala/fonts/fontawesome-webfont.svg +2671 -0
  200. data/vendor/assets/stylesheets/xmt_froala/fonts/fontawesome-webfont.ttf +0 -0
  201. data/vendor/assets/stylesheets/xmt_froala/fonts/fontawesome-webfont.woff +0 -0
  202. data/vendor/assets/stylesheets/xmt_froala/fonts/fontawesome-webfont.woff2 +0 -0
  203. metadata +273 -0
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * froala_editor v2.8.1 (https://www.froala.com/wysiwyg-editor)
3
+ * License https://froala.com/wysiwyg-editor/terms/
4
+ * Copyright 2014-2018 Froala Labs
5
+ */
6
+
7
+ !function(T){"function"==typeof define&&define.amd?define(["jquery"],T):"object"==typeof module&&module.exports?module.exports=function(E,c){return c===undefined&&(c="undefined"!=typeof window?require("jquery"):require("jquery")(E)),T(c)}:T(window.jQuery)}(function(s){s.extend(s.FE.DEFAULTS,{specialCharactersSets:[{title:"Latin",list:[{"char":"&iexcl;",desc:"INVERTED EXCLAMATION MARK"},{"char":"&cent;",desc:"CENT SIGN"},{"char":"&pound;",desc:"POUND SIGN"},{"char":"&curren;",desc:"CURRENCY SIGN"},{"char":"&yen;",desc:"YEN SIGN"},{"char":"&brvbar;",desc:"BROKEN BAR"},{"char":"&sect;",desc:"SECTION SIGN"},{"char":"&uml;",desc:"DIAERESIS"},{"char":"&copy;",desc:"COPYRIGHT SIGN"},{"char":"&trade;",desc:"TRADEMARK SIGN"},{"char":"&ordf;",desc:"FEMININE ORDINAL INDICATOR"},{"char":"&laquo;",desc:"LEFT-POINTING DOUBLE ANGLE QUOTATION MARK"},{"char":"&not;",desc:"NOT SIGN"},{"char":"&reg;",desc:"REGISTERED SIGN"},{"char":"&macr;",desc:"MACRON"},{"char":"&deg;",desc:"DEGREE SIGN"},{"char":"&plusmn;",desc:"PLUS-MINUS SIGN"},{"char":"&sup2;",desc:"SUPERSCRIPT TWO"},{"char":"&sup3;",desc:"SUPERSCRIPT THREE"},{"char":"&acute;",desc:"ACUTE ACCENT"},{"char":"&micro;",desc:"MICRO SIGN"},{"char":"&para;",desc:"PILCROW SIGN"},{"char":"&middot;",desc:"MIDDLE DOT"},{"char":"&cedil;",desc:"CEDILLA"},{"char":"&sup1;",desc:"SUPERSCRIPT ONE"},{"char":"&ordm;",desc:"MASCULINE ORDINAL INDICATOR"},{"char":"&raquo;",desc:"RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK"},{"char":"&frac14;",desc:"VULGAR FRACTION ONE QUARTER"},{"char":"&frac12;",desc:"VULGAR FRACTION ONE HALF"},{"char":"&frac34;",desc:"VULGAR FRACTION THREE QUARTERS"},{"char":"&iquest;",desc:"INVERTED QUESTION MARK"},{"char":"&Agrave;",desc:"LATIN CAPITAL LETTER A WITH GRAVE"},{"char":"&Aacute;",desc:"LATIN CAPITAL LETTER A WITH ACUTE"},{"char":"&Acirc;",desc:"LATIN CAPITAL LETTER A WITH CIRCUMFLEX"},{"char":"&Atilde;",desc:"LATIN CAPITAL LETTER A WITH TILDE"},{"char":"&Auml;",desc:"LATIN CAPITAL LETTER A WITH DIAERESIS "},{"char":"&Aring;",desc:"LATIN CAPITAL LETTER A WITH RING ABOVE"},{"char":"&AElig;",desc:"LATIN CAPITAL LETTER AE"},{"char":"&Ccedil;",desc:"LATIN CAPITAL LETTER C WITH CEDILLA"},{"char":"&Egrave;",desc:"LATIN CAPITAL LETTER E WITH GRAVE"},{"char":"&Eacute;",desc:"LATIN CAPITAL LETTER E WITH ACUTE"},{"char":"&Ecirc;",desc:"LATIN CAPITAL LETTER E WITH CIRCUMFLEX"},{"char":"&Euml;",desc:"LATIN CAPITAL LETTER E WITH DIAERESIS"},{"char":"&Igrave;",desc:"LATIN CAPITAL LETTER I WITH GRAVE"},{"char":"&Iacute;",desc:"LATIN CAPITAL LETTER I WITH ACUTE"},{"char":"&Icirc;",desc:"LATIN CAPITAL LETTER I WITH CIRCUMFLEX"},{"char":"&Iuml;",desc:"LATIN CAPITAL LETTER I WITH DIAERESIS"},{"char":"&ETH;",desc:"LATIN CAPITAL LETTER ETH"},{"char":"&Ntilde;",desc:"LATIN CAPITAL LETTER N WITH TILDE"},{"char":"&Ograve;",desc:"LATIN CAPITAL LETTER O WITH GRAVE"},{"char":"&Oacute;",desc:"LATIN CAPITAL LETTER O WITH ACUTE"},{"char":"&Ocirc;",desc:"LATIN CAPITAL LETTER O WITH CIRCUMFLEX"},{"char":"&Otilde;",desc:"LATIN CAPITAL LETTER O WITH TILDE"},{"char":"&Ouml;",desc:"LATIN CAPITAL LETTER O WITH DIAERESIS"},{"char":"&times;",desc:"MULTIPLICATION SIGN"},{"char":"&Oslash;",desc:"LATIN CAPITAL LETTER O WITH STROKE"},{"char":"&Ugrave;",desc:"LATIN CAPITAL LETTER U WITH GRAVE"},{"char":"&Uacute;",desc:"LATIN CAPITAL LETTER U WITH ACUTE"},{"char":"&Ucirc;",desc:"LATIN CAPITAL LETTER U WITH CIRCUMFLEX"},{"char":"&Uuml;",desc:"LATIN CAPITAL LETTER U WITH DIAERESIS"},{"char":"&Yacute;",desc:"LATIN CAPITAL LETTER Y WITH ACUTE"},{"char":"&THORN;",desc:"LATIN CAPITAL LETTER THORN"},{"char":"&szlig;",desc:"LATIN SMALL LETTER SHARP S"},{"char":"&agrave;",desc:"LATIN SMALL LETTER A WITH GRAVE"},{"char":"&aacute;",desc:"LATIN SMALL LETTER A WITH ACUTE "},{"char":"&acirc;",desc:"LATIN SMALL LETTER A WITH CIRCUMFLEX"},{"char":"&atilde;",desc:"LATIN SMALL LETTER A WITH TILDE"},{"char":"&auml;",desc:"LATIN SMALL LETTER A WITH DIAERESIS"},{"char":"&aring;",desc:"LATIN SMALL LETTER A WITH RING ABOVE"},{"char":"&aelig;",desc:"LATIN SMALL LETTER AE"},{"char":"&ccedil;",desc:"LATIN SMALL LETTER C WITH CEDILLA"},{"char":"&egrave;",desc:"LATIN SMALL LETTER E WITH GRAVE"},{"char":"&eacute;",desc:"LATIN SMALL LETTER E WITH ACUTE"},{"char":"&ecirc;",desc:"LATIN SMALL LETTER E WITH CIRCUMFLEX"},{"char":"&euml;",desc:"LATIN SMALL LETTER E WITH DIAERESIS"},{"char":"&igrave;",desc:"LATIN SMALL LETTER I WITH GRAVE"},{"char":"&iacute;",desc:"LATIN SMALL LETTER I WITH ACUTE"},{"char":"&icirc;",desc:"LATIN SMALL LETTER I WITH CIRCUMFLEX"},{"char":"&iuml;",desc:"LATIN SMALL LETTER I WITH DIAERESIS"},{"char":"&eth;",desc:"LATIN SMALL LETTER ETH"},{"char":"&ntilde;",desc:"LATIN SMALL LETTER N WITH TILDE"},{"char":"&ograve;",desc:"LATIN SMALL LETTER O WITH GRAVE"},{"char":"&oacute;",desc:"LATIN SMALL LETTER O WITH ACUTE"},{"char":"&ocirc;",desc:"LATIN SMALL LETTER O WITH CIRCUMFLEX"},{"char":"&otilde;",desc:"LATIN SMALL LETTER O WITH TILDE"},{"char":"&ouml;",desc:"LATIN SMALL LETTER O WITH DIAERESIS"},{"char":"&divide;",desc:"DIVISION SIGN"},{"char":"&oslash;",desc:"LATIN SMALL LETTER O WITH STROKE"},{"char":"&ugrave;",desc:"LATIN SMALL LETTER U WITH GRAVE"},{"char":"&uacute;",desc:"LATIN SMALL LETTER U WITH ACUTE"},{"char":"&ucirc;",desc:"LATIN SMALL LETTER U WITH CIRCUMFLEX"},{"char":"&uuml;",desc:"LATIN SMALL LETTER U WITH DIAERESIS"},{"char":"&yacute;",desc:"LATIN SMALL LETTER Y WITH ACUTE"},{"char":"&thorn;",desc:"LATIN SMALL LETTER THORN"},{"char":"&yuml;",desc:"LATIN SMALL LETTER Y WITH DIAERESIS"}]},{title:"Greek",list:[{"char":"&Alpha;",desc:"GREEK CAPITAL LETTER ALPHA"},{"char":"&Beta;",desc:"GREEK CAPITAL LETTER BETA"},{"char":"&Gamma;",desc:"GREEK CAPITAL LETTER GAMMA"},{"char":"&Delta;",desc:"GREEK CAPITAL LETTER DELTA"},{"char":"&Epsilon;",desc:"GREEK CAPITAL LETTER EPSILON"},{"char":"&Zeta;",desc:"GREEK CAPITAL LETTER ZETA"},{"char":"&Eta;",desc:"GREEK CAPITAL LETTER ETA"},{"char":"&Theta;",desc:"GREEK CAPITAL LETTER THETA"},{"char":"&Iota;",desc:"GREEK CAPITAL LETTER IOTA"},{"char":"&Kappa;",desc:"GREEK CAPITAL LETTER KAPPA"},{"char":"&Lambda;",desc:"GREEK CAPITAL LETTER LAMBDA"},{"char":"&Mu;",desc:"GREEK CAPITAL LETTER MU"},{"char":"&Nu;",desc:"GREEK CAPITAL LETTER NU"},{"char":"&Xi;",desc:"GREEK CAPITAL LETTER XI"},{"char":"&Omicron;",desc:"GREEK CAPITAL LETTER OMICRON"},{"char":"&Pi;",desc:"GREEK CAPITAL LETTER PI"},{"char":"&Rho;",desc:"GREEK CAPITAL LETTER RHO"},{"char":"&Sigma;",desc:"GREEK CAPITAL LETTER SIGMA"},{"char":"&Tau;",desc:"GREEK CAPITAL LETTER TAU"},{"char":"&Upsilon;",desc:"GREEK CAPITAL LETTER UPSILON"},{"char":"&Phi;",desc:"GREEK CAPITAL LETTER PHI"},{"char":"&Chi;",desc:"GREEK CAPITAL LETTER CHI"},{"char":"&Psi;",desc:"GREEK CAPITAL LETTER PSI"},{"char":"&Omega;",desc:"GREEK CAPITAL LETTER OMEGA"},{"char":"&alpha;",desc:"GREEK SMALL LETTER ALPHA"},{"char":"&beta;",desc:"GREEK SMALL LETTER BETA"},{"char":"&gamma;",desc:"GREEK SMALL LETTER GAMMA"},{"char":"&delta;",desc:"GREEK SMALL LETTER DELTA"},{"char":"&epsilon;",desc:"GREEK SMALL LETTER EPSILON"},{"char":"&zeta;",desc:"GREEK SMALL LETTER ZETA"},{"char":"&eta;",desc:"GREEK SMALL LETTER ETA"},{"char":"&theta;",desc:"GREEK SMALL LETTER THETA"},{"char":"&iota;",desc:"GREEK SMALL LETTER IOTA"},{"char":"&kappa;",desc:"GREEK SMALL LETTER KAPPA"},{"char":"&lambda;",desc:"GREEK SMALL LETTER LAMBDA"},{"char":"&mu;",desc:"GREEK SMALL LETTER MU"},{"char":"&nu;",desc:"GREEK SMALL LETTER NU"},{"char":"&xi;",desc:"GREEK SMALL LETTER XI"},{"char":"&omicron;",desc:"GREEK SMALL LETTER OMICRON"},{"char":"&pi;",desc:"GREEK SMALL LETTER PI"},{"char":"&rho;",desc:"GREEK SMALL LETTER RHO"},{"char":"&sigmaf;",desc:"GREEK SMALL LETTER FINAL SIGMA"},{"char":"&sigma;",desc:"GREEK SMALL LETTER SIGMA"},{"char":"&tau;",desc:"GREEK SMALL LETTER TAU"},{"char":"&upsilon;",desc:"GREEK SMALL LETTER UPSILON"},{"char":"&phi;",desc:"GREEK SMALL LETTER PHI"},{"char":"&chi;",desc:"GREEK SMALL LETTER CHI"},{"char":"&psi;",desc:"GREEK SMALL LETTER PSI"},{"char":"&omega;",desc:"GREEK SMALL LETTER OMEGA"},{"char":"&thetasym;",desc:"GREEK THETA SYMBOL"},{"char":"&upsih;",desc:"GREEK UPSILON WITH HOOK SYMBOL"},{"char":"&straightphi;",desc:"GREEK PHI SYMBOL"},{"char":"&piv;",desc:"GREEK PI SYMBOL"},{"char":"&Gammad;",desc:"GREEK LETTER DIGAMMA"},{"char":"&gammad;",desc:"GREEK SMALL LETTER DIGAMMA"},{"char":"&varkappa;",desc:"GREEK KAPPA SYMBOL"},{"char":"&varrho;",desc:"GREEK RHO SYMBOL"},{"char":"&straightepsilon;",desc:"GREEK LUNATE EPSILON SYMBOL"},{"char":"&backepsilon;",desc:"GREEK REVERSED LUNATE EPSILON SYMBOL"}]},{title:"Cyrillic",list:[{"char":"&#x400",desc:"CYRILLIC CAPITAL LETTER IE WITH GRAVE"},{"char":"&#x401",desc:"CYRILLIC CAPITAL LETTER IO"},{"char":"&#x402",desc:"CYRILLIC CAPITAL LETTER DJE"},{"char":"&#x403",desc:"CYRILLIC CAPITAL LETTER GJE"},{"char":"&#x404",desc:"CYRILLIC CAPITAL LETTER UKRAINIAN IE"},{"char":"&#x405",desc:"CYRILLIC CAPITAL LETTER DZE"},{"char":"&#x406",desc:"CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I"},{"char":"&#x407",desc:"CYRILLIC CAPITAL LETTER YI"},{"char":"&#x408",desc:"CYRILLIC CAPITAL LETTER JE"},{"char":"&#x409",desc:"CYRILLIC CAPITAL LETTER LJE"},{"char":"&#x40A",desc:"CYRILLIC CAPITAL LETTER NJE"},{"char":"&#x40B",desc:"CYRILLIC CAPITAL LETTER TSHE"},{"char":"&#x40C",desc:"CYRILLIC CAPITAL LETTER KJE"},{"char":"&#x40D",desc:"CYRILLIC CAPITAL LETTER I WITH GRAVE"},{"char":"&#x40E",desc:"CYRILLIC CAPITAL LETTER SHORT U"},{"char":"&#x40F",desc:"CYRILLIC CAPITAL LETTER DZHE"},{"char":"&#x410",desc:"CYRILLIC CAPITAL LETTER A"},{"char":"&#x411",desc:"CYRILLIC CAPITAL LETTER BE"},{"char":"&#x412",desc:"CYRILLIC CAPITAL LETTER VE"},{"char":"&#x413",desc:"CYRILLIC CAPITAL LETTER GHE"},{"char":"&#x414",desc:"CYRILLIC CAPITAL LETTER DE"},{"char":"&#x415",desc:"CYRILLIC CAPITAL LETTER IE"},{"char":"&#x416",desc:"CYRILLIC CAPITAL LETTER ZHE"},{"char":"&#x417",desc:"CYRILLIC CAPITAL LETTER ZE"},{"char":"&#x418",desc:"CYRILLIC CAPITAL LETTER I"},{"char":"&#x419",desc:"CYRILLIC CAPITAL LETTER SHORT I"},{"char":"&#x41A",desc:"CYRILLIC CAPITAL LETTER KA"},{"char":"&#x41B",desc:"CYRILLIC CAPITAL LETTER EL"},{"char":"&#x41C",desc:"CYRILLIC CAPITAL LETTER EM"},{"char":"&#x41D",desc:"CYRILLIC CAPITAL LETTER EN"},{"char":"&#x41E",desc:"CYRILLIC CAPITAL LETTER O"},{"char":"&#x41F",desc:"CYRILLIC CAPITAL LETTER PE"},{"char":"&#x420",desc:"CYRILLIC CAPITAL LETTER ER"},{"char":"&#x421",desc:"CYRILLIC CAPITAL LETTER ES"},{"char":"&#x422",desc:"CYRILLIC CAPITAL LETTER TE"},{"char":"&#x423",desc:"CYRILLIC CAPITAL LETTER U"},{"char":"&#x424",desc:"CYRILLIC CAPITAL LETTER EF"},{"char":"&#x425",desc:"CYRILLIC CAPITAL LETTER HA"},{"char":"&#x426",desc:"CYRILLIC CAPITAL LETTER TSE"},{"char":"&#x427",desc:"CYRILLIC CAPITAL LETTER CHE"},{"char":"&#x428",desc:"CYRILLIC CAPITAL LETTER SHA"},{"char":"&#x429",desc:"CYRILLIC CAPITAL LETTER SHCHA"},{"char":"&#x42A",desc:"CYRILLIC CAPITAL LETTER HARD SIGN"},{"char":"&#x42B",desc:"CYRILLIC CAPITAL LETTER YERU"},{"char":"&#x42C",desc:"CYRILLIC CAPITAL LETTER SOFT SIGN"},{"char":"&#x42D",desc:"CYRILLIC CAPITAL LETTER E"},{"char":"&#x42E",desc:"CYRILLIC CAPITAL LETTER YU"},{"char":"&#x42F",desc:"CYRILLIC CAPITAL LETTER YA"},{"char":"&#x430",desc:"CYRILLIC SMALL LETTER A"},{"char":"&#x431",desc:"CYRILLIC SMALL LETTER BE"},{"char":"&#x432",desc:"CYRILLIC SMALL LETTER VE"},{"char":"&#x433",desc:"CYRILLIC SMALL LETTER GHE"},{"char":"&#x434",desc:"CYRILLIC SMALL LETTER DE"},{"char":"&#x435",desc:"CYRILLIC SMALL LETTER IE"},{"char":"&#x436",desc:"CYRILLIC SMALL LETTER ZHE"},{"char":"&#x437",desc:"CYRILLIC SMALL LETTER ZE"},{"char":"&#x438",desc:"CYRILLIC SMALL LETTER I"},{"char":"&#x439",desc:"CYRILLIC SMALL LETTER SHORT I"},{"char":"&#x43A",desc:"CYRILLIC SMALL LETTER KA"},{"char":"&#x43B",desc:"CYRILLIC SMALL LETTER EL"},{"char":"&#x43C",desc:"CYRILLIC SMALL LETTER EM"},{"char":"&#x43D",desc:"CYRILLIC SMALL LETTER EN"},{"char":"&#x43E",desc:"CYRILLIC SMALL LETTER O"},{"char":"&#x43F",desc:"CYRILLIC SMALL LETTER PE"},{"char":"&#x440",desc:"CYRILLIC SMALL LETTER ER"},{"char":"&#x441",desc:"CYRILLIC SMALL LETTER ES"},{"char":"&#x442",desc:"CYRILLIC SMALL LETTER TE"},{"char":"&#x443",desc:"CYRILLIC SMALL LETTER U"},{"char":"&#x444",desc:"CYRILLIC SMALL LETTER EF"},{"char":"&#x445",desc:"CYRILLIC SMALL LETTER HA"},{"char":"&#x446",desc:"CYRILLIC SMALL LETTER TSE"},{"char":"&#x447",desc:"CYRILLIC SMALL LETTER CHE"},{"char":"&#x448",desc:"CYRILLIC SMALL LETTER SHA"},{"char":"&#x449",desc:"CYRILLIC SMALL LETTER SHCHA"},{"char":"&#x44A",desc:"CYRILLIC SMALL LETTER HARD SIGN"},{"char":"&#x44B",desc:"CYRILLIC SMALL LETTER YERU"},{"char":"&#x44C",desc:"CYRILLIC SMALL LETTER SOFT SIGN"},{"char":"&#x44D",desc:"CYRILLIC SMALL LETTER E"},{"char":"&#x44E",desc:"CYRILLIC SMALL LETTER YU"},{"char":"&#x44F",desc:"CYRILLIC SMALL LETTER YA"},{"char":"&#x450",desc:"CYRILLIC SMALL LETTER IE WITH GRAVE"},{"char":"&#x451",desc:"CYRILLIC SMALL LETTER IO"},{"char":"&#x452",desc:"CYRILLIC SMALL LETTER DJE"},{"char":"&#x453",desc:"CYRILLIC SMALL LETTER GJE"},{"char":"&#x454",desc:"CYRILLIC SMALL LETTER UKRAINIAN IE"},{"char":"&#x455",desc:"CYRILLIC SMALL LETTER DZE"},{"char":"&#x456",desc:"CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I"},{"char":"&#x457",desc:"CYRILLIC SMALL LETTER YI"},{"char":"&#x458",desc:"CYRILLIC SMALL LETTER JE"},{"char":"&#x459",desc:"CYRILLIC SMALL LETTER LJE"},{"char":"&#x45A",desc:"CYRILLIC SMALL LETTER NJE"},{"char":"&#x45B",desc:"CYRILLIC SMALL LETTER TSHE"},{"char":"&#x45C",desc:"CYRILLIC SMALL LETTER KJE"},{"char":"&#x45D",desc:"CYRILLIC SMALL LETTER I WITH GRAVE"},{"char":"&#x45E",desc:"CYRILLIC SMALL LETTER SHORT U"},{"char":"&#x45F",desc:"CYRILLIC SMALL LETTER DZHE"}]},{title:"Punctuation",list:[{"char":"&ndash;",desc:"EN DASH"},{"char":"&mdash;",desc:"EM DASH"},{"char":"&lsquo;",desc:"LEFT SINGLE QUOTATION MARK"},{"char":"&rsquo;",desc:"RIGHT SINGLE QUOTATION MARK"},{"char":"&sbquo;",desc:"SINGLE LOW-9 QUOTATION MARK"},{"char":"&ldquo;",desc:"LEFT DOUBLE QUOTATION MARK"},{"char":"&rdquo;",desc:"RIGHT DOUBLE QUOTATION MARK"},{"char":"&bdquo;",desc:"DOUBLE LOW-9 QUOTATION MARK"},{"char":"&dagger;",desc:"DAGGER"},{"char":"&Dagger;",desc:"DOUBLE DAGGER"},{"char":"&bull;",desc:"BULLET"},{"char":"&hellip;",desc:"HORIZONTAL ELLIPSIS"},{"char":"&permil;",desc:"PER MILLE SIGN"},{"char":"&prime;",desc:"PRIME"},{"char":"&Prime;",desc:"DOUBLE PRIME"},{"char":"&lsaquo;",desc:"SINGLE LEFT-POINTING ANGLE QUOTATION MARK"},{"char":"&rsaquo;",desc:"SINGLE RIGHT-POINTING ANGLE QUOTATION MARK"},{"char":"&oline;",desc:"OVERLINE"},{"char":"&frasl;",desc:"FRACTION SLASH"}]},{title:"Currency",list:[{"char":"&#x20A0",desc:"EURO-CURRENCY SIGN"},{"char":"&#x20A1",desc:"COLON SIGN"},{"char":"&#x20A2",desc:"CRUZEIRO SIGN"},{"char":"&#x20A3",desc:"FRENCH FRANC SIGN"},{"char":"&#x20A4",desc:"LIRA SIGN"},{"char":"&#x20A5",desc:"MILL SIGN"},{"char":"&#x20A6",desc:"NAIRA SIGN"},{"char":"&#x20A7",desc:"PESETA SIGN"},{"char":"&#x20A8",desc:"RUPEE SIGN"},{"char":"&#x20A9",desc:"WON SIGN"},{"char":"&#x20AA",desc:"NEW SHEQEL SIGN"},{"char":"&#x20AB",desc:"DONG SIGN"},{"char":"&#x20AC",desc:"EURO SIGN"},{"char":"&#x20AD",desc:"KIP SIGN"},{"char":"&#x20AE",desc:"TUGRIK SIGN"},{"char":"&#x20AF",desc:"DRACHMA SIGN"},{"char":"&#x20B0",desc:"GERMAN PENNY SYMBOL"},{"char":"&#x20B1",desc:"PESO SIGN"},{"char":"&#x20B2",desc:"GUARANI SIGN"},{"char":"&#x20B3",desc:"AUSTRAL SIGN"},{"char":"&#x20B4",desc:"HRYVNIA SIGN"},{"char":"&#x20B5",desc:"CEDI SIGN"},{"char":"&#x20B6",desc:"LIVRE TOURNOIS SIGN"},{"char":"&#x20B7",desc:"SPESMILO SIGN"},{"char":"&#x20B8",desc:"TENGE SIGN"},{"char":"&#x20B9",desc:"INDIAN RUPEE SIGN"}]},{title:"Arrows",list:[{"char":"&#x2190",desc:"LEFTWARDS ARROW"},{"char":"&#x2191",desc:"UPWARDS ARROW"},{"char":"&#x2192",desc:"RIGHTWARDS ARROW"},{"char":"&#x2193",desc:"DOWNWARDS ARROW"},{"char":"&#x2194",desc:"LEFT RIGHT ARROW"},{"char":"&#x2195",desc:"UP DOWN ARROW"},{"char":"&#x2196",desc:"NORTH WEST ARROW"},{"char":"&#x2197",desc:"NORTH EAST ARROW"},{"char":"&#x2198",desc:"SOUTH EAST ARROW"},{"char":"&#x2199",desc:"SOUTH WEST ARROW"},{"char":"&#x219A",desc:"LEFTWARDS ARROW WITH STROKE"},{"char":"&#x219B",desc:"RIGHTWARDS ARROW WITH STROKE"},{"char":"&#x219C",desc:"LEFTWARDS WAVE ARROW"},{"char":"&#x219D",desc:"RIGHTWARDS WAVE ARROW"},{"char":"&#x219E",desc:"LEFTWARDS TWO HEADED ARROW"},{"char":"&#x219F",desc:"UPWARDS TWO HEADED ARROW"},{"char":"&#x21A0",desc:"RIGHTWARDS TWO HEADED ARROW"},{"char":"&#x21A1",desc:"DOWNWARDS TWO HEADED ARROW"},{"char":"&#x21A2",desc:"LEFTWARDS ARROW WITH TAIL"},{"char":"&#x21A3",desc:"RIGHTWARDS ARROW WITH TAIL"},{"char":"&#x21A4",desc:"LEFTWARDS ARROW FROM BAR"},{"char":"&#x21A5",desc:"UPWARDS ARROW FROM BAR"},{"char":"&#x21A6",desc:"RIGHTWARDS ARROW FROM BAR"},{"char":"&#x21A7",desc:"DOWNWARDS ARROW FROM BAR"},{"char":"&#x21A8",desc:"UP DOWN ARROW WITH BASE"},{"char":"&#x21A9",desc:"LEFTWARDS ARROW WITH HOOK"},{"char":"&#x21AA",desc:"RIGHTWARDS ARROW WITH HOOK"},{"char":"&#x21AB",desc:"LEFTWARDS ARROW WITH LOOP"},{"char":"&#x21AC",desc:"RIGHTWARDS ARROW WITH LOOP"},{"char":"&#x21AD",desc:"LEFT RIGHT WAVE ARROW"},{"char":"&#x21AE",desc:"LEFT RIGHT ARROW WITH STROKE"},{"char":"&#x21AF",desc:"DOWNWARDS ZIGZAG ARROW"},{"char":"&#x21B0",desc:"UPWARDS ARROW WITH TIP LEFTWARDS"},{"char":"&#x21B1",desc:"UPWARDS ARROW WITH TIP RIGHTWARDS"},{"char":"&#x21B2",desc:"DOWNWARDS ARROW WITH TIP LEFTWARDS"},{"char":"&#x21B3",desc:"DOWNWARDS ARROW WITH TIP RIGHTWARDS"},{"char":"&#x21B4",desc:"RIGHTWARDS ARROW WITH CORNER DOWNWARDS"},{"char":"&#x21B5",desc:"DOWNWARDS ARROW WITH CORNER LEFTWARDS"},{"char":"&#x21B6",desc:"ANTICLOCKWISE TOP SEMICIRCLE ARROW"},{"char":"&#x21B7",desc:"CLOCKWISE TOP SEMICIRCLE ARROW"},{"char":"&#x21B8",desc:"NORTH WEST ARROW TO LONG BAR"},{"char":"&#x21B9",desc:"LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR"},{"char":"&#x21BA",desc:"ANTICLOCKWISE OPEN CIRCLE ARROW"},{"char":"&#x21BB",desc:"CLOCKWISE OPEN CIRCLE ARROW"},{"char":"&#x21BC",desc:"LEFTWARDS HARPOON WITH BARB UPWARDS"},{"char":"&#x21BD",desc:"LEFTWARDS HARPOON WITH BARB DOWNWARDS"},{"char":"&#x21BE",desc:"UPWARDS HARPOON WITH BARB RIGHTWARDS"},{"char":"&#x21BF",desc:"UPWARDS HARPOON WITH BARB LEFTWARDS"},{"char":"&#x21C0",desc:"RIGHTWARDS HARPOON WITH BARB UPWARDS"},{"char":"&#x21C1",desc:"RIGHTWARDS HARPOON WITH BARB DOWNWARDS"},{"char":"&#x21C2",desc:"DOWNWARDS HARPOON WITH BARB RIGHTWARDS"},{"char":"&#x21C3",desc:"DOWNWARDS HARPOON WITH BARB LEFTWARDS"},{"char":"&#x21C4",desc:"RIGHTWARDS ARROW OVER LEFTWARDS ARROW"},{"char":"&#x21C5",desc:"UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW"},{"char":"&#x21C6",desc:"LEFTWARDS ARROW OVER RIGHTWARDS ARROW"},{"char":"&#x21C7",desc:"LEFTWARDS PAIRED ARROWS"},{"char":"&#x21C8",desc:"UPWARDS PAIRED ARROWS"},{"char":"&#x21C9",desc:"RIGHTWARDS PAIRED ARROWS"},{"char":"&#x21CA",desc:"DOWNWARDS PAIRED ARROWS"},{"char":"&#x21CB",desc:"LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON"},{"char":"&#x21CC",desc:"RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON"},{"char":"&#x21CD",desc:"LEFTWARDS DOUBLE ARROW WITH STROKE"},{"char":"&#x21CE",desc:"LEFT RIGHT DOUBLE ARROW WITH STROKE"},{"char":"&#x21CF",desc:"RIGHTWARDS DOUBLE ARROW WITH STROKE"},{"char":"&#x21D0",desc:"LEFTWARDS DOUBLE ARROW"},{"char":"&#x21D1",desc:"UPWARDS DOUBLE ARROW"},{"char":"&#x21D2",desc:"RIGHTWARDS DOUBLE ARROW"},{"char":"&#x21D3",desc:"DOWNWARDS DOUBLE ARROW"},{"char":"&#x21D4",desc:"LEFT RIGHT DOUBLE ARROW"},{"char":"&#x21D5",desc:"UP DOWN DOUBLE ARROW"},{"char":"&#x21D6",desc:"NORTH WEST DOUBLE ARROW"},{"char":"&#x21D7",desc:"NORTH EAST DOUBLE ARROW"},{"char":"&#x21D8",desc:"SOUTH EAST DOUBLE ARROW"},{"char":"&#x21D9",desc:"SOUTH WEST DOUBLE ARROW"},{"char":"&#x21DA",desc:"LEFTWARDS TRIPLE ARROW"},{"char":"&#x21DB",desc:"RIGHTWARDS TRIPLE ARROW"},{"char":"&#x21DC",desc:"LEFTWARDS SQUIGGLE ARROW"},{"char":"&#x21DD",desc:"RIGHTWARDS SQUIGGLE ARROW"},{"char":"&#x21DE",desc:"UPWARDS ARROW WITH DOUBLE STROKE"},{"char":"&#x21DF",desc:"DOWNWARDS ARROW WITH DOUBLE STROKE"},{"char":"&#x21E0",desc:"LEFTWARDS DASHED ARROW"},{"char":"&#x21E1",desc:"UPWARDS DASHED ARROW"},{"char":"&#x21E2",desc:"RIGHTWARDS DASHED ARROW"},{"char":"&#x21E3",desc:"DOWNWARDS DASHED ARROW"},{"char":"&#x21E4",desc:"LEFTWARDS ARROW TO BAR"},{"char":"&#x21E5",desc:"RIGHTWARDS ARROW TO BAR"},{"char":"&#x21E6",desc:"LEFTWARDS WHITE ARROW"},{"char":"&#x21E7",desc:"UPWARDS WHITE ARROW"},{"char":"&#x21E8",desc:"RIGHTWARDS WHITE ARROW"},{"char":"&#x21E9",desc:"DOWNWARDS WHITE ARROW"},{"char":"&#x21EA",desc:"UPWARDS WHITE ARROW FROM BAR"},{"char":"&#x21EB",desc:"UPWARDS WHITE ARROW ON PEDESTAL"},{"char":"&#x21EC",desc:"UPWARDS WHITE ARROW ON PEDESTAL WITH HORIZONTAL BAR"},{"char":"&#x21ED",desc:"UPWARDS WHITE ARROW ON PEDESTAL WITH VERTICAL BAR"},{"char":"&#x21EE",desc:"UPWARDS WHITE DOUBLE ARROW"},{"char":"&#x21EF",desc:"UPWARDS WHITE DOUBLE ARROW ON PEDESTAL"},{"char":"&#x21F0",desc:"RIGHTWARDS WHITE ARROW FROM WALL"},{"char":"&#x21F1",desc:"NORTH WEST ARROW TO CORNER"},{"char":"&#x21F2",desc:"SOUTH EAST ARROW TO CORNER"},{"char":"&#x21F3",desc:"UP DOWN WHITE ARROW"},{"char":"&#x21F4",desc:"RIGHT ARROW WITH SMALL CIRCLE"},{"char":"&#x21F5",desc:"DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW"},{"char":"&#x21F6",desc:"THREE RIGHTWARDS ARROWS"},{"char":"&#x21F7",desc:"LEFTWARDS ARROW WITH VERTICAL STROKE"},{"char":"&#x21F8",desc:"RIGHTWARDS ARROW WITH VERTICAL STROKE"},{"char":"&#x21F9",desc:"LEFT RIGHT ARROW WITH VERTICAL STROKE"},{"char":"&#x21FA",desc:"LEFTWARDS ARROW WITH DOUBLE VERTICAL STROKE"},{"char":"&#x21FB",desc:"RIGHTWARDS ARROW WITH DOUBLE VERTICAL STROKE"},{"char":"&#x21FC",desc:"LEFT RIGHT ARROW WITH DOUBLE VERTICAL STROKE"},{"char":"&#x21FD",desc:"LEFTWARDS OPEN-HEADED ARROW"},{"char":"&#x21FE",desc:"RIGHTWARDS OPEN-HEADED ARROW"},{"char":"&#x21FF",desc:"LEFT RIGHT OPEN-HEADED ARROW"}]},{title:"Math",list:[{"char":"&forall;",desc:"FOR ALL"},{"char":"&part;",desc:"PARTIAL DIFFERENTIAL"},{"char":"&exist;",desc:"THERE EXISTS"},{"char":"&empty;",desc:"EMPTY SET"},{"char":"&nabla;",desc:"NABLA"},{"char":"&isin;",desc:"ELEMENT OF"},{"char":"&notin;",desc:"NOT AN ELEMENT OF"},{"char":"&ni;",desc:"CONTAINS AS MEMBER"},{"char":"&prod;",desc:"N-ARY PRODUCT"},{"char":"&sum;",desc:"N-ARY SUMMATION"},{"char":"&minus;",desc:"MINUS SIGN"},{"char":"&lowast;",desc:"ASTERISK OPERATOR"},{"char":"&radic;",desc:"SQUARE ROOT"},{"char":"&prop;",desc:"PROPORTIONAL TO"},{"char":"&infin;",desc:"INFINITY"},{"char":"&ang;",desc:"ANGLE"},{"char":"&and;",desc:"LOGICAL AND"},{"char":"&or;",desc:"LOGICAL OR"},{"char":"&cap;",desc:"INTERSECTION"},{"char":"&cup;",desc:"UNION"},{"char":"&int;",desc:"INTEGRAL"},{"char":"&there4;",desc:"THEREFORE"},{"char":"&sim;",desc:"TILDE OPERATOR"},{"char":"&cong;",desc:"APPROXIMATELY EQUAL TO"},{"char":"&asymp;",desc:"ALMOST EQUAL TO"},{"char":"&ne;",desc:"NOT EQUAL TO"},{"char":"&equiv;",desc:"IDENTICAL TO"},{"char":"&le;",desc:"LESS-THAN OR EQUAL TO"},{"char":"&ge;",desc:"GREATER-THAN OR EQUAL TO"},{"char":"&sub;",desc:"SUBSET OF"},{"char":"&sup;",desc:"SUPERSET OF"},{"char":"&nsub;",desc:"NOT A SUBSET OF"},{"char":"&sube;",desc:"SUBSET OF OR EQUAL TO"},{"char":"&supe;",desc:"SUPERSET OF OR EQUAL TO"},{"char":"&oplus;",desc:"CIRCLED PLUS"},{"char":"&otimes;",desc:"CIRCLED TIMES"},{"char":"&perp;",desc:"UP TACK"}]},{title:"Misc",list:[{"char":"&spades;",desc:"BLACK SPADE SUIT"},{"char":"&clubs;",desc:"BLACK CLUB SUIT"},{"char":"&hearts;",desc:"BLACK HEART SUIT"},{"char":"&diams;",desc:"BLACK DIAMOND SUIT"},{"char":"&#x2669",desc:"QUARTER NOTE"},{"char":"&#x266A",desc:"EIGHTH NOTE"},{"char":"&#x266B",desc:"BEAMED EIGHTH NOTES"},{"char":"&#x266C",desc:"BEAMED SIXTEENTH NOTES"},{"char":"&#x266D",desc:"MUSIC FLAT SIGN"},{"char":"&#x266E",desc:"MUSIC NATURAL SIGN"},{"char":"&#x2600",desc:"BLACK SUN WITH RAYS"},{"char":"&#x2601",desc:"CLOUD"},{"char":"&#x2602",desc:"UMBRELLA"},{"char":"&#x2603",desc:"SNOWMAN"},{"char":"&#x2615",desc:"HOT BEVERAGE"},{"char":"&#x2618",desc:"SHAMROCK"},{"char":"&#x262F",desc:"YIN YANG"},{"char":"&#x2714",desc:"HEAVY CHECK MARK"},{"char":"&#x2716",desc:"HEAVY MULTIPLICATION X"},{"char":"&#x2744",desc:"SNOWFLAKE"},{"char":"&#x275B",desc:"HEAVY SINGLE TURNED COMMA QUOTATION MARK ORNAMENT"},{"char":"&#x275C",desc:"HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT"},{"char":"&#x275D",desc:"HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT"},{"char":"&#x275E",desc:"HEAVY DOUBLE COMMA QUOTATION MARK ORNAMENT"},{"char":"&#x2764",desc:"HEAVY BLACK HEART"}]}]}),s.FE.PLUGINS.specialCharacters=function(a){var I,e,R="special_characters";function r(E,c){a.events.disableBlur(),E.focus(),c.preventDefault(),c.stopPropagation()}return{_init:function(){},show:function(){if(!I){var E="<h4>"+a.language.translate("Special Characters")+"</h4>",c=function(){for(var E='<div class="fr-special-characters-modal">',c=0;c<a.opts.specialCharactersSets.length;c++){for(var T=a.opts.specialCharactersSets[c],R=T.list,L='<div class="fr-special-characters-list"><p class="fr-special-characters-title">'+a.language.translate(T.title)+"</p>",A=0;A<R.length;A++){var I=R[A];L+='<span class="fr-command fr-special-character" tabIndex="-1" role="button" value="'+I["char"]+'" title="'+I.desc+'">'+I["char"]+'<span class="fr-sr-only">'+a.language.translate(I.desc)+"&nbsp;&nbsp;&nbsp;</span></span>"}E+=L+"</div>"}return E+="</div>"}(),T=a.modals.create(R,E,c);I=T.$modal,T.$head,e=T.$body,a.events.$on(s(a.o_win),"resize",function(){(I.data("instance")||a).modals.resize(R)}),a.events.bindClick(e,".fr-special-character",function(E){var c=I.data("instance")||a,T=s(E.currentTarget);c.specialCharacters.insert(T)}),a.events.$on(e,"keydown",function(E){var c=E.which,T=e.find("span.fr-special-character:focus:first");if(!(T.length||c!=s.FE.KEYCODE.F10||a.keys.ctrlKey(E)||E.shiftKey)&&E.altKey)return r(e.find("span.fr-special-character:first"),E),!1;if(c==s.FE.KEYCODE.TAB||c==s.FE.KEYCODE.ARROW_LEFT||c==s.FE.KEYCODE.ARROW_RIGHT){var R=null,L=null,A=!1;return c==s.FE.KEYCODE.ARROW_LEFT||c==s.FE.KEYCODE.ARROW_RIGHT?(L=c==s.FE.KEYCODE.ARROW_RIGHT,A=!0):L=!E.shiftKey,T.length?(A&&(R=L?T.nextAll("span.fr-special-character:first"):T.prevAll("span.fr-special-character:first")),R&&R.length||(R=L?T.parent().next().find("span.fr-special-character:first"):T.parent().prev().find("span.fr-special-character:"+(A?"last":"first"))).length||(R=e.find("span.fr-special-character:"+(L?"first":"last")))):R=e.find("span.fr-special-character:"+(L?"first":"last")),r(R,E),!1}if(c!=s.FE.KEYCODE.ENTER||!T.length)return!0;(I.data("instance")||a).specialCharacters.insert(T)},!0)}a.modals.show(R),a.modals.resize(R)},hide:function(){a.modals.hide(R)},insert:function(E){a.specialCharacters.hide(),a.undo.saveStep(),a.html.insert(E.attr("value"),!0),a.undo.saveStep()}}},s.FroalaEditor.DefineIcon("specialCharacters",{template:"text",NAME:"&#937;"}),s.FE.RegisterCommand("specialCharacters",{title:"Special Characters",icon:"specialCharacters",undo:!1,focus:!1,modal:!0,callback:function(){this.specialCharacters.show()},plugin:"specialCharacters",showOnMobile:!1})});
@@ -0,0 +1,4194 @@
1
+ /*!
2
+ * froala_editor v2.8.1 (https://www.froala.com/wysiwyg-editor)
3
+ * License https://froala.com/wysiwyg-editor/terms/
4
+ * Copyright 2014-2018 Froala Labs
5
+ */
6
+
7
+ (function (factory) {
8
+ if (typeof define === 'function' && define.amd) {
9
+ // AMD. Register as an anonymous module.
10
+ define(['jquery'], factory);
11
+ } else if (typeof module === 'object' && module.exports) {
12
+ // Node/CommonJS
13
+ module.exports = function( root, jQuery ) {
14
+ if ( jQuery === undefined ) {
15
+ // require('jQuery') returns a factory that requires window to
16
+ // build a jQuery instance, we normalize how we use modules
17
+ // that require this pattern but the window provided is a noop
18
+ // if it's defined (how jquery works)
19
+ if ( typeof window !== 'undefined' ) {
20
+ jQuery = require('jquery');
21
+ }
22
+ else {
23
+ jQuery = require('jquery')(root);
24
+ }
25
+ }
26
+ return factory(jQuery);
27
+ };
28
+ } else {
29
+ // Browser globals
30
+ factory(window.jQuery);
31
+ }
32
+ }(function ($) {
33
+
34
+
35
+ $.extend($.FE.POPUP_TEMPLATES, {
36
+ 'table.insert': '[_BUTTONS_][_ROWS_COLUMNS_]',
37
+ 'table.edit': '[_BUTTONS_]',
38
+ 'table.colors': '[_BUTTONS_][_COLORS_][_CUSTOM_COLOR_]'
39
+ })
40
+
41
+ // Extend defaults.
42
+ $.extend($.FE.DEFAULTS, {
43
+ tableInsertMaxSize: 10,
44
+ tableEditButtons: ['tableHeader', 'tableRemove', '|', 'tableRows', 'tableColumns', 'tableStyle', '-', 'tableCells', 'tableCellBackground', 'tableCellVerticalAlign', 'tableCellHorizontalAlign', 'tableCellStyle'],
45
+ tableInsertButtons: ['tableBack', '|'],
46
+ tableResizer: true,
47
+ tableDefaultWidth: '100%',
48
+ tableResizerOffset: 5,
49
+ tableResizingLimit: 30,
50
+ tableColorsButtons: ['tableBack', '|'],
51
+ tableColors: [
52
+ '#61BD6D', '#1ABC9C', '#54ACD2', '#2C82C9', '#9365B8', '#475577', '#CCCCCC',
53
+ '#41A85F', '#00A885', '#3D8EB9', '#2969B0', '#553982', '#28324E', '#000000',
54
+ '#F7DA64', '#FBA026', '#EB6B56', '#E25041', '#A38F84', '#EFEFEF', '#FFFFFF',
55
+ '#FAC51C', '#F37934', '#D14841', '#B8312F', '#7C706B', '#D1D5D8', 'REMOVE'
56
+ ],
57
+ tableColorsStep: 7,
58
+ tableCellStyles: {
59
+ 'fr-highlighted': 'Highlighted',
60
+ 'fr-thick': 'Thick'
61
+ },
62
+ tableStyles: {
63
+ 'fr-dashed-borders': 'Dashed Borders',
64
+ 'fr-alternate-rows': 'Alternate Rows'
65
+ },
66
+ tableCellMultipleStyles: true,
67
+ tableMultipleStyles: true,
68
+ tableInsertHelper: true,
69
+ tableInsertHelperOffset: 15
70
+ });
71
+
72
+ $.FE.PLUGINS.table = function (editor) {
73
+ var $resizer;
74
+ var $insert_helper;
75
+ var mouseDownCellFlag;
76
+ var mouseDownFlag;
77
+ var mouseDownCell;
78
+ var mouseMoveTimer;
79
+ var resizingFlag;
80
+
81
+ /*
82
+ * Show the insert table popup.
83
+ */
84
+ function _showInsertPopup () {
85
+ var $btn = editor.$tb.find('.fr-command[data-cmd="insertTable"]');
86
+
87
+ var $popup = editor.popups.get('table.insert');
88
+
89
+ if (!$popup) $popup = _initInsertPopup();
90
+
91
+ if (!$popup.hasClass('fr-active')) {
92
+
93
+ // Insert table popup
94
+ editor.popups.refresh('table.insert');
95
+ editor.popups.setContainer('table.insert', editor.$tb);
96
+
97
+ // Insert table left and top position.
98
+ var left = $btn.offset().left + $btn.outerWidth() / 2;
99
+ var top = $btn.offset().top + (editor.opts.toolbarBottom ? 10 : $btn.outerHeight() - 10);
100
+ editor.popups.show('table.insert', left, top, $btn.outerHeight());
101
+ }
102
+ }
103
+
104
+ /*
105
+ * Show the table edit popup.
106
+ */
107
+ function _showEditPopup () {
108
+ // Set popup position.
109
+ var map = _tableMap();
110
+
111
+ if (map) {
112
+ var $popup = editor.popups.get('table.edit');
113
+
114
+ if (!$popup) $popup = _initEditPopup();
115
+
116
+ if ($popup) {
117
+ editor.popups.setContainer('table.edit', editor.$sc);
118
+ var offset = _selectionOffset(map);
119
+ var left = (offset.left + offset.right) / 2;
120
+ var top = offset.bottom;
121
+
122
+ editor.popups.show('table.edit', left, top, offset.bottom - offset.top);
123
+
124
+ // Disable toolbar buttons only if there are more than one cells selected.
125
+ if (editor.edit.isDisabled()) {
126
+
127
+ // Disable toolbar.
128
+ if (selectedCells().length > 1) {
129
+ editor.toolbar.disable();
130
+ }
131
+
132
+ // Allow text selection.
133
+ editor.$el.removeClass('fr-no-selection');
134
+ editor.edit.on();
135
+
136
+ editor.button.bulkRefresh();
137
+
138
+ // https://github.com/froala/wysiwyg-editor/issues/1851.
139
+ // https://github.com/froala-labs/froala-editor-js-2/issues/314.
140
+ // https://github.com/froala/wysiwyg-editor/issues/1656.
141
+ // https://github.com/froala-labs/froala-editor-js-2/issues/294.
142
+
143
+ // Place selection in last selected table cell.
144
+ editor.selection.setAtEnd(editor.$el.find('.fr-selected-cell:last').get(0));
145
+ editor.selection.restore();
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ /*
152
+ * Show the table colors popup.
153
+ */
154
+ function _showColorsPopup () {
155
+
156
+ // Set popup position.
157
+ var map = _tableMap();
158
+
159
+ if (map) {
160
+ var $popup = editor.popups.get('table.colors');
161
+
162
+ if (!$popup) $popup = _initColorsPopup();
163
+
164
+ editor.popups.setContainer('table.colors', editor.$sc);
165
+ var offset = _selectionOffset(map);
166
+ var left = (offset.left + offset.right) / 2;
167
+ var top = offset.bottom;
168
+
169
+ // Refresh selected color.
170
+ _refreshColor();
171
+
172
+ editor.popups.show('table.colors', left, top, offset.bottom - offset.top);
173
+ }
174
+ }
175
+
176
+ /*
177
+ * Called on table edit popup hide.
178
+ */
179
+ function _hideEditPopup () {
180
+
181
+ // Enable toolbar.
182
+ if (selectedCells().length === 0) {
183
+ editor.toolbar.enable();
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Init the insert table popup.
189
+ */
190
+ function _initInsertPopup (delayed) {
191
+ if (delayed) {
192
+ editor.popups.onHide('table.insert', function () {
193
+
194
+ // Clear previous cell selection.
195
+ editor.popups.get('table.insert').find('.fr-table-size .fr-select-table-size > span[data-row="1"][data-col="1"]').trigger('mouseenter');
196
+ });
197
+
198
+ return true;
199
+ }
200
+
201
+ // Table buttons.
202
+ var table_buttons = '';
203
+
204
+ if (editor.opts.tableInsertButtons.length > 0) {
205
+ table_buttons = '<div class="fr-buttons">' + editor.button.buildList(editor.opts.tableInsertButtons) + '</div>';
206
+ }
207
+
208
+ var template = {
209
+ buttons: table_buttons,
210
+ rows_columns: _insertTableHtml()
211
+ };
212
+
213
+ var $popup = editor.popups.create('table.insert', template);
214
+
215
+ // Initialize insert table grid events.
216
+ editor.events.$on($popup, 'mouseenter', '.fr-table-size .fr-select-table-size .fr-table-cell', function (e) {
217
+ _hoverCell($(e.currentTarget));
218
+ }, true);
219
+
220
+ _addAccessibility($popup);
221
+
222
+ return $popup;
223
+ }
224
+
225
+ /*
226
+ * Hover table cell.
227
+ */
228
+ function _hoverCell ($table_cell) {
229
+ var row = $table_cell.data('row');
230
+ var col = $table_cell.data('col');
231
+ var $select_size = $table_cell.parent();
232
+
233
+ // Update size in title.
234
+ $select_size.siblings('.fr-table-size-info').html(row + ' &times; ' + col);
235
+
236
+ // Remove hover and fr-active-item class from all cells.
237
+ $select_size.find('> span').removeClass('hover fr-active-item');
238
+
239
+ // Add hover class only to the correct cells.
240
+ for (var i = 1; i <= editor.opts.tableInsertMaxSize; i++) {
241
+ for (var j = 0; j <= editor.opts.tableInsertMaxSize; j++) {
242
+ var $cell = $select_size.find('> span[data-row="' + i + '"][data-col="' + j + '"]');
243
+
244
+ if (i <= row && j <= col) {
245
+ $cell.addClass('hover');
246
+ }
247
+ else if ((i <= row + 1 || (i <= 2 && !editor.helpers.isMobile()))) {
248
+ $cell.css('display', 'inline-block');
249
+ }
250
+ else if (i > 2 && !editor.helpers.isMobile()) {
251
+ $cell.css('display', 'none');
252
+ }
253
+ }
254
+ }
255
+
256
+ // Mark table cell as the active item.
257
+ $table_cell.addClass('fr-active-item');
258
+ }
259
+
260
+ /*
261
+ * The HTML for insert table grid.
262
+ */
263
+ function _insertTableHtml () {
264
+
265
+ // Grid html
266
+ var rows_columns = '<div class="fr-table-size"><div class="fr-table-size-info">1 &times; 1</div><div class="fr-select-table-size">'
267
+
268
+ for (var i = 1; i <= editor.opts.tableInsertMaxSize; i++) {
269
+ for (var j = 1; j <= editor.opts.tableInsertMaxSize; j++) {
270
+ var display = 'inline-block';
271
+
272
+ // Display only first 2 rows.
273
+ if (i > 2 && !editor.helpers.isMobile()) {
274
+ display = 'none';
275
+ }
276
+
277
+ var cls = 'fr-table-cell ';
278
+
279
+ if (i == 1 && j == 1) {
280
+ cls += ' hover';
281
+ }
282
+
283
+ rows_columns += '<span class="fr-command ' + cls + '" tabIndex="-1" data-cmd="tableInsert" data-row="' + i + '" data-col="' + j + '" data-param1="' + i + '" data-param2="' + j + '" style="display: ' + display + ';" role="button"><span></span><span class="fr-sr-only">' + i + ' &times; ' + j + '&nbsp;&nbsp;&nbsp;</span></span>';
284
+ }
285
+ rows_columns += '<div class="new-line"></div>';
286
+ }
287
+
288
+ rows_columns += '</div></div>';
289
+
290
+ return rows_columns;
291
+ }
292
+
293
+ /*
294
+ * Register keyboard events.
295
+ */
296
+ function _addAccessibility ($popup) {
297
+
298
+ // Hover cell when table.insert cells are focused.
299
+ editor.events.$on($popup, 'focus', '[tabIndex]', function (e) {
300
+ var $focused_el = $(e.currentTarget);
301
+ _hoverCell($focused_el);
302
+ });
303
+
304
+ // Register popup event.
305
+ editor.events.on('popup.tab', function (e) {
306
+ var $focused_item = $(e.currentTarget);
307
+
308
+ // Skip if popup is not visible or focus is elsewere.
309
+ if (!editor.popups.isVisible('table.insert') || !$focused_item.is('span, a')) {
310
+ return true;
311
+ }
312
+
313
+ var key_code = e.which;
314
+ var status;
315
+
316
+ if ($.FE.KEYCODE.ARROW_UP == key_code || $.FE.KEYCODE.ARROW_DOWN == key_code || $.FE.KEYCODE.ARROW_LEFT == key_code || $.FE.KEYCODE.ARROW_RIGHT == key_code) {
317
+ if ($focused_item.is('span.fr-table-cell')) {
318
+
319
+ // Get all current cells.
320
+ var $cells = $focused_item.parent().find('span.fr-table-cell');
321
+
322
+ // Get focused item position.
323
+ var index = $cells.index($focused_item);
324
+
325
+ // Get cell matrix dimensions.
326
+ var columns = editor.opts.tableInsertMaxSize;
327
+
328
+ // Get focused item coordinates.
329
+ var column = index % columns;
330
+ var line = Math.floor(index / columns);
331
+
332
+ // Calculate next coordinates. Go to the other opposite site of the matrix if there is no next adjacent element.
333
+ if ($.FE.KEYCODE.ARROW_UP == key_code) {
334
+ line = Math.max(0, line - 1);
335
+ }
336
+ else if ($.FE.KEYCODE.ARROW_DOWN == key_code) {
337
+ line = Math.min(editor.opts.tableInsertMaxSize - 1, line + 1);
338
+ }
339
+ else if ($.FE.KEYCODE.ARROW_LEFT == key_code) {
340
+ column = Math.max(0, column - 1);
341
+ }
342
+ else if ($.FE.KEYCODE.ARROW_RIGHT == key_code) {
343
+ column = Math.min(editor.opts.tableInsertMaxSize - 1, column + 1);
344
+ }
345
+
346
+ // Get the next element based on the new coordinates.
347
+ var nextIndex = line * columns + column;
348
+ var $el = $($cells.get(nextIndex));
349
+
350
+ // Hover cell
351
+ _hoverCell($el);
352
+
353
+ // Focus.
354
+ editor.events.disableBlur();
355
+ $el.focus();
356
+
357
+ status = false;
358
+ }
359
+ }
360
+
361
+ // ENTER or SPACE.
362
+ else if ($.FE.KEYCODE.ENTER == key_code) {
363
+
364
+ editor.button.exec($focused_item);
365
+ status = false;
366
+ }
367
+
368
+ // Prevent propagation.
369
+ if (status === false) {
370
+ e.preventDefault();
371
+ e.stopPropagation();
372
+ }
373
+
374
+ return status;
375
+ }, true);
376
+ }
377
+
378
+ /**
379
+ * Init the table edit popup.
380
+ */
381
+ function _initEditPopup (delayed) {
382
+ if (delayed) {
383
+ editor.popups.onHide('table.edit', _hideEditPopup);
384
+
385
+ return true;
386
+ }
387
+
388
+ // Table buttons.
389
+ var table_buttons = '';
390
+
391
+ if (editor.opts.tableEditButtons.length > 0) {
392
+ table_buttons = '<div class="fr-buttons">' + editor.button.buildList(editor.opts.tableEditButtons) + '</div>';
393
+
394
+ var template = {
395
+ buttons: table_buttons
396
+ };
397
+
398
+ var $popup = editor.popups.create('table.edit', template);
399
+
400
+ editor.events.$on(editor.$wp, 'scroll.table-edit', function () {
401
+ if (editor.popups.isVisible('table.edit')) {
402
+ _showEditPopup();
403
+ }
404
+ });
405
+
406
+ return $popup;
407
+ }
408
+
409
+ return false;
410
+ }
411
+
412
+ /*
413
+ * Init the table cell background popup.
414
+ */
415
+ function _initColorsPopup () {
416
+
417
+ // Table colors buttons.
418
+ var table_buttons = '';
419
+
420
+ if (editor.opts.tableColorsButtons.length > 0) {
421
+ table_buttons = '<div class="fr-buttons fr-table-colors-buttons">' + editor.button.buildList(editor.opts.tableColorsButtons) + '</div>';
422
+ }
423
+
424
+ // Custom HEX.
425
+ var custom_color = '';
426
+
427
+ if (editor.opts.colorsHEXInput) {
428
+ custom_color = '<div class="fr-table-colors-hex-layer fr-active fr-layer" id="fr-table-colors-hex-layer-' + editor.id + '"><div class="fr-input-line"><input maxlength="7" id="fr-table-colors-hex-layer-text-' + editor.id + '" type="text" placeholder="' + editor.language.translate('HEX Color') + '" tabIndex="1" aria-required="true"></div><div class="fr-action-buttons"><button type="button" class="fr-command fr-submit" data-cmd="tableCellBackgroundCustomColor" tabIndex="2" role="button">' + editor.language.translate('OK') + '</button></div></div>';
429
+ }
430
+
431
+ var template = {
432
+ buttons: table_buttons,
433
+ colors: _colorsHTML(),
434
+ custom_color: custom_color
435
+ };
436
+
437
+ var $popup = editor.popups.create('table.colors', template);
438
+
439
+ editor.events.$on(editor.$wp, 'scroll.table-colors', function () {
440
+ if (editor.popups.isVisible('table.colors')) {
441
+ _showColorsPopup();
442
+ }
443
+ });
444
+
445
+ _addColorsAccessibility($popup);
446
+
447
+ return $popup;
448
+ }
449
+
450
+ /*
451
+ * HTML for the table colors.
452
+ */
453
+ function _colorsHTML () {
454
+
455
+ // Create colors html.
456
+ var colors_html = '<div class="fr-table-colors">';
457
+
458
+ // Add colors.
459
+ for (var i = 0; i < editor.opts.tableColors.length; i++) {
460
+ if (i !== 0 && i % editor.opts.tableColorsStep === 0) {
461
+ colors_html += '<br>';
462
+ }
463
+
464
+ if (editor.opts.tableColors[i] != 'REMOVE') {
465
+ colors_html += '<span class="fr-command" style="background: ' + editor.opts.tableColors[i] + ';" tabIndex="-1" role="button" data-cmd="tableCellBackgroundColor" data-param1="' + editor.opts.tableColors[i] + '"><span class="fr-sr-only">' + editor.language.translate('Color') + ' ' + editor.opts.tableColors[i] + '&nbsp;&nbsp;&nbsp;</span></span>';
466
+ }
467
+
468
+ else {
469
+ colors_html += '<span class="fr-command" data-cmd="tableCellBackgroundColor" tabIndex="-1" role="button" data-param1="REMOVE" title="' + editor.language.translate('Clear Formatting') + '">' + editor.icon.create('tableColorRemove') + '<span class="fr-sr-only">' + editor.language.translate('Clear Formatting') + '</span></span>';
470
+ }
471
+ }
472
+
473
+ colors_html += '</div>';
474
+
475
+ return colors_html;
476
+ }
477
+
478
+ /*
479
+ * Set custom color
480
+ */
481
+ function customColor () {
482
+ var $popup = editor.popups.get('table.colors');
483
+ var $input = $popup.find('.fr-table-colors-hex-layer input');
484
+
485
+ if ($input.length) {
486
+ setBackground($input.val())
487
+ }
488
+ }
489
+
490
+ /*
491
+ * Register keyboard events for colors.
492
+ */
493
+ function _addColorsAccessibility ($popup) {
494
+
495
+ // Register popup event.
496
+ editor.events.on('popup.tab', function (e) {
497
+ var $focused_item = $(e.currentTarget);
498
+
499
+ // Skip if popup is not visible or focus is elsewere.
500
+ if (!editor.popups.isVisible('table.colors') || !$focused_item.is('span')) {
501
+ return true;
502
+ }
503
+ var key_code = e.which;
504
+ var status = true;
505
+
506
+ // Tabbing.
507
+ if ($.FE.KEYCODE.TAB == key_code) {
508
+ var $tb = $popup.find('.fr-buttons');
509
+
510
+ // Focus back the popup's toolbar if exists.
511
+ status = !editor.accessibility.focusToolbar($tb, (e.shiftKey ? true : false));
512
+ }
513
+
514
+ // Arrows.
515
+ else if ($.FE.KEYCODE.ARROW_UP == key_code || $.FE.KEYCODE.ARROW_DOWN == key_code || $.FE.KEYCODE.ARROW_LEFT == key_code || $.FE.KEYCODE.ARROW_RIGHT == key_code) {
516
+
517
+ // Get all current colors.
518
+ var $colors = $focused_item.parent().find('span.fr-command');
519
+
520
+ // Get focused item position.
521
+ var index = $colors.index($focused_item);
522
+
523
+ // Get color matrix dimensions.
524
+ var columns = editor.opts.colorsStep;
525
+ var lines = Math.floor($colors.length / columns);
526
+
527
+ // Get focused item coordinates.
528
+ var column = index % columns;
529
+ var line = Math.floor(index / columns);
530
+
531
+ var nextIndex = line * columns + column;
532
+ var dimension = lines * columns;
533
+
534
+ // Calculate next index. Go to the other opposite site of the matrix if there is no next adjacent element.
535
+ // Up/Down: Traverse matrix lines.
536
+ // Left/Right: Traverse the matrix as it is a vector.
537
+ if ($.FE.KEYCODE.ARROW_UP == key_code) {
538
+ nextIndex = (((nextIndex - columns) % dimension) + dimension) % dimension; // Javascript negative modulo bug.
539
+ }
540
+ else if ($.FE.KEYCODE.ARROW_DOWN == key_code) {
541
+ nextIndex = (nextIndex + columns) % dimension;
542
+ }
543
+ else if ($.FE.KEYCODE.ARROW_LEFT == key_code) {
544
+ nextIndex = (((nextIndex - 1) % dimension) + dimension) % dimension; // Javascript negative modulo bug.
545
+ }
546
+ else if ($.FE.KEYCODE.ARROW_RIGHT == key_code) {
547
+ nextIndex = (nextIndex + 1) % dimension;
548
+ }
549
+
550
+ // Get the next element based on the new index.
551
+ var $el = $($colors.get(nextIndex));
552
+
553
+ // Focus.
554
+ editor.events.disableBlur();
555
+ $el.focus();
556
+
557
+ status = false;
558
+ }
559
+
560
+ // ENTER or SPACE.
561
+ else if ($.FE.KEYCODE.ENTER == key_code) {
562
+
563
+ editor.button.exec($focused_item);
564
+ status = false;
565
+ }
566
+
567
+ // Prevent propagation.
568
+ if (status === false) {
569
+ e.preventDefault();
570
+ e.stopPropagation();
571
+ }
572
+
573
+ return status;
574
+ }, true);
575
+ }
576
+
577
+ /*
578
+ * Show the current selected color.
579
+ */
580
+ function _refreshColor () {
581
+ var $popup = editor.popups.get('table.colors');
582
+ var $cell = editor.$el.find('.fr-selected-cell:first');
583
+ var color = editor.helpers.RGBToHex($cell.css('background-color'));
584
+ var $input = $popup.find('.fr-table-colors-hex-layer input');
585
+
586
+ // Remove current color selection.
587
+ $popup.find('.fr-selected-color').removeClass('fr-selected-color fr-active-item');
588
+
589
+ // Find the selected color.
590
+ $popup.find('span[data-param1="' + color + '"]').addClass('fr-selected-color fr-active-item');
591
+ $input.val(color).trigger('change');
592
+ }
593
+
594
+ /*
595
+ * Insert table method.
596
+ */
597
+ function insert (rows, cols) {
598
+
599
+ // Create table HTML.
600
+ var table = '<table ' + (!editor.opts.tableDefaultWidth ? '' : 'style="width: ' + editor.opts.tableDefaultWidth + ';" ') + 'class="fr-inserted-table"><tbody>';
601
+ var cell_width = 100 / cols;
602
+ var i;
603
+ var j;
604
+
605
+ for (i = 0; i < rows; i++) {
606
+ table += '<tr>';
607
+
608
+ for (j = 0; j < cols; j++) {
609
+ table += '<td' + (!editor.opts.tableDefaultWidth ? '' : ' style="width: ' + cell_width.toFixed(4) + '%;"') + '>';
610
+
611
+ if (i === 0 && j === 0)table += $.FE.MARKERS;
612
+ table += '<br></td>';
613
+ }
614
+ table += '</tr>';
615
+ }
616
+ table += '</tbody></table>';
617
+
618
+ editor.html.insert(table);
619
+
620
+ // Update cursor position.
621
+ editor.selection.restore()
622
+
623
+ var $table = editor.$el.find('.fr-inserted-table');
624
+ $table.removeClass('fr-inserted-table');
625
+ editor.events.trigger('table.inserted', [$table.get(0)]);
626
+ }
627
+
628
+ /*
629
+ * Delete table method.
630
+ */
631
+ function remove () {
632
+ if (selectedCells().length > 0) {
633
+ var $current_table = selectedTable();
634
+
635
+ // Update cursor position.
636
+ editor.selection.setBefore($current_table.get(0)) || editor.selection.setAfter($current_table.get(0));
637
+ editor.selection.restore();
638
+
639
+ // Hide table edit popup.
640
+ editor.popups.hide('table.edit');
641
+
642
+ // Delete table.
643
+ $current_table.remove();
644
+
645
+ // Enable toolbar.
646
+ editor.toolbar.enable();
647
+ }
648
+ }
649
+
650
+ /*
651
+ * Add table header.
652
+ */
653
+ function addHeader () {
654
+ var $table = selectedTable();
655
+
656
+ // If there is a selection in the table and the table doesn't have a header already.
657
+ if ($table.length > 0 && $table.find('th').length === 0) {
658
+
659
+ // Create header HTML.
660
+ var thead = '<thead><tr>';
661
+
662
+ var i;
663
+ var col = 0;
664
+
665
+ // Get first row and count table columns.
666
+ $table.find('tr:first > td').each (function () {
667
+ var $td = $(this);
668
+
669
+ col += parseInt($td.attr('colspan'), 10) || 1;
670
+ });
671
+
672
+ // Add cells.
673
+ for (i = 0; i < col; i++) {
674
+ thead += '<th><br></th>';
675
+ }
676
+
677
+ thead += '</tr></thead>'
678
+
679
+ $table.prepend(thead);
680
+
681
+ // Reposition table edit popup.
682
+ _showEditPopup();
683
+ }
684
+ }
685
+
686
+ /*
687
+ * Remove table header.
688
+ */
689
+ function removeHeader () {
690
+ var $current_table = selectedTable();
691
+ var $table_header = $current_table.find('thead');
692
+
693
+ // Table has a header.
694
+ if ($table_header.length > 0) {
695
+
696
+ // If table does not have any other rows then delete table.
697
+ if ($current_table.find('tbody tr').length === 0) {
698
+
699
+ // Remove table.
700
+ remove();
701
+ }
702
+
703
+ else {
704
+ $table_header.remove();
705
+
706
+ // Reposition table edit popup if there any more selected celss.
707
+ if (selectedCells().length > 0) {
708
+ _showEditPopup();
709
+ }
710
+ else {
711
+
712
+ // Hide popup.
713
+ editor.popups.hide('table.edit');
714
+
715
+ // Update cursor position.
716
+ var td = $current_table.find('tbody tr:first td:first').get(0);
717
+
718
+ if (td) {
719
+ editor.selection.setAtEnd(td);
720
+ editor.selection.restore();
721
+ }
722
+ }
723
+ }
724
+ }
725
+ }
726
+
727
+ /*
728
+ * Insert row method.
729
+ */
730
+ function insertRow (position) {
731
+ var $table = selectedTable();
732
+
733
+ // We have selection in a table.
734
+ if ($table.length > 0) {
735
+
736
+ // Cannot insert row above the table header.
737
+ if (editor.$el.find('th.fr-selected-cell').length > 0 && position == 'above') {
738
+ return;
739
+ }
740
+
741
+ else {
742
+ var i;
743
+ var ref_row;
744
+ var $ref_row;
745
+
746
+ // Create a table map.
747
+ var map = _tableMap();
748
+
749
+ // Get selected cells from the table.
750
+ var selection = _currentSelection(map);
751
+
752
+ // Reference row.
753
+ if (position == 'above') {
754
+ ref_row = selection.min_i;
755
+ }
756
+ else {
757
+ ref_row = selection.max_i;
758
+ }
759
+
760
+ // Create row HTML.
761
+ var tr = '<tr>';
762
+
763
+ // Add cells.
764
+ for (i = 0; i < map[ref_row].length; i++) {
765
+
766
+ // If cell has rowspan we should increase it.
767
+ if ((position == 'below' && ref_row < map.length - 1 && map[ref_row][i] == map[ref_row + 1][i]) ||
768
+ (position == 'above' && ref_row > 0 && map[ref_row][i] == map[ref_row - 1][i])) {
769
+
770
+ // Don't increase twice for colspan.
771
+ if (i === 0 || (i > 0 && map[ref_row][i] != map[ref_row][i - 1])) {
772
+ var $cell = $(map[ref_row][i]);
773
+ $cell.attr('rowspan', parseInt($cell.attr('rowspan'), 10) + 1);
774
+ }
775
+
776
+ }
777
+ else {
778
+ tr += '<td><br></td>';
779
+ }
780
+ }
781
+
782
+ // Close row tag.
783
+ tr += '</tr>';
784
+
785
+ // Current selection is in header. Should insert in table body
786
+ if (editor.$el.find('th.fr-selected-cell').length > 0 && position == 'below') {
787
+ $ref_row = $($table.find('tbody').not($table.find('table tbody')));
788
+ }
789
+
790
+ // Selection is in body
791
+ else {
792
+ $ref_row = $($table.find('tr').not($table.find('table tr')).get(ref_row));
793
+ }
794
+
795
+ // Insert new row.
796
+ if (position == 'below') {
797
+ if ($ref_row.prop('tagName') == 'TBODY') {
798
+ $ref_row.prepend(tr);
799
+ }
800
+ else {
801
+ $ref_row.after(tr);
802
+ }
803
+ }
804
+ else if (position == 'above') {
805
+ $ref_row.before(tr);
806
+
807
+ // Reposition table edit popup.
808
+ if (editor.popups.isVisible('table.edit')) {
809
+ _showEditPopup();
810
+ }
811
+ }
812
+ }
813
+ }
814
+ }
815
+
816
+ /*
817
+ * Delete row method.
818
+ */
819
+ function deleteRow () {
820
+ var $table = selectedTable();
821
+
822
+ // We have selection in a table.
823
+ if ($table.length > 0) {
824
+ var i;
825
+ var j;
826
+ var $row;
827
+
828
+ // Create a table map.
829
+ var map = _tableMap();
830
+
831
+ // Get selected cells from the table.
832
+ var selection = _currentSelection(map);
833
+
834
+ // If all the rows are selected then delete the entire table.
835
+ if (selection.min_i === 0 && selection.max_i == map.length - 1) {
836
+ remove();
837
+
838
+ }
839
+ else {
840
+
841
+ // We should delete selected rows.
842
+ for (i = selection.max_i; i >= selection.min_i; i--) {
843
+ $row = $($table.find('tr').not($table.find('table tr')).get(i));
844
+
845
+ // Go through the table map to check for rowspan on the row to delete.
846
+ for (j = 0; j < map[i].length; j++) {
847
+
848
+ // Don't do this twice if we have a colspan.
849
+ if (j === 0 || map[i][j] != map[i][j - 1]) {
850
+ var $cell = $(map[i][j]);
851
+
852
+ // We should decrease rowspan.
853
+ if (parseInt($cell.attr('rowspan'), 10) > 1) {
854
+ var rowspan = parseInt($cell.attr('rowspan'), 10) - 1;
855
+
856
+ if (rowspan == 1) {
857
+ $cell.removeAttr('rowspan');
858
+ }
859
+ else {
860
+ $cell.attr('rowspan', rowspan);
861
+ }
862
+ }
863
+
864
+ // We might need to move tds on the row below if we have a rowspan that starts here.
865
+ if (i < map.length - 1 && map[i][j] == map[i + 1][j] && (i === 0 || map[i][j] != map[i - 1][j])) {
866
+
867
+ // Move td to the row below.
868
+ var td = map[i][j];
869
+ var col = j;
870
+
871
+ // Go back until we get the last element (we might have colspan).
872
+ while (col > 0 && map[i][col] == map[i][col - 1]) {
873
+ col--;
874
+ }
875
+
876
+ // Add td at the beginning of the row below.
877
+ if (col === 0) {
878
+ $($table.find('tr').not($table.find('table tr')).get(i + 1)).prepend(td);
879
+
880
+ }
881
+ else {
882
+ $(map[i + 1][col - 1]).after(td);
883
+ }
884
+ }
885
+ }
886
+ }
887
+
888
+ // Remove tbody or thead if there are no more rows.
889
+ var $row_parent = $row.parent();
890
+ $row.remove();
891
+
892
+ if ($row_parent.find('tr').length === 0) {
893
+ $row_parent.remove();
894
+ }
895
+
896
+ // Table has changed.
897
+ map = _tableMap($table);
898
+ }
899
+
900
+ _updateCellSpan(0, map.length - 1, 0, map[0].length - 1, $table);
901
+
902
+ // Update cursor position
903
+ if (selection.min_i > 0) {
904
+
905
+ // Place cursor in the row above selection.
906
+ editor.selection.setAtEnd(map[selection.min_i - 1][0]);
907
+ }
908
+ else {
909
+
910
+ // Place cursor in the row below selection.
911
+ editor.selection.setAtEnd(map[0][0]);
912
+ }
913
+ editor.selection.restore();
914
+
915
+ // Hide table edit popup.
916
+ editor.popups.hide('table.edit');
917
+ }
918
+ }
919
+ }
920
+
921
+ /*
922
+ * Insert column method.
923
+ */
924
+ function insertColumn (position) {
925
+ var $table = selectedTable();
926
+
927
+ // We have selection in a table.
928
+ if ($table.length > 0) {
929
+
930
+ // Create a table map.
931
+ var map = _tableMap();
932
+
933
+ // Get selected cells from the table.
934
+ var selection = _currentSelection(map);
935
+
936
+ // Reference row.
937
+ var ref_col;
938
+
939
+ if (position == 'before') {
940
+ ref_col = selection.min_j;
941
+ }
942
+ else {
943
+ ref_col = selection.max_j;
944
+ }
945
+
946
+ // Old and new column widths.
947
+ var old_width = 100 / map[0].length;
948
+ var new_width = 100 / (map[0].length + 1);
949
+
950
+ // Go through all cells and get their initial (old) widths.
951
+ var $cell;
952
+
953
+ $table.find('th, td').each (function () {
954
+ $cell = $(this);
955
+ $cell.data('old-width', $cell.outerWidth() / $table.outerWidth() * 100);
956
+ });
957
+
958
+ // Parse each row to insert a new td.
959
+ $table.find('tr').not($table.find('table tr')).each (function (index) {
960
+
961
+ // Get the exact td index before / after which we have to add the new td.
962
+ // ref_col means the table column number, but this is not the same with the td number in a row.
963
+ // We might have colspan or rowspan greater than 1.
964
+ var $row = $(this);
965
+ var col_no = 0;
966
+ var td_no = 0;
967
+ var ref_td;
968
+
969
+ // Start with the first td (td_no = 0) in the current row.
970
+ // Sum colspans (col_no) to see when we reach ref_col.
971
+ // Summing colspans we get the same number with the table column number.
972
+ while (col_no - 1 < ref_col) {
973
+
974
+ // Get current td.
975
+ ref_td = $row.find('> th, > td').get(td_no);
976
+
977
+ // There are no more tds in this row.
978
+ if (!ref_td) {
979
+ ref_td = null;
980
+ break;
981
+ }
982
+
983
+ // The current td is the same with the td from the table map.
984
+ if (ref_td == map[index][col_no]) {
985
+
986
+ // The current td might have colspan.
987
+ col_no += parseInt($(ref_td).attr('colspan'), 10) || 1;
988
+
989
+ // Go to the next td on the current row.
990
+ td_no++;
991
+ }
992
+
993
+ // If current td is not the same with the td from the table map.
994
+ // This means that this table cell (map[index][td_no]) has rowspan.
995
+ // There is at least one td less on this row due to rowspan (based on map[index][td_no] colspan value).
996
+ // We want to count this as a column as well.
997
+ else {
998
+ col_no += parseInt($(map[index][col_no]).attr('colspan'), 10) || 1;
999
+
1000
+ // ref_td is one td ahead. Get previous td if we want to insert column after.
1001
+ if (position == 'after') {
1002
+
1003
+ // There is a rowspan and so ref_td is the first td, but it is not in the first column.
1004
+ if (td_no === 0) {
1005
+ ref_td = -1;
1006
+
1007
+ }
1008
+ else {
1009
+ ref_td = $row.find('> th, > td').get(td_no - 1);
1010
+ }
1011
+ }
1012
+ }
1013
+ }
1014
+
1015
+ var $ref_td = $(ref_td);
1016
+
1017
+ // If the computed column number is higher than the reference number
1018
+ // then on this row we have a colspan longer than the reference column.
1019
+ // When adding a column after we should increase colspan on this row.
1020
+ //
1021
+ // If we want to add a column before, but the td on the reference column is
1022
+ // the same with the previous one then we have a td with colspan that
1023
+ // starts before the column reference number.
1024
+ // When adding a column before we should increase colspan on this row.
1025
+ if ((position == 'after' && col_no - 1 > ref_col) ||
1026
+ (position == 'before' && ref_col > 0 && map[index][ref_col] == map[index][ref_col - 1])) {
1027
+
1028
+ // Don't increase twice for rowspan.
1029
+ if (index === 0 || (index > 0 && map[index][ref_col] != map[index - 1][ref_col])) {
1030
+ var colspan = parseInt($ref_td.attr('colspan'), 10) + 1;
1031
+
1032
+ $ref_td.attr('colspan', colspan)
1033
+
1034
+ // Update this cell's width.
1035
+ $ref_td.css('width', ($ref_td.data('old-width') * new_width / old_width + new_width).toFixed(4) + '%');
1036
+ $ref_td.removeData('old-width');
1037
+ }
1038
+
1039
+ }
1040
+
1041
+ else {
1042
+
1043
+ // Create HTML for a new cell.
1044
+ var td;
1045
+
1046
+ // Might be a td or a th.
1047
+ if ($row.find('th').length > 0) {
1048
+ td = '<th style="width: ' + new_width.toFixed(4) + '%;"><br></th>';
1049
+ }
1050
+ else {
1051
+ td = '<td style="width: ' + new_width.toFixed(4) + '%;"><br></td>';
1052
+ }
1053
+
1054
+ // Insert exactly at the beginning.
1055
+ if (ref_td == -1) {
1056
+ $row.prepend(td);
1057
+
1058
+ // Insert exactly at the end.
1059
+ }
1060
+ else if (ref_td == null) {
1061
+ $row.append(td);
1062
+
1063
+ // Insert td on the current row.
1064
+ }
1065
+ else {
1066
+ if (position == 'before') {
1067
+ $ref_td.before(td);
1068
+ }
1069
+
1070
+ else if (position == 'after') {
1071
+ $ref_td.after(td);
1072
+ }
1073
+ }
1074
+ }
1075
+ });
1076
+
1077
+ // Parse each row to update cells' width.
1078
+ $table.find('th, td').each (function () {
1079
+ $cell = $(this);
1080
+
1081
+ // Update width and remove data.
1082
+ if ($cell.data('old-width')) {
1083
+ $cell.css('width', ($cell.data('old-width') * new_width / old_width).toFixed(4) + '%');
1084
+ $cell.removeData('old-width')
1085
+ }
1086
+ });
1087
+
1088
+ // Reposition table edit popup.
1089
+ if (editor.popups.isVisible('table.edit')) {
1090
+ _showEditPopup();
1091
+ }
1092
+ }
1093
+ }
1094
+
1095
+ /*
1096
+ * Delete column method.
1097
+ */
1098
+ function deleteColumn () {
1099
+ var $table = selectedTable();
1100
+
1101
+ // We have selection in a table.
1102
+ if ($table.length > 0) {
1103
+ var i;
1104
+ var j;
1105
+ var $cell;
1106
+
1107
+ // Create a table map.
1108
+ var map = _tableMap();
1109
+
1110
+ // Get selected cells from the table.
1111
+ var selection = _currentSelection(map);
1112
+
1113
+ // If all the rows are selected then delete the entire table.
1114
+ if (selection.min_j === 0 && selection.max_j == map[0].length - 1) {
1115
+ remove();
1116
+ }
1117
+ else {
1118
+ // Old width of all the columns that remain afte the delete.
1119
+ var old_width = 0;
1120
+
1121
+ // Go through all cells and get their initial (old) widths.
1122
+ for (i = 0; i < map.length; i++) {
1123
+ for (j = 0; j < map[0].length; j++) {
1124
+ $cell = $(map[i][j]);
1125
+
1126
+ if (!$cell.hasClass('fr-selected-cell')) {
1127
+ $cell.data('old-width', $cell.outerWidth() / $table.outerWidth() * 100);
1128
+
1129
+ if (j < selection.min_j || j > selection.max_j) {
1130
+ old_width += $cell.outerWidth() / $table.outerWidth() * 100;
1131
+ }
1132
+ }
1133
+ }
1134
+ }
1135
+
1136
+ old_width /= map.length;
1137
+
1138
+ // We should delete selected columns.
1139
+ for (j = selection.max_j; j >= selection.min_j; j--) {
1140
+
1141
+ // Go through the table map to check for colspan.
1142
+ for (i = 0; i < map.length; i++) {
1143
+
1144
+ // Don't do this twice if we have a rowspan.
1145
+ if (i === 0 || map[i][j] != map[i - 1][j]) {
1146
+
1147
+ // We should decrease colspan.
1148
+ $cell = $(map[i][j]);
1149
+
1150
+ if ((parseInt($cell.attr('colspan'), 10) || 1) > 1) {
1151
+ var colspan = parseInt($cell.attr('colspan'), 10) - 1;
1152
+
1153
+ if (colspan == 1) {
1154
+ $cell.removeAttr('colspan');
1155
+ }
1156
+ else {
1157
+ $cell.attr('colspan', colspan);
1158
+ }
1159
+
1160
+ // Update cell width.
1161
+ $cell.css('width', (($cell.data('old-width') - _columnWidth(j, map)) * 100 / old_width).toFixed(4) + '%');
1162
+ $cell.removeData('old-width');
1163
+
1164
+ // If there is no colspan delete the cell.
1165
+ }
1166
+ else {
1167
+
1168
+ // We might need to delete the row too if it is empty.
1169
+ var $row = $($cell.parent().get(0));
1170
+
1171
+ $cell.remove();
1172
+
1173
+ // Check if there are any more tds in the current row.
1174
+ if ($row.find('> th, > td').length === 0) {
1175
+
1176
+ // Check if it is okay to delete the tr.
1177
+ if ($row.prev().length === 0 || $row.next().length === 0 ||
1178
+ $row.prev().find('> th[rowspan], > td[rowspan]').length < $row.prev().find('> th, > td').length) {
1179
+ $row.remove();
1180
+ }
1181
+ }
1182
+ }
1183
+ }
1184
+ }
1185
+ }
1186
+
1187
+ _updateCellSpan(0, map.length - 1, 0, map[0].length - 1, $table);
1188
+
1189
+ // Update cursor position
1190
+ if (selection.min_j > 0) {
1191
+ // Place cursor in the column before selection.
1192
+ editor.selection.setAtEnd(map[selection.min_i][selection.min_j - 1]);
1193
+ }
1194
+ else {
1195
+ // Place cursor in the column after selection.
1196
+ editor.selection.setAtEnd(map[selection.min_i][0]);
1197
+ }
1198
+ editor.selection.restore();
1199
+
1200
+ // Hide table edit popup.
1201
+ editor.popups.hide('table.edit');
1202
+
1203
+ // Scale current cells' width after column has been deleted.
1204
+ $table.find('th, td').each (function () {
1205
+ $cell = $(this);
1206
+
1207
+ // Update width and remove data.
1208
+ if ($cell.data('old-width')) {
1209
+ $cell.css('width', ($cell.data('old-width') * 100 / old_width).toFixed(4) + '%');
1210
+ $cell.removeData('old-width');
1211
+ }
1212
+ });
1213
+ }
1214
+ }
1215
+ }
1216
+
1217
+ /*
1218
+ * Update or remove colspan attribute.
1219
+ */
1220
+ function _updateColspan (min_j, max_j, $table) {
1221
+ var i;
1222
+ var j;
1223
+ var k;
1224
+ var first_span;
1225
+ var span;
1226
+ var decrease = 0;
1227
+
1228
+ // Create a table map.
1229
+ var map = _tableMap($table);
1230
+
1231
+ // A column might have been deleted.
1232
+ max_j = Math.min(max_j, map[0].length - 1);
1233
+
1234
+ // If max_j and min_j are the same, then only one column is selected and colspan is preserved.
1235
+ if (max_j > min_j) {
1236
+ // Find out how much we should decrease colspan.
1237
+ // Parsing only the first row is enough.
1238
+ for (j = min_j; j <= max_j; j++) {
1239
+
1240
+ // This cell has colspan and has already been checked.
1241
+ if (j > min_j && map[0][j] == map[0][j - 1]) {
1242
+ continue;
1243
+ }
1244
+
1245
+ // Current cell colspan
1246
+ first_span = Math.min(parseInt(map[0][j].getAttribute('colspan'), 10) || 1, max_j - min_j + 1);
1247
+
1248
+ // Cell has colspan between min_j and max_j.
1249
+ /* j + 1 will never exceed the number of columns in a table.
1250
+ * A colspan is detected before the last column and all next cells on that row are skipped.
1251
+ */
1252
+ if (first_span > 1 && map[0][j] == map[0][j + 1]) {
1253
+
1254
+ // The value we should decrease colspan with.
1255
+ decrease = first_span - 1;
1256
+
1257
+ // Check all columns on the current row.
1258
+ // We found a colspan on the first row (i = 0), continue with second row (i = 1).
1259
+ for (i = 1; i < map.length; i++) {
1260
+
1261
+ // This cell has rowspan and has already been checked.
1262
+ if (map[i][j] == map[i - 1][j]) {
1263
+ continue;
1264
+ }
1265
+
1266
+ // Look for a colspan on the same columns.
1267
+ for (k = j; k < j + first_span; k++) {
1268
+ span = parseInt(map[i][k].getAttribute('colspan'), 10) || 1;
1269
+
1270
+ // There are other cells with colspan on this column.
1271
+ /* k + 1 will never exceed the number of columns in a table.
1272
+ * A colspan is detected before the last column and all next cells on that row are skipped.
1273
+ */
1274
+ if (span > 1 && map[i][k] == map[i][k + 1]) {
1275
+ decrease = Math.min(decrease, span - 1);
1276
+
1277
+ // Skip colspan.
1278
+ k += decrease;
1279
+ }
1280
+ else {
1281
+ decrease = Math.max (0, decrease - 1);
1282
+
1283
+ // Stop if decrease reaches 0.
1284
+ if (!decrease) {
1285
+ break;
1286
+ }
1287
+ }
1288
+ }
1289
+
1290
+ // Stop looking on the next columns if decrease reaches 0.
1291
+ if (!decrease) {
1292
+ break;
1293
+ }
1294
+ }
1295
+ }
1296
+ }
1297
+ }
1298
+
1299
+ // Update colspan attribute.
1300
+ if (decrease) {
1301
+ _decreaseCellSpan(map, decrease, 'colspan', 0, map.length - 1, min_j, max_j);
1302
+ }
1303
+ }
1304
+
1305
+ /*
1306
+ * Update or remove rowspan attribute.
1307
+ */
1308
+ function _updateRowspan (min_i, max_i, $table) {
1309
+ var i;
1310
+ var j;
1311
+ var k;
1312
+ var first_span;
1313
+ var span;
1314
+ var decrease = 0;
1315
+
1316
+ // Create a table map.
1317
+ var map = _tableMap($table);
1318
+
1319
+ // A row might have been deleted.
1320
+ max_i = Math.min(max_i, map.length - 1);
1321
+
1322
+ // If max_i and min_i are the same, then only one row is selected and rowspan is preserved.
1323
+ if (max_i > min_i) {
1324
+ // Find out how much we should decrease rowspan.
1325
+ // Parsing only the first column is enough.
1326
+ for (i = min_i; i <= max_i; i++) {
1327
+
1328
+ // This cell has rowspan and has already been checked.
1329
+ if (i > min_i && map[i][0] == map[i - 1][0]) {
1330
+ continue;
1331
+ }
1332
+
1333
+ // Current cell rowspan
1334
+ first_span = Math.min(parseInt(map[i][0].getAttribute('rowspan'), 10) || 1, max_i - min_i + 1);
1335
+
1336
+ // Cell has rowspan between min_i and max_i.
1337
+ /* i + 1 will never exceed the number of rows in a table.
1338
+ * A rowspan is detected before the last row and all next cells on that column are skipped.
1339
+ */
1340
+ if (first_span > 1 && map[i][0] == map[i + 1][0]) {
1341
+
1342
+ // The value we should decrease rowspan with.
1343
+ decrease = first_span - 1;
1344
+
1345
+ // Check all columns on the current row.
1346
+ // We found a rowspan on the first column (j = 0), continue with second column (j = 1).
1347
+ for (j = 1; j < map[0].length; j++) {
1348
+
1349
+ // This cell has colspan and has already been checked.
1350
+ if (map[i][j] == map[i][j - 1]) {
1351
+ continue;
1352
+ }
1353
+
1354
+ // Look for a rowspan on the same rows.
1355
+ for (k = i; k < i + first_span; k++) {
1356
+ span = parseInt(map[k][j].getAttribute('rowspan'), 10) || 1;
1357
+
1358
+ // There are other cells with rowspan on this row.
1359
+ /* k + 1 will never exceed the number of rows in a table.
1360
+ * A rowspan is detected before the last row and all next cells on that column are skipped.
1361
+ */
1362
+ if (span > 1 && map[k][j] == map[k + 1][j]) {
1363
+ decrease = Math.min(decrease, span - 1);
1364
+
1365
+ // Skip rowspan.
1366
+ k += decrease;
1367
+ }
1368
+ else {
1369
+ decrease = Math.max (0, decrease - 1);
1370
+
1371
+ // Stop if decrease reaches 0.
1372
+ if (!decrease) {
1373
+ break;
1374
+ }
1375
+ }
1376
+ }
1377
+
1378
+ // Stop looking on the next columns if decrease reaches 0.
1379
+ if (!decrease) {
1380
+ break;
1381
+ }
1382
+ }
1383
+ }
1384
+ }
1385
+ }
1386
+
1387
+ // Update rowspan attribute.
1388
+ if (decrease) {
1389
+ _decreaseCellSpan(map, decrease, 'rowspan', min_i, max_i, 0, map[0].length - 1);
1390
+ }
1391
+ }
1392
+
1393
+ /*
1394
+ * Decrease the colspan or rowspan with the amount specified.
1395
+ */
1396
+ function _decreaseCellSpan (map, decrease, span_type, min_i, max_i, min_j, max_j) {
1397
+
1398
+ // Update span attribute.
1399
+ var i;
1400
+ var j;
1401
+ var span;
1402
+
1403
+ // Go only through lines and columns that need to be updated.
1404
+ for (i = min_i; i <= max_i; i++) {
1405
+ for (j = min_j; j <= max_j; j++) {
1406
+
1407
+ // This cell has rowspan or colspan and has already been checked.
1408
+ if ((i > min_i && map[i][j] == map[i - 1][j]) || (j > min_j && map[i][j] == map[i][j - 1])) {
1409
+ continue;
1410
+ }
1411
+
1412
+ span = parseInt(map[i][j].getAttribute(span_type), 10) || 1;
1413
+
1414
+ // Update cell span.
1415
+ if (span > 1) {
1416
+ if (span - decrease > 1) map[i][j].setAttribute(span_type, span - decrease);
1417
+ else map[i][j].removeAttribute(span_type);
1418
+ }
1419
+ }
1420
+ }
1421
+ }
1422
+
1423
+ /*
1424
+ * Update or remove colspan and rowspan attributes.
1425
+ */
1426
+ function _updateCellSpan (min_i, max_i, min_j, max_j, $table) {
1427
+ _updateRowspan(min_i, max_i, $table);
1428
+ _updateColspan(min_j, max_j, $table);
1429
+ }
1430
+
1431
+ /*
1432
+ * Merge selected cells method.
1433
+ */
1434
+ function mergeCells () {
1435
+
1436
+ // We have more than one cell selected in a table. Cannot merge td and th.
1437
+ if (selectedCells().length > 1 && (editor.$el.find('th.fr-selected-cell').length === 0 || editor.$el.find('td.fr-selected-cell').length === 0)) {
1438
+
1439
+ _removeKeyboardSelectionHandlers();
1440
+
1441
+ // Create a table map.
1442
+ var map = _tableMap();
1443
+
1444
+ // Get selected cells.
1445
+ var selection = _currentSelection(map);
1446
+
1447
+ var i;
1448
+ var $cell;
1449
+ var cells = editor.$el.find('.fr-selected-cell');
1450
+ var $first_cell = $(cells[0]);
1451
+ var $first_row = $first_cell.parent();
1452
+ var first_row_cells = $first_row.find('.fr-selected-cell');
1453
+ var $current_table = $first_cell.closest('table');
1454
+ var content = $first_cell.html();
1455
+ var width = 0;
1456
+
1457
+ // Update cell width.
1458
+ for (i = 0; i < first_row_cells.length; i++) {
1459
+ width += $(first_row_cells[i]).outerWidth();
1460
+ }
1461
+
1462
+ $first_cell.css('width', (width / $current_table.outerWidth() * 100).toFixed(4) + '%');
1463
+
1464
+ // Set the colspan for the merged cells.
1465
+ if (selection.min_j < selection.max_j) {
1466
+ $first_cell.attr('colspan', selection.max_j - selection.min_j + 1);
1467
+ }
1468
+
1469
+ // Set the rowspan for the merged cells.
1470
+ if (selection.min_i < selection.max_i) {
1471
+ $first_cell.attr('rowspan', selection.max_i - selection.min_i + 1);
1472
+ }
1473
+
1474
+ // Go through all selected cells to merge their content.
1475
+ for (i = 1; i < cells.length; i++) {
1476
+ $cell = $(cells[i])
1477
+
1478
+ // If cell is empty, don't add only <br> tags.
1479
+ if ($cell.html() != '<br>' && $cell.html() !== '') {
1480
+ content += '<br>' + $cell.html();
1481
+ }
1482
+
1483
+ // Remove cell.
1484
+ $cell.remove();
1485
+ }
1486
+
1487
+ // Set the HTML content.
1488
+ $first_cell.html(content);
1489
+ editor.selection.setAtEnd($first_cell.get(0));
1490
+ editor.selection.restore();
1491
+
1492
+ // Enable toolbar.
1493
+ editor.toolbar.enable();
1494
+
1495
+ // Update rowspan before removing empty rows (otherwise table map is not correct).
1496
+ _updateRowspan(selection.min_i, selection.max_i, $current_table);
1497
+
1498
+ // Merge is done, check if we have empty trs to clean.
1499
+ var empty_trs = $current_table.find('tr:empty');
1500
+
1501
+ for (i = empty_trs.length - 1; i >= 0; i--) {
1502
+ $(empty_trs[i]).remove();
1503
+ }
1504
+
1505
+ // Update colspan after removing empty rows and updating rowspan.
1506
+ _updateColspan(selection.min_j, selection.max_j, $current_table);
1507
+
1508
+ // Reposition table edit popup.
1509
+ _showEditPopup();
1510
+ }
1511
+ }
1512
+
1513
+ /*
1514
+ * Split cell horizontally method.
1515
+ */
1516
+ function splitCellHorizontally () {
1517
+
1518
+ // We have only one cell selected in a table.
1519
+ if (selectedCells().length == 1) {
1520
+ var $selected_cell = editor.$el.find('.fr-selected-cell');
1521
+ var $current_row = $selected_cell.parent();
1522
+ var $current_table = $selected_cell.closest('table');
1523
+ var current_rowspan = parseInt($selected_cell.attr('rowspan'), 10);
1524
+
1525
+ // Create a table map.
1526
+ var map = _tableMap();
1527
+ var cell_origin = _cellOrigin($selected_cell.get(0), map);
1528
+
1529
+ // Create new td.
1530
+ var $new_td = $selected_cell.clone().html('<br>');
1531
+
1532
+ // Cell has rowspan.
1533
+ if (current_rowspan > 1) {
1534
+
1535
+ // Split current cell's rowspan.
1536
+ var new_rowspan = Math.ceil(current_rowspan / 2);
1537
+
1538
+ if (new_rowspan > 1) {
1539
+ $selected_cell.attr('rowspan', new_rowspan);
1540
+ }
1541
+ else {
1542
+ $selected_cell.removeAttr('rowspan');
1543
+ }
1544
+
1545
+ // Update new td's rowspan.
1546
+ if (current_rowspan - new_rowspan > 1) {
1547
+ $new_td.attr('rowspan', current_rowspan - new_rowspan);
1548
+ }
1549
+ else {
1550
+ $new_td.removeAttr('rowspan');
1551
+ }
1552
+
1553
+ // Find where we should insert the new td.
1554
+ var row = cell_origin.row + new_rowspan;
1555
+ var col = cell_origin.col === 0 ? cell_origin.col : cell_origin.col - 1;
1556
+
1557
+ // Go back until we find a td on this row. We might have colspans and rowspans.
1558
+ while (col >= 0 && (map[row][col] == map[row][col - 1] || (row > 0 && map[row][col] == map[row - 1][col]))) {
1559
+ col--;
1560
+ }
1561
+
1562
+ if (col == -1) {
1563
+
1564
+ // We couldn't find a previous td on this row. Prepend the new td.
1565
+ $($current_table.find('tr').not($current_table.find('table tr')).get(row)).prepend($new_td);
1566
+
1567
+ }
1568
+ else {
1569
+ $(map[row][col]).after($new_td);
1570
+ }
1571
+
1572
+ }
1573
+ else {
1574
+
1575
+ // Add new row bellow with only one cell.
1576
+ var $row = $('<tr>').append($new_td);
1577
+ var i;
1578
+
1579
+ // Increase rowspan for all cells on the current row.
1580
+ for (i = 0; i < map[0].length; i++) {
1581
+
1582
+ // Don't do this twice if we have a colspan.
1583
+ if (i === 0 || map[cell_origin.row][i] != map[cell_origin.row][i - 1]) {
1584
+ var $cell = $(map[cell_origin.row][i]);
1585
+
1586
+ if (!$cell.is($selected_cell)) {
1587
+ $cell.attr('rowspan', (parseInt($cell.attr('rowspan'), 10) || 1) + 1);
1588
+ }
1589
+ }
1590
+ }
1591
+
1592
+ $current_row.after($row);
1593
+ }
1594
+
1595
+ // Remove selection
1596
+ _removeSelection();
1597
+
1598
+ // Hide table edit popup.
1599
+ editor.popups.hide('table.edit');
1600
+ }
1601
+ }
1602
+
1603
+ /*
1604
+ * Split cell vertically method.
1605
+ */
1606
+ function splitCellVertically () {
1607
+
1608
+ // We have only one cell selected in a table.
1609
+ if (selectedCells().length == 1) {
1610
+ var $selected_cell = editor.$el.find('.fr-selected-cell');
1611
+ var current_colspan = parseInt($selected_cell.attr('colspan'), 10) || 1;
1612
+ var parent_width = $selected_cell.parent().outerWidth();
1613
+ var width = $selected_cell.outerWidth();
1614
+
1615
+ // Create new td.
1616
+ var $new_td = $selected_cell.clone().html('<br>');
1617
+
1618
+ // Create a table map.
1619
+ var map = _tableMap();
1620
+ var cell_origin = _cellOrigin($selected_cell.get(0), map);
1621
+
1622
+ if (current_colspan > 1) {
1623
+
1624
+ // Split current colspan.
1625
+ var new_colspan = Math.ceil(current_colspan / 2);
1626
+
1627
+ width = _columnsWidth(cell_origin.col, cell_origin.col + new_colspan - 1, map) / parent_width * 100;
1628
+ var new_width = _columnsWidth(cell_origin.col + new_colspan, cell_origin.col + current_colspan - 1, map) / parent_width * 100;
1629
+
1630
+ // Update colspan for current cell.
1631
+ if (new_colspan > 1) {
1632
+ $selected_cell.attr('colspan', new_colspan);
1633
+ }
1634
+ else {
1635
+ $selected_cell.removeAttr('colspan');
1636
+ }
1637
+
1638
+ // Update new td's colspan.
1639
+ if (current_colspan - new_colspan > 1) {
1640
+ $new_td.attr('colspan', current_colspan - new_colspan);
1641
+ }
1642
+ else {
1643
+ $new_td.removeAttr('colspan');
1644
+ }
1645
+
1646
+ // Update cell width.
1647
+ $selected_cell.css('width', width.toFixed(4) + '%');
1648
+ $new_td.css('width', new_width.toFixed(4) + '%');
1649
+
1650
+ // Increase colspan for all cells on the current column.
1651
+ }
1652
+ else {
1653
+ var i;
1654
+
1655
+ for (i = 0; i < map.length; i++) {
1656
+
1657
+ // Don't do this twice if we have a rowspan.
1658
+ if (i === 0 || map[i][cell_origin.col] != map[i - 1][cell_origin.col]) {
1659
+ var $cell = $(map[i][cell_origin.col]);
1660
+
1661
+ if (!$cell.is($selected_cell)) {
1662
+ var colspan = (parseInt($cell.attr('colspan'), 10) || 1) + 1;
1663
+ $cell.attr('colspan', colspan);
1664
+ }
1665
+ }
1666
+ }
1667
+
1668
+ // Update cell width.
1669
+ width = width / parent_width * 100 / 2;
1670
+
1671
+ $selected_cell.css('width', width.toFixed(4) + '%');
1672
+ $new_td.css('width', width.toFixed(4) + '%');
1673
+ }
1674
+
1675
+ // Add a new td after the current one.
1676
+ $selected_cell.after($new_td);
1677
+
1678
+ // Remove selection
1679
+ _removeSelection();
1680
+
1681
+ // Hide table edit popup.
1682
+ editor.popups.hide('table.edit');
1683
+ }
1684
+ }
1685
+
1686
+ /*
1687
+ * Set background color to selected cells.
1688
+ */
1689
+ function setBackground (color) {
1690
+ var $selected_cells = editor.$el.find('.fr-selected-cell');
1691
+
1692
+ // Set background color.
1693
+ if (color != 'REMOVE') {
1694
+ $selected_cells.css('background-color', editor.helpers.HEXtoRGB(color));
1695
+ }
1696
+
1697
+ // Remove background color.
1698
+ else {
1699
+ $selected_cells.css('background-color', '');
1700
+ }
1701
+
1702
+ _showEditPopup();
1703
+ }
1704
+
1705
+ /*
1706
+ * Set vertical align to selected cells.
1707
+ */
1708
+ function verticalAlign (val) {
1709
+ editor.$el.find('.fr-selected-cell').css('vertical-align', val);
1710
+ }
1711
+
1712
+ /*
1713
+ * Apply horizontal alignment to selected cells.
1714
+ */
1715
+ function horizontalAlign (val) {
1716
+ editor.$el.find('.fr-selected-cell').css('text-align', val);
1717
+ }
1718
+
1719
+ /**
1720
+ * Apply specific style to selected table or selected cells.
1721
+ * val class to apply.
1722
+ * obj table or selected cells.
1723
+ * multiple_styles editor.opts.tableStyles or editor.opts.tableCellStyles.
1724
+ * style editor.opts.tableStyles or editor.opts.tableCellStyles
1725
+ */
1726
+ function applyStyle (val, obj, multiple_styles, styles) {
1727
+ if (obj.length > 0) {
1728
+
1729
+ // Remove multiple styles.
1730
+ if (!multiple_styles) {
1731
+ var classes = Object.keys(styles);
1732
+ classes.splice(classes.indexOf(val), 1);
1733
+ obj.removeClass(classes.join(' '));
1734
+ }
1735
+
1736
+ obj.toggleClass(val);
1737
+ }
1738
+ }
1739
+
1740
+ /*
1741
+ * Create a table map.
1742
+ */
1743
+ function _tableMap ($table) {
1744
+ $table = $table || null;
1745
+
1746
+ var map = [];
1747
+
1748
+ if ($table == null && selectedCells().length > 0) {
1749
+ $table = selectedTable();
1750
+ }
1751
+
1752
+ if ($table) {
1753
+ $table.find('tr').not($table.find('table tr')).each (function (row, tr) {
1754
+ var $tr = $(tr);
1755
+
1756
+ var c_index = 0;
1757
+ $tr.find('> th, > td').each (function (col, td) {
1758
+ var $td = $(td);
1759
+ var cspan = parseInt($td.attr('colspan'), 10) || 1;
1760
+ var rspan = parseInt($td.attr('rowspan'), 10) || 1;
1761
+
1762
+ for (var i = row; i < row + rspan; i++) {
1763
+ for (var j = c_index; j < c_index + cspan; j++) {
1764
+ if (!map[i]) map[i] = [];
1765
+
1766
+ if (!map[i][j]) {
1767
+ map[i][j] = td;
1768
+ }
1769
+ else {
1770
+ c_index++;
1771
+ }
1772
+ }
1773
+ }
1774
+
1775
+ c_index += cspan;
1776
+ })
1777
+ });
1778
+ }
1779
+
1780
+ return map;
1781
+ }
1782
+
1783
+ /*
1784
+ * Get the i, j coordinates of a cell in the table map.
1785
+ * These are the coordinates where the cell starts.
1786
+ */
1787
+ function _cellOrigin (td, map) {
1788
+ for (var i = 0; i < map.length; i++) {
1789
+ for (var j = 0; j < map[i].length; j++) {
1790
+ if (map[i][j] == td) {
1791
+ return {
1792
+ row: i,
1793
+ col: j
1794
+ };
1795
+ }
1796
+ }
1797
+ }
1798
+ }
1799
+
1800
+ /*
1801
+ * Get the i, j coordinates where a cell ends in the table map.
1802
+ */
1803
+ function _cellEnds (origin_i, origin_j, map) {
1804
+ var max_i = origin_i + 1;
1805
+ var max_j = origin_j + 1;
1806
+
1807
+ // Compute max_i
1808
+ while (max_i < map.length) {
1809
+ if (map[max_i][origin_j] != map[origin_i][origin_j]) {
1810
+ max_i--;
1811
+ break;
1812
+ }
1813
+
1814
+ max_i++;
1815
+ }
1816
+
1817
+ if (max_i == map.length) {
1818
+ max_i--;
1819
+ }
1820
+
1821
+ // Compute max_j
1822
+ while (max_j < map[origin_i].length) {
1823
+ if (map[origin_i][max_j] != map[origin_i][origin_j]) {
1824
+ max_j--;
1825
+ break;
1826
+ }
1827
+
1828
+ max_j++;
1829
+ }
1830
+
1831
+ if (max_j == map[origin_i].length) {
1832
+ max_j--;
1833
+ }
1834
+
1835
+ return {
1836
+ row: max_i,
1837
+ col: max_j
1838
+ };
1839
+ }
1840
+
1841
+ /*
1842
+ * Remove handler classes that are used to select table cells with keyboard.
1843
+ */
1844
+ function _removeKeyboardSelectionHandlers () {
1845
+ if (editor.el.querySelector('.fr-cell-fixed')) {
1846
+ editor.el.querySelector('.fr-cell-fixed').classList.remove('fr-cell-fixed');
1847
+ }
1848
+
1849
+ if (editor.el.querySelector('.fr-cell-handler')) {
1850
+ editor.el.querySelector('.fr-cell-handler').classList.remove('fr-cell-handler');
1851
+ }
1852
+ }
1853
+
1854
+ /*
1855
+ * Remove selection from cells.
1856
+ */
1857
+ function _removeSelection () {
1858
+ var cells = editor.$el.find('.fr-selected-cell');
1859
+
1860
+ if (cells.length > 0) {
1861
+
1862
+ // Remove selection.
1863
+ cells.each (function () {
1864
+ var $cell = $(this);
1865
+
1866
+ $cell.removeClass('fr-selected-cell');
1867
+
1868
+ if ($cell.attr('class') === '') {
1869
+ $cell.removeAttr('class');
1870
+ }
1871
+ });
1872
+ }
1873
+
1874
+ _removeKeyboardSelectionHandlers();
1875
+ }
1876
+
1877
+ /*
1878
+ * Clear selection to prevent Firefox cell selection.
1879
+ */
1880
+ function _clearSelection () {
1881
+ editor.events.disableBlur();
1882
+ editor.selection.clear();
1883
+
1884
+ // Prevent text selection while selecting multiple cells.
1885
+ // Happens in Chrome.
1886
+ editor.$el.addClass('fr-no-selection');
1887
+
1888
+ // Cursor will not appear if we don't make blur.
1889
+ editor.$el.blur();
1890
+ editor.events.enableBlur();
1891
+ }
1892
+
1893
+ /*
1894
+ * Get current selected cells coordintates.
1895
+ */
1896
+ function _currentSelection (map) {
1897
+ var cells = editor.$el.find('.fr-selected-cell');
1898
+
1899
+ if (cells.length > 0) {
1900
+ var min_i = map.length;
1901
+ var max_i = 0;
1902
+ var min_j = map[0].length;
1903
+ var max_j = 0;
1904
+ var i;
1905
+
1906
+ for (i = 0; i < cells.length; i++) {
1907
+ var cellOrigin = _cellOrigin(cells[i], map);
1908
+ var cellEnd = _cellEnds(cellOrigin.row, cellOrigin.col, map);
1909
+
1910
+ min_i = Math.min(cellOrigin.row, min_i);
1911
+ max_i = Math.max(cellEnd.row, max_i);
1912
+ min_j = Math.min(cellOrigin.col, min_j);
1913
+ max_j = Math.max(cellEnd.col, max_j);
1914
+ }
1915
+
1916
+ return {
1917
+ min_i: min_i,
1918
+ max_i: max_i,
1919
+ min_j: min_j,
1920
+ max_j: max_j
1921
+ };
1922
+ }
1923
+ else {
1924
+ return null;
1925
+ }
1926
+ }
1927
+
1928
+ /*
1929
+ * Minimum and maximum coordinates for the selection in the table map.
1930
+ */
1931
+ function _selectionLimits (min_i, max_i, min_j, max_j, map) {
1932
+ var first_i = min_i;
1933
+ var last_i = max_i;
1934
+ var first_j = min_j;
1935
+ var last_j = max_j;
1936
+ var i;
1937
+ var j;
1938
+ var cellOrigin;
1939
+ var cellEnd;
1940
+
1941
+ // Go through first and last columns.
1942
+ for (i = first_i; i <= last_i; i++) {
1943
+
1944
+ // First column.
1945
+ if ((parseInt($(map[i][first_j]).attr('rowspan'), 10) || 1) > 1 ||
1946
+ (parseInt($(map[i][first_j]).attr('colspan'), 10) || 1) > 1) {
1947
+ cellOrigin = _cellOrigin(map[i][first_j], map);
1948
+ cellEnd = _cellEnds(cellOrigin.row, cellOrigin.col, map);
1949
+
1950
+ first_i = Math.min(cellOrigin.row, first_i);
1951
+ last_i = Math.max(cellEnd.row, last_i);
1952
+ first_j = Math.min(cellOrigin.col, first_j);
1953
+ last_j = Math.max(cellEnd.col, last_j);
1954
+ }
1955
+
1956
+ // Last column.
1957
+ if ((parseInt($(map[i][last_j]).attr('rowspan'), 10) || 1) > 1 ||
1958
+ (parseInt($(map[i][last_j]).attr('colspan'), 10) || 1) > 1) {
1959
+ cellOrigin = _cellOrigin(map[i][last_j], map);
1960
+ cellEnd = _cellEnds(cellOrigin.row, cellOrigin.col, map);
1961
+
1962
+ first_i = Math.min(cellOrigin.row, first_i);
1963
+ last_i = Math.max(cellEnd.row, last_i);
1964
+ first_j = Math.min(cellOrigin.col, first_j);
1965
+ last_j = Math.max(cellEnd.col, last_j);
1966
+ }
1967
+ }
1968
+
1969
+ // Go through first and last rows.
1970
+ for (j = first_j; j <= last_j; j++) {
1971
+
1972
+ // First row.
1973
+ if ((parseInt($(map[first_i][j]).attr('rowspan'), 10) || 1) > 1 ||
1974
+ (parseInt($(map[first_i][j]).attr('colspan'), 10) || 1) > 1) {
1975
+ cellOrigin = _cellOrigin(map[first_i][j], map);
1976
+ cellEnd = _cellEnds(cellOrigin.row, cellOrigin.col, map);
1977
+
1978
+ first_i = Math.min(cellOrigin.row, first_i);
1979
+ last_i = Math.max(cellEnd.row, last_i);
1980
+ first_j = Math.min(cellOrigin.col, first_j);
1981
+ last_j = Math.max(cellEnd.col, last_j);
1982
+ }
1983
+
1984
+ // Last column.
1985
+ if ((parseInt($(map[last_i][j]).attr('rowspan'), 10) || 1) > 1 ||
1986
+ (parseInt($(map[last_i][j]).attr('colspan'), 10) || 1) > 1) {
1987
+ cellOrigin = _cellOrigin(map[last_i][j], map);
1988
+ cellEnd = _cellEnds(cellOrigin.row, cellOrigin.col, map);
1989
+
1990
+ first_i = Math.min(cellOrigin.row, first_i);
1991
+ last_i = Math.max(cellEnd.row, last_i);
1992
+ first_j = Math.min(cellOrigin.col, first_j);
1993
+ last_j = Math.max(cellEnd.col, last_j);
1994
+ }
1995
+ }
1996
+
1997
+ if (first_i == min_i && last_i == max_i && first_j == min_j && last_j == max_j) {
1998
+ return {
1999
+ min_i: min_i,
2000
+ max_i: max_i,
2001
+ min_j: min_j,
2002
+ max_j: max_j
2003
+ };
2004
+
2005
+ }
2006
+ else {
2007
+ return _selectionLimits(first_i, last_i, first_j, last_j, map);
2008
+ }
2009
+ }
2010
+
2011
+ /*
2012
+ * Get the left and right offset position for the current selection.
2013
+ */
2014
+ function _selectionOffset (map) {
2015
+ var selection = _currentSelection(map);
2016
+
2017
+ // Top left cell.
2018
+ var $tl = $(map[selection.min_i][selection.min_j]);
2019
+
2020
+ // Top right cell.
2021
+ var $tr = $(map[selection.min_i][selection.max_j]);
2022
+
2023
+ // Bottom left cell.
2024
+ var $bl = $(map[selection.max_i][selection.min_j]);
2025
+
2026
+ var left = $tl.offset().left
2027
+ var right = $tr.offset().left + $tr.outerWidth();
2028
+ var top = $tl.offset().top;
2029
+ var bottom = $bl.offset().top + $bl.outerHeight();
2030
+
2031
+ return {
2032
+ left: left,
2033
+ right: right,
2034
+ top: top,
2035
+ bottom: bottom
2036
+ };
2037
+ }
2038
+
2039
+ /*
2040
+ * Select table cells.
2041
+ * firstCell is either the top left corner or the fr-cell-fixed corner of the selection.
2042
+ * lastCell is either the bottom right corner ot the fr-cell-handler of the selection.
2043
+ */
2044
+ function _selectCells (firstCell, lastCell) {
2045
+
2046
+ // If the first and last cells are the same then just select it.
2047
+ if ($(firstCell).is(lastCell)) {
2048
+
2049
+ // Remove previous selection.
2050
+ _removeSelection();
2051
+
2052
+ $(firstCell).addClass('fr-selected-cell');
2053
+ }
2054
+
2055
+ // Select multiple cells.
2056
+ else {
2057
+ // Prevent Firefox cell selection.
2058
+ _clearSelection();
2059
+
2060
+ // Turn editor toolbar off.
2061
+ editor.edit.off();
2062
+
2063
+ // Create a table map.
2064
+ var map = _tableMap();
2065
+
2066
+ // Get first and last cell's i and j map coordinates.
2067
+ var firstCellOrigin = _cellOrigin(firstCell, map);
2068
+ var lastCellOrigin = _cellOrigin(lastCell, map);
2069
+
2070
+ // Some cells between these coordinates might have colspan or rowspan.
2071
+ // The selected area exceeds first and last cells' coordinates.
2072
+ var limits = _selectionLimits(Math.min(firstCellOrigin.row, lastCellOrigin.row),
2073
+ Math.max(firstCellOrigin.row, lastCellOrigin.row),
2074
+ Math.min(firstCellOrigin.col, lastCellOrigin.col),
2075
+ Math.max(firstCellOrigin.col, lastCellOrigin.col),
2076
+ map);
2077
+
2078
+ // Remove previous selection.
2079
+ _removeSelection();
2080
+
2081
+ // We always need to set the selection handler classes as user may use keyboard to select at anytime.
2082
+ firstCell.classList.add('fr-cell-fixed');
2083
+ lastCell.classList.add('fr-cell-handler');
2084
+
2085
+ // Select all cells between the first and last cell.
2086
+ for (var i = limits.min_i; i <= limits.max_i; i++) {
2087
+ for (var j = limits.min_j; j <= limits.max_j; j++) {
2088
+ $(map[i][j]).addClass('fr-selected-cell');
2089
+ }
2090
+ }
2091
+ }
2092
+ }
2093
+
2094
+ /*
2095
+ * Get the cell under the mouse cursor.
2096
+ */
2097
+ function _getCellUnder (e) {
2098
+ var cell = null;
2099
+ var $target = $(e.target);
2100
+
2101
+ if (e.target.tagName == 'TD' || e.target.tagName == 'TH') {
2102
+ cell = e.target;
2103
+ }
2104
+ else if ($target.closest('td').length > 0) {
2105
+ cell = $target.closest('td').get(0);
2106
+ }
2107
+ else if ($target.closest('th').length > 0) {
2108
+ cell = $target.closest('th').get(0);
2109
+ }
2110
+
2111
+ // Cell should reside inside editor.
2112
+ if (editor.$el.find(cell).length === 0) return null;
2113
+
2114
+ return cell;
2115
+ }
2116
+
2117
+ /*
2118
+ * Stop table cell editing and allow text editing.
2119
+ */
2120
+ function _stopEdit () {
2121
+
2122
+ // Clear previous selection.
2123
+ _removeSelection();
2124
+
2125
+ // Hide table edit popup.
2126
+ editor.popups.hide('table.edit');
2127
+ }
2128
+
2129
+ /*
2130
+ * Mark that mouse is down.
2131
+ */
2132
+ function _mouseDown (e) {
2133
+ var cell = _getCellUnder(e);
2134
+
2135
+ if ($(cell).parents('[contenteditable]:not(.fr-element):not(.fr-img-caption):not(body):first').attr('contenteditable') == 'false') return true;
2136
+
2137
+ // Stop table editing if user clicks outside the table.
2138
+ if (selectedCells().length > 0 && !cell) {
2139
+ _stopEdit();
2140
+ }
2141
+
2142
+ // Only do mouseDown if the editor is not disabled by user.
2143
+ if (!editor.edit.isDisabled() || editor.popups.isVisible('table.edit')) {
2144
+
2145
+ // On left click.
2146
+ if (e.which == 1 && !(e.which == 1 && editor.helpers.isMac() && e.ctrlKey)) {
2147
+ mouseDownFlag = true;
2148
+
2149
+ // User clicked on a table cell.
2150
+ if (cell) {
2151
+
2152
+ // We always have to clear previous selection except when using shift key to select multiple cells.
2153
+ if (selectedCells().length > 0 && !e.shiftKey) {
2154
+ _stopEdit();
2155
+ }
2156
+
2157
+ e.stopPropagation();
2158
+
2159
+ editor.events.trigger('image.hideResizer');
2160
+ editor.events.trigger('video.hideResizer');
2161
+
2162
+ // Keep record of left mouse click being down
2163
+ mouseDownCellFlag = true;
2164
+
2165
+ var tag_name = cell.tagName.toLowerCase();
2166
+
2167
+ // Select multiple cells using Shift key
2168
+ if (e.shiftKey && editor.$el.find(tag_name + '.fr-selected-cell').length > 0) {
2169
+
2170
+ // Cells must be in the same table.
2171
+ if ($(editor.$el.find(tag_name + '.fr-selected-cell').closest('table')).is($(cell).closest('table'))) {
2172
+
2173
+ // Select cells between.
2174
+ _selectCells(mouseDownCell, cell);
2175
+
2176
+ // Do nothing if cells are not in the same table.
2177
+ }
2178
+ else {
2179
+
2180
+ // Prevent Firefox selection.
2181
+ _clearSelection();
2182
+ }
2183
+ }
2184
+
2185
+ else {
2186
+
2187
+ // Prevent Firefox selection for ctrl / cmd key.
2188
+ // https://github.com/froala/wysiwyg-editor/issues/1323:
2189
+ // - we have more than one cell selected or
2190
+ // - selection is starting in another cell than the one we clicked on.
2191
+ if ((editor.keys.ctrlKey(e) || e.shiftKey) && (selectedCells().length > 1 || ($(cell).find(editor.selection.element()).length === 0 && !$(cell).is(editor.selection.element())))) {
2192
+ _clearSelection();
2193
+ }
2194
+
2195
+ // Save cell where mouse has been clicked
2196
+ mouseDownCell = cell;
2197
+
2198
+ // Select cell.
2199
+ _selectCells(mouseDownCell, mouseDownCell);
2200
+ }
2201
+ }
2202
+ }
2203
+
2204
+ // On right click stop table editing.
2205
+ else if ((e.which == 3 || (e.which == 1 && editor.helpers.isMac() && e.ctrlKey)) && cell) {
2206
+ _stopEdit();
2207
+ }
2208
+ }
2209
+ }
2210
+
2211
+ /*
2212
+ * Notify that mouse is no longer pressed.
2213
+ */
2214
+ function _mouseUp (e) {
2215
+ // User clicked somewhere else in the editor (except the toolbar).
2216
+ // We need this because mouse down is not triggered outside the editor.
2217
+ if (!mouseDownCellFlag && !editor.$tb.is(e.target) && !editor.$tb.is($(e.target).closest(editor.$tb.get(0)))) {
2218
+ if (selectedCells().length > 0) {
2219
+ editor.toolbar.enable();
2220
+ }
2221
+
2222
+ _removeSelection();
2223
+ }
2224
+
2225
+ // On left click.
2226
+ if (e.which == 1 && !(e.which == 1 && editor.helpers.isMac() && e.ctrlKey)) {
2227
+ mouseDownFlag = false;
2228
+
2229
+ // Mouse down was in a table cell.
2230
+ if (mouseDownCellFlag) {
2231
+
2232
+ // Left click is no longer pressed.
2233
+ mouseDownCellFlag = false;
2234
+
2235
+ var cell = _getCellUnder(e);
2236
+
2237
+ // If we have one selected cell and mouse is lifted somewhere else.
2238
+ if (!cell && selectedCells().length == 1) {
2239
+
2240
+ // We have a text selection and not cell selection.
2241
+ _removeSelection();
2242
+ }
2243
+
2244
+ // If there are selected cells then show table edit popup.
2245
+ else if (selectedCells().length > 0) {
2246
+ if (editor.selection.isCollapsed()) {
2247
+ _showEditPopup();
2248
+ }
2249
+
2250
+ // No text selection.
2251
+ else {
2252
+ _removeSelection();
2253
+ }
2254
+ }
2255
+ }
2256
+
2257
+ // Resizing stops.
2258
+ if (resizingFlag) {
2259
+ resizingFlag = false;
2260
+
2261
+ $resizer.removeClass('fr-moving');
2262
+
2263
+ // Allow text selection.
2264
+ editor.$el.removeClass('fr-no-selection');
2265
+ editor.edit.on();
2266
+
2267
+ // Set release Y coordinate.
2268
+ var left = parseFloat($resizer.css('left')) + editor.opts.tableResizerOffset + editor.$wp.offset().left;
2269
+
2270
+ if (editor.opts.iframe) {
2271
+ left -= editor.$iframe.offset().left;
2272
+ }
2273
+ $resizer.data('release-position', left);
2274
+
2275
+ // Clear resizing limits.
2276
+ $resizer.removeData('max-left');
2277
+ $resizer.removeData('max-right');
2278
+
2279
+ // Resize.
2280
+ _resize(e);
2281
+
2282
+ // Hide resizer.
2283
+ _hideResizer();
2284
+ }
2285
+ }
2286
+ }
2287
+
2288
+ /*
2289
+ * User drags mouse over multiple cells to select them.
2290
+ */
2291
+ function _mouseEnter (e) {
2292
+ if (mouseDownCellFlag === true) {
2293
+ var $cell = $(e.currentTarget);
2294
+
2295
+ // Cells should be in the same table.
2296
+ if ($cell.closest('table').is(selectedTable())) {
2297
+
2298
+ // Don't select both ths and tds.
2299
+ if (e.currentTarget.tagName == 'TD' && editor.$el.find('th.fr-selected-cell').length === 0) {
2300
+
2301
+ // Select cells between.
2302
+ _selectCells(mouseDownCell, e.currentTarget);
2303
+
2304
+ return;
2305
+ }
2306
+
2307
+ else if (e.currentTarget.tagName == 'TH' && editor.$el.find('td.fr-selected-cell').length === 0) {
2308
+
2309
+ // Select cells between.
2310
+ _selectCells(mouseDownCell, e.currentTarget);
2311
+
2312
+ return;
2313
+ }
2314
+ }
2315
+
2316
+ // Prevent firefox selection.
2317
+ _clearSelection();
2318
+ }
2319
+ }
2320
+
2321
+ /*
2322
+ * Move cursor in a nested table.
2323
+ */
2324
+ function _moveInNestedTable (cell, direction) {
2325
+ var table = cell;
2326
+
2327
+ // Get parent table (editor might be initialized inside cell).
2328
+ while (table && table.tagName != 'TABLE' && table.parentNode != editor.el) {
2329
+ table = table.parentNode;
2330
+ }
2331
+
2332
+ if (table && table.tagName == 'TABLE') {
2333
+ var new_map = _tableMap($(table));
2334
+
2335
+ // Move up in the parent table.
2336
+ if (direction == 'up') _moveUp(_cellOrigin(cell, new_map), table, new_map);
2337
+ else if (direction == 'down') _moveDown(_cellOrigin(cell, new_map), table, new_map);
2338
+ }
2339
+ }
2340
+
2341
+ /*
2342
+ * Move cursor up or down outside table.
2343
+ */
2344
+ function _moveWithArrows (origin, table, map, direction) {
2345
+ var up = table;
2346
+ var sibling;
2347
+
2348
+ // Look up in DOM for the previous or next element.
2349
+ while (up != editor.el) {
2350
+
2351
+ // Nested table.
2352
+ if (up.tagName == 'TD' || up.tagName == 'TH') {
2353
+ break;
2354
+ }
2355
+
2356
+ // The table has a sibling element.
2357
+ if (direction == 'up') sibling = up.previousElementSibling;
2358
+ else if (direction == 'down') sibling = up.nextElementSibling;
2359
+
2360
+ if (sibling) {
2361
+ break;
2362
+ }
2363
+
2364
+ // Table might be in a block tag.
2365
+ up = up.parentNode;
2366
+ }
2367
+
2368
+ // We have another table (nested).
2369
+ if (up.tagName == 'TD' || up.tagName == 'TH') {
2370
+ _moveInNestedTable(up, direction);
2371
+ }
2372
+
2373
+ // Table has a sibling.
2374
+ else if (sibling) {
2375
+ if (direction == 'up') editor.selection.setAtEnd(sibling);
2376
+
2377
+ if (direction == 'down') editor.selection.setAtStart(sibling);
2378
+ }
2379
+ }
2380
+
2381
+ /*
2382
+ * Move cursor up while in table cell.
2383
+ */
2384
+ function _moveUp (origin, table, map) {
2385
+
2386
+ // Not the first line.
2387
+ if (origin.row > 0) {
2388
+ editor.selection.setAtEnd(map[origin.row - 1][origin.col])
2389
+ }
2390
+
2391
+ // First line.
2392
+ else {
2393
+ _moveWithArrows(origin, table, map, 'up');
2394
+ }
2395
+ }
2396
+
2397
+ /*
2398
+ * Move cursor down while in table cell.
2399
+ */
2400
+ function _moveDown (origin, table, map) {
2401
+
2402
+ // Cell might have rowspan.
2403
+ var row = parseInt(map[origin.row][origin.col].getAttribute('rowspan'), 10) || 1;
2404
+
2405
+ // Not the last line.
2406
+ if (origin.row < map.length - row) {
2407
+ editor.selection.setAtStart(map[origin.row + row][origin.col]);
2408
+ }
2409
+
2410
+ // Last line.
2411
+ else {
2412
+ _moveWithArrows(origin, table, map, 'down');
2413
+ }
2414
+ }
2415
+
2416
+ /*
2417
+ * Using the arrow keys to move the cursor through the table will not select cells.
2418
+ */
2419
+ function _navigateWithArrows (e) {
2420
+ var key_code = e.which;
2421
+
2422
+ // Get current selection.
2423
+ var sel = editor.selection.blocks();
2424
+
2425
+ if (sel.length) {
2426
+ sel = sel[0];
2427
+
2428
+ // Selection should be in a table cell.
2429
+ if (sel.tagName == 'TD' || sel.tagName == 'TH') {
2430
+ var table = sel;
2431
+
2432
+ // Get parent table (editor might be initialized inside cell).
2433
+ while (table && table.tagName != 'TABLE' && table.parentNode != editor.el) {
2434
+ table = table.parentNode;
2435
+ }
2436
+
2437
+ if (table && table.tagName == 'TABLE') {
2438
+ if ($.FE.KEYCODE.ARROW_LEFT == key_code || $.FE.KEYCODE.ARROW_UP == key_code || $.FE.KEYCODE.ARROW_RIGHT == key_code || $.FE.KEYCODE.ARROW_DOWN == key_code) {
2439
+ if (selectedCells().length > 0) {
2440
+ _stopEdit();
2441
+ }
2442
+
2443
+ // Up and down in Webkit.
2444
+ if (editor.browser.webkit && ($.FE.KEYCODE.ARROW_UP == key_code || $.FE.KEYCODE.ARROW_DOWN == key_code)) {
2445
+ var node = editor.selection.ranges(0).startContainer;
2446
+
2447
+ if (node.nodeType == Node.TEXT_NODE && (($.FE.KEYCODE.ARROW_UP == key_code && node.previousSibling) || ($.FE.KEYCODE.ARROW_DOWN == key_code && node.nextSibling))) {
2448
+ return;
2449
+ }
2450
+
2451
+ e.preventDefault();
2452
+ e.stopPropagation();
2453
+
2454
+ // Table map.
2455
+ var map = _tableMap($(table));
2456
+
2457
+ // Current cell map coordinates.
2458
+ var origin = _cellOrigin(sel, map);
2459
+
2460
+ // Arrow up
2461
+ if ($.FE.KEYCODE.ARROW_UP == key_code) {
2462
+ _moveUp(origin, table, map);
2463
+ }
2464
+
2465
+ // Arrow down
2466
+ else if ($.FE.KEYCODE.ARROW_DOWN == key_code) {
2467
+ _moveDown(origin, table, map);
2468
+ }
2469
+
2470
+ // Update cursor position.
2471
+ editor.selection.restore();
2472
+
2473
+ return false;
2474
+ }
2475
+ }
2476
+ }
2477
+ }
2478
+ }
2479
+ }
2480
+
2481
+ /*
2482
+ * Initilize table resizer.
2483
+ */
2484
+ function _initResizer () {
2485
+
2486
+ // Append resizer HTML to editor wrapper.
2487
+ if (!editor.shared.$table_resizer) editor.shared.$table_resizer = $('<div class="fr-table-resizer"><div></div></div>');
2488
+ $resizer = editor.shared.$table_resizer;
2489
+
2490
+ // Resize table. Mousedown.
2491
+ editor.events.$on($resizer, 'mousedown', function (e) {
2492
+ if (!editor.core.sameInstance($resizer)) return true;
2493
+
2494
+ // Stop table editing.
2495
+ if (selectedCells().length > 0) {
2496
+ _stopEdit();
2497
+ }
2498
+
2499
+ // Resize table only using left click.
2500
+ if (e.which == 1) {
2501
+
2502
+ // Save selection so that we can put cursor back at the end.
2503
+ editor.selection.save();
2504
+
2505
+ resizingFlag = true;
2506
+
2507
+ $resizer.addClass('fr-moving');
2508
+
2509
+ // Prevent text selection while dragging the table resizer.
2510
+ _clearSelection();
2511
+
2512
+ // Turn editor toolbar off while resizing.
2513
+ editor.edit.off();
2514
+
2515
+ // Show resizer.
2516
+ $resizer.find('div').css('opacity', 1);
2517
+
2518
+ // Prevent selecting text when doing resize.
2519
+ return false;
2520
+ }
2521
+ });
2522
+
2523
+ // Mousemove on table resizer.
2524
+ editor.events.$on($resizer, 'mousemove', function (e) {
2525
+ if (!editor.core.sameInstance($resizer)) return true;
2526
+
2527
+ if (resizingFlag) {
2528
+ if (editor.opts.iframe) {
2529
+ e.pageX -= editor.$iframe.offset().left;
2530
+ }
2531
+
2532
+ _mouseMove(e);
2533
+ }
2534
+ })
2535
+
2536
+ // Editor destroy.
2537
+ editor.events.on('shared.destroy', function () {
2538
+ $resizer.html('').removeData().remove();
2539
+ $resizer = null;
2540
+ }, true);
2541
+
2542
+ editor.events.on('destroy', function () {
2543
+ editor.$el.find('.fr-selected-cell').removeClass('fr-selected-cell');
2544
+ $resizer.hide().appendTo($('body:first'));
2545
+ }, true);
2546
+ }
2547
+
2548
+ /*
2549
+ * Also clears top and left values, so it doesn't interfer with the insert helper.
2550
+ */
2551
+ function _hideResizer () {
2552
+ if ($resizer) {
2553
+ $resizer.find('div').css('opacity', 0);
2554
+ $resizer.css('top', 0);
2555
+ $resizer.css('left', 0);
2556
+ $resizer.css('height', 0);
2557
+ $resizer.find('div').css('height', 0);
2558
+ $resizer.hide();
2559
+ }
2560
+ }
2561
+
2562
+ /**
2563
+ * Hide the insert helper.
2564
+ */
2565
+ function _hideInsertHelper () {
2566
+ if ($insert_helper) $insert_helper.removeClass('fr-visible').css('left', '-9999px');
2567
+ }
2568
+
2569
+ /*
2570
+ * Place the table resizer between the columns where the mouse is.
2571
+ */
2572
+ function _placeResizer (e, tag_under) {
2573
+ var $tag_under = $(tag_under);
2574
+ var $table = $tag_under.closest('table');
2575
+ var $table_parent = $table.parent();
2576
+
2577
+ // We might have another tag inside the table cell.
2578
+ if (tag_under && (tag_under.tagName != 'TD' && tag_under.tagName != 'TH')) {
2579
+ if ($tag_under.closest('td').length > 0) {
2580
+ tag_under = $tag_under.closest('td');
2581
+ }
2582
+ else if ($tag_under.closest('th').length > 0) {
2583
+ tag_under = $tag_under.closest('th');
2584
+ }
2585
+ }
2586
+
2587
+ // The tag should be a table cell (TD or TH).
2588
+ if (tag_under && (tag_under.tagName == 'TD' || tag_under.tagName == 'TH')) {
2589
+ $tag_under = $(tag_under);
2590
+
2591
+ // https://github.com/froala/wysiwyg-editor/issues/786.
2592
+ if (editor.$el.find($tag_under).length === 0) return false;
2593
+
2594
+ // Tag's left and right coordinate.
2595
+ var tag_left = $tag_under.offset().left - 1;
2596
+ var tag_right = tag_left + $tag_under.outerWidth();
2597
+
2598
+ // Only if the mouse is close enough to the left or right edges.
2599
+ if (Math.abs(e.pageX - tag_left) <= editor.opts.tableResizerOffset ||
2600
+ Math.abs(tag_right - e.pageX) <= editor.opts.tableResizerOffset) {
2601
+
2602
+ // Create a table map.
2603
+ var map = _tableMap($table);
2604
+ var tag_origin = _cellOrigin(tag_under, map);
2605
+
2606
+ var tag_end = _cellEnds(tag_origin.row, tag_origin.col, map);
2607
+
2608
+ // The column numbers from the map that have to be resized.
2609
+ var first;
2610
+ var second;
2611
+
2612
+ // Table resizer position and height.
2613
+ var resizer_top = $table.offset().top;
2614
+ var resizer_height = $table.outerHeight() - 1;
2615
+ var resizer_left;
2616
+
2617
+ // The left and right limits between which the resizer can be moved.
2618
+ var max_left;
2619
+ var max_right;
2620
+
2621
+ if (editor.opts.direction != 'rtl') {
2622
+
2623
+ // Mouse is near the cells's left margin.
2624
+ if (e.pageX - tag_left <= editor.opts.tableResizerOffset) {
2625
+
2626
+ // Table resizer's left position.
2627
+ resizer_left = tag_left;
2628
+
2629
+ // Resize cells.
2630
+ if (tag_origin.col > 0) {
2631
+
2632
+ // Left limit.
2633
+ max_left = tag_left - _columnWidth(tag_origin.col - 1, map) + editor.opts.tableResizingLimit;
2634
+
2635
+ // Right limit.
2636
+ max_right = tag_left + _columnWidth(tag_origin.col, map) - editor.opts.tableResizingLimit;
2637
+
2638
+ // Columns to resize.
2639
+ first = tag_origin.col - 1;
2640
+ second = tag_origin.col;
2641
+ }
2642
+
2643
+ // Resize table.
2644
+ else {
2645
+
2646
+ // Columns to resize.
2647
+ first = null;
2648
+ second = 0;
2649
+
2650
+ // Resizer limits.
2651
+ max_left = $table.offset().left - 1 - parseInt($table.css('margin-left'), 10);
2652
+ max_right = $table.offset().left - 1 + $table.width() - map[0].length * editor.opts.tableResizingLimit;
2653
+ }
2654
+ }
2655
+
2656
+ // Mouse is near the cell's right margin.
2657
+ else if (tag_right - e.pageX <= editor.opts.tableResizerOffset) {
2658
+
2659
+ // Table resizer's left possition.
2660
+ resizer_left = tag_right;
2661
+
2662
+ // Check for next td.
2663
+ if (tag_end.col < map[tag_end.row].length && map[tag_end.row][tag_end.col + 1]) {
2664
+
2665
+ // Left limit.
2666
+ max_left = tag_right - _columnWidth(tag_end.col, map) + editor.opts.tableResizingLimit;
2667
+
2668
+ // Right limit.
2669
+ max_right = tag_right + _columnWidth(tag_end.col + 1, map) - editor.opts.tableResizingLimit;
2670
+
2671
+ // Columns to resize.
2672
+ first = tag_end.col;
2673
+ second = tag_end.col + 1;
2674
+ }
2675
+
2676
+ // Resize table.
2677
+ else {
2678
+
2679
+ // Columns to resize.
2680
+ first = tag_end.col;
2681
+ second = null;
2682
+
2683
+ // Resizer limits.
2684
+ max_left = $table.offset().left - 1 + map[0].length * editor.opts.tableResizingLimit;
2685
+ max_right = $table_parent.offset().left - 1 + $table_parent.width() + parseFloat($table_parent.css('padding-left'));
2686
+ }
2687
+ }
2688
+ }
2689
+
2690
+ // RTL
2691
+ else {
2692
+
2693
+ // Mouse is near the cell's right margin.
2694
+ if (tag_right - e.pageX <= editor.opts.tableResizerOffset) {
2695
+
2696
+ // Table resizer's left position.
2697
+ resizer_left = tag_right;
2698
+
2699
+ // Resize cells.
2700
+ if (tag_origin.col > 0) {
2701
+
2702
+ // Left limit.
2703
+ max_left = tag_right - _columnWidth(tag_origin.col, map) + editor.opts.tableResizingLimit;
2704
+
2705
+ // Right limit.
2706
+ max_right = tag_right + _columnWidth(tag_origin.col - 1, map) - editor.opts.tableResizingLimit;
2707
+
2708
+ // Columns to resize.
2709
+ first = tag_origin.col;
2710
+ second = tag_origin.col - 1;
2711
+ }
2712
+
2713
+ // Resize table.
2714
+ else {
2715
+ first = null;
2716
+ second = 0;
2717
+
2718
+ // Resizer limits.
2719
+ max_left = $table.offset().left + map[0].length * editor.opts.tableResizingLimit;
2720
+ max_right = $table_parent.offset().left - 1 + $table_parent.width() + parseFloat($table_parent.css('padding-left'));
2721
+ }
2722
+ }
2723
+
2724
+ // Mouse is near the cell's left margin.
2725
+ else if (e.pageX - tag_left <= editor.opts.tableResizerOffset) {
2726
+
2727
+ // Table resizer's left position.
2728
+ resizer_left = tag_left;
2729
+
2730
+ // Check for next td.
2731
+ if (tag_end.col < map[tag_end.row].length && map[tag_end.row][tag_end.col + 1]) {
2732
+
2733
+ // Left limit.
2734
+ max_left = tag_left - _columnWidth(tag_end.col + 1, map) + editor.opts.tableResizingLimit;
2735
+
2736
+ // Right limit.
2737
+ max_right = tag_left + _columnWidth(tag_end.col, map) - editor.opts.tableResizingLimit;
2738
+
2739
+ // Columns to resize.
2740
+ first = tag_end.col + 1;
2741
+ second = tag_end.col;
2742
+ }
2743
+
2744
+ // Resize table.
2745
+ else {
2746
+
2747
+ // Columns to resize.
2748
+ first = tag_end.col;
2749
+ second = null;
2750
+
2751
+ // Resizer limits.
2752
+ max_left = $table_parent.offset().left + parseFloat($table_parent.css('padding-left'));
2753
+ max_right = $table.offset().left - 1 + $table.width() - map[0].length * editor.opts.tableResizingLimit;
2754
+ }
2755
+ }
2756
+ }
2757
+
2758
+ if (!$resizer) _initResizer();
2759
+
2760
+ // Save table.
2761
+ $resizer.data('table', $table);
2762
+
2763
+ // Save columns to resize.
2764
+ $resizer.data('first', first);
2765
+ $resizer.data('second', second);
2766
+
2767
+ $resizer.data('instance', editor);
2768
+ editor.$wp.append($resizer);
2769
+
2770
+ var left = resizer_left - editor.win.pageXOffset - editor.opts.tableResizerOffset - editor.$wp.offset().left;
2771
+ var top = resizer_top - editor.$wp.offset().top + editor.$wp.scrollTop();
2772
+
2773
+ if (editor.opts.iframe) {
2774
+ left += editor.$iframe.offset().left;
2775
+ top += editor.$iframe.offset().top;
2776
+
2777
+ max_left += editor.$iframe.offset().left;
2778
+ max_right += editor.$iframe.offset().left;
2779
+ }
2780
+
2781
+ // Set resizing limits.
2782
+ $resizer.data('max-left', max_left);
2783
+ $resizer.data('max-right', max_right);
2784
+
2785
+ // Initial position of the resizer
2786
+ $resizer.data('origin', resizer_left - editor.win.pageXOffset);
2787
+
2788
+ // Set table resizer's top, left and height.
2789
+ $resizer.css('top', top);
2790
+ $resizer.css('left', left);
2791
+ $resizer.css('height', resizer_height);
2792
+ $resizer.find('div').css('height', resizer_height);
2793
+
2794
+ // Set padding according to tableResizerOffset.
2795
+ $resizer.css('padding-left', editor.opts.tableResizerOffset);
2796
+ $resizer.css('padding-right', editor.opts.tableResizerOffset);
2797
+
2798
+ // Show table resizer.
2799
+ $resizer.show();
2800
+ }
2801
+
2802
+ // Hide resizer when the mouse moves away from the cell's border.
2803
+ else {
2804
+ if (editor.core.sameInstance($resizer)) _hideResizer();
2805
+ }
2806
+ }
2807
+
2808
+ // Hide resizer if mouse is no longer over it.
2809
+ else if ($resizer && $tag_under.get(0) != $resizer.get(0) && $tag_under.parent().get(0) != $resizer.get(0)) {
2810
+ if (editor.core.sameInstance($resizer)) _hideResizer();
2811
+ }
2812
+ }
2813
+
2814
+ /*
2815
+ * Show the insert column helper button.
2816
+ */
2817
+ function _showInsertColHelper (e, table) {
2818
+ if (editor.$box.find('.fr-line-breaker').is(':visible')) return false;
2819
+
2820
+ // Insert Helper.
2821
+ if (!$insert_helper) _initInsertHelper();
2822
+
2823
+ editor.$box.append($insert_helper);
2824
+ $insert_helper.data('instance', editor);
2825
+ var $table = $(table);
2826
+ var $row = $table.find('tr:first');
2827
+ var mouseX = e.pageX;
2828
+ var left = 0;
2829
+ var top = 0;
2830
+
2831
+ if (editor.opts.iframe) {
2832
+ left += editor.$iframe.offset().left - editor.helpers.scrollLeft();
2833
+ top += editor.$iframe.offset().top - editor.helpers.scrollTop();
2834
+ }
2835
+
2836
+ // Check where the column should be inserted.
2837
+ var btn_width;
2838
+
2839
+ $row.find('th, td').each (function () {
2840
+ var $td = $(this);
2841
+
2842
+ // Insert before this td.
2843
+ if ($td.offset().left <= mouseX && mouseX < $td.offset().left + $td.outerWidth() / 2) {
2844
+ btn_width = parseInt($insert_helper.find('a').css('width'), 10);
2845
+ $insert_helper.css('top', top + $td.offset().top - editor.$box.offset().top - editor.win.pageYOffset - btn_width - 5);
2846
+ $insert_helper.css('left', left + $td.offset().left - editor.$box.offset().left - editor.win.pageXOffset - btn_width / 2);
2847
+ $insert_helper.data('selected-cell', $td);
2848
+ $insert_helper.data('position', 'before');
2849
+ $insert_helper.addClass('fr-visible');
2850
+
2851
+ return false;
2852
+
2853
+ // Insert after this td.
2854
+ }
2855
+ else if ($td.offset().left + $td.outerWidth() / 2 <= mouseX && mouseX < $td.offset().left + $td.outerWidth()) {
2856
+ btn_width = parseInt($insert_helper.find('a').css('width'), 10);
2857
+
2858
+ $insert_helper.css('top', top + $td.offset().top - editor.$box.offset().top - editor.win.pageYOffset - btn_width - 5);
2859
+ $insert_helper.css('left', left + $td.offset().left - editor.$box.offset().left + $td.outerWidth() - editor.win.pageXOffset - btn_width / 2);
2860
+ $insert_helper.data('selected-cell', $td);
2861
+ $insert_helper.data('position', 'after');
2862
+ $insert_helper.addClass('fr-visible');
2863
+
2864
+ return false;
2865
+ }
2866
+ });
2867
+ }
2868
+
2869
+ /*
2870
+ * Show the insert row helper button.
2871
+ */
2872
+ function _showInsertRowHelper (e, table) {
2873
+ if (editor.$box.find('.fr-line-breaker').is(':visible')) return false;
2874
+
2875
+ if (!$insert_helper) _initInsertHelper();
2876
+
2877
+ editor.$box.append($insert_helper);
2878
+ $insert_helper.data('instance', editor);
2879
+ var $table = $(table);
2880
+ var mouseY = e.pageY;
2881
+ var left = 0;
2882
+ var top = 0;
2883
+
2884
+ if (editor.opts.iframe) {
2885
+ left += editor.$iframe.offset().left - editor.helpers.scrollLeft();
2886
+ top += editor.$iframe.offset().top - editor.helpers.scrollTop();
2887
+ }
2888
+
2889
+ // Check where the row should be inserted.
2890
+ var btn_width;
2891
+
2892
+ $table.find('tr').each (function () {
2893
+ var $tr = $(this);
2894
+
2895
+ // Insert above this tr.
2896
+ if ($tr.offset().top <= mouseY && mouseY < $tr.offset().top + $tr.outerHeight() / 2) {
2897
+ btn_width = parseInt($insert_helper.find('a').css('width'), 10);
2898
+
2899
+ $insert_helper.css('top', top + $tr.offset().top - editor.$box.offset().top - editor.win.pageYOffset - btn_width / 2);
2900
+ $insert_helper.css('left', left + $tr.offset().left - editor.$box.offset().left - editor.win.pageXOffset - btn_width - 5);
2901
+ $insert_helper.data('selected-cell', $tr.find('td:first'));
2902
+ $insert_helper.data('position', 'above');
2903
+ $insert_helper.addClass('fr-visible');
2904
+
2905
+ return false;
2906
+
2907
+ // Insert below this tr.
2908
+ }
2909
+ else if ($tr.offset().top + $tr.outerHeight() / 2 <= mouseY && mouseY < $tr.offset().top + $tr.outerHeight()) {
2910
+ btn_width = parseInt($insert_helper.find('a').css('width'), 10);
2911
+
2912
+ $insert_helper.css('top', top + $tr.offset().top - editor.$box.offset().top + $tr.outerHeight() - editor.win.pageYOffset - btn_width / 2);
2913
+ $insert_helper.css('left', left + $tr.offset().left - editor.$box.offset().left - editor.win.pageXOffset - btn_width - 5);
2914
+ $insert_helper.data('selected-cell', $tr.find('td:first'));
2915
+ $insert_helper.data('position', 'below');
2916
+ $insert_helper.addClass('fr-visible');
2917
+
2918
+ return false;
2919
+ }
2920
+ });
2921
+ }
2922
+
2923
+ /*
2924
+ * Check if should show the insert column / row helper button.
2925
+ */
2926
+ function _insertHelper (e, tag_under) {
2927
+
2928
+ // Don't show the insert helper if there are table cells selected.
2929
+ if (selectedCells().length === 0) {
2930
+ var i;
2931
+ var tag_below;
2932
+ var tag_right;
2933
+
2934
+ // Tag is the editor element or body (inline toolbar). Look for closest tag bellow and at the right.
2935
+ if (tag_under && (tag_under.tagName == 'HTML' || tag_under.tagName == 'BODY' || editor.node.isElement(tag_under))) {
2936
+
2937
+ // Look 1px down until a table tag is found or the insert helper offset is reached.
2938
+ for (i = 1; i <= editor.opts.tableInsertHelperOffset; i++) {
2939
+
2940
+ // Look for tag below.
2941
+ tag_below = editor.doc.elementFromPoint(e.pageX - editor.win.pageXOffset, e.pageY - editor.win.pageYOffset + i);
2942
+
2943
+ // We're on tooltip.
2944
+ if ($(tag_below).hasClass('fr-tooltip')) return true;
2945
+
2946
+ // We found a tag bellow.
2947
+ if (tag_below && ((tag_below.tagName == 'TH' || tag_below.tagName == 'TD' || tag_below.tagName == 'TABLE') && ($(tag_below).parents('.fr-wrapper').length || editor.opts.iframe))) {
2948
+
2949
+ // Show the insert column helper button.
2950
+ _showInsertColHelper (e, $(tag_below).closest('table'));
2951
+
2952
+ return true;
2953
+ }
2954
+
2955
+ // Look for tag at the right.
2956
+ tag_right = editor.doc.elementFromPoint(e.pageX - editor.win.pageXOffset + i, e.pageY - editor.win.pageYOffset);
2957
+
2958
+ // We're on tooltip.
2959
+ if ($(tag_right).hasClass('fr-tooltip')) return true;
2960
+
2961
+ // We found a tag at the right.
2962
+ if (tag_right && ((tag_right.tagName == 'TH' || tag_right.tagName == 'TD' || tag_right.tagName == 'TABLE') && ($(tag_right).parents('.fr-wrapper').length || editor.opts.iframe))) {
2963
+
2964
+ // Show the insert row helper button.
2965
+ _showInsertRowHelper (e, $(tag_right).closest('table'));
2966
+
2967
+ return true;
2968
+ }
2969
+ }
2970
+ }
2971
+
2972
+ // Hide insert helper.
2973
+ if (editor.core.sameInstance($insert_helper)) {
2974
+ _hideInsertHelper();
2975
+ }
2976
+ }
2977
+ }
2978
+
2979
+ /*
2980
+ * Check tag under the mouse on mouse move.
2981
+ */
2982
+ function _tagUnder (e) {
2983
+ mouseMoveTimer = null;
2984
+
2985
+ // The tag under the mouse cursor.
2986
+ var tag_under = editor.doc.elementFromPoint(e.pageX - editor.win.pageXOffset, e.pageY - editor.win.pageYOffset);
2987
+
2988
+ // Place table resizer if necessary.
2989
+ if (editor.opts.tableResizer && (!editor.popups.areVisible() || (editor.popups.areVisible() && editor.popups.isVisible('table.edit')))) {
2990
+ _placeResizer(e, tag_under);
2991
+ }
2992
+
2993
+ // Show the insert column / row helper button.
2994
+ if (editor.opts.tableInsertHelper && !editor.popups.areVisible() && !(editor.$tb.hasClass('fr-inline') && editor.$tb.is(':visible'))) {
2995
+ _insertHelper(e, tag_under);
2996
+ }
2997
+ }
2998
+
2999
+ /*
3000
+ * Repositon the resizer if the user scrolls while resizing.
3001
+ */
3002
+ function _repositionResizer () {
3003
+ if (resizingFlag) {
3004
+ var $table = $resizer.data('table');
3005
+ var top = $table.offset().top - editor.win.pageYOffset;
3006
+
3007
+ if (editor.opts.iframe) {
3008
+ top += editor.$iframe.offset().top - editor.helpers.scrollTop();
3009
+ }
3010
+
3011
+ $resizer.css('top', top);
3012
+ }
3013
+ }
3014
+
3015
+ /*
3016
+ * Resize table method.
3017
+ */
3018
+ function _resize () {
3019
+
3020
+ // Resizer initial position.
3021
+ var initial_positon = $resizer.data('origin');
3022
+
3023
+ // Resizer release position.
3024
+ var release_position = $resizer.data('release-position');
3025
+
3026
+ // Do resize only if the resizer's position has changed.
3027
+ if (initial_positon !== release_position) {
3028
+
3029
+ // Columns that have to be resized.
3030
+ var first = $resizer.data('first');
3031
+ var second = $resizer.data('second');
3032
+
3033
+ var $table = $resizer.data('table');
3034
+ var table_width = $table.outerWidth();
3035
+
3036
+ if (!editor.undo.canDo()) editor.undo.saveStep();
3037
+
3038
+ // Resize columns and not the table.
3039
+ if (first !== null && second !== null) {
3040
+
3041
+ // Create a table map.
3042
+ var map = _tableMap($table);
3043
+
3044
+ // Got through all cells on these columns and get their initial width.
3045
+ var first_widths = [];
3046
+ var first_percentages = [];
3047
+ var second_widths = [];
3048
+ var second_percentages = [];
3049
+ var i;
3050
+ var $first_cell;
3051
+ var $second_cell;
3052
+
3053
+ // We must do this before updating widths.
3054
+ for (i = 0; i < map.length; i++) {
3055
+ $first_cell = $(map[i][first]);
3056
+ $second_cell = $(map[i][second]);
3057
+
3058
+ // Widths in px.
3059
+ first_widths[i] = $first_cell.outerWidth();
3060
+ second_widths[i] = $second_cell.outerWidth();
3061
+
3062
+ // Widths in percentages.
3063
+ first_percentages[i] = first_widths[i] / table_width * 100;
3064
+ second_percentages[i] = second_widths[i] / table_width * 100;
3065
+ }
3066
+
3067
+ // Got through all cells on these columns and update their widths.
3068
+ for (i = 0; i < map.length; i++) {
3069
+ $first_cell = $(map[i][first]);
3070
+ $second_cell = $(map[i][second]);
3071
+
3072
+ // New percentage for the first cell.
3073
+ var first_cell_percentage = (first_percentages[i] * (first_widths[i] + release_position - initial_positon) / first_widths[i]).toFixed(4);
3074
+
3075
+ $first_cell.css('width', first_cell_percentage + '%');
3076
+ $second_cell.css('width', (first_percentages[i] + second_percentages[i] - first_cell_percentage).toFixed(4) + '%');
3077
+ }
3078
+ }
3079
+
3080
+ // Resize the table.
3081
+ else {
3082
+ var $table_parent = $table.parent();
3083
+ var table_percentage = table_width / $table_parent.width() * 100;
3084
+ var left_margin = (parseInt($table.css('margin-left'), 10) || 0) / $table_parent.width() * 100;
3085
+ var right_margin = (parseInt($table.css('margin-right'), 10) || 0) / $table_parent.width() * 100;
3086
+ var width;
3087
+
3088
+ // Right border RTL or LTR.
3089
+ if ((editor.opts.direction == 'rtl' && second === 0) || (editor.opts.direction != 'rtl' && second !== 0)) {
3090
+ width = (table_width + release_position - initial_positon) / table_width * table_percentage;
3091
+ $table.css('margin-right', 'calc(100% - ' + Math.round(width).toFixed(4) + '% - ' + Math.round(left_margin).toFixed(4) + '%)');
3092
+ }
3093
+
3094
+ // Left border RTL or LTR.
3095
+ else if ((editor.opts.direction == 'rtl' && second !== 0) || (editor.opts.direction != 'rtl' && second === 0)) {
3096
+ width = (table_width - release_position + initial_positon) / table_width * table_percentage;
3097
+ $table.css('margin-left', 'calc(100% - ' + Math.round(width).toFixed(4) + '% - ' + Math.round(right_margin).toFixed(4) + '%)');
3098
+ }
3099
+
3100
+ // Update table width.
3101
+ $table.css('width', Math.round(width).toFixed(4) + '%');
3102
+ }
3103
+
3104
+ editor.selection.restore();
3105
+ editor.undo.saveStep();
3106
+ }
3107
+
3108
+ // Clear resizer data.
3109
+ $resizer.removeData('origin');
3110
+ $resizer.removeData('release-position');
3111
+ $resizer.removeData('first');
3112
+ $resizer.removeData('second');
3113
+ $resizer.removeData('table');
3114
+ }
3115
+
3116
+ /*
3117
+ * Get the width of the column. (columns may have colspan)
3118
+ */
3119
+ function _columnWidth (col, map) {
3120
+ var i;
3121
+ var width = $(map[0][col]).outerWidth();
3122
+
3123
+ for (i = 1; i < map.length; i++) {
3124
+ width = Math.min(width, $(map[i][col]).outerWidth());
3125
+ }
3126
+
3127
+ return width;
3128
+ }
3129
+
3130
+ /*
3131
+ * Get the width of the columns between specified indexes.
3132
+ */
3133
+ function _columnsWidth(col1, col2, map) {
3134
+ var i;
3135
+ var width = 0;
3136
+
3137
+ // Sum all columns widths.
3138
+ for (i = col1; i <= col2; i++) {
3139
+ width += _columnWidth(i, map);
3140
+ }
3141
+
3142
+ return width;
3143
+ }
3144
+
3145
+ /*
3146
+ * Set mouse timer to improve performance.
3147
+ */
3148
+ function _mouseMove (e) {
3149
+
3150
+ // Prevent selecting text when we have cells selected.
3151
+ if (selectedCells().length > 1 && mouseDownFlag) {
3152
+ _clearSelection();
3153
+ }
3154
+
3155
+ // Reset or set timer.
3156
+ if (mouseDownFlag === false && mouseDownCellFlag === false && resizingFlag === false) {
3157
+ if (mouseMoveTimer) {
3158
+ clearTimeout(mouseMoveTimer);
3159
+ }
3160
+
3161
+ // Only resize table if the editor is not disabled by user.
3162
+ if (!editor.edit.isDisabled() || editor.popups.isVisible('table.edit')) {
3163
+
3164
+ // Check tag under in order to place the table resizer or insert helper button.
3165
+ mouseMoveTimer = setTimeout(_tagUnder, 30, e);
3166
+ }
3167
+
3168
+ // Move table resizer.
3169
+ }
3170
+ else if (resizingFlag) {
3171
+
3172
+ // Cursor position.
3173
+ var pos = e.pageX - editor.win.pageXOffset;
3174
+
3175
+ if (editor.opts.iframe) {
3176
+ pos += editor.$iframe.offset().left;
3177
+ }
3178
+
3179
+ // Left and right limits.
3180
+ var left_limit = $resizer.data('max-left');
3181
+ var right_limit = $resizer.data('max-right');
3182
+
3183
+ // Cursor is between the left and right limits.
3184
+ if (pos >= left_limit && pos <= right_limit) {
3185
+ $resizer.css('left', pos - editor.opts.tableResizerOffset - editor.$wp.offset().left);
3186
+
3187
+ // Cursor has exceeded the left limit. Don't update if it already has the correct value.
3188
+ }
3189
+ else if (pos < left_limit && parseFloat($resizer.css('left'), 10) > left_limit - editor.opts.tableResizerOffset) {
3190
+ $resizer.css('left', left_limit - editor.opts.tableResizerOffset - editor.$wp.offset().left);
3191
+
3192
+ // Cursor has exceeded the right limit. Don't update if it already has the correct value.
3193
+ }
3194
+ else if (pos > right_limit && parseFloat($resizer.css('left'), 10) < right_limit - editor.opts.tableResizerOffset) {
3195
+ $resizer.css('left', right_limit - editor.opts.tableResizerOffset - editor.$wp.offset().left);
3196
+ }
3197
+ }
3198
+ else if (mouseDownFlag) {
3199
+ _hideInsertHelper();
3200
+ }
3201
+ }
3202
+
3203
+ /*
3204
+ * Place selection markers in a table cell.
3205
+ */
3206
+ function _addMarkersInCell ($cell) {
3207
+ if (editor.node.isEmpty($cell.get(0))) {
3208
+ $cell.prepend($.FE.MARKERS);
3209
+ }
3210
+ else {
3211
+ $cell.prepend($.FE.START_MARKER).append($.FE.END_MARKER);
3212
+ }
3213
+ }
3214
+
3215
+ /*
3216
+ * Use TAB key to navigate through cells.
3217
+ */
3218
+ function _useTab (e) {
3219
+ var key_code = e.which;
3220
+
3221
+ if (key_code == $.FE.KEYCODE.TAB) {
3222
+
3223
+ // Get starting cell.
3224
+ var $cell;
3225
+
3226
+ if (selectedCells().length > 0) {
3227
+ $cell = editor.$el.find('.fr-selected-cell:last')
3228
+ }
3229
+ else {
3230
+ var cell = editor.selection.element();
3231
+
3232
+ if (cell.tagName == 'TD' || cell.tagName == 'TH') {
3233
+ $cell = $(cell);
3234
+ }
3235
+ else if (cell != editor.el) {
3236
+ if ($(cell).parentsUntil(editor.$el, 'td').length > 0) {
3237
+ $cell = $(cell).parents('td:first');
3238
+ }
3239
+ else if ($(cell).parentsUntil(editor.$el, 'th').length > 0) {
3240
+ $cell = $(cell).parents('th:first');
3241
+ }
3242
+ }
3243
+ }
3244
+
3245
+ if ($cell) {
3246
+ e.preventDefault();
3247
+
3248
+ if ($(editor.selection.element()).parentsUntil(editor.$el, 'ol, ul').length > 0 && ($(editor.selection.element()).parents('li').prev().length > 0 || ($(editor.selection.element()).is('li') && $(editor.selection.element()).prev().length > 0))) {
3249
+
3250
+ return true;
3251
+ }
3252
+
3253
+ _stopEdit();
3254
+
3255
+ // Go backwards.
3256
+ if (e.shiftKey) {
3257
+
3258
+ // Go to previous cell.
3259
+ if ($cell.prev().length > 0) {
3260
+ _addMarkersInCell($cell.prev());
3261
+ }
3262
+
3263
+ // Go to prev row, last cell.
3264
+ else if ($cell.closest('tr').length > 0 && $cell.closest('tr').prev().length > 0) {
3265
+ _addMarkersInCell($cell.closest('tr').prev().find('td:last'));
3266
+ }
3267
+
3268
+ // Go in THEAD, last cell.
3269
+ else if ($cell.closest('tbody').length > 0 && $cell.closest('table').find('thead tr').length > 0) {
3270
+ _addMarkersInCell($cell.closest('table').find('thead tr th:last'));
3271
+ }
3272
+ }
3273
+
3274
+ // Go forward.
3275
+ else {
3276
+
3277
+ // Go to next cell.
3278
+ if ($cell.next().length > 0) {
3279
+ _addMarkersInCell($cell.next());
3280
+ }
3281
+
3282
+ // Go to next row, first cell.
3283
+ else if ($cell.closest('tr').length > 0 && $cell.closest('tr').next().length > 0) {
3284
+ _addMarkersInCell($cell.closest('tr').next().find('td:first'));
3285
+ }
3286
+
3287
+ // Cursor is in THEAD. Go to next row in TBODY
3288
+ else if ($cell.closest('thead').length > 0 && $cell.closest('table').find('tbody tr').length > 0) {
3289
+ _addMarkersInCell($cell.closest('table').find('tbody tr td:first'));
3290
+ }
3291
+
3292
+ // Add new row.
3293
+ else {
3294
+ $cell.addClass('fr-selected-cell');
3295
+ insertRow('below');
3296
+ _removeSelection();
3297
+ _addMarkersInCell($cell.closest('tr').next().find('td:first'));
3298
+ }
3299
+ }
3300
+
3301
+ // Update cursor position.
3302
+ editor.selection.restore();
3303
+
3304
+ // Prevent event propagation.
3305
+ return false;
3306
+ }
3307
+ }
3308
+ }
3309
+
3310
+ /*
3311
+ * Initilize insert helper.
3312
+ */
3313
+ function _initInsertHelper () {
3314
+
3315
+ // Append insert helper HTML to editor wrapper.
3316
+ if (!editor.shared.$ti_helper) {
3317
+ editor.shared.$ti_helper = $('<div class="fr-insert-helper"><a class="fr-floating-btn" role="button" tabIndex="-1" title="' + editor.language.translate('Insert') + '"><svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M22,16.75 L16.75,16.75 L16.75,22 L15.25,22.000 L15.25,16.75 L10,16.75 L10,15.25 L15.25,15.25 L15.25,10 L16.75,10 L16.75,15.25 L22,15.25 L22,16.75 Z"/></svg></a></div>');
3318
+
3319
+ // Click on insert helper.
3320
+ editor.events.bindClick(editor.shared.$ti_helper, 'a', function () {
3321
+ var $td = $insert_helper.data('selected-cell');
3322
+ var position = $insert_helper.data('position');
3323
+ var inst = $insert_helper.data('instance') || editor;
3324
+
3325
+ if (position == 'before') {
3326
+ editor.undo.saveStep();
3327
+
3328
+ $td.addClass('fr-selected-cell');
3329
+ inst.table.insertColumn(position);
3330
+ $td.removeClass('fr-selected-cell');
3331
+
3332
+ editor.undo.saveStep();
3333
+ }
3334
+ else if (position == 'after') {
3335
+ editor.undo.saveStep();
3336
+
3337
+ $td.addClass('fr-selected-cell');
3338
+ inst.table.insertColumn(position);
3339
+ $td.removeClass('fr-selected-cell');
3340
+
3341
+ editor.undo.saveStep();
3342
+ }
3343
+ else if (position == 'above') {
3344
+ editor.undo.saveStep();
3345
+
3346
+ $td.addClass('fr-selected-cell');
3347
+ inst.table.insertRow(position);
3348
+ $td.removeClass('fr-selected-cell');
3349
+
3350
+ editor.undo.saveStep();
3351
+ }
3352
+ else if (position == 'below') {
3353
+ editor.undo.saveStep();
3354
+
3355
+ $td.addClass('fr-selected-cell');
3356
+ inst.table.insertRow(position);
3357
+ $td.removeClass('fr-selected-cell');
3358
+
3359
+ editor.undo.saveStep();
3360
+ }
3361
+
3362
+ // Hide the insert helper so it will reposition.
3363
+ _hideInsertHelper();
3364
+ });
3365
+
3366
+ // Editor destroy.
3367
+ editor.events.on('shared.destroy', function () {
3368
+ editor.shared.$ti_helper.html('').removeData().remove();
3369
+ editor.shared.$ti_helper = null;
3370
+ }, true);
3371
+
3372
+ // Prevent the insert helper hide when mouse is over it.
3373
+ editor.events.$on(editor.shared.$ti_helper, 'mousemove', function (e) {
3374
+ e.stopPropagation();
3375
+ }, true);
3376
+
3377
+ // Hide the insert helper if the page is scrolled.
3378
+ editor.events.$on($(editor.o_win), 'scroll', function () {
3379
+ _hideInsertHelper();
3380
+ }, true);
3381
+
3382
+ editor.events.$on(editor.$wp, 'scroll', function () {
3383
+ _hideInsertHelper();
3384
+ }, true);
3385
+ }
3386
+
3387
+ $insert_helper = editor.shared.$ti_helper;
3388
+
3389
+ editor.events.on('destroy', function () {
3390
+ $insert_helper = null;
3391
+ });
3392
+
3393
+ // Table insert helper tooltip.
3394
+ editor.tooltip.bind(editor.$box, '.fr-insert-helper > a.fr-floating-btn');
3395
+ }
3396
+
3397
+ /**
3398
+ * Destroy
3399
+ */
3400
+ function _destroy () {
3401
+ mouseDownCell = null;
3402
+ clearTimeout(mouseMoveTimer);
3403
+ }
3404
+
3405
+ /*
3406
+ * Go back to the table edit popup.
3407
+ */
3408
+ function back () {
3409
+ if (selectedCells().length > 0) {
3410
+ _showEditPopup();
3411
+ }
3412
+ else {
3413
+ editor.popups.hide('table.insert');
3414
+ editor.toolbar.showInline();
3415
+ }
3416
+ }
3417
+
3418
+ /**
3419
+ * Return selected cells.
3420
+ */
3421
+ function selectedCells () {
3422
+ return editor.el.querySelectorAll('.fr-selected-cell');
3423
+ }
3424
+
3425
+ /**
3426
+ * Return selected table.
3427
+ */
3428
+ function selectedTable () {
3429
+ var cells = selectedCells();
3430
+
3431
+ if (cells.length) {
3432
+ var cell = cells[0];
3433
+
3434
+ while (cell && cell.tagName != 'TABLE' && cell.parentNode != editor.el) {
3435
+ cell = cell.parentNode;
3436
+ }
3437
+
3438
+ if (cell && cell.tagName == 'TABLE') return $(cell);
3439
+
3440
+ return $([]);
3441
+ }
3442
+
3443
+ return $([]);
3444
+ }
3445
+
3446
+ /**
3447
+ * Select table cell with alt + space.
3448
+ */
3449
+ function _selectCellWithKeyboard (e) {
3450
+
3451
+ // Alt+space was hit. Try to select cell.
3452
+ if (e.altKey && e.which == $.FE.KEYCODE.SPACE) {
3453
+ var cell;
3454
+ var el = editor.selection.element();
3455
+
3456
+ // Get cell where cursor is.
3457
+ if (el.tagName == 'TD' || el.tagName == 'TH') {
3458
+ cell = el;
3459
+ }
3460
+ else if ($(el).closest('td').length > 0) {
3461
+ cell = $(el).closest('td').get(0);
3462
+ }
3463
+ else if ($(el).closest('th').length > 0) {
3464
+ cell = $(el).closest('th').get(0);
3465
+ }
3466
+
3467
+ // Select this cell.
3468
+ if (cell) {
3469
+ e.preventDefault();
3470
+ _selectCells(cell, cell);
3471
+ _showEditPopup();
3472
+
3473
+ return false;
3474
+ }
3475
+ }
3476
+ }
3477
+
3478
+ /**
3479
+ * Select table cells using arrows.
3480
+ */
3481
+ function _selectCellsWithKeyboard (e) {
3482
+ var selection = selectedCells();
3483
+
3484
+ // There are some selected cells.
3485
+ if (selection.length > 0) {
3486
+ var map = _tableMap();
3487
+ var key_code = e.which;
3488
+ var fixedCell;
3489
+ var handlerCell;
3490
+
3491
+ // Only one cell is selected.
3492
+ if (selection.length == 1) {
3493
+ fixedCell = selection[0];
3494
+ handlerCell = fixedCell;
3495
+ }
3496
+
3497
+ else {
3498
+ fixedCell = editor.el.querySelector('.fr-cell-fixed');
3499
+ handlerCell = editor.el.querySelector('.fr-cell-handler');
3500
+ }
3501
+
3502
+ var handlerOrigin = _cellOrigin(handlerCell, map);
3503
+
3504
+ // Select column at the right.
3505
+ if ($.FE.KEYCODE.ARROW_RIGHT == key_code) {
3506
+ if (handlerOrigin.col < map[0].length - 1) {
3507
+ _selectCells(fixedCell, map[handlerOrigin.row][handlerOrigin.col + 1]);
3508
+
3509
+ return false;
3510
+ }
3511
+ }
3512
+
3513
+ // Select row below.
3514
+ else if ($.FE.KEYCODE.ARROW_DOWN == key_code) {
3515
+ if (handlerOrigin.row < map.length - 1) {
3516
+ _selectCells(fixedCell, map[handlerOrigin.row + 1][handlerOrigin.col]);
3517
+
3518
+ return false;
3519
+ }
3520
+ }
3521
+
3522
+ // Select column at the left.
3523
+ else if ($.FE.KEYCODE.ARROW_LEFT == key_code) {
3524
+ if (handlerOrigin.col > 0) {
3525
+ _selectCells(fixedCell, map[handlerOrigin.row][handlerOrigin.col - 1]);
3526
+
3527
+ return false;
3528
+ }
3529
+ }
3530
+
3531
+ // Select row above.
3532
+ else if ($.FE.KEYCODE.ARROW_UP == key_code) {
3533
+ if (handlerOrigin.row > 0) {
3534
+ _selectCells(fixedCell, map[handlerOrigin.row - 1][handlerOrigin.col]);
3535
+
3536
+ return false;
3537
+ }
3538
+ }
3539
+ }
3540
+ }
3541
+
3542
+ /*
3543
+ * Init table.
3544
+ */
3545
+ function _init () {
3546
+ if (!editor.$wp) return false;
3547
+
3548
+ // Do cell selection only on desktops (no touch devices)
3549
+ if (!editor.helpers.isMobile()) {
3550
+
3551
+ // Remember if mouse is clicked.
3552
+ mouseDownFlag = false;
3553
+ mouseDownCellFlag = false;
3554
+ resizingFlag = false;
3555
+
3556
+ // Mouse is down in a table cell.
3557
+ editor.events.$on(editor.$el, 'mousedown', _mouseDown);
3558
+
3559
+ // Deselect table cells when user clicks on an image.
3560
+ editor.popups.onShow('image.edit', function () {
3561
+ _removeSelection();
3562
+ mouseDownFlag = false;
3563
+ mouseDownCellFlag = false;
3564
+ });
3565
+
3566
+ // Deselect table cells when user clicks on a link.
3567
+ editor.popups.onShow('link.edit', function () {
3568
+ _removeSelection();
3569
+ mouseDownFlag = false;
3570
+ mouseDownCellFlag = false;
3571
+ });
3572
+
3573
+ // Deselect table cells when a command is run.
3574
+ editor.events.on('commands.mousedown', function ($btn) {
3575
+ if ($btn.parents('.fr-toolbar').length > 0) {
3576
+ _removeSelection();
3577
+ }
3578
+ });
3579
+
3580
+ // Mouse enter's a table cell.
3581
+ editor.events.$on(editor.$el, 'mouseenter', 'th, td', _mouseEnter);
3582
+
3583
+ // Mouse is no longer pressed.
3584
+ editor.events.$on(editor.$win, 'mouseup', _mouseUp);
3585
+
3586
+ // Iframe mouseup.
3587
+ if (editor.opts.iframe) {
3588
+ editor.events.$on($(editor.o_win), 'mouseup', _mouseUp);
3589
+ }
3590
+
3591
+ // Check tags under the mouse to see if the resizer needs to be shown.
3592
+ editor.events.$on(editor.$win, 'mousemove', _mouseMove);
3593
+
3594
+ // Update resizer's position on scroll.
3595
+ editor.events.$on($(editor.o_win), 'scroll', _repositionResizer);
3596
+
3597
+ // Reposition table edit popup when table cell content changes.
3598
+ editor.events.on('contentChanged', function () {
3599
+ if (selectedCells().length > 0) {
3600
+ _showEditPopup();
3601
+
3602
+ // Make sure we reposition on image load.
3603
+ editor.$el.find('img').on('load.selected-cells', function () {
3604
+ $(this).off('load.selected-cells');
3605
+
3606
+ if (selectedCells().length > 0) {
3607
+ _showEditPopup();
3608
+ }
3609
+ });
3610
+ }
3611
+ });
3612
+
3613
+ // Reposition table edit popup on window resize.
3614
+ editor.events.$on($(editor.o_win), 'resize', function () {
3615
+ _removeSelection();
3616
+ });
3617
+
3618
+ editor.events.on('toolbar.esc', function () {
3619
+ if (selectedCells().length > 0) {
3620
+ editor.events.disableBlur();
3621
+ editor.events.focus();
3622
+
3623
+ return false;
3624
+ }
3625
+ }, true);
3626
+
3627
+ // Allow keyboard while selecting table cells.
3628
+ // https://github.com/froala/wysiwyg-editor/issues/2256
3629
+ editor.events.$on($(editor.o_win), 'keydown', function () {
3630
+ if (mouseDownFlag && mouseDownCellFlag) {
3631
+ mouseDownFlag = false;
3632
+ mouseDownCellFlag = false;
3633
+
3634
+ // Allow text selection.
3635
+ editor.$el.removeClass('fr-no-selection');
3636
+ editor.edit.on();
3637
+
3638
+ editor.selection.setAtEnd(editor.$el.find('.fr-selected-cell:last').get(0));
3639
+ editor.selection.restore();
3640
+
3641
+ // Remove selected cells.
3642
+ _removeSelection();
3643
+ }
3644
+ });
3645
+
3646
+ // Selecting cells with keyboard or moving cursor with arrow keys.
3647
+ editor.events.$on(editor.$el, 'keydown', function (e) {
3648
+ if (e.shiftKey) {
3649
+ if (_selectCellsWithKeyboard(e) === false) {
3650
+
3651
+ // Timeout needed due to clearSelection timeout.
3652
+ setTimeout(function () {
3653
+ _showEditPopup();
3654
+ }, 0);
3655
+ }
3656
+ }
3657
+ else {
3658
+ _navigateWithArrows(e);
3659
+ }
3660
+ });
3661
+
3662
+ // Prevent backspace from doing browser back.
3663
+ editor.events.on('keydown', function (e) {
3664
+
3665
+ // Tab in cell.
3666
+ if (_useTab(e) === false) return false;
3667
+
3668
+ var selected_cells = selectedCells();
3669
+
3670
+ if (selected_cells.length > 0) {
3671
+
3672
+ // CMD + A clear table cell selection and allow propagation.
3673
+ if (selected_cells.length > 0 && editor.keys.ctrlKey(e) && e.which == $.FE.KEYCODE.A) {
3674
+ _removeSelection();
3675
+
3676
+ if (editor.popups.isVisible('table.edit')) {
3677
+ editor.popups.hide('table.edit');
3678
+ }
3679
+ selected_cells = [];
3680
+
3681
+ return true;
3682
+ }
3683
+
3684
+ // ESC clear table cell selection.
3685
+ if (e.which == $.FE.KEYCODE.ESC) {
3686
+ if (editor.popups.isVisible('table.edit')) {
3687
+ _removeSelection();
3688
+ editor.popups.hide('table.edit');
3689
+ e.preventDefault();
3690
+ e.stopPropagation();
3691
+ e.stopImmediatePropagation();
3692
+ selected_cells = [];
3693
+
3694
+ return false;
3695
+ }
3696
+ }
3697
+
3698
+ // Backspace clears selected cells content.
3699
+ if (selected_cells.length > 1 && (e.which == $.FE.KEYCODE.BACKSPACE || e.which == $.FE.KEYCODE.DELETE)) {
3700
+ editor.undo.saveStep();
3701
+
3702
+ for (var i = 0; i < selected_cells.length; i++) {
3703
+ $(selected_cells[i]).html('<br>');
3704
+
3705
+ if (i == selected_cells.length - 1) {
3706
+ $(selected_cells[i]).prepend($.FE.MARKERS);
3707
+ }
3708
+ }
3709
+
3710
+ editor.selection.restore();
3711
+ editor.undo.saveStep();
3712
+ selected_cells = [];
3713
+
3714
+ return false;
3715
+ }
3716
+
3717
+ // Prevent typing if cells are selected. (Allow browser refresh using keyboard)
3718
+ if (selected_cells.length > 1 && e.which != $.FE.KEYCODE.F10 && !editor.keys.isBrowserAction(e)) {
3719
+ e.preventDefault();
3720
+ selected_cells = [];
3721
+
3722
+ return false;
3723
+ }
3724
+ }
3725
+
3726
+ // We may want to select a cell with keyboard.
3727
+ else {
3728
+
3729
+ // Garbage collector.
3730
+ selected_cells = [];
3731
+
3732
+ if (_selectCellWithKeyboard(e) === false) return false;
3733
+ }
3734
+ }, true);
3735
+
3736
+ // Clean selected cells.
3737
+ var c_selected_cells = [];
3738
+ editor.events.on('html.beforeGet', function () {
3739
+ c_selected_cells = selectedCells();
3740
+
3741
+ for (var i = 0; i < c_selected_cells.length; i++) {
3742
+ c_selected_cells[i].className = (c_selected_cells[i].className || '').replace(/fr-selected-cell/g, '');
3743
+ }
3744
+ });
3745
+
3746
+ editor.events.on('html.afterGet', function () {
3747
+ for (var i = 0; i < c_selected_cells.length; i++) {
3748
+ c_selected_cells[i].className = (c_selected_cells[i].className ? c_selected_cells[i].className.trim() + ' ' : '') + 'fr-selected-cell';
3749
+ }
3750
+ c_selected_cells = [];
3751
+ });
3752
+
3753
+ _initInsertPopup(true);
3754
+ _initEditPopup(true);
3755
+ }
3756
+
3757
+ editor.events.on('destroy', _destroy);
3758
+ }
3759
+
3760
+ return {
3761
+ _init: _init,
3762
+ insert: insert,
3763
+ remove: remove,
3764
+ insertRow: insertRow,
3765
+ deleteRow: deleteRow,
3766
+ insertColumn: insertColumn,
3767
+ deleteColumn: deleteColumn,
3768
+ mergeCells: mergeCells,
3769
+ splitCellVertically: splitCellVertically,
3770
+ splitCellHorizontally: splitCellHorizontally,
3771
+ addHeader: addHeader,
3772
+ removeHeader: removeHeader,
3773
+ setBackground: setBackground,
3774
+ showInsertPopup: _showInsertPopup,
3775
+ showEditPopup: _showEditPopup,
3776
+ showColorsPopup: _showColorsPopup,
3777
+ back: back,
3778
+ verticalAlign: verticalAlign,
3779
+ horizontalAlign: horizontalAlign,
3780
+ applyStyle: applyStyle,
3781
+ selectedTable: selectedTable,
3782
+ selectedCells: selectedCells,
3783
+ customColor: customColor
3784
+ }
3785
+ };
3786
+
3787
+ // Insert table button.
3788
+ $.FE.DefineIcon('insertTable', { NAME: 'table' });
3789
+ $.FE.RegisterCommand('insertTable', {
3790
+ title: 'Insert Table',
3791
+ undo: false,
3792
+ focus: true,
3793
+ refreshOnCallback: false,
3794
+ popup: true,
3795
+ callback: function () {
3796
+ if (!this.popups.isVisible('table.insert')) {
3797
+ this.table.showInsertPopup();
3798
+ }
3799
+ else {
3800
+ if (this.$el.find('.fr-marker').length) {
3801
+ this.events.disableBlur();
3802
+ this.selection.restore();
3803
+ }
3804
+ this.popups.hide('table.insert');
3805
+ }
3806
+ },
3807
+ plugin: 'table'
3808
+ });
3809
+
3810
+ $.FE.RegisterCommand('tableInsert', {
3811
+ callback: function (cmd, rows, cols) {
3812
+ this.table.insert(rows, cols);
3813
+ this.popups.hide('table.insert');
3814
+ }
3815
+ })
3816
+
3817
+ // Table header button.
3818
+ $.FE.DefineIcon('tableHeader', {
3819
+ NAME: 'header',
3820
+ FA5NAME: 'heading'
3821
+ })
3822
+ $.FE.RegisterCommand('tableHeader', {
3823
+ title: 'Table Header',
3824
+ focus: false,
3825
+ toggle: true,
3826
+ callback: function () {
3827
+ var $btn = this.popups.get('table.edit').find('.fr-command[data-cmd="tableHeader"]');
3828
+
3829
+ // If button is active the table has a header,
3830
+ if ($btn.hasClass('fr-active')) {
3831
+ this.table.removeHeader();
3832
+ }
3833
+
3834
+ // Add table header.
3835
+ else {
3836
+ this.table.addHeader();
3837
+ }
3838
+ },
3839
+ refresh: function ($btn) {
3840
+ var $table = this.table.selectedTable();
3841
+
3842
+ if ($table.length > 0) {
3843
+
3844
+ // If table doesn't have a header.
3845
+ if ($table.find('th').length === 0) {
3846
+ $btn.removeClass('fr-active').attr('aria-pressed', false);
3847
+ }
3848
+
3849
+ // Header button is active if table has header.
3850
+ else {
3851
+ $btn.addClass('fr-active').attr('aria-pressed', true);
3852
+ }
3853
+ }
3854
+ }
3855
+ });
3856
+
3857
+ // Table rows action dropdown.
3858
+ $.FE.DefineIcon('tableRows', { NAME: 'bars' })
3859
+ $.FE.RegisterCommand('tableRows', {
3860
+ type: 'dropdown',
3861
+ focus: false,
3862
+ title: 'Row',
3863
+ options: {
3864
+ above: 'Insert row above',
3865
+ below: 'Insert row below',
3866
+ 'delete': 'Delete row'
3867
+ },
3868
+ html: function () {
3869
+ var c = '<ul class="fr-dropdown-list" role="presentation">';
3870
+ var options = $.FE.COMMANDS.tableRows.options;
3871
+
3872
+ for (var val in options) {
3873
+ if (options.hasOwnProperty(val)) {
3874
+ c += '<li role="presentation"><a class="fr-command" tabIndex="-1" role="option" data-cmd="tableRows" data-param1="' + val + '" title="' + this.language.translate(options[val]) + '">' + this.language.translate(options[val]) + '</a></li>';
3875
+ }
3876
+ }
3877
+ c += '</ul>';
3878
+
3879
+ return c;
3880
+ },
3881
+ callback: function (cmd, val) {
3882
+ if (val == 'above' || val == 'below') {
3883
+ this.table.insertRow(val);
3884
+ }
3885
+ else {
3886
+ this.table.deleteRow();
3887
+ }
3888
+ }
3889
+ });
3890
+
3891
+ // Table columns action dropdown.
3892
+ $.FE.DefineIcon('tableColumns', { NAME: 'bars fa-rotate-90' })
3893
+ $.FE.RegisterCommand('tableColumns', {
3894
+ type: 'dropdown',
3895
+ focus: false,
3896
+ title: 'Column',
3897
+ options: {
3898
+ before: 'Insert column before',
3899
+ after: 'Insert column after',
3900
+ 'delete': 'Delete column'
3901
+ },
3902
+ html: function () {
3903
+ var c = '<ul class="fr-dropdown-list" role="presentation">';
3904
+ var options = $.FE.COMMANDS.tableColumns.options;
3905
+
3906
+ for (var val in options) {
3907
+ if (options.hasOwnProperty(val)) {
3908
+ c += '<li role="presentation"><a class="fr-command" tabIndex="-1" role="option" data-cmd="tableColumns" data-param1="' + val + '" title="' + this.language.translate(options[val]) + '">' + this.language.translate(options[val]) + '</a></li>';
3909
+ }
3910
+ }
3911
+ c += '</ul>';
3912
+
3913
+ return c;
3914
+ },
3915
+ callback: function (cmd, val) {
3916
+ if (val == 'before' || val == 'after') {
3917
+ this.table.insertColumn(val);
3918
+ }
3919
+ else {
3920
+ this.table.deleteColumn();
3921
+ }
3922
+ }
3923
+ });
3924
+
3925
+ // Table cells action dropdown.
3926
+ $.FE.DefineIcon('tableCells', {
3927
+ NAME: 'square-o',
3928
+ FA5NAME: 'square'
3929
+ })
3930
+ $.FE.RegisterCommand('tableCells', {
3931
+ type: 'dropdown',
3932
+ focus: false,
3933
+ title: 'Cell',
3934
+ options: {
3935
+ merge: 'Merge cells',
3936
+ 'vertical-split': 'Vertical split',
3937
+ 'horizontal-split': 'Horizontal split'
3938
+ },
3939
+ html: function () {
3940
+ var c = '<ul class="fr-dropdown-list" role="presentation">';
3941
+ var options = $.FE.COMMANDS.tableCells.options;
3942
+
3943
+ for (var val in options) {
3944
+ if (options.hasOwnProperty(val)) {
3945
+ c += '<li role="presentation"><a class="fr-command" tabIndex="-1" role="option" data-cmd="tableCells" data-param1="' + val + '" title="' + this.language.translate(options[val]) + '">' + this.language.translate(options[val]) + '</a></li>';
3946
+ }
3947
+ }
3948
+ c += '</ul>';
3949
+
3950
+ return c;
3951
+ },
3952
+ callback: function (cmd, val) {
3953
+ if (val == 'merge') {
3954
+ this.table.mergeCells();
3955
+ }
3956
+ else if (val == 'vertical-split') {
3957
+ this.table.splitCellVertically();
3958
+ }
3959
+
3960
+ // 'horizontal-split'
3961
+ else {
3962
+ this.table.splitCellHorizontally();
3963
+ }
3964
+ },
3965
+ refreshOnShow: function ($btn, $dropdown) {
3966
+
3967
+ // More than one cell selected.
3968
+ if (this.$el.find('.fr-selected-cell').length > 1) {
3969
+ $dropdown.find('a[data-param1="vertical-split"]').addClass('fr-disabled').attr('aria-disabled', true);
3970
+ $dropdown.find('a[data-param1="horizontal-split"]').addClass('fr-disabled').attr('aria-disabled', true);
3971
+ $dropdown.find('a[data-param1="merge"]').removeClass('fr-disabled').attr('aria-disabled', false);
3972
+ }
3973
+
3974
+ // Only one selected cell.
3975
+ else {
3976
+ $dropdown.find('a[data-param1="merge"]').addClass('fr-disabled').attr('aria-disabled', true);
3977
+ $dropdown.find('a[data-param1="vertical-split"]').removeClass('fr-disabled').attr('aria-disabled', false);
3978
+ $dropdown.find('a[data-param1="horizontal-split"]').removeClass('fr-disabled').attr('aria-disabled', false);
3979
+ }
3980
+ }
3981
+ });
3982
+
3983
+ // Remove table button.
3984
+ $.FE.DefineIcon('tableRemove', { NAME: 'trash' })
3985
+ $.FE.RegisterCommand('tableRemove', {
3986
+ title: 'Remove Table',
3987
+ focus: false,
3988
+ callback: function () {
3989
+ this.table.remove();
3990
+ }
3991
+ });
3992
+
3993
+ // Table styles.
3994
+ $.FE.DefineIcon('tableStyle', { NAME: 'paint-brush' })
3995
+ $.FE.RegisterCommand('tableStyle', {
3996
+ title: 'Table Style',
3997
+ type: 'dropdown',
3998
+ focus: false,
3999
+ html: function () {
4000
+ var c = '<ul class="fr-dropdown-list" role="presentation">';
4001
+ var options = this.opts.tableStyles;
4002
+
4003
+ for (var val in options) {
4004
+ if (options.hasOwnProperty(val)) {
4005
+ c += '<li role="presentation"><a class="fr-command" tabIndex="-1" role="option" data-cmd="tableStyle" data-param1="' + val + '" title="' + this.language.translate(options[val]) + '">' + this.language.translate(options[val]) + '</a></li>';
4006
+ }
4007
+ }
4008
+ c += '</ul>';
4009
+
4010
+ return c;
4011
+ },
4012
+ callback: function (cmd, val) {
4013
+ this.table.applyStyle(val, this.$el.find('.fr-selected-cell').closest('table'), this.opts.tableMultipleStyles, this.opts.tableStyles);
4014
+ },
4015
+ refreshOnShow: function ($btn, $dropdown) {
4016
+ var $table = this.$el.find('.fr-selected-cell').closest('table');
4017
+
4018
+ if ($table) {
4019
+ $dropdown.find('.fr-command').each (function () {
4020
+ var cls = $(this).data('param1');
4021
+ var active = $table.hasClass(cls);
4022
+ $(this).toggleClass('fr-active', active).attr('aria-selected', active);
4023
+ })
4024
+ }
4025
+ }
4026
+ });
4027
+
4028
+ // Table cell background color button.
4029
+ $.FE.DefineIcon('tableCellBackground', { NAME: 'tint' })
4030
+ $.FE.RegisterCommand('tableCellBackground', {
4031
+ title: 'Cell Background',
4032
+ focus: false,
4033
+ popup: true,
4034
+ callback: function () {
4035
+ this.table.showColorsPopup();
4036
+ }
4037
+ });
4038
+
4039
+ // Select table cell background color command.
4040
+ $.FE.RegisterCommand('tableCellBackgroundColor', {
4041
+ undo: true,
4042
+ focus: false,
4043
+ callback: function (cmd, val) {
4044
+ this.table.setBackground(val);
4045
+ }
4046
+ });
4047
+
4048
+ // Table back.
4049
+ $.FE.DefineIcon('tableBack', { NAME: 'arrow-left' });
4050
+ $.FE.RegisterCommand('tableBack', {
4051
+ title: 'Back',
4052
+ undo: false,
4053
+ focus: false,
4054
+ back: true,
4055
+ callback: function () {
4056
+ this.table.back();
4057
+ },
4058
+ refresh: function ($btn) {
4059
+ if (this.table.selectedCells().length === 0 && !this.opts.toolbarInline) {
4060
+ $btn.addClass('fr-hidden');
4061
+ $btn.next('.fr-separator').addClass('fr-hidden');
4062
+ }
4063
+ else {
4064
+ $btn.removeClass('fr-hidden');
4065
+ $btn.next('.fr-separator').removeClass('fr-hidden');
4066
+ }
4067
+ }
4068
+ });
4069
+
4070
+ // Table vertical align dropdown.
4071
+ $.FE.DefineIcon('tableCellVerticalAlign', {
4072
+ NAME: 'arrows-v',
4073
+ FA5NAME: 'arrows-alt-v'
4074
+ })
4075
+ $.FE.RegisterCommand('tableCellVerticalAlign', {
4076
+ type: 'dropdown',
4077
+ focus: false,
4078
+ title: 'Vertical Align',
4079
+ options: {
4080
+ Top: 'Align Top',
4081
+ Middle: 'Align Middle',
4082
+ Bottom: 'Align Bottom'
4083
+ },
4084
+ html: function () {
4085
+ var c = '<ul class="fr-dropdown-list" role="presentation">';
4086
+ var options = $.FE.COMMANDS.tableCellVerticalAlign.options;
4087
+
4088
+ for (var val in options) {
4089
+ if (options.hasOwnProperty(val)) {
4090
+ c += '<li role="presentation"><a class="fr-command" tabIndex="-1" role="option" data-cmd="tableCellVerticalAlign" data-param1="' + val.toLowerCase() + '" title="' + this.language.translate(options[val]) + '">' + this.language.translate(val) + '</a></li>';
4091
+ }
4092
+ }
4093
+ c += '</ul>';
4094
+
4095
+ return c;
4096
+ },
4097
+ callback: function (cmd, val) {
4098
+ this.table.verticalAlign(val);
4099
+ },
4100
+ refreshOnShow: function ($btn, $dropdown) {
4101
+ $dropdown.find('.fr-command[data-param1="' + this.$el.find('.fr-selected-cell').css('vertical-align') + '"]').addClass('fr-active').attr('aria-selected', true);
4102
+ }
4103
+ });
4104
+
4105
+ // Table horizontal align dropdown.
4106
+ $.FE.DefineIcon('tableCellHorizontalAlign', { NAME: 'align-left' });
4107
+ $.FE.DefineIcon('align-left', { NAME: 'align-left' });
4108
+ $.FE.DefineIcon('align-right', { NAME: 'align-right' });
4109
+ $.FE.DefineIcon('align-center', { NAME: 'align-center' });
4110
+ $.FE.DefineIcon('align-justify', { NAME: 'align-justify' });
4111
+ $.FE.RegisterCommand('tableCellHorizontalAlign', {
4112
+ type: 'dropdown',
4113
+ focus: false,
4114
+ title: 'Horizontal Align',
4115
+ options: {
4116
+ left: 'Align Left',
4117
+ center: 'Align Center',
4118
+ right: 'Align Right',
4119
+ justify: 'Align Justify'
4120
+ },
4121
+ html: function () {
4122
+ var c = '<ul class="fr-dropdown-list" role="presentation">';
4123
+ var options = $.FE.COMMANDS.tableCellHorizontalAlign.options;
4124
+
4125
+ for (var val in options) {
4126
+ if (options.hasOwnProperty(val)) {
4127
+ c += '<li role="presentation"><a class="fr-command fr-title" tabIndex="-1" role="option" data-cmd="tableCellHorizontalAlign" data-param1="' + val + '" title="' + this.language.translate(options[val]) + '">' + this.icon.create('align-' + val) + '<span class="fr-sr-only">' + this.language.translate(options[val]) + '</span></a></li>';
4128
+ }
4129
+ }
4130
+ c += '</ul>';
4131
+
4132
+ return c;
4133
+ },
4134
+ callback: function (cmd, val) {
4135
+ this.table.horizontalAlign(val);
4136
+ },
4137
+ refresh: function ($btn) {
4138
+ var selected_cells = this.table.selectedCells();
4139
+
4140
+ if (selected_cells.length) {
4141
+ $btn.find('> *:first').replaceWith(this.icon.create('align-' + this.helpers.getAlignment($(selected_cells[0]))));
4142
+ }
4143
+ },
4144
+ refreshOnShow: function ($btn, $dropdown) {
4145
+ $dropdown.find('.fr-command[data-param1="' + this.helpers.getAlignment(this.$el.find('.fr-selected-cell:first')) + '"]').addClass('fr-active').attr('aria-selected', true);
4146
+ }
4147
+ });
4148
+
4149
+ // Table cell styles.
4150
+ $.FE.DefineIcon('tableCellStyle', { NAME: 'magic' })
4151
+ $.FE.RegisterCommand('tableCellStyle', {
4152
+ title: 'Cell Style',
4153
+ type: 'dropdown',
4154
+ focus: false,
4155
+ html: function () {
4156
+ var c = '<ul class="fr-dropdown-list" role="presentation">';
4157
+ var options = this.opts.tableCellStyles;
4158
+
4159
+ for (var val in options) {
4160
+ if (options.hasOwnProperty(val)) {
4161
+ c += '<li role="presentation"><a class="fr-command" tabIndex="-1" role="option" data-cmd="tableCellStyle" data-param1="' + val + '" title="' + this.language.translate(options[val]) + '">' + this.language.translate(options[val]) + '</a></li>';
4162
+ }
4163
+ }
4164
+ c += '</ul>';
4165
+
4166
+ return c;
4167
+ },
4168
+ callback: function (cmd, val) {
4169
+ this.table.applyStyle(val, this.$el.find('.fr-selected-cell'), this.opts.tableCellMultipleStyles, this.opts.tableCellStyles);
4170
+ },
4171
+ refreshOnShow: function ($btn, $dropdown) {
4172
+ var $cell = this.$el.find('.fr-selected-cell:first');
4173
+
4174
+ if ($cell) {
4175
+ $dropdown.find('.fr-command').each (function () {
4176
+ var cls = $(this).data('param1');
4177
+ var active = $cell.hasClass(cls);
4178
+ $(this).toggleClass('fr-active', active).attr('aria-selected', active);
4179
+ })
4180
+ }
4181
+ }
4182
+ });
4183
+
4184
+ $.FE.RegisterCommand('tableCellBackgroundCustomColor', {
4185
+ title: 'OK',
4186
+ undo: true,
4187
+ callback: function () {
4188
+ this.table.customColor();
4189
+ }
4190
+ });
4191
+
4192
+ $.FE.DefineIcon('tableColorRemove', { NAME: 'eraser' });
4193
+
4194
+ }));