xmt_froala 0.3.1

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