spiderfw 0.6.38 → 0.6.39

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