spiderfw 0.6.38 → 0.6.39

Sign up to get free protection for your applications and to get access to all the features.
Files changed (515) hide show
  1. checksums.yaml +13 -5
  2. data/CHANGELOG +23 -0
  3. data/VERSION +1 -1
  4. data/apps/core/admin/views/admin.layout.shtml +6 -0
  5. data/apps/core/auth/controllers/login_controller.rb +1 -1
  6. data/apps/core/components/assets.rb +55 -6
  7. data/apps/core/components/public/bootstrap/scss/_variables.scss +5 -2
  8. data/apps/core/components/public/bootstrap/scss/bootstrap.css +942 -236
  9. data/apps/core/components/public/bootstrap/scss/responsive.css +187 -30
  10. data/apps/core/components/public/css/tinymce/skins/lightgray/AbsoluteLayout.less +17 -0
  11. data/apps/core/components/public/css/tinymce/skins/lightgray/Animations.less +10 -0
  12. data/apps/core/components/public/css/tinymce/skins/lightgray/Button.less +172 -0
  13. data/apps/core/components/public/css/tinymce/skins/lightgray/ButtonGroup.less +71 -0
  14. data/apps/core/components/public/css/tinymce/skins/lightgray/Checkbox.less +49 -0
  15. data/apps/core/components/public/css/tinymce/skins/lightgray/ColorBox.less +6 -0
  16. data/apps/core/components/public/css/tinymce/skins/lightgray/ColorButton.less +72 -0
  17. data/apps/core/components/public/css/tinymce/skins/lightgray/ColorPicker.less +80 -0
  18. data/apps/core/components/public/css/tinymce/skins/lightgray/ComboBox.less +39 -0
  19. data/apps/core/components/public/css/tinymce/skins/lightgray/Container.less +9 -0
  20. data/apps/core/components/public/css/tinymce/skins/lightgray/Content.Inline.less +4 -0
  21. data/apps/core/components/public/css/tinymce/skins/lightgray/Content.Objects.less +166 -0
  22. data/apps/core/components/public/css/tinymce/skins/lightgray/Content.less +27 -0
  23. data/apps/core/components/public/css/tinymce/skins/lightgray/CropRect.less +54 -0
  24. data/apps/core/components/public/css/tinymce/skins/lightgray/FieldSet.less +15 -0
  25. data/apps/core/components/public/css/tinymce/skins/lightgray/FitLayout.less +9 -0
  26. data/apps/core/components/public/css/tinymce/skins/lightgray/FloatPanel.less +69 -0
  27. data/apps/core/components/public/css/tinymce/skins/lightgray/FlowLayout.less +36 -0
  28. data/apps/core/components/public/css/tinymce/skins/lightgray/Icons.Ie7.less +136 -0
  29. data/apps/core/components/public/css/tinymce/skins/lightgray/Icons.less +180 -0
  30. data/apps/core/components/public/css/tinymce/skins/lightgray/Iframe.less +6 -0
  31. data/apps/core/components/public/css/tinymce/skins/lightgray/ImagePanel.less +20 -0
  32. data/apps/core/components/public/css/tinymce/skins/lightgray/InfoBox.less +71 -0
  33. data/apps/core/components/public/css/tinymce/skins/lightgray/Label.less +38 -0
  34. data/apps/core/components/public/css/tinymce/skins/lightgray/ListBox.less +26 -0
  35. data/apps/core/components/public/css/tinymce/skins/lightgray/Menu.less +34 -0
  36. data/apps/core/components/public/css/tinymce/skins/lightgray/MenuBar.less +32 -0
  37. data/apps/core/components/public/css/tinymce/skins/lightgray/MenuButton.less +34 -0
  38. data/apps/core/components/public/css/tinymce/skins/lightgray/MenuItem.less +142 -0
  39. data/apps/core/components/public/css/tinymce/skins/lightgray/Mixins.less +54 -0
  40. data/apps/core/components/public/css/tinymce/skins/lightgray/Notification.less +144 -0
  41. data/apps/core/components/public/css/tinymce/skins/lightgray/Panel.less +7 -0
  42. data/apps/core/components/public/css/tinymce/skins/lightgray/Path.less +45 -0
  43. data/apps/core/components/public/css/tinymce/skins/lightgray/Progress.less +34 -0
  44. data/apps/core/components/public/css/tinymce/skins/lightgray/Radio.less +1 -0
  45. data/apps/core/components/public/css/tinymce/skins/lightgray/Reset.less +32 -0
  46. data/apps/core/components/public/css/tinymce/skins/lightgray/ResizeHandle.less +18 -0
  47. data/apps/core/components/public/css/tinymce/skins/lightgray/Scrollable.less +44 -0
  48. data/apps/core/components/public/css/tinymce/skins/lightgray/SelectBox.less +6 -0
  49. data/apps/core/components/public/css/tinymce/skins/lightgray/Slider.less +29 -0
  50. data/apps/core/components/public/css/tinymce/skins/lightgray/Spacer.less +5 -0
  51. data/apps/core/components/public/css/tinymce/skins/lightgray/SplitButton.less +49 -0
  52. data/apps/core/components/public/css/tinymce/skins/lightgray/StackLayout.less +5 -0
  53. data/apps/core/components/public/css/tinymce/skins/lightgray/TabPanel.less +44 -0
  54. data/apps/core/components/public/css/tinymce/skins/lightgray/TextBox.less +41 -0
  55. data/apps/core/components/public/css/tinymce/skins/lightgray/Throbber.less +19 -0
  56. data/apps/core/components/public/css/tinymce/skins/lightgray/TinyMCE.less +159 -0
  57. data/apps/core/components/public/css/tinymce/skins/lightgray/ToolTip.less +129 -0
  58. data/apps/core/components/public/css/tinymce/skins/lightgray/Variables.less +214 -0
  59. data/apps/core/components/public/css/tinymce/skins/lightgray/Window.less +127 -0
  60. data/apps/core/components/public/css/tinymce/skins/lightgray/content.inline.min.css +1 -0
  61. data/apps/core/components/public/css/tinymce/skins/lightgray/content.min.css +1 -0
  62. data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/readme.md +1 -0
  63. data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce-small.eot +0 -0
  64. data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce-small.json +1277 -0
  65. data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce-small.svg +63 -0
  66. data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce-small.ttf +0 -0
  67. data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce-small.woff +0 -0
  68. data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce.eot +0 -0
  69. data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce.json +3381 -0
  70. data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce.svg +129 -0
  71. data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce.ttf +0 -0
  72. data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce.woff +0 -0
  73. data/apps/core/components/public/css/tinymce/skins/lightgray/img/anchor.gif +0 -0
  74. data/apps/core/components/public/css/tinymce/skins/lightgray/img/loader.gif +0 -0
  75. data/apps/core/components/public/css/tinymce/skins/lightgray/img/object.gif +0 -0
  76. data/apps/core/components/public/css/tinymce/skins/lightgray/img/trans.gif +0 -0
  77. data/apps/core/components/public/css/tinymce/skins/lightgray/skin.dev.less +46 -0
  78. data/apps/core/components/public/css/tinymce/skins/lightgray/skin.ie7.dev.less +46 -0
  79. data/apps/core/components/public/css/tinymce/skins/lightgray/skin.ie7.less +2542 -0
  80. data/apps/core/components/public/css/tinymce/skins/lightgray/skin.ie7.min.css +1 -0
  81. data/apps/core/components/public/css/tinymce/skins/lightgray/skin.less +2586 -0
  82. data/apps/core/components/public/css/tinymce/skins/lightgray/skin.min.css +1 -0
  83. data/apps/core/components/public/js/bootbox_2/bootbox.js +551 -0
  84. data/apps/core/components/public/js/jquery/jquery-ui-1.9.2/ui/i18n/jquery.ui.datepicker-it-min.js +4 -0
  85. data/apps/core/components/public/js/spider.js +7 -3
  86. data/apps/core/components/public/js/tinymce/classes/AddOnManager.js +265 -0
  87. data/apps/core/components/public/js/tinymce/classes/Compat.js +90 -0
  88. data/apps/core/components/public/js/tinymce/classes/DragDropOverrides.js +224 -0
  89. data/apps/core/components/public/js/tinymce/classes/Editor.js +2221 -0
  90. data/apps/core/components/public/js/tinymce/classes/EditorCommands.js +1028 -0
  91. data/apps/core/components/public/js/tinymce/classes/EditorManager.js +715 -0
  92. data/apps/core/components/public/js/tinymce/classes/EditorObservable.js +204 -0
  93. data/apps/core/components/public/js/tinymce/classes/EditorUpload.js +191 -0
  94. data/apps/core/components/public/js/tinymce/classes/EnterKey.js +676 -0
  95. data/apps/core/components/public/js/tinymce/classes/Env.js +177 -0
  96. data/apps/core/components/public/js/tinymce/classes/FocusManager.js +266 -0
  97. data/apps/core/components/public/js/tinymce/classes/ForceBlocks.js +138 -0
  98. data/apps/core/components/public/js/tinymce/classes/Formatter.js +2378 -0
  99. data/apps/core/components/public/js/tinymce/classes/LegacyInput.js +82 -0
  100. data/apps/core/components/public/js/tinymce/classes/Mode.js +54 -0
  101. data/apps/core/components/public/js/tinymce/classes/NodeChange.js +154 -0
  102. data/apps/core/components/public/js/tinymce/classes/NotificationManager.js +156 -0
  103. data/apps/core/components/public/js/tinymce/classes/Register.js +34 -0
  104. data/apps/core/components/public/js/tinymce/classes/SelectionOverrides.js +810 -0
  105. data/apps/core/components/public/js/tinymce/classes/Shortcuts.js +172 -0
  106. data/apps/core/components/public/js/tinymce/classes/UndoManager.js +346 -0
  107. data/apps/core/components/public/js/tinymce/classes/WindowManager.js +278 -0
  108. data/apps/core/components/public/js/tinymce/classes/caret/CaretBookmark.js +263 -0
  109. data/apps/core/components/public/js/tinymce/classes/caret/CaretCandidate.js +86 -0
  110. data/apps/core/components/public/js/tinymce/classes/caret/CaretContainer.js +151 -0
  111. data/apps/core/components/public/js/tinymce/classes/caret/CaretPosition.js +371 -0
  112. data/apps/core/components/public/js/tinymce/classes/caret/CaretUtils.js +302 -0
  113. data/apps/core/components/public/js/tinymce/classes/caret/CaretWalker.js +215 -0
  114. data/apps/core/components/public/js/tinymce/classes/caret/FakeCaret.js +192 -0
  115. data/apps/core/components/public/js/tinymce/classes/caret/LineUtils.js +135 -0
  116. data/apps/core/components/public/js/tinymce/classes/caret/LineWalker.js +166 -0
  117. data/apps/core/components/public/js/tinymce/classes/data/Binding.js +79 -0
  118. data/apps/core/components/public/js/tinymce/classes/data/ObservableArray.js +196 -0
  119. data/apps/core/components/public/js/tinymce/classes/data/ObservableObject.js +194 -0
  120. data/apps/core/components/public/js/tinymce/classes/dom/BookmarkManager.js +456 -0
  121. data/apps/core/components/public/js/tinymce/classes/dom/ControlSelection.js +634 -0
  122. data/apps/core/components/public/js/tinymce/classes/dom/DOMUtils.js +1853 -0
  123. data/apps/core/components/public/js/tinymce/classes/dom/Dimensions.js +64 -0
  124. data/apps/core/components/public/js/tinymce/classes/dom/DomQuery.js +1578 -0
  125. data/apps/core/components/public/js/tinymce/classes/dom/ElementUtils.js +118 -0
  126. data/apps/core/components/public/js/tinymce/classes/dom/EventUtils.js +583 -0
  127. data/apps/core/components/public/js/tinymce/classes/dom/NodePath.js +50 -0
  128. data/apps/core/components/public/js/tinymce/classes/dom/NodeType.js +110 -0
  129. data/apps/core/components/public/js/tinymce/classes/dom/Range.js +785 -0
  130. data/apps/core/components/public/js/tinymce/classes/dom/RangeUtils.js +628 -0
  131. data/apps/core/components/public/js/tinymce/classes/dom/ScriptLoader.js +254 -0
  132. data/apps/core/components/public/js/tinymce/classes/dom/Selection.js +1008 -0
  133. data/apps/core/components/public/js/tinymce/classes/dom/Serializer.js +506 -0
  134. data/apps/core/components/public/js/tinymce/classes/dom/Sizzle.jQuery.js +23 -0
  135. data/apps/core/components/public/js/tinymce/classes/dom/Sizzle.js +2039 -0
  136. data/apps/core/components/public/js/tinymce/classes/dom/StyleSheetLoader.js +190 -0
  137. data/apps/core/components/public/js/tinymce/classes/dom/TreeWalker.js +127 -0
  138. data/apps/core/components/public/js/tinymce/classes/dom/TridentSelection.js +508 -0
  139. data/apps/core/components/public/js/tinymce/classes/file/BlobCache.js +72 -0
  140. data/apps/core/components/public/js/tinymce/classes/file/Conversions.js +106 -0
  141. data/apps/core/components/public/js/tinymce/classes/file/ImageScanner.js +145 -0
  142. data/apps/core/components/public/js/tinymce/classes/file/Uploader.js +193 -0
  143. data/apps/core/components/public/js/tinymce/classes/fmt/Hooks.js +66 -0
  144. data/apps/core/components/public/js/tinymce/classes/fmt/Preview.js +151 -0
  145. data/apps/core/components/public/js/tinymce/classes/geom/ClientRect.js +136 -0
  146. data/apps/core/components/public/js/tinymce/classes/geom/Rect.js +214 -0
  147. data/apps/core/components/public/js/tinymce/classes/html/DomParser.js +822 -0
  148. data/apps/core/components/public/js/tinymce/classes/html/Entities.js +268 -0
  149. data/apps/core/components/public/js/tinymce/classes/html/Node.js +496 -0
  150. data/apps/core/components/public/js/tinymce/classes/html/SaxParser.js +474 -0
  151. data/apps/core/components/public/js/tinymce/classes/html/Schema.js +1004 -0
  152. data/apps/core/components/public/js/tinymce/classes/html/Serializer.js +158 -0
  153. data/apps/core/components/public/js/tinymce/classes/html/Styles.js +363 -0
  154. data/apps/core/components/public/js/tinymce/classes/html/Writer.js +199 -0
  155. data/apps/core/components/public/js/tinymce/classes/jquery.tinymce.js +377 -0
  156. data/apps/core/components/public/js/tinymce/classes/text/ExtendingChar.js +53 -0
  157. data/apps/core/components/public/js/tinymce/classes/text/Zwsp.js +36 -0
  158. data/apps/core/components/public/js/tinymce/classes/ui/AbsoluteLayout.js +63 -0
  159. data/apps/core/components/public/js/tinymce/classes/ui/BoxUtils.js +98 -0
  160. data/apps/core/components/public/js/tinymce/classes/ui/Button.js +199 -0
  161. data/apps/core/components/public/js/tinymce/classes/ui/ButtonGroup.js +62 -0
  162. data/apps/core/components/public/js/tinymce/classes/ui/Checkbox.js +162 -0
  163. data/apps/core/components/public/js/tinymce/classes/ui/ClassList.js +149 -0
  164. data/apps/core/components/public/js/tinymce/classes/ui/Collection.js +438 -0
  165. data/apps/core/components/public/js/tinymce/classes/ui/ColorBox.js +72 -0
  166. data/apps/core/components/public/js/tinymce/classes/ui/ColorButton.js +124 -0
  167. data/apps/core/components/public/js/tinymce/classes/ui/ColorPicker.js +206 -0
  168. data/apps/core/components/public/js/tinymce/classes/ui/ComboBox.js +306 -0
  169. data/apps/core/components/public/js/tinymce/classes/ui/Container.js +506 -0
  170. data/apps/core/components/public/js/tinymce/classes/ui/Control.js +1301 -0
  171. data/apps/core/components/public/js/tinymce/classes/ui/DomUtils.js +107 -0
  172. data/apps/core/components/public/js/tinymce/classes/ui/DragHelper.js +144 -0
  173. data/apps/core/components/public/js/tinymce/classes/ui/ElementPath.js +79 -0
  174. data/apps/core/components/public/js/tinymce/classes/ui/Factory.js +105 -0
  175. data/apps/core/components/public/js/tinymce/classes/ui/FieldSet.js +59 -0
  176. data/apps/core/components/public/js/tinymce/classes/ui/FilePicker.js +85 -0
  177. data/apps/core/components/public/js/tinymce/classes/ui/FitLayout.js +48 -0
  178. data/apps/core/components/public/js/tinymce/classes/ui/FlexLayout.js +246 -0
  179. data/apps/core/components/public/js/tinymce/classes/ui/FloatPanel.js +409 -0
  180. data/apps/core/components/public/js/tinymce/classes/ui/FlowLayout.js +46 -0
  181. data/apps/core/components/public/js/tinymce/classes/ui/Form.js +157 -0
  182. data/apps/core/components/public/js/tinymce/classes/ui/FormItem.js +56 -0
  183. data/apps/core/components/public/js/tinymce/classes/ui/FormatControls.js +535 -0
  184. data/apps/core/components/public/js/tinymce/classes/ui/GridLayout.js +233 -0
  185. data/apps/core/components/public/js/tinymce/classes/ui/Iframe.js +85 -0
  186. data/apps/core/components/public/js/tinymce/classes/ui/InfoBox.js +93 -0
  187. data/apps/core/components/public/js/tinymce/classes/ui/KeyboardNavigation.js +406 -0
  188. data/apps/core/components/public/js/tinymce/classes/ui/Label.js +144 -0
  189. data/apps/core/components/public/js/tinymce/classes/ui/Layout.js +120 -0
  190. data/apps/core/components/public/js/tinymce/classes/ui/ListBox.js +152 -0
  191. data/apps/core/components/public/js/tinymce/classes/ui/Menu.js +195 -0
  192. data/apps/core/components/public/js/tinymce/classes/ui/MenuBar.js +33 -0
  193. data/apps/core/components/public/js/tinymce/classes/ui/MenuButton.js +268 -0
  194. data/apps/core/components/public/js/tinymce/classes/ui/MenuItem.js +336 -0
  195. data/apps/core/components/public/js/tinymce/classes/ui/MessageBox.js +202 -0
  196. data/apps/core/components/public/js/tinymce/classes/ui/Movable.js +200 -0
  197. data/apps/core/components/public/js/tinymce/classes/ui/Notification.js +154 -0
  198. data/apps/core/components/public/js/tinymce/classes/ui/Panel.js +67 -0
  199. data/apps/core/components/public/js/tinymce/classes/ui/PanelButton.js +114 -0
  200. data/apps/core/components/public/js/tinymce/classes/ui/Path.js +127 -0
  201. data/apps/core/components/public/js/tinymce/classes/ui/Progress.js +81 -0
  202. data/apps/core/components/public/js/tinymce/classes/ui/Radio.js +29 -0
  203. data/apps/core/components/public/js/tinymce/classes/ui/ReflowQueue.js +79 -0
  204. data/apps/core/components/public/js/tinymce/classes/ui/Resizable.js +68 -0
  205. data/apps/core/components/public/js/tinymce/classes/ui/ResizeHandle.js +86 -0
  206. data/apps/core/components/public/js/tinymce/classes/ui/Scrollable.js +141 -0
  207. data/apps/core/components/public/js/tinymce/classes/ui/SelectBox.js +106 -0
  208. data/apps/core/components/public/js/tinymce/classes/ui/Selector.js +369 -0
  209. data/apps/core/components/public/js/tinymce/classes/ui/Slider.js +161 -0
  210. data/apps/core/components/public/js/tinymce/classes/ui/Spacer.js +39 -0
  211. data/apps/core/components/public/js/tinymce/classes/ui/SplitButton.js +146 -0
  212. data/apps/core/components/public/js/tinymce/classes/ui/StackLayout.js +34 -0
  213. data/apps/core/components/public/js/tinymce/classes/ui/TabPanel.js +178 -0
  214. data/apps/core/components/public/js/tinymce/classes/ui/TextBox.js +208 -0
  215. data/apps/core/components/public/js/tinymce/classes/ui/Throbber.js +88 -0
  216. data/apps/core/components/public/js/tinymce/classes/ui/Toolbar.js +56 -0
  217. data/apps/core/components/public/js/tinymce/classes/ui/Tooltip.js +73 -0
  218. data/apps/core/components/public/js/tinymce/classes/ui/Widget.js +151 -0
  219. data/apps/core/components/public/js/tinymce/classes/ui/Window.js +474 -0
  220. data/apps/core/components/public/js/tinymce/classes/util/Arr.js +153 -0
  221. data/apps/core/components/public/js/tinymce/classes/util/Class.js +167 -0
  222. data/apps/core/components/public/js/tinymce/classes/util/Color.js +235 -0
  223. data/apps/core/components/public/js/tinymce/classes/util/Delay.js +191 -0
  224. data/apps/core/components/public/js/tinymce/classes/util/EventDispatcher.js +294 -0
  225. data/apps/core/components/public/js/tinymce/classes/util/Fun.js +87 -0
  226. data/apps/core/components/public/js/tinymce/classes/util/I18n.js +116 -0
  227. data/apps/core/components/public/js/tinymce/classes/util/JSON.js +109 -0
  228. data/apps/core/components/public/js/tinymce/classes/util/JSONP.js +38 -0
  229. data/apps/core/components/public/js/tinymce/classes/util/JSONRequest.js +110 -0
  230. data/apps/core/components/public/js/tinymce/classes/util/LocalStorage.js +213 -0
  231. data/apps/core/components/public/js/tinymce/classes/util/Observable.js +129 -0
  232. data/apps/core/components/public/js/tinymce/classes/util/Promise.js +200 -0
  233. data/apps/core/components/public/js/tinymce/classes/util/Quirks.js +1701 -0
  234. data/apps/core/components/public/js/tinymce/classes/util/Tools.js +442 -0
  235. data/apps/core/components/public/js/tinymce/classes/util/URI.js +411 -0
  236. data/apps/core/components/public/js/tinymce/classes/util/VK.js +37 -0
  237. data/apps/core/components/public/js/tinymce/classes/util/XHR.js +110 -0
  238. data/apps/core/components/public/js/tinymce/jquery.tinymce.min.js +1 -0
  239. data/apps/core/components/public/js/tinymce/langs/it.js +219 -0
  240. data/apps/core/components/public/js/tinymce/langs/readme.md +3 -0
  241. data/apps/core/components/public/js/tinymce/license.txt +504 -0
  242. data/apps/core/components/public/js/tinymce/plugins/advlist/plugin.js +97 -0
  243. data/apps/core/components/public/js/tinymce/plugins/advlist/plugin.min.js +1 -0
  244. data/apps/core/components/public/js/tinymce/plugins/anchor/plugin.js +55 -0
  245. data/apps/core/components/public/js/tinymce/plugins/anchor/plugin.min.js +1 -0
  246. data/apps/core/components/public/js/tinymce/plugins/autolink/plugin.js +204 -0
  247. data/apps/core/components/public/js/tinymce/plugins/autolink/plugin.min.js +1 -0
  248. data/apps/core/components/public/js/tinymce/plugins/autoresize/plugin.js +162 -0
  249. data/apps/core/components/public/js/tinymce/plugins/autoresize/plugin.min.js +1 -0
  250. data/apps/core/components/public/js/tinymce/plugins/autosave/plugin.js +165 -0
  251. data/apps/core/components/public/js/tinymce/plugins/autosave/plugin.min.js +1 -0
  252. data/apps/core/components/public/js/tinymce/plugins/bbcode/plugin.js +123 -0
  253. data/apps/core/components/public/js/tinymce/plugins/bbcode/plugin.min.js +1 -0
  254. data/apps/core/components/public/js/tinymce/plugins/charmap/plugin.js +450 -0
  255. data/apps/core/components/public/js/tinymce/plugins/charmap/plugin.min.js +1 -0
  256. data/apps/core/components/public/js/tinymce/plugins/code/plugin.js +60 -0
  257. data/apps/core/components/public/js/tinymce/plugins/code/plugin.min.js +1 -0
  258. data/apps/core/components/public/js/tinymce/plugins/codesample/classes/Dialog.js +121 -0
  259. data/apps/core/components/public/js/tinymce/plugins/codesample/classes/Plugin.js +95 -0
  260. data/apps/core/components/public/js/tinymce/plugins/codesample/classes/Prism.js +937 -0
  261. data/apps/core/components/public/js/tinymce/plugins/codesample/classes/Utils.js +33 -0
  262. data/apps/core/components/public/js/tinymce/plugins/codesample/css/prism.css +138 -0
  263. data/apps/core/components/public/js/tinymce/plugins/codesample/plugin.dev.js +141 -0
  264. data/apps/core/components/public/js/tinymce/plugins/codesample/plugin.js +1299 -0
  265. data/apps/core/components/public/js/tinymce/plugins/codesample/plugin.min.js +1 -0
  266. data/apps/core/components/public/js/tinymce/plugins/colorpicker/plugin.js +112 -0
  267. data/apps/core/components/public/js/tinymce/plugins/colorpicker/plugin.min.js +1 -0
  268. data/apps/core/components/public/js/tinymce/plugins/compat3x/css/dialog.css +118 -0
  269. data/apps/core/components/public/js/tinymce/plugins/compat3x/img/buttons.png +0 -0
  270. data/apps/core/components/public/js/tinymce/plugins/compat3x/img/icons.gif +0 -0
  271. data/apps/core/components/public/js/tinymce/plugins/compat3x/img/items.gif +0 -0
  272. data/apps/core/components/public/js/tinymce/plugins/compat3x/img/menu_arrow.gif +0 -0
  273. data/apps/core/components/public/js/tinymce/plugins/compat3x/img/menu_check.gif +0 -0
  274. data/apps/core/components/public/js/tinymce/plugins/compat3x/img/progress.gif +0 -0
  275. data/apps/core/components/public/js/tinymce/plugins/compat3x/img/tabs.gif +0 -0
  276. data/apps/core/components/public/js/tinymce/plugins/compat3x/plugin.js +297 -0
  277. data/apps/core/components/public/js/tinymce/plugins/compat3x/plugin.min.js +1 -0
  278. data/apps/core/components/public/js/tinymce/plugins/compat3x/tiny_mce_popup.js +542 -0
  279. data/apps/core/components/public/js/tinymce/plugins/compat3x/utils/editable_selects.js +70 -0
  280. data/apps/core/components/public/js/tinymce/plugins/compat3x/utils/form_utils.js +210 -0
  281. data/apps/core/components/public/js/tinymce/plugins/compat3x/utils/mctabs.js +164 -0
  282. data/apps/core/components/public/js/tinymce/plugins/compat3x/utils/validate.js +252 -0
  283. data/apps/core/components/public/js/tinymce/plugins/contextmenu/plugin.js +88 -0
  284. data/apps/core/components/public/js/tinymce/plugins/contextmenu/plugin.min.js +1 -0
  285. data/apps/core/components/public/js/tinymce/plugins/directionality/plugin.js +64 -0
  286. data/apps/core/components/public/js/tinymce/plugins/directionality/plugin.min.js +1 -0
  287. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-cool.gif +0 -0
  288. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-cry.gif +0 -0
  289. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-embarassed.gif +0 -0
  290. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-foot-in-mouth.gif +0 -0
  291. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-frown.gif +0 -0
  292. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-innocent.gif +0 -0
  293. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-kiss.gif +0 -0
  294. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-laughing.gif +0 -0
  295. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-money-mouth.gif +0 -0
  296. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-sealed.gif +0 -0
  297. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-smile.gif +0 -0
  298. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-surprised.gif +0 -0
  299. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-tongue-out.gif +0 -0
  300. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-undecided.gif +0 -0
  301. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-wink.gif +0 -0
  302. data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-yell.gif +0 -0
  303. data/apps/core/components/public/js/tinymce/plugins/emoticons/plugin.js +65 -0
  304. data/apps/core/components/public/js/tinymce/plugins/emoticons/plugin.min.js +1 -0
  305. data/apps/core/components/public/js/tinymce/plugins/example/dialog.html +8 -0
  306. data/apps/core/components/public/js/tinymce/plugins/example/plugin.js +68 -0
  307. data/apps/core/components/public/js/tinymce/plugins/example/plugin.min.js +1 -0
  308. data/apps/core/components/public/js/tinymce/plugins/example_dependency/plugin.js +22 -0
  309. data/apps/core/components/public/js/tinymce/plugins/example_dependency/plugin.min.js +1 -0
  310. data/apps/core/components/public/js/tinymce/plugins/fullpage/plugin.js +490 -0
  311. data/apps/core/components/public/js/tinymce/plugins/fullpage/plugin.min.js +1 -0
  312. data/apps/core/components/public/js/tinymce/plugins/fullscreen/plugin.js +154 -0
  313. data/apps/core/components/public/js/tinymce/plugins/fullscreen/plugin.min.js +1 -0
  314. data/apps/core/components/public/js/tinymce/plugins/hr/plugin.js +30 -0
  315. data/apps/core/components/public/js/tinymce/plugins/hr/plugin.min.js +1 -0
  316. data/apps/core/components/public/js/tinymce/plugins/image/plugin.js +630 -0
  317. data/apps/core/components/public/js/tinymce/plugins/image/plugin.min.js +1 -0
  318. data/apps/core/components/public/js/tinymce/plugins/imagetools/config/bolt/atomic.js +6 -0
  319. data/apps/core/components/public/js/tinymce/plugins/imagetools/config/bolt/browser.js +6 -0
  320. data/apps/core/components/public/js/tinymce/plugins/imagetools/config/bolt/demo.js +8 -0
  321. data/apps/core/components/public/js/tinymce/plugins/imagetools/config/bolt/prod.js +6 -0
  322. data/apps/core/components/public/js/tinymce/plugins/imagetools/plugin.js +2668 -0
  323. data/apps/core/components/public/js/tinymce/plugins/imagetools/plugin.min.js +1 -0
  324. data/apps/core/components/public/js/tinymce/plugins/imagetools/src/demo/html/demo.html +16 -0
  325. data/apps/core/components/public/js/tinymce/plugins/imagetools/src/demo/js/Demo.js +92 -0
  326. data/apps/core/components/public/js/tinymce/plugins/imagetools/src/main/js/CropRect.js +214 -0
  327. data/apps/core/components/public/js/tinymce/plugins/imagetools/src/main/js/Dialog.js +485 -0
  328. data/apps/core/components/public/js/tinymce/plugins/imagetools/src/main/js/ImagePanel.js +218 -0
  329. data/apps/core/components/public/js/tinymce/plugins/imagetools/src/main/js/Plugin.js +411 -0
  330. data/apps/core/components/public/js/tinymce/plugins/imagetools/src/main/js/UndoStack.js +57 -0
  331. data/apps/core/components/public/js/tinymce/plugins/importcss/plugin.js +227 -0
  332. data/apps/core/components/public/js/tinymce/plugins/importcss/plugin.min.js +1 -0
  333. data/apps/core/components/public/js/tinymce/plugins/insertdatetime/plugin.js +121 -0
  334. data/apps/core/components/public/js/tinymce/plugins/insertdatetime/plugin.min.js +1 -0
  335. data/apps/core/components/public/js/tinymce/plugins/layer/plugin.js +225 -0
  336. data/apps/core/components/public/js/tinymce/plugins/layer/plugin.min.js +1 -0
  337. data/apps/core/components/public/js/tinymce/plugins/legacyoutput/plugin.js +211 -0
  338. data/apps/core/components/public/js/tinymce/plugins/legacyoutput/plugin.min.js +1 -0
  339. data/apps/core/components/public/js/tinymce/plugins/link/plugin.js +403 -0
  340. data/apps/core/components/public/js/tinymce/plugins/link/plugin.min.js +1 -0
  341. data/apps/core/components/public/js/tinymce/plugins/lists/plugin.js +885 -0
  342. data/apps/core/components/public/js/tinymce/plugins/lists/plugin.min.js +1 -0
  343. data/apps/core/components/public/js/tinymce/plugins/media/moxieplayer.swf +0 -0
  344. data/apps/core/components/public/js/tinymce/plugins/media/plugin.js +878 -0
  345. data/apps/core/components/public/js/tinymce/plugins/media/plugin.min.js +1 -0
  346. data/apps/core/components/public/js/tinymce/plugins/nonbreaking/plugin.js +53 -0
  347. data/apps/core/components/public/js/tinymce/plugins/nonbreaking/plugin.min.js +1 -0
  348. data/apps/core/components/public/js/tinymce/plugins/noneditable/plugin.js +101 -0
  349. data/apps/core/components/public/js/tinymce/plugins/noneditable/plugin.min.js +1 -0
  350. data/apps/core/components/public/js/tinymce/plugins/pagebreak/plugin.js +88 -0
  351. data/apps/core/components/public/js/tinymce/plugins/pagebreak/plugin.min.js +1 -0
  352. data/apps/core/components/public/js/tinymce/plugins/paste/classes/Clipboard.js +672 -0
  353. data/apps/core/components/public/js/tinymce/plugins/paste/classes/Plugin.js +121 -0
  354. data/apps/core/components/public/js/tinymce/plugins/paste/classes/Quirks.js +159 -0
  355. data/apps/core/components/public/js/tinymce/plugins/paste/classes/Utils.js +140 -0
  356. data/apps/core/components/public/js/tinymce/plugins/paste/classes/WordFilter.js +500 -0
  357. data/apps/core/components/public/js/tinymce/plugins/paste/plugin.dev.js +142 -0
  358. data/apps/core/components/public/js/tinymce/plugins/paste/plugin.js +1708 -0
  359. data/apps/core/components/public/js/tinymce/plugins/paste/plugin.min.js +1 -0
  360. data/apps/core/components/public/js/tinymce/plugins/preview/plugin.js +88 -0
  361. data/apps/core/components/public/js/tinymce/plugins/preview/plugin.min.js +1 -0
  362. data/apps/core/components/public/js/tinymce/plugins/print/plugin.js +32 -0
  363. data/apps/core/components/public/js/tinymce/plugins/print/plugin.min.js +1 -0
  364. data/apps/core/components/public/js/tinymce/plugins/save/plugin.js +98 -0
  365. data/apps/core/components/public/js/tinymce/plugins/save/plugin.min.js +1 -0
  366. data/apps/core/components/public/js/tinymce/plugins/searchreplace/plugin.js +609 -0
  367. data/apps/core/components/public/js/tinymce/plugins/searchreplace/plugin.min.js +1 -0
  368. data/apps/core/components/public/js/tinymce/plugins/spellchecker/classes/DomTextMatcher.js +480 -0
  369. data/apps/core/components/public/js/tinymce/plugins/spellchecker/classes/Plugin.js +439 -0
  370. data/apps/core/components/public/js/tinymce/plugins/spellchecker/plugin.dev.js +139 -0
  371. data/apps/core/components/public/js/tinymce/plugins/spellchecker/plugin.js +1026 -0
  372. data/apps/core/components/public/js/tinymce/plugins/spellchecker/plugin.min.js +1 -0
  373. data/apps/core/components/public/js/tinymce/plugins/tabfocus/plugin.js +120 -0
  374. data/apps/core/components/public/js/tinymce/plugins/tabfocus/plugin.min.js +1 -0
  375. data/apps/core/components/public/js/tinymce/plugins/table/classes/CellSelection.js +202 -0
  376. data/apps/core/components/public/js/tinymce/plugins/table/classes/Dialogs.js +824 -0
  377. data/apps/core/components/public/js/tinymce/plugins/table/classes/Plugin.js +599 -0
  378. data/apps/core/components/public/js/tinymce/plugins/table/classes/Quirks.js +400 -0
  379. data/apps/core/components/public/js/tinymce/plugins/table/classes/ResizeBars.js +984 -0
  380. data/apps/core/components/public/js/tinymce/plugins/table/classes/TableGrid.js +941 -0
  381. data/apps/core/components/public/js/tinymce/plugins/table/classes/Utils.js +36 -0
  382. data/apps/core/components/public/js/tinymce/plugins/table/plugin.dev.js +142 -0
  383. data/apps/core/components/public/js/tinymce/plugins/table/plugin.js +4106 -0
  384. data/apps/core/components/public/js/tinymce/plugins/table/plugin.min.js +2 -0
  385. data/apps/core/components/public/js/tinymce/plugins/template/plugin.js +270 -0
  386. data/apps/core/components/public/js/tinymce/plugins/template/plugin.min.js +1 -0
  387. data/apps/core/components/public/js/tinymce/plugins/textcolor/plugin.js +282 -0
  388. data/apps/core/components/public/js/tinymce/plugins/textcolor/plugin.min.js +1 -0
  389. data/apps/core/components/public/js/tinymce/plugins/textpattern/plugin.js +268 -0
  390. data/apps/core/components/public/js/tinymce/plugins/textpattern/plugin.min.js +1 -0
  391. data/apps/core/components/public/js/tinymce/plugins/visualblocks/css/visualblocks.css +135 -0
  392. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/address.gif +0 -0
  393. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/article.gif +0 -0
  394. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/aside.gif +0 -0
  395. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/blockquote.gif +0 -0
  396. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/div.gif +0 -0
  397. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/dl.gif +0 -0
  398. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/figure.gif +0 -0
  399. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/h1.gif +0 -0
  400. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/h2.gif +0 -0
  401. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/h3.gif +0 -0
  402. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/h4.gif +0 -0
  403. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/h5.gif +0 -0
  404. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/h6.gif +0 -0
  405. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/hgroup.gif +0 -0
  406. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/ol.gif +0 -0
  407. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/p.gif +0 -0
  408. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/pre.gif +0 -0
  409. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/section.gif +0 -0
  410. data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/ul.gif +0 -0
  411. data/apps/core/components/public/js/tinymce/plugins/visualblocks/plugin.js +86 -0
  412. data/apps/core/components/public/js/tinymce/plugins/visualblocks/plugin.min.js +1 -0
  413. data/apps/core/components/public/js/tinymce/plugins/visualchars/plugin.js +123 -0
  414. data/apps/core/components/public/js/tinymce/plugins/visualchars/plugin.min.js +1 -0
  415. data/apps/core/components/public/js/tinymce/plugins/wordcount/plugin.js +69 -0
  416. data/apps/core/components/public/js/tinymce/plugins/wordcount/plugin.min.js +1 -0
  417. data/apps/core/components/public/js/tinymce/skins/lightgray/AbsoluteLayout.less +17 -0
  418. data/apps/core/components/public/js/tinymce/skins/lightgray/Animations.less +10 -0
  419. data/apps/core/components/public/js/tinymce/skins/lightgray/Button.less +172 -0
  420. data/apps/core/components/public/js/tinymce/skins/lightgray/ButtonGroup.less +71 -0
  421. data/apps/core/components/public/js/tinymce/skins/lightgray/Checkbox.less +49 -0
  422. data/apps/core/components/public/js/tinymce/skins/lightgray/ColorBox.less +6 -0
  423. data/apps/core/components/public/js/tinymce/skins/lightgray/ColorButton.less +72 -0
  424. data/apps/core/components/public/js/tinymce/skins/lightgray/ColorPicker.less +80 -0
  425. data/apps/core/components/public/js/tinymce/skins/lightgray/ComboBox.less +39 -0
  426. data/apps/core/components/public/js/tinymce/skins/lightgray/Container.less +9 -0
  427. data/apps/core/components/public/js/tinymce/skins/lightgray/Content.Inline.less +4 -0
  428. data/apps/core/components/public/js/tinymce/skins/lightgray/Content.Objects.less +166 -0
  429. data/apps/core/components/public/js/tinymce/skins/lightgray/Content.less +27 -0
  430. data/apps/core/components/public/js/tinymce/skins/lightgray/CropRect.less +54 -0
  431. data/apps/core/components/public/js/tinymce/skins/lightgray/FieldSet.less +15 -0
  432. data/apps/core/components/public/js/tinymce/skins/lightgray/FitLayout.less +9 -0
  433. data/apps/core/components/public/js/tinymce/skins/lightgray/FloatPanel.less +69 -0
  434. data/apps/core/components/public/js/tinymce/skins/lightgray/FlowLayout.less +36 -0
  435. data/apps/core/components/public/js/tinymce/skins/lightgray/Icons.Ie7.less +136 -0
  436. data/apps/core/components/public/js/tinymce/skins/lightgray/Icons.less +180 -0
  437. data/apps/core/components/public/js/tinymce/skins/lightgray/Iframe.less +6 -0
  438. data/apps/core/components/public/js/tinymce/skins/lightgray/ImagePanel.less +20 -0
  439. data/apps/core/components/public/js/tinymce/skins/lightgray/InfoBox.less +71 -0
  440. data/apps/core/components/public/js/tinymce/skins/lightgray/Label.less +38 -0
  441. data/apps/core/components/public/js/tinymce/skins/lightgray/ListBox.less +26 -0
  442. data/apps/core/components/public/js/tinymce/skins/lightgray/Menu.less +34 -0
  443. data/apps/core/components/public/js/tinymce/skins/lightgray/MenuBar.less +32 -0
  444. data/apps/core/components/public/js/tinymce/skins/lightgray/MenuButton.less +34 -0
  445. data/apps/core/components/public/js/tinymce/skins/lightgray/MenuItem.less +142 -0
  446. data/apps/core/components/public/js/tinymce/skins/lightgray/Mixins.less +54 -0
  447. data/apps/core/components/public/js/tinymce/skins/lightgray/Notification.less +144 -0
  448. data/apps/core/components/public/js/tinymce/skins/lightgray/Panel.less +7 -0
  449. data/apps/core/components/public/js/tinymce/skins/lightgray/Path.less +45 -0
  450. data/apps/core/components/public/js/tinymce/skins/lightgray/Progress.less +34 -0
  451. data/apps/core/components/public/js/tinymce/skins/lightgray/Radio.less +1 -0
  452. data/apps/core/components/public/js/tinymce/skins/lightgray/Reset.less +32 -0
  453. data/apps/core/components/public/js/tinymce/skins/lightgray/ResizeHandle.less +18 -0
  454. data/apps/core/components/public/js/tinymce/skins/lightgray/Scrollable.less +44 -0
  455. data/apps/core/components/public/js/tinymce/skins/lightgray/SelectBox.less +6 -0
  456. data/apps/core/components/public/js/tinymce/skins/lightgray/Slider.less +29 -0
  457. data/apps/core/components/public/js/tinymce/skins/lightgray/Spacer.less +5 -0
  458. data/apps/core/components/public/js/tinymce/skins/lightgray/SplitButton.less +49 -0
  459. data/apps/core/components/public/js/tinymce/skins/lightgray/StackLayout.less +5 -0
  460. data/apps/core/components/public/js/tinymce/skins/lightgray/TabPanel.less +44 -0
  461. data/apps/core/components/public/js/tinymce/skins/lightgray/TextBox.less +41 -0
  462. data/apps/core/components/public/js/tinymce/skins/lightgray/Throbber.less +19 -0
  463. data/apps/core/components/public/js/tinymce/skins/lightgray/TinyMCE.less +159 -0
  464. data/apps/core/components/public/js/tinymce/skins/lightgray/ToolTip.less +129 -0
  465. data/apps/core/components/public/js/tinymce/skins/lightgray/Variables.less +214 -0
  466. data/apps/core/components/public/js/tinymce/skins/lightgray/Window.less +127 -0
  467. data/apps/core/components/public/js/tinymce/skins/lightgray/content.inline.min.css +1 -0
  468. data/apps/core/components/public/js/tinymce/skins/lightgray/content.min.css +1 -0
  469. data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/readme.md +1 -0
  470. data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce-small.eot +0 -0
  471. data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce-small.json +1277 -0
  472. data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce-small.svg +63 -0
  473. data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce-small.ttf +0 -0
  474. data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce-small.woff +0 -0
  475. data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce.eot +0 -0
  476. data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce.json +3381 -0
  477. data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce.svg +129 -0
  478. data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce.ttf +0 -0
  479. data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce.woff +0 -0
  480. data/apps/core/components/public/js/tinymce/skins/lightgray/img/anchor.gif +0 -0
  481. data/apps/core/components/public/js/tinymce/skins/lightgray/img/loader.gif +0 -0
  482. data/apps/core/components/public/js/tinymce/skins/lightgray/img/object.gif +0 -0
  483. data/apps/core/components/public/js/tinymce/skins/lightgray/img/trans.gif +0 -0
  484. data/apps/core/components/public/js/tinymce/skins/lightgray/skin.dev.less +46 -0
  485. data/apps/core/components/public/js/tinymce/skins/lightgray/skin.ie7.dev.less +46 -0
  486. data/apps/core/components/public/js/tinymce/skins/lightgray/skin.ie7.less +2542 -0
  487. data/apps/core/components/public/js/tinymce/skins/lightgray/skin.ie7.min.css +1 -0
  488. data/apps/core/components/public/js/tinymce/skins/lightgray/skin.less +2586 -0
  489. data/apps/core/components/public/js/tinymce/skins/lightgray/skin.min.css +1 -0
  490. data/apps/core/components/public/js/tinymce/themes/modern/theme.js +878 -0
  491. data/apps/core/components/public/js/tinymce/themes/modern/theme.min.js +1 -0
  492. data/apps/core/components/public/js/tinymce/tinymce.dev.js +286 -0
  493. data/apps/core/components/public/js/tinymce/tinymce.js +45811 -0
  494. data/apps/core/components/public/js/tinymce/tinymce.min.js +13 -0
  495. data/apps/core/forms/widgets/form/form.rb +3 -1
  496. data/apps/core/forms/widgets/form/form.shtml +1 -0
  497. data/apps/core/forms/widgets/inputs/search_select/search_select.rb +1 -1
  498. data/apps/core/forms/widgets/inputs/select/select.rb +1 -1
  499. data/apps/core/forms/widgets/inputs/select/select.shtml +0 -1
  500. data/apps/messenger/backends/sms/skebby.rb +1 -1
  501. data/apps/messenger/controllers/mixins/messenger_helper.rb +23 -5
  502. data/lib/spiderfw/config/options/spider.rb +1 -1
  503. data/lib/spiderfw/controller/mixins/visual.rb +2 -0
  504. data/lib/spiderfw/http/adapters/rack.rb +10 -5
  505. data/lib/spiderfw/http/http.rb +4 -2
  506. data/lib/spiderfw/model/base_model.rb +4 -4
  507. data/lib/spiderfw/model/mappers/db_mapper.rb +4 -2
  508. data/lib/spiderfw/model/storage/db/adapters/mysql.rb +2 -1
  509. data/lib/spiderfw/model/storage/db/db_storage.rb +4 -0
  510. data/lib/spiderfw/spider.rb +6 -0
  511. data/lib/spiderfw/templates/layout.rb +1 -1
  512. data/lib/spiderfw/utils/monkey/object.rb +3 -0
  513. data/spider.gemspec +6 -2
  514. metadata +539 -42
  515. data/blueprints/.DS_Store +0 -0
@@ -0,0 +1,2378 @@
1
+ /**
2
+ * Formatter.js
3
+ *
4
+ * Released under LGPL License.
5
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
6
+ *
7
+ * License: http://www.tinymce.com/license
8
+ * Contributing: http://www.tinymce.com/contributing
9
+ */
10
+
11
+ /**
12
+ * Text formatter engine class. This class is used to apply formats like bold, italic, font size
13
+ * etc to the current selection or specific nodes. This engine was built to replace the browser's
14
+ * default formatting logic for execCommand due to its inconsistent and buggy behavior.
15
+ *
16
+ * @class tinymce.Formatter
17
+ * @example
18
+ * tinymce.activeEditor.formatter.register('mycustomformat', {
19
+ * inline: 'span',
20
+ * styles: {color: '#ff0000'}
21
+ * });
22
+ *
23
+ * tinymce.activeEditor.formatter.apply('mycustomformat');
24
+ */
25
+ define("tinymce/Formatter", [
26
+ "tinymce/dom/TreeWalker",
27
+ "tinymce/dom/RangeUtils",
28
+ "tinymce/dom/BookmarkManager",
29
+ "tinymce/dom/ElementUtils",
30
+ "tinymce/util/Tools",
31
+ "tinymce/fmt/Preview",
32
+ "tinymce/fmt/Hooks"
33
+ ], function(TreeWalker, RangeUtils, BookmarkManager, ElementUtils, Tools, Preview, Hooks) {
34
+ /**
35
+ * Constructs a new formatter instance.
36
+ *
37
+ * @constructor Formatter
38
+ * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to.
39
+ */
40
+ return function(ed) {
41
+ var formats = {},
42
+ dom = ed.dom,
43
+ selection = ed.selection,
44
+ rangeUtils = new RangeUtils(dom),
45
+ isValid = ed.schema.isValidChild,
46
+ isBlock = dom.isBlock,
47
+ forcedRootBlock = ed.settings.forced_root_block,
48
+ nodeIndex = dom.nodeIndex,
49
+ INVISIBLE_CHAR = '\uFEFF',
50
+ MCE_ATTR_RE = /^(src|href|style)$/,
51
+ FALSE = false,
52
+ TRUE = true,
53
+ formatChangeData,
54
+ undef,
55
+ getContentEditable = dom.getContentEditable,
56
+ disableCaretContainer,
57
+ markCaretContainersBogus,
58
+ isBookmarkNode = BookmarkManager.isBookmarkNode;
59
+
60
+ var each = Tools.each,
61
+ grep = Tools.grep,
62
+ walk = Tools.walk,
63
+ extend = Tools.extend;
64
+
65
+ function isTextBlock(name) {
66
+ if (name.nodeType) {
67
+ name = name.nodeName;
68
+ }
69
+
70
+ return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
71
+ }
72
+
73
+ function isTableCell(node) {
74
+ return /^(TH|TD)$/.test(node.nodeName);
75
+ }
76
+
77
+ function isInlineBlock(node) {
78
+ return node && /^(IMG)$/.test(node.nodeName);
79
+ }
80
+
81
+ function getParents(node, selector) {
82
+ return dom.getParents(node, selector, dom.getRoot());
83
+ }
84
+
85
+ function isCaretNode(node) {
86
+ return node.nodeType === 1 && node.id === '_mce_caret';
87
+ }
88
+
89
+ function defaultFormats() {
90
+ register({
91
+ valigntop: [
92
+ {selector: 'td,th', styles: {'verticalAlign': 'top'}}
93
+ ],
94
+
95
+ valignmiddle: [
96
+ {selector: 'td,th', styles: {'verticalAlign': 'middle'}}
97
+ ],
98
+
99
+ valignbottom: [
100
+ {selector: 'td,th', styles: {'verticalAlign': 'bottom'}}
101
+ ],
102
+
103
+ alignleft: [
104
+ {selector: 'figure.image', collapsed: false, classes: 'align-left', ceFalseOverride: true},
105
+ {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'},
106
+ {selector: 'img,table', collapsed: false, styles: {'float': 'left'}}
107
+ ],
108
+
109
+ aligncenter: [
110
+ {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'center'}, defaultBlock: 'div'},
111
+ {selector: 'figure.image', collapsed: false, classes: 'align-center', ceFalseOverride: true},
112
+ {selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}},
113
+ {selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}}
114
+ ],
115
+
116
+ alignright: [
117
+ {selector: 'figure.image', collapsed: false, classes: 'align-right', ceFalseOverride: true},
118
+ {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'right'}, defaultBlock: 'div'},
119
+ {selector: 'img,table', collapsed: false, styles: {'float': 'right'}}
120
+ ],
121
+
122
+ alignjustify: [
123
+ {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'justify'}, defaultBlock: 'div'}
124
+ ],
125
+
126
+ bold: [
127
+ {inline: 'strong', remove: 'all'},
128
+ {inline: 'span', styles: {fontWeight: 'bold'}},
129
+ {inline: 'b', remove: 'all'}
130
+ ],
131
+
132
+ italic: [
133
+ {inline: 'em', remove: 'all'},
134
+ {inline: 'span', styles: {fontStyle: 'italic'}},
135
+ {inline: 'i', remove: 'all'}
136
+ ],
137
+
138
+ underline: [
139
+ {inline: 'span', styles: {textDecoration: 'underline'}, exact: true},
140
+ {inline: 'u', remove: 'all'}
141
+ ],
142
+
143
+ strikethrough: [
144
+ {inline: 'span', styles: {textDecoration: 'line-through'}, exact: true},
145
+ {inline: 'strike', remove: 'all'}
146
+ ],
147
+
148
+ forecolor: {inline: 'span', styles: {color: '%value'}, links: true, remove_similar: true},
149
+ hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, links: true, remove_similar: true},
150
+ fontname: {inline: 'span', styles: {fontFamily: '%value'}},
151
+ fontsize: {inline: 'span', styles: {fontSize: '%value'}},
152
+ fontsize_class: {inline: 'span', attributes: {'class': '%value'}},
153
+ blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'},
154
+ subscript: {inline: 'sub'},
155
+ superscript: {inline: 'sup'},
156
+ code: {inline: 'code'},
157
+
158
+ link: {inline: 'a', selector: 'a', remove: 'all', split: true, deep: true,
159
+ onmatch: function() {
160
+ return true;
161
+ },
162
+
163
+ onformat: function(elm, fmt, vars) {
164
+ each(vars, function(value, key) {
165
+ dom.setAttrib(elm, key, value);
166
+ });
167
+ }
168
+ },
169
+
170
+ removeformat: [
171
+ {
172
+ selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q,del,ins',
173
+ remove: 'all',
174
+ split: true,
175
+ expand: false,
176
+ block_expand: true,
177
+ deep: true
178
+ },
179
+ {selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true},
180
+ {selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true}
181
+ ]
182
+ });
183
+
184
+ // Register default block formats
185
+ each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function(name) {
186
+ register(name, {block: name, remove: 'all'});
187
+ });
188
+
189
+ // Register user defined formats
190
+ register(ed.settings.formats);
191
+ }
192
+
193
+ function addKeyboardShortcuts() {
194
+ // Add some inline shortcuts
195
+ ed.addShortcut('meta+b', 'bold_desc', 'Bold');
196
+ ed.addShortcut('meta+i', 'italic_desc', 'Italic');
197
+ ed.addShortcut('meta+u', 'underline_desc', 'Underline');
198
+
199
+ // BlockFormat shortcuts keys
200
+ for (var i = 1; i <= 6; i++) {
201
+ ed.addShortcut('access+' + i, '', ['FormatBlock', false, 'h' + i]);
202
+ }
203
+
204
+ ed.addShortcut('access+7', '', ['FormatBlock', false, 'p']);
205
+ ed.addShortcut('access+8', '', ['FormatBlock', false, 'div']);
206
+ ed.addShortcut('access+9', '', ['FormatBlock', false, 'address']);
207
+ }
208
+
209
+ // Public functions
210
+
211
+ /**
212
+ * Returns the format by name or all formats if no name is specified.
213
+ *
214
+ * @method get
215
+ * @param {String} name Optional name to retrieve by.
216
+ * @return {Array/Object} Array/Object with all registered formats or a specific format.
217
+ */
218
+ function get(name) {
219
+ return name ? formats[name] : formats;
220
+ }
221
+
222
+ /**
223
+ * Registers a specific format by name.
224
+ *
225
+ * @method register
226
+ * @param {Object/String} name Name of the format for example "bold".
227
+ * @param {Object/Array} format Optional format object or array of format variants
228
+ * can only be omitted if the first arg is an object.
229
+ */
230
+ function register(name, format) {
231
+ if (name) {
232
+ if (typeof name !== 'string') {
233
+ each(name, function(format, name) {
234
+ register(name, format);
235
+ });
236
+ } else {
237
+ // Force format into array and add it to internal collection
238
+ format = format.length ? format : [format];
239
+
240
+ each(format, function(format) {
241
+ // Set deep to false by default on selector formats this to avoid removing
242
+ // alignment on images inside paragraphs when alignment is changed on paragraphs
243
+ if (format.deep === undef) {
244
+ format.deep = !format.selector;
245
+ }
246
+
247
+ // Default to true
248
+ if (format.split === undef) {
249
+ format.split = !format.selector || format.inline;
250
+ }
251
+
252
+ // Default to true
253
+ if (format.remove === undef && format.selector && !format.inline) {
254
+ format.remove = 'none';
255
+ }
256
+
257
+ // Mark format as a mixed format inline + block level
258
+ if (format.selector && format.inline) {
259
+ format.mixed = true;
260
+ format.block_expand = true;
261
+ }
262
+
263
+ // Split classes if needed
264
+ if (typeof format.classes === 'string') {
265
+ format.classes = format.classes.split(/\s+/);
266
+ }
267
+ });
268
+
269
+ formats[name] = format;
270
+ }
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Unregister a specific format by name.
276
+ *
277
+ * @method unregister
278
+ * @param {String} name Name of the format for example "bold".
279
+ */
280
+ function unregister(name) {
281
+ if (name && formats[name]) {
282
+ delete formats[name];
283
+ }
284
+
285
+ return formats;
286
+ }
287
+
288
+ function getTextDecoration(node) {
289
+ var decoration;
290
+
291
+ ed.dom.getParent(node, function(n) {
292
+ decoration = ed.dom.getStyle(n, 'text-decoration');
293
+ return decoration && decoration !== 'none';
294
+ });
295
+
296
+ return decoration;
297
+ }
298
+
299
+ function processUnderlineAndColor(node) {
300
+ var textDecoration;
301
+ if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
302
+ textDecoration = getTextDecoration(node.parentNode);
303
+ if (ed.dom.getStyle(node, 'color') && textDecoration) {
304
+ ed.dom.setStyle(node, 'text-decoration', textDecoration);
305
+ } else if (ed.dom.getStyle(node, 'text-decoration') === textDecoration) {
306
+ ed.dom.setStyle(node, 'text-decoration', null);
307
+ }
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Applies the specified format to the current selection or specified node.
313
+ *
314
+ * @method apply
315
+ * @param {String} name Name of format to apply.
316
+ * @param {Object} vars Optional list of variables to replace within format before applying it.
317
+ * @param {Node} node Optional node to apply the format to defaults to current selection.
318
+ */
319
+ function apply(name, vars, node) {
320
+ var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed();
321
+
322
+ function setElementFormat(elm, fmt) {
323
+ fmt = fmt || format;
324
+
325
+ if (elm) {
326
+ if (fmt.onformat) {
327
+ fmt.onformat(elm, fmt, vars, node);
328
+ }
329
+
330
+ each(fmt.styles, function(value, name) {
331
+ dom.setStyle(elm, name, replaceVars(value, vars));
332
+ });
333
+
334
+ // Needed for the WebKit span spam bug
335
+ // TODO: Remove this once WebKit/Blink fixes this
336
+ if (fmt.styles) {
337
+ var styleVal = dom.getAttrib(elm, 'style');
338
+
339
+ if (styleVal) {
340
+ elm.setAttribute('data-mce-style', styleVal);
341
+ }
342
+ }
343
+
344
+ each(fmt.attributes, function(value, name) {
345
+ dom.setAttrib(elm, name, replaceVars(value, vars));
346
+ });
347
+
348
+ each(fmt.classes, function(value) {
349
+ value = replaceVars(value, vars);
350
+
351
+ if (!dom.hasClass(elm, value)) {
352
+ dom.addClass(elm, value);
353
+ }
354
+ });
355
+ }
356
+ }
357
+
358
+ function adjustSelectionToVisibleSelection() {
359
+ function findSelectionEnd(start, end) {
360
+ var walker = new TreeWalker(end);
361
+ for (node = walker.current(); node; node = walker.prev()) {
362
+ if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
363
+ return node;
364
+ }
365
+ }
366
+ }
367
+
368
+ // Adjust selection so that a end container with a end offset of zero is not included in the selection
369
+ // as this isn't visible to the user.
370
+ var rng = ed.selection.getRng();
371
+ var start = rng.startContainer;
372
+ var end = rng.endContainer;
373
+
374
+ if (start != end && rng.endOffset === 0) {
375
+ var newEnd = findSelectionEnd(start, end);
376
+ var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
377
+
378
+ rng.setEnd(newEnd, endOffset);
379
+ }
380
+
381
+ return rng;
382
+ }
383
+
384
+ function applyRngStyle(rng, bookmark, node_specific) {
385
+ var newWrappers = [], wrapName, wrapElm, contentEditable = true;
386
+
387
+ // Setup wrapper element
388
+ wrapName = format.inline || format.block;
389
+ wrapElm = dom.create(wrapName);
390
+ setElementFormat(wrapElm);
391
+
392
+ rangeUtils.walk(rng, function(nodes) {
393
+ var currentWrapElm;
394
+
395
+ /**
396
+ * Process a list of nodes wrap them.
397
+ */
398
+ function process(node) {
399
+ var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
400
+
401
+ lastContentEditable = contentEditable;
402
+ nodeName = node.nodeName.toLowerCase();
403
+ parentName = node.parentNode.nodeName.toLowerCase();
404
+
405
+ // Node has a contentEditable value
406
+ if (node.nodeType === 1 && getContentEditable(node)) {
407
+ lastContentEditable = contentEditable;
408
+ contentEditable = getContentEditable(node) === "true";
409
+ hasContentEditableState = true; // We don't want to wrap the container only it's children
410
+ }
411
+
412
+ // Stop wrapping on br elements
413
+ if (isEq(nodeName, 'br')) {
414
+ currentWrapElm = 0;
415
+
416
+ // Remove any br elements when we wrap things
417
+ if (format.block) {
418
+ dom.remove(node);
419
+ }
420
+
421
+ return;
422
+ }
423
+
424
+ // If node is wrapper type
425
+ if (format.wrapper && matchNode(node, name, vars)) {
426
+ currentWrapElm = 0;
427
+ return;
428
+ }
429
+
430
+ // Can we rename the block
431
+ // TODO: Break this if up, too complex
432
+ if (contentEditable && !hasContentEditableState && format.block &&
433
+ !format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) {
434
+ node = dom.rename(node, wrapName);
435
+ setElementFormat(node);
436
+ newWrappers.push(node);
437
+ currentWrapElm = 0;
438
+ return;
439
+ }
440
+
441
+ // Handle selector patterns
442
+ if (format.selector) {
443
+ // Look for matching formats
444
+ each(formatList, function(format) {
445
+ // Check collapsed state if it exists
446
+ if ('collapsed' in format && format.collapsed !== isCollapsed) {
447
+ return;
448
+ }
449
+
450
+ if (dom.is(node, format.selector) && !isCaretNode(node)) {
451
+ setElementFormat(node, format);
452
+ found = true;
453
+ return false;
454
+ }
455
+ });
456
+
457
+ // Continue processing if a selector match wasn't found and a inline element is defined
458
+ if (!format.inline || found) {
459
+ currentWrapElm = 0;
460
+ return;
461
+ }
462
+ }
463
+
464
+ // Is it valid to wrap this item
465
+ // TODO: Break this if up, too complex
466
+ if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
467
+ !(!node_specific && node.nodeType === 3 &&
468
+ node.nodeValue.length === 1 &&
469
+ node.nodeValue.charCodeAt(0) === 65279) &&
470
+ !isCaretNode(node) &&
471
+ (!format.inline || !isBlock(node))) {
472
+ // Start wrapping
473
+ if (!currentWrapElm) {
474
+ // Wrap the node
475
+ currentWrapElm = dom.clone(wrapElm, FALSE);
476
+ node.parentNode.insertBefore(currentWrapElm, node);
477
+ newWrappers.push(currentWrapElm);
478
+ }
479
+
480
+ currentWrapElm.appendChild(node);
481
+ } else {
482
+ // Start a new wrapper for possible children
483
+ currentWrapElm = 0;
484
+
485
+ each(grep(node.childNodes), process);
486
+
487
+ if (hasContentEditableState) {
488
+ contentEditable = lastContentEditable; // Restore last contentEditable state from stack
489
+ }
490
+
491
+ // End the last wrapper
492
+ currentWrapElm = 0;
493
+ }
494
+ }
495
+
496
+ // Process siblings from range
497
+ each(nodes, process);
498
+ });
499
+
500
+ // Apply formats to links as well to get the color of the underline to change as well
501
+ if (format.links === true) {
502
+ each(newWrappers, function(node) {
503
+ function process(node) {
504
+ if (node.nodeName === 'A') {
505
+ setElementFormat(node, format);
506
+ }
507
+
508
+ each(grep(node.childNodes), process);
509
+ }
510
+
511
+ process(node);
512
+ });
513
+ }
514
+
515
+ // Cleanup
516
+ each(newWrappers, function(node) {
517
+ var childCount;
518
+
519
+ function getChildCount(node) {
520
+ var count = 0;
521
+
522
+ each(node.childNodes, function(node) {
523
+ if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) {
524
+ count++;
525
+ }
526
+ });
527
+
528
+ return count;
529
+ }
530
+
531
+ function mergeStyles(node) {
532
+ var child, clone;
533
+
534
+ each(node.childNodes, function(node) {
535
+ if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
536
+ child = node;
537
+ return FALSE; // break loop
538
+ }
539
+ });
540
+
541
+ // If child was found and of the same type as the current node
542
+ if (child && !isBookmarkNode(child) && matchName(child, format)) {
543
+ clone = dom.clone(child, FALSE);
544
+ setElementFormat(clone);
545
+
546
+ dom.replace(clone, node, TRUE);
547
+ dom.remove(child, 1);
548
+ }
549
+
550
+ return clone || node;
551
+ }
552
+
553
+ childCount = getChildCount(node);
554
+
555
+ // Remove empty nodes but only if there is multiple wrappers and they are not block
556
+ // elements so never remove single <h1></h1> since that would remove the
557
+ // current empty block element where the caret is at
558
+ if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
559
+ dom.remove(node, 1);
560
+ return;
561
+ }
562
+
563
+ if (format.inline || format.wrapper) {
564
+ // Merges the current node with it's children of similar type to reduce the number of elements
565
+ if (!format.exact && childCount === 1) {
566
+ node = mergeStyles(node);
567
+ }
568
+
569
+ // Remove/merge children
570
+ each(formatList, function(format) {
571
+ // Merge all children of similar type will move styles from child to parent
572
+ // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
573
+ // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
574
+ each(dom.select(format.inline, node), function(child) {
575
+ if (isBookmarkNode(child)) {
576
+ return;
577
+ }
578
+
579
+ removeFormat(format, vars, child, format.exact ? child : null);
580
+ });
581
+ });
582
+
583
+ // Remove child if direct parent is of same type
584
+ if (matchNode(node.parentNode, name, vars)) {
585
+ dom.remove(node, 1);
586
+ node = 0;
587
+ return TRUE;
588
+ }
589
+
590
+ // Look for parent with similar style format
591
+ if (format.merge_with_parents) {
592
+ dom.getParent(node.parentNode, function(parent) {
593
+ if (matchNode(parent, name, vars)) {
594
+ dom.remove(node, 1);
595
+ node = 0;
596
+ return TRUE;
597
+ }
598
+ });
599
+ }
600
+
601
+ // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
602
+ if (node && format.merge_siblings !== false) {
603
+ node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
604
+ node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
605
+ }
606
+ }
607
+ });
608
+ }
609
+
610
+ if (getContentEditable(selection.getNode()) === "false") {
611
+ node = selection.getNode();
612
+ for (var i = 0, l = formatList.length; i < l; i++) {
613
+ if (formatList[i].ceFalseOverride && dom.is(node, formatList[i].selector)) {
614
+ setElementFormat(node, formatList[i]);
615
+ return;
616
+ }
617
+ }
618
+
619
+ return;
620
+ }
621
+
622
+ if (format) {
623
+ if (node) {
624
+ if (node.nodeType) {
625
+ rng = dom.createRng();
626
+ rng.setStartBefore(node);
627
+ rng.setEndAfter(node);
628
+ applyRngStyle(expandRng(rng, formatList), null, true);
629
+ } else {
630
+ applyRngStyle(node, null, true);
631
+ }
632
+ } else {
633
+ if (!isCollapsed || !format.inline || dom.select('td[data-mce-selected],th[data-mce-selected]').length) {
634
+ // Obtain selection node before selection is unselected by applyRngStyle()
635
+ var curSelNode = ed.selection.getNode();
636
+
637
+ // If the formats have a default block and we can't find a parent block then
638
+ // start wrapping it with a DIV this is for forced_root_blocks: false
639
+ // It's kind of a hack but people should be using the default block type P since all desktop editors work that way
640
+ if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
641
+ apply(formatList[0].defaultBlock);
642
+ }
643
+
644
+ // Apply formatting to selection
645
+ ed.selection.setRng(adjustSelectionToVisibleSelection());
646
+ bookmark = selection.getBookmark();
647
+ applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
648
+
649
+ // Colored nodes should be underlined so that the color of the underline matches the text color.
650
+ if (format.styles && (format.styles.color || format.styles.textDecoration)) {
651
+ walk(curSelNode, processUnderlineAndColor, 'childNodes');
652
+ processUnderlineAndColor(curSelNode);
653
+ }
654
+
655
+ selection.moveToBookmark(bookmark);
656
+ moveStart(selection.getRng(TRUE));
657
+ ed.nodeChanged();
658
+ } else {
659
+ performCaretAction('apply', name, vars);
660
+ }
661
+ }
662
+
663
+ Hooks.postProcess(name, ed);
664
+ }
665
+ }
666
+
667
+ /**
668
+ * Removes the specified format from the current selection or specified node.
669
+ *
670
+ * @method remove
671
+ * @param {String} name Name of format to remove.
672
+ * @param {Object} vars Optional list of variables to replace within format before removing it.
673
+ * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection.
674
+ */
675
+ function remove(name, vars, node, similar) {
676
+ var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true;
677
+
678
+ // Merges the styles for each node
679
+ function process(node) {
680
+ var children, i, l, lastContentEditable, hasContentEditableState;
681
+
682
+ // Node has a contentEditable value
683
+ if (node.nodeType === 1 && getContentEditable(node)) {
684
+ lastContentEditable = contentEditable;
685
+ contentEditable = getContentEditable(node) === "true";
686
+ hasContentEditableState = true; // We don't want to wrap the container only it's children
687
+ }
688
+
689
+ // Grab the children first since the nodelist might be changed
690
+ children = grep(node.childNodes);
691
+
692
+ // Process current node
693
+ if (contentEditable && !hasContentEditableState) {
694
+ for (i = 0, l = formatList.length; i < l; i++) {
695
+ if (removeFormat(formatList[i], vars, node, node)) {
696
+ break;
697
+ }
698
+ }
699
+ }
700
+
701
+ // Process the children
702
+ if (format.deep) {
703
+ if (children.length) {
704
+ for (i = 0, l = children.length; i < l; i++) {
705
+ process(children[i]);
706
+ }
707
+
708
+ if (hasContentEditableState) {
709
+ contentEditable = lastContentEditable; // Restore last contentEditable state from stack
710
+ }
711
+ }
712
+ }
713
+ }
714
+
715
+ function findFormatRoot(container) {
716
+ var formatRoot;
717
+
718
+ // Find format root
719
+ each(getParents(container.parentNode).reverse(), function(parent) {
720
+ var format;
721
+
722
+ // Find format root element
723
+ if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
724
+ // Is the node matching the format we are looking for
725
+ format = matchNode(parent, name, vars, similar);
726
+ if (format && format.split !== false) {
727
+ formatRoot = parent;
728
+ }
729
+ }
730
+ });
731
+
732
+ return formatRoot;
733
+ }
734
+
735
+ function wrapAndSplit(formatRoot, container, target, split) {
736
+ var parent, clone, lastClone, firstClone, i, formatRootParent;
737
+
738
+ // Format root found then clone formats and split it
739
+ if (formatRoot) {
740
+ formatRootParent = formatRoot.parentNode;
741
+
742
+ for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
743
+ clone = dom.clone(parent, FALSE);
744
+
745
+ for (i = 0; i < formatList.length; i++) {
746
+ if (removeFormat(formatList[i], vars, clone, clone)) {
747
+ clone = 0;
748
+ break;
749
+ }
750
+ }
751
+
752
+ // Build wrapper node
753
+ if (clone) {
754
+ if (lastClone) {
755
+ clone.appendChild(lastClone);
756
+ }
757
+
758
+ if (!firstClone) {
759
+ firstClone = clone;
760
+ }
761
+
762
+ lastClone = clone;
763
+ }
764
+ }
765
+
766
+ // Never split block elements if the format is mixed
767
+ if (split && (!format.mixed || !isBlock(formatRoot))) {
768
+ container = dom.split(formatRoot, container);
769
+ }
770
+
771
+ // Wrap container in cloned formats
772
+ if (lastClone) {
773
+ target.parentNode.insertBefore(lastClone, target);
774
+ firstClone.appendChild(target);
775
+ }
776
+ }
777
+
778
+ return container;
779
+ }
780
+
781
+ function splitToFormatRoot(container) {
782
+ return wrapAndSplit(findFormatRoot(container), container, container, true);
783
+ }
784
+
785
+ function unwrap(start) {
786
+ var node = dom.get(start ? '_start' : '_end'),
787
+ out = node[start ? 'firstChild' : 'lastChild'];
788
+
789
+ // If the end is placed within the start the result will be removed
790
+ // So this checks if the out node is a bookmark node if it is it
791
+ // checks for another more suitable node
792
+ if (isBookmarkNode(out)) {
793
+ out = out[start ? 'firstChild' : 'lastChild'];
794
+ }
795
+
796
+ // Since dom.remove removes empty text nodes then we need to try to find a better node
797
+ if (out.nodeType == 3 && out.data.length === 0) {
798
+ out = start ? node.previousSibling || node.nextSibling : node.nextSibling || node.previousSibling;
799
+ }
800
+
801
+ dom.remove(node, true);
802
+
803
+ return out;
804
+ }
805
+
806
+ function removeRngStyle(rng) {
807
+ var startContainer, endContainer;
808
+ var commonAncestorContainer = rng.commonAncestorContainer;
809
+
810
+ rng = expandRng(rng, formatList, TRUE);
811
+
812
+ if (format.split) {
813
+ startContainer = getContainer(rng, TRUE);
814
+ endContainer = getContainer(rng);
815
+
816
+ if (startContainer != endContainer) {
817
+ // WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN
818
+ // so let's see if we can use the first child instead
819
+ // This will happen if you triple click a table cell and use remove formatting
820
+ if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
821
+ if (startContainer.nodeName == "TR") {
822
+ startContainer = startContainer.firstChild.firstChild || startContainer;
823
+ } else {
824
+ startContainer = startContainer.firstChild || startContainer;
825
+ }
826
+ }
827
+
828
+ // Try to adjust endContainer as well if cells on the same row were selected - bug #6410
829
+ if (commonAncestorContainer &&
830
+ /^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) &&
831
+ isTableCell(endContainer) && endContainer.firstChild) {
832
+ endContainer = endContainer.firstChild || endContainer;
833
+ }
834
+
835
+ if (dom.isChildOf(startContainer, endContainer) && !isBlock(endContainer) &&
836
+ !isTableCell(startContainer) && !isTableCell(endContainer)) {
837
+ startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
838
+ splitToFormatRoot(startContainer);
839
+ startContainer = unwrap(TRUE);
840
+ return;
841
+ }
842
+
843
+ // Wrap start/end nodes in span element since these might be cloned/moved
844
+ startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
845
+ endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'});
846
+
847
+ // Split start/end
848
+ splitToFormatRoot(startContainer);
849
+ splitToFormatRoot(endContainer);
850
+
851
+ // Unwrap start/end to get real elements again
852
+ startContainer = unwrap(TRUE);
853
+ endContainer = unwrap();
854
+ } else {
855
+ startContainer = endContainer = splitToFormatRoot(startContainer);
856
+ }
857
+
858
+ // Update range positions since they might have changed after the split operations
859
+ rng.startContainer = startContainer.parentNode ? startContainer.parentNode : startContainer;
860
+ rng.startOffset = nodeIndex(startContainer);
861
+ rng.endContainer = endContainer.parentNode ? endContainer.parentNode : endContainer;
862
+ rng.endOffset = nodeIndex(endContainer) + 1;
863
+ }
864
+
865
+ // Remove items between start/end
866
+ rangeUtils.walk(rng, function(nodes) {
867
+ each(nodes, function(node) {
868
+ process(node);
869
+
870
+ // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
871
+ if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' &&
872
+ node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
873
+ removeFormat({
874
+ 'deep': false,
875
+ 'exact': true,
876
+ 'inline': 'span',
877
+ 'styles': {
878
+ 'textDecoration': 'underline'
879
+ }
880
+ }, null, node);
881
+ }
882
+ });
883
+ });
884
+ }
885
+
886
+ // Handle node
887
+ if (node) {
888
+ if (node.nodeType) {
889
+ rng = dom.createRng();
890
+ rng.setStartBefore(node);
891
+ rng.setEndAfter(node);
892
+ removeRngStyle(rng);
893
+ } else {
894
+ removeRngStyle(node);
895
+ }
896
+
897
+ return;
898
+ }
899
+
900
+ if (getContentEditable(selection.getNode()) === "false") {
901
+ node = selection.getNode();
902
+ for (var i = 0, l = formatList.length; i < l; i++) {
903
+ if (formatList[i].ceFalseOverride) {
904
+ if (removeFormat(formatList[i], vars, node, node)) {
905
+ break;
906
+ }
907
+ }
908
+ }
909
+
910
+ return;
911
+ }
912
+
913
+ if (!selection.isCollapsed() || !format.inline || dom.select('td[data-mce-selected],th[data-mce-selected]').length) {
914
+ bookmark = selection.getBookmark();
915
+ removeRngStyle(selection.getRng(TRUE));
916
+ selection.moveToBookmark(bookmark);
917
+
918
+ // Check if start element still has formatting then we are at: "<b>text|</b>text"
919
+ // and need to move the start into the next text node
920
+ if (format.inline && match(name, vars, selection.getStart())) {
921
+ moveStart(selection.getRng(true));
922
+ }
923
+
924
+ ed.nodeChanged();
925
+ } else {
926
+ performCaretAction('remove', name, vars, similar);
927
+ }
928
+ }
929
+
930
+ /**
931
+ * Toggles the specified format on/off.
932
+ *
933
+ * @method toggle
934
+ * @param {String} name Name of format to apply/remove.
935
+ * @param {Object} vars Optional list of variables to replace within format before applying/removing it.
936
+ * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection.
937
+ */
938
+ function toggle(name, vars, node) {
939
+ var fmt = get(name);
940
+
941
+ if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) {
942
+ remove(name, vars, node);
943
+ } else {
944
+ apply(name, vars, node);
945
+ }
946
+ }
947
+
948
+ /**
949
+ * Return true/false if the specified node has the specified format.
950
+ *
951
+ * @method matchNode
952
+ * @param {Node} node Node to check the format on.
953
+ * @param {String} name Format name to check.
954
+ * @param {Object} vars Optional list of variables to replace before checking it.
955
+ * @param {Boolean} similar Match format that has similar properties.
956
+ * @return {Object} Returns the format object it matches or undefined if it doesn't match.
957
+ */
958
+ function matchNode(node, name, vars, similar) {
959
+ var formatList = get(name), format, i, classes;
960
+
961
+ function matchItems(node, format, item_name) {
962
+ var key, value, items = format[item_name], i;
963
+
964
+ // Custom match
965
+ if (format.onmatch) {
966
+ return format.onmatch(node, format, item_name);
967
+ }
968
+
969
+ // Check all items
970
+ if (items) {
971
+ // Non indexed object
972
+ if (items.length === undef) {
973
+ for (key in items) {
974
+ if (items.hasOwnProperty(key)) {
975
+ if (item_name === 'attributes') {
976
+ value = dom.getAttrib(node, key);
977
+ } else {
978
+ value = getStyle(node, key);
979
+ }
980
+
981
+ if (similar && !value && !format.exact) {
982
+ return;
983
+ }
984
+
985
+ if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) {
986
+ return;
987
+ }
988
+ }
989
+ }
990
+ } else {
991
+ // Only one match needed for indexed arrays
992
+ for (i = 0; i < items.length; i++) {
993
+ if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) {
994
+ return format;
995
+ }
996
+ }
997
+ }
998
+ }
999
+
1000
+ return format;
1001
+ }
1002
+
1003
+ if (formatList && node) {
1004
+ // Check each format in list
1005
+ for (i = 0; i < formatList.length; i++) {
1006
+ format = formatList[i];
1007
+
1008
+ // Name name, attributes, styles and classes
1009
+ if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
1010
+ // Match classes
1011
+ if ((classes = format.classes)) {
1012
+ for (i = 0; i < classes.length; i++) {
1013
+ if (!dom.hasClass(node, classes[i])) {
1014
+ return;
1015
+ }
1016
+ }
1017
+ }
1018
+
1019
+ return format;
1020
+ }
1021
+ }
1022
+ }
1023
+ }
1024
+
1025
+ /**
1026
+ * Matches the current selection or specified node against the specified format name.
1027
+ *
1028
+ * @method match
1029
+ * @param {String} name Name of format to match.
1030
+ * @param {Object} vars Optional list of variables to replace before checking it.
1031
+ * @param {Node} node Optional node to check.
1032
+ * @return {boolean} true/false if the specified selection/node matches the format.
1033
+ */
1034
+ function match(name, vars, node) {
1035
+ var startNode;
1036
+
1037
+ function matchParents(node) {
1038
+ var root = dom.getRoot();
1039
+
1040
+ if (node === root) {
1041
+ return false;
1042
+ }
1043
+
1044
+ // Find first node with similar format settings
1045
+ node = dom.getParent(node, function(node) {
1046
+ return node.parentNode === root || !!matchNode(node, name, vars, true);
1047
+ });
1048
+
1049
+ // Do an exact check on the similar format element
1050
+ return matchNode(node, name, vars);
1051
+ }
1052
+
1053
+ // Check specified node
1054
+ if (node) {
1055
+ return matchParents(node);
1056
+ }
1057
+
1058
+ // Check selected node
1059
+ node = selection.getNode();
1060
+ if (matchParents(node)) {
1061
+ return TRUE;
1062
+ }
1063
+
1064
+ // Check start node if it's different
1065
+ startNode = selection.getStart();
1066
+ if (startNode != node) {
1067
+ if (matchParents(startNode)) {
1068
+ return TRUE;
1069
+ }
1070
+ }
1071
+
1072
+ return FALSE;
1073
+ }
1074
+
1075
+ /**
1076
+ * Matches the current selection against the array of formats and returns a new array with matching formats.
1077
+ *
1078
+ * @method matchAll
1079
+ * @param {Array} names Name of format to match.
1080
+ * @param {Object} vars Optional list of variables to replace before checking it.
1081
+ * @return {Array} Array with matched formats.
1082
+ */
1083
+ function matchAll(names, vars) {
1084
+ var startElement, matchedFormatNames = [], checkedMap = {};
1085
+
1086
+ // Check start of selection for formats
1087
+ startElement = selection.getStart();
1088
+ dom.getParent(startElement, function(node) {
1089
+ var i, name;
1090
+
1091
+ for (i = 0; i < names.length; i++) {
1092
+ name = names[i];
1093
+
1094
+ if (!checkedMap[name] && matchNode(node, name, vars)) {
1095
+ checkedMap[name] = true;
1096
+ matchedFormatNames.push(name);
1097
+ }
1098
+ }
1099
+ }, dom.getRoot());
1100
+
1101
+ return matchedFormatNames;
1102
+ }
1103
+
1104
+ /**
1105
+ * Returns true/false if the specified format can be applied to the current selection or not. It
1106
+ * will currently only check the state for selector formats, it returns true on all other format types.
1107
+ *
1108
+ * @method canApply
1109
+ * @param {String} name Name of format to check.
1110
+ * @return {boolean} true/false if the specified format can be applied to the current selection/node.
1111
+ */
1112
+ function canApply(name) {
1113
+ var formatList = get(name), startNode, parents, i, x, selector;
1114
+
1115
+ if (formatList) {
1116
+ startNode = selection.getStart();
1117
+ parents = getParents(startNode);
1118
+
1119
+ for (x = formatList.length - 1; x >= 0; x--) {
1120
+ selector = formatList[x].selector;
1121
+
1122
+ // Format is not selector based then always return TRUE
1123
+ // Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line
1124
+ if (!selector || formatList[x].defaultBlock) {
1125
+ return TRUE;
1126
+ }
1127
+
1128
+ for (i = parents.length - 1; i >= 0; i--) {
1129
+ if (dom.is(parents[i], selector)) {
1130
+ return TRUE;
1131
+ }
1132
+ }
1133
+ }
1134
+ }
1135
+
1136
+ return FALSE;
1137
+ }
1138
+
1139
+ /**
1140
+ * Executes the specified callback when the current selection matches the formats or not.
1141
+ *
1142
+ * @method formatChanged
1143
+ * @param {String} formats Comma separated list of formats to check for.
1144
+ * @param {function} callback Callback with state and args when the format is changed/toggled on/off.
1145
+ * @param {Boolean} similar True/false state if the match should handle similar or exact formats.
1146
+ */
1147
+ function formatChanged(formats, callback, similar) {
1148
+ var currentFormats;
1149
+
1150
+ // Setup format node change logic
1151
+ if (!formatChangeData) {
1152
+ formatChangeData = {};
1153
+ currentFormats = {};
1154
+
1155
+ ed.on('NodeChange', function(e) {
1156
+ var parents = getParents(e.element), matchedFormats = {};
1157
+
1158
+ // Ignore bogus nodes like the <a> tag created by moveStart()
1159
+ parents = Tools.grep(parents, function(node) {
1160
+ return node.nodeType == 1 && !node.getAttribute('data-mce-bogus');
1161
+ });
1162
+
1163
+ // Check for new formats
1164
+ each(formatChangeData, function(callbacks, format) {
1165
+ each(parents, function(node) {
1166
+ if (matchNode(node, format, {}, callbacks.similar)) {
1167
+ if (!currentFormats[format]) {
1168
+ // Execute callbacks
1169
+ each(callbacks, function(callback) {
1170
+ callback(true, {node: node, format: format, parents: parents});
1171
+ });
1172
+
1173
+ currentFormats[format] = callbacks;
1174
+ }
1175
+
1176
+ matchedFormats[format] = callbacks;
1177
+ return false;
1178
+ }
1179
+ });
1180
+ });
1181
+
1182
+ // Check if current formats still match
1183
+ each(currentFormats, function(callbacks, format) {
1184
+ if (!matchedFormats[format]) {
1185
+ delete currentFormats[format];
1186
+
1187
+ each(callbacks, function(callback) {
1188
+ callback(false, {node: e.element, format: format, parents: parents});
1189
+ });
1190
+ }
1191
+ });
1192
+ });
1193
+ }
1194
+
1195
+ // Add format listeners
1196
+ each(formats.split(','), function(format) {
1197
+ if (!formatChangeData[format]) {
1198
+ formatChangeData[format] = [];
1199
+ formatChangeData[format].similar = similar;
1200
+ }
1201
+
1202
+ formatChangeData[format].push(callback);
1203
+ });
1204
+
1205
+ return this;
1206
+ }
1207
+
1208
+ /**
1209
+ * Returns a preview css text for the specified format.
1210
+ *
1211
+ * @method getCssText
1212
+ * @param {String/Object} format Format to generate preview css text for.
1213
+ * @return {String} Css text for the specified format.
1214
+ * @example
1215
+ * var cssText1 = editor.formatter.getCssText('bold');
1216
+ * var cssText2 = editor.formatter.getCssText({inline: 'b'});
1217
+ */
1218
+ function getCssText(format) {
1219
+ return Preview.getCssText(ed, format);
1220
+ }
1221
+
1222
+ // Expose to public
1223
+ extend(this, {
1224
+ get: get,
1225
+ register: register,
1226
+ unregister: unregister,
1227
+ apply: apply,
1228
+ remove: remove,
1229
+ toggle: toggle,
1230
+ match: match,
1231
+ matchAll: matchAll,
1232
+ matchNode: matchNode,
1233
+ canApply: canApply,
1234
+ formatChanged: formatChanged,
1235
+ getCssText: getCssText
1236
+ });
1237
+
1238
+ // Initialize
1239
+ defaultFormats();
1240
+ addKeyboardShortcuts();
1241
+ ed.on('BeforeGetContent', function(e) {
1242
+ if (markCaretContainersBogus && e.format != 'raw') {
1243
+ markCaretContainersBogus();
1244
+ }
1245
+ });
1246
+ ed.on('mouseup keydown', function(e) {
1247
+ if (disableCaretContainer) {
1248
+ disableCaretContainer(e);
1249
+ }
1250
+ });
1251
+
1252
+ // Private functions
1253
+
1254
+ /**
1255
+ * Checks if the specified nodes name matches the format inline/block or selector.
1256
+ *
1257
+ * @private
1258
+ * @param {Node} node Node to match against the specified format.
1259
+ * @param {Object} format Format object o match with.
1260
+ * @return {boolean} true/false if the format matches.
1261
+ */
1262
+ function matchName(node, format) {
1263
+ // Check for inline match
1264
+ if (isEq(node, format.inline)) {
1265
+ return TRUE;
1266
+ }
1267
+
1268
+ // Check for block match
1269
+ if (isEq(node, format.block)) {
1270
+ return TRUE;
1271
+ }
1272
+
1273
+ // Check for selector match
1274
+ if (format.selector) {
1275
+ return node.nodeType == 1 && dom.is(node, format.selector);
1276
+ }
1277
+ }
1278
+
1279
+ /**
1280
+ * Compares two string/nodes regardless of their case.
1281
+ *
1282
+ * @private
1283
+ * @param {String/Node} str1 Node or string to compare.
1284
+ * @param {String/Node} str2 Node or string to compare.
1285
+ * @return {boolean} True/false if they match.
1286
+ */
1287
+ function isEq(str1, str2) {
1288
+ str1 = str1 || '';
1289
+ str2 = str2 || '';
1290
+
1291
+ str1 = '' + (str1.nodeName || str1);
1292
+ str2 = '' + (str2.nodeName || str2);
1293
+
1294
+ return str1.toLowerCase() == str2.toLowerCase();
1295
+ }
1296
+
1297
+ /**
1298
+ * Returns the style by name on the specified node. This method modifies the style
1299
+ * contents to make it more easy to match. This will resolve a few browser issues.
1300
+ *
1301
+ * @private
1302
+ * @param {Node} node to get style from.
1303
+ * @param {String} name Style name to get.
1304
+ * @return {String} Style item value.
1305
+ */
1306
+ function getStyle(node, name) {
1307
+ return normalizeStyleValue(dom.getStyle(node, name), name);
1308
+ }
1309
+
1310
+ /**
1311
+ * Normalize style value by name. This method modifies the style contents
1312
+ * to make it more easy to match. This will resolve a few browser issues.
1313
+ *
1314
+ * @private
1315
+ * @param {String} value Value to get style from.
1316
+ * @param {String} name Style name to get.
1317
+ * @return {String} Style item value.
1318
+ */
1319
+ function normalizeStyleValue(value, name) {
1320
+ // Force the format to hex
1321
+ if (name == 'color' || name == 'backgroundColor') {
1322
+ value = dom.toHex(value);
1323
+ }
1324
+
1325
+ // Opera will return bold as 700
1326
+ if (name == 'fontWeight' && value == 700) {
1327
+ value = 'bold';
1328
+ }
1329
+
1330
+ // Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font"
1331
+ if (name == 'fontFamily') {
1332
+ value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ',');
1333
+ }
1334
+
1335
+ return '' + value;
1336
+ }
1337
+
1338
+ /**
1339
+ * Replaces variables in the value. The variable format is %var.
1340
+ *
1341
+ * @private
1342
+ * @param {String} value Value to replace variables in.
1343
+ * @param {Object} vars Name/value array with variables to replace.
1344
+ * @return {String} New value with replaced variables.
1345
+ */
1346
+ function replaceVars(value, vars) {
1347
+ if (typeof value != "string") {
1348
+ value = value(vars);
1349
+ } else if (vars) {
1350
+ value = value.replace(/%(\w+)/g, function(str, name) {
1351
+ return vars[name] || str;
1352
+ });
1353
+ }
1354
+
1355
+ return value;
1356
+ }
1357
+
1358
+ function isWhiteSpaceNode(node) {
1359
+ return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
1360
+ }
1361
+
1362
+ function wrap(node, name, attrs) {
1363
+ var wrapper = dom.create(name, attrs);
1364
+
1365
+ node.parentNode.insertBefore(wrapper, node);
1366
+ wrapper.appendChild(node);
1367
+
1368
+ return wrapper;
1369
+ }
1370
+
1371
+ /**
1372
+ * Expands the specified range like object to depending on format.
1373
+ *
1374
+ * For example on block formats it will move the start/end position
1375
+ * to the beginning of the current block.
1376
+ *
1377
+ * @private
1378
+ * @param {Object} rng Range like object.
1379
+ * @param {Array} format Array with formats to expand by.
1380
+ * @param {Boolean} remove
1381
+ * @return {Object} Expanded range like object.
1382
+ */
1383
+ function expandRng(rng, format, remove) {
1384
+ var lastIdx, leaf, endPoint,
1385
+ startContainer = rng.startContainer,
1386
+ startOffset = rng.startOffset,
1387
+ endContainer = rng.endContainer,
1388
+ endOffset = rng.endOffset;
1389
+
1390
+ // This function walks up the tree if there is no siblings before/after the node
1391
+ function findParentContainer(start) {
1392
+ var container, parent, sibling, siblingName, root;
1393
+
1394
+ container = parent = start ? startContainer : endContainer;
1395
+ siblingName = start ? 'previousSibling' : 'nextSibling';
1396
+ root = dom.getRoot();
1397
+
1398
+ function isBogusBr(node) {
1399
+ return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
1400
+ }
1401
+
1402
+ // If it's a text node and the offset is inside the text
1403
+ if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
1404
+ if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
1405
+ return container;
1406
+ }
1407
+ }
1408
+
1409
+ /*eslint no-constant-condition:0 */
1410
+ while (true) {
1411
+ // Stop expanding on block elements
1412
+ if (!format[0].block_expand && isBlock(parent)) {
1413
+ return parent;
1414
+ }
1415
+
1416
+ // Walk left/right
1417
+ for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
1418
+ if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
1419
+ return parent;
1420
+ }
1421
+ }
1422
+
1423
+ // Check if we can move up are we at root level or body level
1424
+ if (parent == root || parent.parentNode == root) {
1425
+ container = parent;
1426
+ break;
1427
+ }
1428
+
1429
+ parent = parent.parentNode;
1430
+ }
1431
+
1432
+ return container;
1433
+ }
1434
+
1435
+ // This function walks down the tree to find the leaf at the selection.
1436
+ // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
1437
+ function findLeaf(node, offset) {
1438
+ if (offset === undef) {
1439
+ offset = node.nodeType === 3 ? node.length : node.childNodes.length;
1440
+ }
1441
+
1442
+ while (node && node.hasChildNodes()) {
1443
+ node = node.childNodes[offset];
1444
+ if (node) {
1445
+ offset = node.nodeType === 3 ? node.length : node.childNodes.length;
1446
+ }
1447
+ }
1448
+ return {node: node, offset: offset};
1449
+ }
1450
+
1451
+ // If index based start position then resolve it
1452
+ if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
1453
+ lastIdx = startContainer.childNodes.length - 1;
1454
+ startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
1455
+
1456
+ if (startContainer.nodeType == 3) {
1457
+ startOffset = 0;
1458
+ }
1459
+ }
1460
+
1461
+ // If index based end position then resolve it
1462
+ if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
1463
+ lastIdx = endContainer.childNodes.length - 1;
1464
+ endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
1465
+
1466
+ if (endContainer.nodeType == 3) {
1467
+ endOffset = endContainer.nodeValue.length;
1468
+ }
1469
+ }
1470
+
1471
+ // Expands the node to the closes contentEditable false element if it exists
1472
+ function findParentContentEditable(node) {
1473
+ var parent = node;
1474
+
1475
+ while (parent) {
1476
+ if (parent.nodeType === 1 && getContentEditable(parent)) {
1477
+ return getContentEditable(parent) === "false" ? parent : node;
1478
+ }
1479
+
1480
+ parent = parent.parentNode;
1481
+ }
1482
+
1483
+ return node;
1484
+ }
1485
+
1486
+ function findWordEndPoint(container, offset, start) {
1487
+ var walker, node, pos, lastTextNode;
1488
+
1489
+ function findSpace(node, offset) {
1490
+ var pos, pos2, str = node.nodeValue;
1491
+
1492
+ if (typeof offset == "undefined") {
1493
+ offset = start ? str.length : 0;
1494
+ }
1495
+
1496
+ if (start) {
1497
+ pos = str.lastIndexOf(' ', offset);
1498
+ pos2 = str.lastIndexOf('\u00a0', offset);
1499
+ pos = pos > pos2 ? pos : pos2;
1500
+
1501
+ // Include the space on remove to avoid tag soup
1502
+ if (pos !== -1 && !remove) {
1503
+ pos++;
1504
+ }
1505
+ } else {
1506
+ pos = str.indexOf(' ', offset);
1507
+ pos2 = str.indexOf('\u00a0', offset);
1508
+ pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
1509
+ }
1510
+
1511
+ return pos;
1512
+ }
1513
+
1514
+ if (container.nodeType === 3) {
1515
+ pos = findSpace(container, offset);
1516
+
1517
+ if (pos !== -1) {
1518
+ return {container: container, offset: pos};
1519
+ }
1520
+
1521
+ lastTextNode = container;
1522
+ }
1523
+
1524
+ // Walk the nodes inside the block
1525
+ walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
1526
+ while ((node = walker[start ? 'prev' : 'next']())) {
1527
+ if (node.nodeType === 3) {
1528
+ lastTextNode = node;
1529
+ pos = findSpace(node);
1530
+
1531
+ if (pos !== -1) {
1532
+ return {container: node, offset: pos};
1533
+ }
1534
+ } else if (isBlock(node)) {
1535
+ break;
1536
+ }
1537
+ }
1538
+
1539
+ if (lastTextNode) {
1540
+ if (start) {
1541
+ offset = 0;
1542
+ } else {
1543
+ offset = lastTextNode.length;
1544
+ }
1545
+
1546
+ return {container: lastTextNode, offset: offset};
1547
+ }
1548
+ }
1549
+
1550
+ function findSelectorEndPoint(container, sibling_name) {
1551
+ var parents, i, y, curFormat;
1552
+
1553
+ if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) {
1554
+ container = container[sibling_name];
1555
+ }
1556
+
1557
+ parents = getParents(container);
1558
+ for (i = 0; i < parents.length; i++) {
1559
+ for (y = 0; y < format.length; y++) {
1560
+ curFormat = format[y];
1561
+
1562
+ // If collapsed state is set then skip formats that doesn't match that
1563
+ if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) {
1564
+ continue;
1565
+ }
1566
+
1567
+ if (dom.is(parents[i], curFormat.selector)) {
1568
+ return parents[i];
1569
+ }
1570
+ }
1571
+ }
1572
+
1573
+ return container;
1574
+ }
1575
+
1576
+ function findBlockEndPoint(container, sibling_name) {
1577
+ var node, root = dom.getRoot();
1578
+
1579
+ // Expand to block of similar type
1580
+ if (!format[0].wrapper) {
1581
+ node = dom.getParent(container, format[0].block, root);
1582
+ }
1583
+
1584
+ // Expand to first wrappable block element or any block element
1585
+ if (!node) {
1586
+ node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) {
1587
+ // Fixes #6183 where it would expand to editable parent element in inline mode
1588
+ return node != root && isTextBlock(node);
1589
+ });
1590
+ }
1591
+
1592
+ // Exclude inner lists from wrapping
1593
+ if (node && format[0].wrapper) {
1594
+ node = getParents(node, 'ul,ol').reverse()[0] || node;
1595
+ }
1596
+
1597
+ // Didn't find a block element look for first/last wrappable element
1598
+ if (!node) {
1599
+ node = container;
1600
+
1601
+ while (node[sibling_name] && !isBlock(node[sibling_name])) {
1602
+ node = node[sibling_name];
1603
+
1604
+ // Break on BR but include it will be removed later on
1605
+ // we can't remove it now since we need to check if it can be wrapped
1606
+ if (isEq(node, 'br')) {
1607
+ break;
1608
+ }
1609
+ }
1610
+ }
1611
+
1612
+ return node || container;
1613
+ }
1614
+
1615
+ // Expand to closest contentEditable element
1616
+ startContainer = findParentContentEditable(startContainer);
1617
+ endContainer = findParentContentEditable(endContainer);
1618
+
1619
+ // Exclude bookmark nodes if possible
1620
+ if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
1621
+ startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
1622
+ startContainer = startContainer.nextSibling || startContainer;
1623
+
1624
+ if (startContainer.nodeType == 3) {
1625
+ startOffset = 0;
1626
+ }
1627
+ }
1628
+
1629
+ if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
1630
+ endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
1631
+ endContainer = endContainer.previousSibling || endContainer;
1632
+
1633
+ if (endContainer.nodeType == 3) {
1634
+ endOffset = endContainer.length;
1635
+ }
1636
+ }
1637
+
1638
+ if (format[0].inline) {
1639
+ if (rng.collapsed) {
1640
+ // Expand left to closest word boundary
1641
+ endPoint = findWordEndPoint(startContainer, startOffset, true);
1642
+ if (endPoint) {
1643
+ startContainer = endPoint.container;
1644
+ startOffset = endPoint.offset;
1645
+ }
1646
+
1647
+ // Expand right to closest word boundary
1648
+ endPoint = findWordEndPoint(endContainer, endOffset);
1649
+ if (endPoint) {
1650
+ endContainer = endPoint.container;
1651
+ endOffset = endPoint.offset;
1652
+ }
1653
+ }
1654
+
1655
+ // Avoid applying formatting to a trailing space.
1656
+ leaf = findLeaf(endContainer, endOffset);
1657
+ if (leaf.node) {
1658
+ while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) {
1659
+ leaf = findLeaf(leaf.node.previousSibling);
1660
+ }
1661
+
1662
+ if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
1663
+ leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
1664
+
1665
+ if (leaf.offset > 1) {
1666
+ endContainer = leaf.node;
1667
+ endContainer.splitText(leaf.offset - 1);
1668
+ }
1669
+ }
1670
+ }
1671
+ }
1672
+
1673
+ // Move start/end point up the tree if the leaves are sharp and if we are in different containers
1674
+ // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
1675
+ // This will reduce the number of wrapper elements that needs to be created
1676
+ // Move start point up the tree
1677
+ if (format[0].inline || format[0].block_expand) {
1678
+ if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
1679
+ startContainer = findParentContainer(true);
1680
+ }
1681
+
1682
+ if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
1683
+ endContainer = findParentContainer();
1684
+ }
1685
+ }
1686
+
1687
+ // Expand start/end container to matching selector
1688
+ if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
1689
+ // Find new startContainer/endContainer if there is better one
1690
+ startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
1691
+ endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
1692
+ }
1693
+
1694
+ // Expand start/end container to matching block element or text node
1695
+ if (format[0].block || format[0].selector) {
1696
+ // Find new startContainer/endContainer if there is better one
1697
+ startContainer = findBlockEndPoint(startContainer, 'previousSibling');
1698
+ endContainer = findBlockEndPoint(endContainer, 'nextSibling');
1699
+
1700
+ // Non block element then try to expand up the leaf
1701
+ if (format[0].block) {
1702
+ if (!isBlock(startContainer)) {
1703
+ startContainer = findParentContainer(true);
1704
+ }
1705
+
1706
+ if (!isBlock(endContainer)) {
1707
+ endContainer = findParentContainer();
1708
+ }
1709
+ }
1710
+ }
1711
+
1712
+ // Setup index for startContainer
1713
+ if (startContainer.nodeType == 1) {
1714
+ startOffset = nodeIndex(startContainer);
1715
+ startContainer = startContainer.parentNode;
1716
+ }
1717
+
1718
+ // Setup index for endContainer
1719
+ if (endContainer.nodeType == 1) {
1720
+ endOffset = nodeIndex(endContainer) + 1;
1721
+ endContainer = endContainer.parentNode;
1722
+ }
1723
+
1724
+ // Return new range like object
1725
+ return {
1726
+ startContainer: startContainer,
1727
+ startOffset: startOffset,
1728
+ endContainer: endContainer,
1729
+ endOffset: endOffset
1730
+ };
1731
+ }
1732
+
1733
+ function isColorFormatAndAnchor(node, format) {
1734
+ return format.links && node.tagName == 'A';
1735
+ }
1736
+
1737
+ /**
1738
+ * Removes the specified format for the specified node. It will also remove the node if it doesn't have
1739
+ * any attributes if the format specifies it to do so.
1740
+ *
1741
+ * @private
1742
+ * @param {Object} format Format object with items to remove from node.
1743
+ * @param {Object} vars Name/value object with variables to apply to format.
1744
+ * @param {Node} node Node to remove the format styles on.
1745
+ * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node.
1746
+ * @return {Boolean} True/false if the node was removed or not.
1747
+ */
1748
+ function removeFormat(format, vars, node, compare_node) {
1749
+ var i, attrs, stylesModified;
1750
+
1751
+ // Check if node matches format
1752
+ if (!matchName(node, format) && !isColorFormatAndAnchor(node, format)) {
1753
+ return FALSE;
1754
+ }
1755
+
1756
+ // Should we compare with format attribs and styles
1757
+ if (format.remove != 'all') {
1758
+ // Remove styles
1759
+ each(format.styles, function(value, name) {
1760
+ value = normalizeStyleValue(replaceVars(value, vars), name);
1761
+
1762
+ // Indexed array
1763
+ if (typeof name === 'number') {
1764
+ name = value;
1765
+ compare_node = 0;
1766
+ }
1767
+
1768
+ if (format.remove_similar || (!compare_node || isEq(getStyle(compare_node, name), value))) {
1769
+ dom.setStyle(node, name, '');
1770
+ }
1771
+
1772
+ stylesModified = 1;
1773
+ });
1774
+
1775
+ // Remove style attribute if it's empty
1776
+ if (stylesModified && dom.getAttrib(node, 'style') === '') {
1777
+ node.removeAttribute('style');
1778
+ node.removeAttribute('data-mce-style');
1779
+ }
1780
+
1781
+ // Remove attributes
1782
+ each(format.attributes, function(value, name) {
1783
+ var valueOut;
1784
+
1785
+ value = replaceVars(value, vars);
1786
+
1787
+ // Indexed array
1788
+ if (typeof name === 'number') {
1789
+ name = value;
1790
+ compare_node = 0;
1791
+ }
1792
+
1793
+ if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
1794
+ // Keep internal classes
1795
+ if (name == 'class') {
1796
+ value = dom.getAttrib(node, name);
1797
+ if (value) {
1798
+ // Build new class value where everything is removed except the internal prefixed classes
1799
+ valueOut = '';
1800
+ each(value.split(/\s+/), function(cls) {
1801
+ if (/mce\-\w+/.test(cls)) {
1802
+ valueOut += (valueOut ? ' ' : '') + cls;
1803
+ }
1804
+ });
1805
+
1806
+ // We got some internal classes left
1807
+ if (valueOut) {
1808
+ dom.setAttrib(node, name, valueOut);
1809
+ return;
1810
+ }
1811
+ }
1812
+ }
1813
+
1814
+ // IE6 has a bug where the attribute doesn't get removed correctly
1815
+ if (name == "class") {
1816
+ node.removeAttribute('className');
1817
+ }
1818
+
1819
+ // Remove mce prefixed attributes
1820
+ if (MCE_ATTR_RE.test(name)) {
1821
+ node.removeAttribute('data-mce-' + name);
1822
+ }
1823
+
1824
+ node.removeAttribute(name);
1825
+ }
1826
+ });
1827
+
1828
+ // Remove classes
1829
+ each(format.classes, function(value) {
1830
+ value = replaceVars(value, vars);
1831
+
1832
+ if (!compare_node || dom.hasClass(compare_node, value)) {
1833
+ dom.removeClass(node, value);
1834
+ }
1835
+ });
1836
+
1837
+ // Check for non internal attributes
1838
+ attrs = dom.getAttribs(node);
1839
+ for (i = 0; i < attrs.length; i++) {
1840
+ if (attrs[i].nodeName.indexOf('_') !== 0) {
1841
+ return FALSE;
1842
+ }
1843
+ }
1844
+ }
1845
+
1846
+ // Remove the inline child if it's empty for example <b> or <span>
1847
+ if (format.remove != 'none') {
1848
+ removeNode(node, format);
1849
+ return TRUE;
1850
+ }
1851
+ }
1852
+
1853
+ /**
1854
+ * Removes the node and wrap it's children in paragraphs before doing so or
1855
+ * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled.
1856
+ *
1857
+ * If the div in the node below gets removed:
1858
+ * text<div>text</div>text
1859
+ *
1860
+ * Output becomes:
1861
+ * text<div><br />text<br /></div>text
1862
+ *
1863
+ * So when the div is removed the result is:
1864
+ * text<br />text<br />text
1865
+ *
1866
+ * @private
1867
+ * @param {Node} node Node to remove + apply BR/P elements to.
1868
+ * @param {Object} format Format rule.
1869
+ * @return {Node} Input node.
1870
+ */
1871
+ function removeNode(node, format) {
1872
+ var parentNode = node.parentNode, rootBlockElm;
1873
+
1874
+ function find(node, next, inc) {
1875
+ node = getNonWhiteSpaceSibling(node, next, inc);
1876
+
1877
+ return !node || (node.nodeName == 'BR' || isBlock(node));
1878
+ }
1879
+
1880
+ if (format.block) {
1881
+ if (!forcedRootBlock) {
1882
+ // Append BR elements if needed before we remove the block
1883
+ if (isBlock(node) && !isBlock(parentNode)) {
1884
+ if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) {
1885
+ node.insertBefore(dom.create('br'), node.firstChild);
1886
+ }
1887
+
1888
+ if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) {
1889
+ node.appendChild(dom.create('br'));
1890
+ }
1891
+ }
1892
+ } else {
1893
+ // Wrap the block in a forcedRootBlock if we are at the root of document
1894
+ if (parentNode == dom.getRoot()) {
1895
+ if (!format.list_block || !isEq(node, format.list_block)) {
1896
+ each(grep(node.childNodes), function(node) {
1897
+ if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
1898
+ if (!rootBlockElm) {
1899
+ rootBlockElm = wrap(node, forcedRootBlock);
1900
+ dom.setAttribs(rootBlockElm, ed.settings.forced_root_block_attrs);
1901
+ } else {
1902
+ rootBlockElm.appendChild(node);
1903
+ }
1904
+ } else {
1905
+ rootBlockElm = 0;
1906
+ }
1907
+ });
1908
+ }
1909
+ }
1910
+ }
1911
+ }
1912
+
1913
+ // Never remove nodes that isn't the specified inline element if a selector is specified too
1914
+ if (format.selector && format.inline && !isEq(format.inline, node)) {
1915
+ return;
1916
+ }
1917
+
1918
+ dom.remove(node, 1);
1919
+ }
1920
+
1921
+ /**
1922
+ * Returns the next/previous non whitespace node.
1923
+ *
1924
+ * @private
1925
+ * @param {Node} node Node to start at.
1926
+ * @param {boolean} next (Optional) Include next or previous node defaults to previous.
1927
+ * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false.
1928
+ * @return {Node} Next or previous node or undefined if it wasn't found.
1929
+ */
1930
+ function getNonWhiteSpaceSibling(node, next, inc) {
1931
+ if (node) {
1932
+ next = next ? 'nextSibling' : 'previousSibling';
1933
+
1934
+ for (node = inc ? node : node[next]; node; node = node[next]) {
1935
+ if (node.nodeType == 1 || !isWhiteSpaceNode(node)) {
1936
+ return node;
1937
+ }
1938
+ }
1939
+ }
1940
+ }
1941
+
1942
+ /**
1943
+ * Merges the next/previous sibling element if they match.
1944
+ *
1945
+ * @private
1946
+ * @param {Node} prev Previous node to compare/merge.
1947
+ * @param {Node} next Next node to compare/merge.
1948
+ * @return {Node} Next node if we didn't merge and prev node if we did.
1949
+ */
1950
+ function mergeSiblings(prev, next) {
1951
+ var sibling, tmpSibling, elementUtils = new ElementUtils(dom);
1952
+
1953
+ function findElementSibling(node, sibling_name) {
1954
+ for (sibling = node; sibling; sibling = sibling[sibling_name]) {
1955
+ if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) {
1956
+ return node;
1957
+ }
1958
+
1959
+ if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) {
1960
+ return sibling;
1961
+ }
1962
+ }
1963
+
1964
+ return node;
1965
+ }
1966
+
1967
+ // Check if next/prev exists and that they are elements
1968
+ if (prev && next) {
1969
+ // If previous sibling is empty then jump over it
1970
+ prev = findElementSibling(prev, 'previousSibling');
1971
+ next = findElementSibling(next, 'nextSibling');
1972
+
1973
+ // Compare next and previous nodes
1974
+ if (elementUtils.compare(prev, next)) {
1975
+ // Append nodes between
1976
+ for (sibling = prev.nextSibling; sibling && sibling != next;) {
1977
+ tmpSibling = sibling;
1978
+ sibling = sibling.nextSibling;
1979
+ prev.appendChild(tmpSibling);
1980
+ }
1981
+
1982
+ // Remove next node
1983
+ dom.remove(next);
1984
+
1985
+ // Move children into prev node
1986
+ each(grep(next.childNodes), function(node) {
1987
+ prev.appendChild(node);
1988
+ });
1989
+
1990
+ return prev;
1991
+ }
1992
+ }
1993
+
1994
+ return next;
1995
+ }
1996
+
1997
+ function getContainer(rng, start) {
1998
+ var container, offset, lastIdx;
1999
+
2000
+ container = rng[start ? 'startContainer' : 'endContainer'];
2001
+ offset = rng[start ? 'startOffset' : 'endOffset'];
2002
+
2003
+ if (container.nodeType == 1) {
2004
+ lastIdx = container.childNodes.length - 1;
2005
+
2006
+ if (!start && offset) {
2007
+ offset--;
2008
+ }
2009
+
2010
+ container = container.childNodes[offset > lastIdx ? lastIdx : offset];
2011
+ }
2012
+
2013
+ // If start text node is excluded then walk to the next node
2014
+ if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
2015
+ container = new TreeWalker(container, ed.getBody()).next() || container;
2016
+ }
2017
+
2018
+ // If end text node is excluded then walk to the previous node
2019
+ if (container.nodeType === 3 && !start && offset === 0) {
2020
+ container = new TreeWalker(container, ed.getBody()).prev() || container;
2021
+ }
2022
+
2023
+ return container;
2024
+ }
2025
+
2026
+ function performCaretAction(type, name, vars, similar) {
2027
+ var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
2028
+
2029
+ // Creates a caret container bogus element
2030
+ function createCaretContainer(fill) {
2031
+ var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
2032
+
2033
+ if (fill) {
2034
+ caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
2035
+ }
2036
+
2037
+ return caretContainer;
2038
+ }
2039
+
2040
+ function isCaretContainerEmpty(node, nodes) {
2041
+ while (node) {
2042
+ if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
2043
+ return false;
2044
+ }
2045
+
2046
+ // Collect nodes
2047
+ if (nodes && node.nodeType === 1) {
2048
+ nodes.push(node);
2049
+ }
2050
+
2051
+ node = node.firstChild;
2052
+ }
2053
+
2054
+ return true;
2055
+ }
2056
+
2057
+ // Returns any parent caret container element
2058
+ function getParentCaretContainer(node) {
2059
+ while (node) {
2060
+ if (node.id === caretContainerId) {
2061
+ return node;
2062
+ }
2063
+
2064
+ node = node.parentNode;
2065
+ }
2066
+ }
2067
+
2068
+ // Finds the first text node in the specified node
2069
+ function findFirstTextNode(node) {
2070
+ var walker;
2071
+
2072
+ if (node) {
2073
+ walker = new TreeWalker(node, node);
2074
+
2075
+ for (node = walker.current(); node; node = walker.next()) {
2076
+ if (node.nodeType === 3) {
2077
+ return node;
2078
+ }
2079
+ }
2080
+ }
2081
+ }
2082
+
2083
+ // Removes the caret container for the specified node or all on the current document
2084
+ function removeCaretContainer(node, move_caret) {
2085
+ var child, rng;
2086
+
2087
+ if (!node) {
2088
+ node = getParentCaretContainer(selection.getStart());
2089
+
2090
+ if (!node) {
2091
+ while ((node = dom.get(caretContainerId))) {
2092
+ removeCaretContainer(node, false);
2093
+ }
2094
+ }
2095
+ } else {
2096
+ rng = selection.getRng(true);
2097
+
2098
+ if (isCaretContainerEmpty(node)) {
2099
+ if (move_caret !== false) {
2100
+ rng.setStartBefore(node);
2101
+ rng.setEndBefore(node);
2102
+ }
2103
+
2104
+ dom.remove(node);
2105
+ } else {
2106
+ child = findFirstTextNode(node);
2107
+
2108
+ if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
2109
+ child.deleteData(0, 1);
2110
+
2111
+ // Fix for bug #6976
2112
+ if (rng.startContainer == child && rng.startOffset > 0) {
2113
+ rng.setStart(child, rng.startOffset - 1);
2114
+ }
2115
+
2116
+ if (rng.endContainer == child && rng.endOffset > 0) {
2117
+ rng.setEnd(child, rng.endOffset - 1);
2118
+ }
2119
+ }
2120
+
2121
+ dom.remove(node, 1);
2122
+ }
2123
+
2124
+ selection.setRng(rng);
2125
+ }
2126
+ }
2127
+
2128
+ // Applies formatting to the caret position
2129
+ function applyCaretFormat() {
2130
+ var rng, caretContainer, textNode, offset, bookmark, container, text;
2131
+
2132
+ rng = selection.getRng(true);
2133
+ offset = rng.startOffset;
2134
+ container = rng.startContainer;
2135
+ text = container.nodeValue;
2136
+
2137
+ caretContainer = getParentCaretContainer(selection.getStart());
2138
+ if (caretContainer) {
2139
+ textNode = findFirstTextNode(caretContainer);
2140
+ }
2141
+
2142
+ // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
2143
+ if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
2144
+ // Get bookmark of caret position
2145
+ bookmark = selection.getBookmark();
2146
+
2147
+ // Collapse bookmark range (WebKit)
2148
+ rng.collapse(true);
2149
+
2150
+ // Expand the range to the closest word and split it at those points
2151
+ rng = expandRng(rng, get(name));
2152
+ rng = rangeUtils.split(rng);
2153
+
2154
+ // Apply the format to the range
2155
+ apply(name, vars, rng);
2156
+
2157
+ // Move selection back to caret position
2158
+ selection.moveToBookmark(bookmark);
2159
+ } else {
2160
+ if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
2161
+ caretContainer = createCaretContainer(true);
2162
+ textNode = caretContainer.firstChild;
2163
+
2164
+ rng.insertNode(caretContainer);
2165
+ offset = 1;
2166
+
2167
+ apply(name, vars, caretContainer);
2168
+ } else {
2169
+ apply(name, vars, caretContainer);
2170
+ }
2171
+
2172
+ // Move selection to text node
2173
+ selection.setCursorLocation(textNode, offset);
2174
+ }
2175
+ }
2176
+
2177
+ function removeCaretFormat() {
2178
+ var rng = selection.getRng(true), container, offset, bookmark,
2179
+ hasContentAfter, node, formatNode, parents = [], i, caretContainer;
2180
+
2181
+ container = rng.startContainer;
2182
+ offset = rng.startOffset;
2183
+ node = container;
2184
+
2185
+ if (container.nodeType == 3) {
2186
+ if (offset != container.nodeValue.length) {
2187
+ hasContentAfter = true;
2188
+ }
2189
+
2190
+ node = node.parentNode;
2191
+ }
2192
+
2193
+ while (node) {
2194
+ if (matchNode(node, name, vars, similar)) {
2195
+ formatNode = node;
2196
+ break;
2197
+ }
2198
+
2199
+ if (node.nextSibling) {
2200
+ hasContentAfter = true;
2201
+ }
2202
+
2203
+ parents.push(node);
2204
+ node = node.parentNode;
2205
+ }
2206
+
2207
+ // Node doesn't have the specified format
2208
+ if (!formatNode) {
2209
+ return;
2210
+ }
2211
+
2212
+ // Is there contents after the caret then remove the format on the element
2213
+ if (hasContentAfter) {
2214
+ // Get bookmark of caret position
2215
+ bookmark = selection.getBookmark();
2216
+
2217
+ // Collapse bookmark range (WebKit)
2218
+ rng.collapse(true);
2219
+
2220
+ // Expand the range to the closest word and split it at those points
2221
+ rng = expandRng(rng, get(name), true);
2222
+ rng = rangeUtils.split(rng);
2223
+
2224
+ // Remove the format from the range
2225
+ remove(name, vars, rng);
2226
+
2227
+ // Move selection back to caret position
2228
+ selection.moveToBookmark(bookmark);
2229
+ } else {
2230
+ caretContainer = createCaretContainer();
2231
+
2232
+ node = caretContainer;
2233
+ for (i = parents.length - 1; i >= 0; i--) {
2234
+ node.appendChild(dom.clone(parents[i], false));
2235
+ node = node.firstChild;
2236
+ }
2237
+
2238
+ // Insert invisible character into inner most format element
2239
+ node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
2240
+ node = node.firstChild;
2241
+
2242
+ var block = dom.getParent(formatNode, isTextBlock);
2243
+
2244
+ if (block && dom.isEmpty(block)) {
2245
+ // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
2246
+ formatNode.parentNode.replaceChild(caretContainer, formatNode);
2247
+ } else {
2248
+ // Insert caret container after the formatted node
2249
+ dom.insertAfter(caretContainer, formatNode);
2250
+ }
2251
+
2252
+ // Move selection to text node
2253
+ selection.setCursorLocation(node, 1);
2254
+
2255
+ // If the formatNode is empty, we can remove it safely.
2256
+ if (dom.isEmpty(formatNode)) {
2257
+ dom.remove(formatNode);
2258
+ }
2259
+ }
2260
+ }
2261
+
2262
+ // Checks if the parent caret container node isn't empty if that is the case it
2263
+ // will remove the bogus state on all children that isn't empty
2264
+ function unmarkBogusCaretParents() {
2265
+ var caretContainer;
2266
+
2267
+ caretContainer = getParentCaretContainer(selection.getStart());
2268
+ if (caretContainer && !dom.isEmpty(caretContainer)) {
2269
+ walk(caretContainer, function(node) {
2270
+ if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
2271
+ dom.setAttrib(node, 'data-mce-bogus', null);
2272
+ }
2273
+ }, 'childNodes');
2274
+ }
2275
+ }
2276
+
2277
+ // Only bind the caret events once
2278
+ if (!ed._hasCaretEvents) {
2279
+ // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
2280
+ markCaretContainersBogus = function() {
2281
+ var nodes = [], i;
2282
+
2283
+ if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
2284
+ // Mark children
2285
+ i = nodes.length;
2286
+ while (i--) {
2287
+ dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
2288
+ }
2289
+ }
2290
+ };
2291
+
2292
+ disableCaretContainer = function(e) {
2293
+ var keyCode = e.keyCode;
2294
+
2295
+ removeCaretContainer();
2296
+
2297
+ // Remove caret container if it's empty
2298
+ if (keyCode == 8 && selection.isCollapsed() && selection.getStart().innerHTML == INVISIBLE_CHAR) {
2299
+ removeCaretContainer(getParentCaretContainer(selection.getStart()));
2300
+ }
2301
+
2302
+ // Remove caret container on keydown and it's left/right arrow keys
2303
+ if (keyCode == 37 || keyCode == 39) {
2304
+ removeCaretContainer(getParentCaretContainer(selection.getStart()));
2305
+ }
2306
+
2307
+ unmarkBogusCaretParents();
2308
+ };
2309
+
2310
+ // Remove bogus state if they got filled by contents using editor.selection.setContent
2311
+ ed.on('SetContent', function(e) {
2312
+ if (e.selection) {
2313
+ unmarkBogusCaretParents();
2314
+ }
2315
+ });
2316
+ ed._hasCaretEvents = true;
2317
+ }
2318
+
2319
+ // Do apply or remove caret format
2320
+ if (type == "apply") {
2321
+ applyCaretFormat();
2322
+ } else {
2323
+ removeCaretFormat();
2324
+ }
2325
+ }
2326
+
2327
+ /**
2328
+ * Moves the start to the first suitable text node.
2329
+ */
2330
+ function moveStart(rng) {
2331
+ var container = rng.startContainer,
2332
+ offset = rng.startOffset, isAtEndOfText,
2333
+ walker, node, nodes, tmpNode;
2334
+
2335
+ if (rng.startContainer == rng.endContainer) {
2336
+ if (isInlineBlock(rng.startContainer.childNodes[rng.startOffset])) {
2337
+ return;
2338
+ }
2339
+ }
2340
+
2341
+ // Convert text node into index if possible
2342
+ if (container.nodeType == 3 && offset >= container.nodeValue.length) {
2343
+ // Get the parent container location and walk from there
2344
+ offset = nodeIndex(container);
2345
+ container = container.parentNode;
2346
+ isAtEndOfText = true;
2347
+ }
2348
+
2349
+ // Move startContainer/startOffset in to a suitable node
2350
+ if (container.nodeType == 1) {
2351
+ nodes = container.childNodes;
2352
+ container = nodes[Math.min(offset, nodes.length - 1)];
2353
+ walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
2354
+
2355
+ // If offset is at end of the parent node walk to the next one
2356
+ if (offset > nodes.length - 1 || isAtEndOfText) {
2357
+ walker.next();
2358
+ }
2359
+
2360
+ for (node = walker.current(); node; node = walker.next()) {
2361
+ if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
2362
+ // IE has a "neat" feature where it moves the start node into the closest element
2363
+ // we can avoid this by inserting an element before it and then remove it after we set the selection
2364
+ tmpNode = dom.create('a', {'data-mce-bogus': 'all'}, INVISIBLE_CHAR);
2365
+ node.parentNode.insertBefore(tmpNode, node);
2366
+
2367
+ // Set selection and remove tmpNode
2368
+ rng.setStart(node, 0);
2369
+ selection.setRng(rng);
2370
+ dom.remove(tmpNode);
2371
+
2372
+ return;
2373
+ }
2374
+ }
2375
+ }
2376
+ }
2377
+ };
2378
+ });