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.
- checksums.yaml +13 -5
- data/CHANGELOG +23 -0
- data/VERSION +1 -1
- data/apps/core/admin/views/admin.layout.shtml +6 -0
- data/apps/core/auth/controllers/login_controller.rb +1 -1
- data/apps/core/components/assets.rb +55 -6
- data/apps/core/components/public/bootstrap/scss/_variables.scss +5 -2
- data/apps/core/components/public/bootstrap/scss/bootstrap.css +942 -236
- data/apps/core/components/public/bootstrap/scss/responsive.css +187 -30
- data/apps/core/components/public/css/tinymce/skins/lightgray/AbsoluteLayout.less +17 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Animations.less +10 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Button.less +172 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/ButtonGroup.less +71 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Checkbox.less +49 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/ColorBox.less +6 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/ColorButton.less +72 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/ColorPicker.less +80 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/ComboBox.less +39 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Container.less +9 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Content.Inline.less +4 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Content.Objects.less +166 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Content.less +27 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/CropRect.less +54 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/FieldSet.less +15 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/FitLayout.less +9 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/FloatPanel.less +69 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/FlowLayout.less +36 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Icons.Ie7.less +136 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Icons.less +180 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Iframe.less +6 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/ImagePanel.less +20 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/InfoBox.less +71 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Label.less +38 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/ListBox.less +26 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Menu.less +34 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/MenuBar.less +32 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/MenuButton.less +34 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/MenuItem.less +142 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Mixins.less +54 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Notification.less +144 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Panel.less +7 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Path.less +45 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Progress.less +34 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Radio.less +1 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Reset.less +32 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/ResizeHandle.less +18 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Scrollable.less +44 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/SelectBox.less +6 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Slider.less +29 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Spacer.less +5 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/SplitButton.less +49 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/StackLayout.less +5 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/TabPanel.less +44 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/TextBox.less +41 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Throbber.less +19 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/TinyMCE.less +159 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/ToolTip.less +129 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Variables.less +214 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/Window.less +127 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/content.inline.min.css +1 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/content.min.css +1 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/readme.md +1 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce-small.eot +0 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce-small.json +1277 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce-small.svg +63 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce-small.ttf +0 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce-small.woff +0 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce.eot +0 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce.json +3381 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce.svg +129 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce.ttf +0 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/fonts/tinymce.woff +0 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/img/anchor.gif +0 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/img/loader.gif +0 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/img/object.gif +0 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/img/trans.gif +0 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/skin.dev.less +46 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/skin.ie7.dev.less +46 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/skin.ie7.less +2542 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/skin.ie7.min.css +1 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/skin.less +2586 -0
- data/apps/core/components/public/css/tinymce/skins/lightgray/skin.min.css +1 -0
- data/apps/core/components/public/js/bootbox_2/bootbox.js +551 -0
- data/apps/core/components/public/js/jquery/jquery-ui-1.9.2/ui/i18n/jquery.ui.datepicker-it-min.js +4 -0
- data/apps/core/components/public/js/spider.js +7 -3
- data/apps/core/components/public/js/tinymce/classes/AddOnManager.js +265 -0
- data/apps/core/components/public/js/tinymce/classes/Compat.js +90 -0
- data/apps/core/components/public/js/tinymce/classes/DragDropOverrides.js +224 -0
- data/apps/core/components/public/js/tinymce/classes/Editor.js +2221 -0
- data/apps/core/components/public/js/tinymce/classes/EditorCommands.js +1028 -0
- data/apps/core/components/public/js/tinymce/classes/EditorManager.js +715 -0
- data/apps/core/components/public/js/tinymce/classes/EditorObservable.js +204 -0
- data/apps/core/components/public/js/tinymce/classes/EditorUpload.js +191 -0
- data/apps/core/components/public/js/tinymce/classes/EnterKey.js +676 -0
- data/apps/core/components/public/js/tinymce/classes/Env.js +177 -0
- data/apps/core/components/public/js/tinymce/classes/FocusManager.js +266 -0
- data/apps/core/components/public/js/tinymce/classes/ForceBlocks.js +138 -0
- data/apps/core/components/public/js/tinymce/classes/Formatter.js +2378 -0
- data/apps/core/components/public/js/tinymce/classes/LegacyInput.js +82 -0
- data/apps/core/components/public/js/tinymce/classes/Mode.js +54 -0
- data/apps/core/components/public/js/tinymce/classes/NodeChange.js +154 -0
- data/apps/core/components/public/js/tinymce/classes/NotificationManager.js +156 -0
- data/apps/core/components/public/js/tinymce/classes/Register.js +34 -0
- data/apps/core/components/public/js/tinymce/classes/SelectionOverrides.js +810 -0
- data/apps/core/components/public/js/tinymce/classes/Shortcuts.js +172 -0
- data/apps/core/components/public/js/tinymce/classes/UndoManager.js +346 -0
- data/apps/core/components/public/js/tinymce/classes/WindowManager.js +278 -0
- data/apps/core/components/public/js/tinymce/classes/caret/CaretBookmark.js +263 -0
- data/apps/core/components/public/js/tinymce/classes/caret/CaretCandidate.js +86 -0
- data/apps/core/components/public/js/tinymce/classes/caret/CaretContainer.js +151 -0
- data/apps/core/components/public/js/tinymce/classes/caret/CaretPosition.js +371 -0
- data/apps/core/components/public/js/tinymce/classes/caret/CaretUtils.js +302 -0
- data/apps/core/components/public/js/tinymce/classes/caret/CaretWalker.js +215 -0
- data/apps/core/components/public/js/tinymce/classes/caret/FakeCaret.js +192 -0
- data/apps/core/components/public/js/tinymce/classes/caret/LineUtils.js +135 -0
- data/apps/core/components/public/js/tinymce/classes/caret/LineWalker.js +166 -0
- data/apps/core/components/public/js/tinymce/classes/data/Binding.js +79 -0
- data/apps/core/components/public/js/tinymce/classes/data/ObservableArray.js +196 -0
- data/apps/core/components/public/js/tinymce/classes/data/ObservableObject.js +194 -0
- data/apps/core/components/public/js/tinymce/classes/dom/BookmarkManager.js +456 -0
- data/apps/core/components/public/js/tinymce/classes/dom/ControlSelection.js +634 -0
- data/apps/core/components/public/js/tinymce/classes/dom/DOMUtils.js +1853 -0
- data/apps/core/components/public/js/tinymce/classes/dom/Dimensions.js +64 -0
- data/apps/core/components/public/js/tinymce/classes/dom/DomQuery.js +1578 -0
- data/apps/core/components/public/js/tinymce/classes/dom/ElementUtils.js +118 -0
- data/apps/core/components/public/js/tinymce/classes/dom/EventUtils.js +583 -0
- data/apps/core/components/public/js/tinymce/classes/dom/NodePath.js +50 -0
- data/apps/core/components/public/js/tinymce/classes/dom/NodeType.js +110 -0
- data/apps/core/components/public/js/tinymce/classes/dom/Range.js +785 -0
- data/apps/core/components/public/js/tinymce/classes/dom/RangeUtils.js +628 -0
- data/apps/core/components/public/js/tinymce/classes/dom/ScriptLoader.js +254 -0
- data/apps/core/components/public/js/tinymce/classes/dom/Selection.js +1008 -0
- data/apps/core/components/public/js/tinymce/classes/dom/Serializer.js +506 -0
- data/apps/core/components/public/js/tinymce/classes/dom/Sizzle.jQuery.js +23 -0
- data/apps/core/components/public/js/tinymce/classes/dom/Sizzle.js +2039 -0
- data/apps/core/components/public/js/tinymce/classes/dom/StyleSheetLoader.js +190 -0
- data/apps/core/components/public/js/tinymce/classes/dom/TreeWalker.js +127 -0
- data/apps/core/components/public/js/tinymce/classes/dom/TridentSelection.js +508 -0
- data/apps/core/components/public/js/tinymce/classes/file/BlobCache.js +72 -0
- data/apps/core/components/public/js/tinymce/classes/file/Conversions.js +106 -0
- data/apps/core/components/public/js/tinymce/classes/file/ImageScanner.js +145 -0
- data/apps/core/components/public/js/tinymce/classes/file/Uploader.js +193 -0
- data/apps/core/components/public/js/tinymce/classes/fmt/Hooks.js +66 -0
- data/apps/core/components/public/js/tinymce/classes/fmt/Preview.js +151 -0
- data/apps/core/components/public/js/tinymce/classes/geom/ClientRect.js +136 -0
- data/apps/core/components/public/js/tinymce/classes/geom/Rect.js +214 -0
- data/apps/core/components/public/js/tinymce/classes/html/DomParser.js +822 -0
- data/apps/core/components/public/js/tinymce/classes/html/Entities.js +268 -0
- data/apps/core/components/public/js/tinymce/classes/html/Node.js +496 -0
- data/apps/core/components/public/js/tinymce/classes/html/SaxParser.js +474 -0
- data/apps/core/components/public/js/tinymce/classes/html/Schema.js +1004 -0
- data/apps/core/components/public/js/tinymce/classes/html/Serializer.js +158 -0
- data/apps/core/components/public/js/tinymce/classes/html/Styles.js +363 -0
- data/apps/core/components/public/js/tinymce/classes/html/Writer.js +199 -0
- data/apps/core/components/public/js/tinymce/classes/jquery.tinymce.js +377 -0
- data/apps/core/components/public/js/tinymce/classes/text/ExtendingChar.js +53 -0
- data/apps/core/components/public/js/tinymce/classes/text/Zwsp.js +36 -0
- data/apps/core/components/public/js/tinymce/classes/ui/AbsoluteLayout.js +63 -0
- data/apps/core/components/public/js/tinymce/classes/ui/BoxUtils.js +98 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Button.js +199 -0
- data/apps/core/components/public/js/tinymce/classes/ui/ButtonGroup.js +62 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Checkbox.js +162 -0
- data/apps/core/components/public/js/tinymce/classes/ui/ClassList.js +149 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Collection.js +438 -0
- data/apps/core/components/public/js/tinymce/classes/ui/ColorBox.js +72 -0
- data/apps/core/components/public/js/tinymce/classes/ui/ColorButton.js +124 -0
- data/apps/core/components/public/js/tinymce/classes/ui/ColorPicker.js +206 -0
- data/apps/core/components/public/js/tinymce/classes/ui/ComboBox.js +306 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Container.js +506 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Control.js +1301 -0
- data/apps/core/components/public/js/tinymce/classes/ui/DomUtils.js +107 -0
- data/apps/core/components/public/js/tinymce/classes/ui/DragHelper.js +144 -0
- data/apps/core/components/public/js/tinymce/classes/ui/ElementPath.js +79 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Factory.js +105 -0
- data/apps/core/components/public/js/tinymce/classes/ui/FieldSet.js +59 -0
- data/apps/core/components/public/js/tinymce/classes/ui/FilePicker.js +85 -0
- data/apps/core/components/public/js/tinymce/classes/ui/FitLayout.js +48 -0
- data/apps/core/components/public/js/tinymce/classes/ui/FlexLayout.js +246 -0
- data/apps/core/components/public/js/tinymce/classes/ui/FloatPanel.js +409 -0
- data/apps/core/components/public/js/tinymce/classes/ui/FlowLayout.js +46 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Form.js +157 -0
- data/apps/core/components/public/js/tinymce/classes/ui/FormItem.js +56 -0
- data/apps/core/components/public/js/tinymce/classes/ui/FormatControls.js +535 -0
- data/apps/core/components/public/js/tinymce/classes/ui/GridLayout.js +233 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Iframe.js +85 -0
- data/apps/core/components/public/js/tinymce/classes/ui/InfoBox.js +93 -0
- data/apps/core/components/public/js/tinymce/classes/ui/KeyboardNavigation.js +406 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Label.js +144 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Layout.js +120 -0
- data/apps/core/components/public/js/tinymce/classes/ui/ListBox.js +152 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Menu.js +195 -0
- data/apps/core/components/public/js/tinymce/classes/ui/MenuBar.js +33 -0
- data/apps/core/components/public/js/tinymce/classes/ui/MenuButton.js +268 -0
- data/apps/core/components/public/js/tinymce/classes/ui/MenuItem.js +336 -0
- data/apps/core/components/public/js/tinymce/classes/ui/MessageBox.js +202 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Movable.js +200 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Notification.js +154 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Panel.js +67 -0
- data/apps/core/components/public/js/tinymce/classes/ui/PanelButton.js +114 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Path.js +127 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Progress.js +81 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Radio.js +29 -0
- data/apps/core/components/public/js/tinymce/classes/ui/ReflowQueue.js +79 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Resizable.js +68 -0
- data/apps/core/components/public/js/tinymce/classes/ui/ResizeHandle.js +86 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Scrollable.js +141 -0
- data/apps/core/components/public/js/tinymce/classes/ui/SelectBox.js +106 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Selector.js +369 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Slider.js +161 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Spacer.js +39 -0
- data/apps/core/components/public/js/tinymce/classes/ui/SplitButton.js +146 -0
- data/apps/core/components/public/js/tinymce/classes/ui/StackLayout.js +34 -0
- data/apps/core/components/public/js/tinymce/classes/ui/TabPanel.js +178 -0
- data/apps/core/components/public/js/tinymce/classes/ui/TextBox.js +208 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Throbber.js +88 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Toolbar.js +56 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Tooltip.js +73 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Widget.js +151 -0
- data/apps/core/components/public/js/tinymce/classes/ui/Window.js +474 -0
- data/apps/core/components/public/js/tinymce/classes/util/Arr.js +153 -0
- data/apps/core/components/public/js/tinymce/classes/util/Class.js +167 -0
- data/apps/core/components/public/js/tinymce/classes/util/Color.js +235 -0
- data/apps/core/components/public/js/tinymce/classes/util/Delay.js +191 -0
- data/apps/core/components/public/js/tinymce/classes/util/EventDispatcher.js +294 -0
- data/apps/core/components/public/js/tinymce/classes/util/Fun.js +87 -0
- data/apps/core/components/public/js/tinymce/classes/util/I18n.js +116 -0
- data/apps/core/components/public/js/tinymce/classes/util/JSON.js +109 -0
- data/apps/core/components/public/js/tinymce/classes/util/JSONP.js +38 -0
- data/apps/core/components/public/js/tinymce/classes/util/JSONRequest.js +110 -0
- data/apps/core/components/public/js/tinymce/classes/util/LocalStorage.js +213 -0
- data/apps/core/components/public/js/tinymce/classes/util/Observable.js +129 -0
- data/apps/core/components/public/js/tinymce/classes/util/Promise.js +200 -0
- data/apps/core/components/public/js/tinymce/classes/util/Quirks.js +1701 -0
- data/apps/core/components/public/js/tinymce/classes/util/Tools.js +442 -0
- data/apps/core/components/public/js/tinymce/classes/util/URI.js +411 -0
- data/apps/core/components/public/js/tinymce/classes/util/VK.js +37 -0
- data/apps/core/components/public/js/tinymce/classes/util/XHR.js +110 -0
- data/apps/core/components/public/js/tinymce/jquery.tinymce.min.js +1 -0
- data/apps/core/components/public/js/tinymce/langs/it.js +219 -0
- data/apps/core/components/public/js/tinymce/langs/readme.md +3 -0
- data/apps/core/components/public/js/tinymce/license.txt +504 -0
- data/apps/core/components/public/js/tinymce/plugins/advlist/plugin.js +97 -0
- data/apps/core/components/public/js/tinymce/plugins/advlist/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/anchor/plugin.js +55 -0
- data/apps/core/components/public/js/tinymce/plugins/anchor/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/autolink/plugin.js +204 -0
- data/apps/core/components/public/js/tinymce/plugins/autolink/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/autoresize/plugin.js +162 -0
- data/apps/core/components/public/js/tinymce/plugins/autoresize/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/autosave/plugin.js +165 -0
- data/apps/core/components/public/js/tinymce/plugins/autosave/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/bbcode/plugin.js +123 -0
- data/apps/core/components/public/js/tinymce/plugins/bbcode/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/charmap/plugin.js +450 -0
- data/apps/core/components/public/js/tinymce/plugins/charmap/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/code/plugin.js +60 -0
- data/apps/core/components/public/js/tinymce/plugins/code/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/codesample/classes/Dialog.js +121 -0
- data/apps/core/components/public/js/tinymce/plugins/codesample/classes/Plugin.js +95 -0
- data/apps/core/components/public/js/tinymce/plugins/codesample/classes/Prism.js +937 -0
- data/apps/core/components/public/js/tinymce/plugins/codesample/classes/Utils.js +33 -0
- data/apps/core/components/public/js/tinymce/plugins/codesample/css/prism.css +138 -0
- data/apps/core/components/public/js/tinymce/plugins/codesample/plugin.dev.js +141 -0
- data/apps/core/components/public/js/tinymce/plugins/codesample/plugin.js +1299 -0
- data/apps/core/components/public/js/tinymce/plugins/codesample/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/colorpicker/plugin.js +112 -0
- data/apps/core/components/public/js/tinymce/plugins/colorpicker/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/css/dialog.css +118 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/img/buttons.png +0 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/img/icons.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/img/items.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/img/menu_arrow.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/img/menu_check.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/img/progress.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/img/tabs.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/plugin.js +297 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/tiny_mce_popup.js +542 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/utils/editable_selects.js +70 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/utils/form_utils.js +210 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/utils/mctabs.js +164 -0
- data/apps/core/components/public/js/tinymce/plugins/compat3x/utils/validate.js +252 -0
- data/apps/core/components/public/js/tinymce/plugins/contextmenu/plugin.js +88 -0
- data/apps/core/components/public/js/tinymce/plugins/contextmenu/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/directionality/plugin.js +64 -0
- data/apps/core/components/public/js/tinymce/plugins/directionality/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-cool.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-cry.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-embarassed.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-foot-in-mouth.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-frown.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-innocent.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-kiss.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-laughing.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-money-mouth.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-sealed.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-smile.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-surprised.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-tongue-out.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-undecided.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-wink.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/img/smiley-yell.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/plugin.js +65 -0
- data/apps/core/components/public/js/tinymce/plugins/emoticons/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/example/dialog.html +8 -0
- data/apps/core/components/public/js/tinymce/plugins/example/plugin.js +68 -0
- data/apps/core/components/public/js/tinymce/plugins/example/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/example_dependency/plugin.js +22 -0
- data/apps/core/components/public/js/tinymce/plugins/example_dependency/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/fullpage/plugin.js +490 -0
- data/apps/core/components/public/js/tinymce/plugins/fullpage/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/fullscreen/plugin.js +154 -0
- data/apps/core/components/public/js/tinymce/plugins/fullscreen/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/hr/plugin.js +30 -0
- data/apps/core/components/public/js/tinymce/plugins/hr/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/image/plugin.js +630 -0
- data/apps/core/components/public/js/tinymce/plugins/image/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/imagetools/config/bolt/atomic.js +6 -0
- data/apps/core/components/public/js/tinymce/plugins/imagetools/config/bolt/browser.js +6 -0
- data/apps/core/components/public/js/tinymce/plugins/imagetools/config/bolt/demo.js +8 -0
- data/apps/core/components/public/js/tinymce/plugins/imagetools/config/bolt/prod.js +6 -0
- data/apps/core/components/public/js/tinymce/plugins/imagetools/plugin.js +2668 -0
- data/apps/core/components/public/js/tinymce/plugins/imagetools/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/imagetools/src/demo/html/demo.html +16 -0
- data/apps/core/components/public/js/tinymce/plugins/imagetools/src/demo/js/Demo.js +92 -0
- data/apps/core/components/public/js/tinymce/plugins/imagetools/src/main/js/CropRect.js +214 -0
- data/apps/core/components/public/js/tinymce/plugins/imagetools/src/main/js/Dialog.js +485 -0
- data/apps/core/components/public/js/tinymce/plugins/imagetools/src/main/js/ImagePanel.js +218 -0
- data/apps/core/components/public/js/tinymce/plugins/imagetools/src/main/js/Plugin.js +411 -0
- data/apps/core/components/public/js/tinymce/plugins/imagetools/src/main/js/UndoStack.js +57 -0
- data/apps/core/components/public/js/tinymce/plugins/importcss/plugin.js +227 -0
- data/apps/core/components/public/js/tinymce/plugins/importcss/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/insertdatetime/plugin.js +121 -0
- data/apps/core/components/public/js/tinymce/plugins/insertdatetime/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/layer/plugin.js +225 -0
- data/apps/core/components/public/js/tinymce/plugins/layer/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/legacyoutput/plugin.js +211 -0
- data/apps/core/components/public/js/tinymce/plugins/legacyoutput/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/link/plugin.js +403 -0
- data/apps/core/components/public/js/tinymce/plugins/link/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/lists/plugin.js +885 -0
- data/apps/core/components/public/js/tinymce/plugins/lists/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/media/moxieplayer.swf +0 -0
- data/apps/core/components/public/js/tinymce/plugins/media/plugin.js +878 -0
- data/apps/core/components/public/js/tinymce/plugins/media/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/nonbreaking/plugin.js +53 -0
- data/apps/core/components/public/js/tinymce/plugins/nonbreaking/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/noneditable/plugin.js +101 -0
- data/apps/core/components/public/js/tinymce/plugins/noneditable/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/pagebreak/plugin.js +88 -0
- data/apps/core/components/public/js/tinymce/plugins/pagebreak/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/paste/classes/Clipboard.js +672 -0
- data/apps/core/components/public/js/tinymce/plugins/paste/classes/Plugin.js +121 -0
- data/apps/core/components/public/js/tinymce/plugins/paste/classes/Quirks.js +159 -0
- data/apps/core/components/public/js/tinymce/plugins/paste/classes/Utils.js +140 -0
- data/apps/core/components/public/js/tinymce/plugins/paste/classes/WordFilter.js +500 -0
- data/apps/core/components/public/js/tinymce/plugins/paste/plugin.dev.js +142 -0
- data/apps/core/components/public/js/tinymce/plugins/paste/plugin.js +1708 -0
- data/apps/core/components/public/js/tinymce/plugins/paste/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/preview/plugin.js +88 -0
- data/apps/core/components/public/js/tinymce/plugins/preview/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/print/plugin.js +32 -0
- data/apps/core/components/public/js/tinymce/plugins/print/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/save/plugin.js +98 -0
- data/apps/core/components/public/js/tinymce/plugins/save/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/searchreplace/plugin.js +609 -0
- data/apps/core/components/public/js/tinymce/plugins/searchreplace/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/spellchecker/classes/DomTextMatcher.js +480 -0
- data/apps/core/components/public/js/tinymce/plugins/spellchecker/classes/Plugin.js +439 -0
- data/apps/core/components/public/js/tinymce/plugins/spellchecker/plugin.dev.js +139 -0
- data/apps/core/components/public/js/tinymce/plugins/spellchecker/plugin.js +1026 -0
- data/apps/core/components/public/js/tinymce/plugins/spellchecker/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/tabfocus/plugin.js +120 -0
- data/apps/core/components/public/js/tinymce/plugins/tabfocus/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/table/classes/CellSelection.js +202 -0
- data/apps/core/components/public/js/tinymce/plugins/table/classes/Dialogs.js +824 -0
- data/apps/core/components/public/js/tinymce/plugins/table/classes/Plugin.js +599 -0
- data/apps/core/components/public/js/tinymce/plugins/table/classes/Quirks.js +400 -0
- data/apps/core/components/public/js/tinymce/plugins/table/classes/ResizeBars.js +984 -0
- data/apps/core/components/public/js/tinymce/plugins/table/classes/TableGrid.js +941 -0
- data/apps/core/components/public/js/tinymce/plugins/table/classes/Utils.js +36 -0
- data/apps/core/components/public/js/tinymce/plugins/table/plugin.dev.js +142 -0
- data/apps/core/components/public/js/tinymce/plugins/table/plugin.js +4106 -0
- data/apps/core/components/public/js/tinymce/plugins/table/plugin.min.js +2 -0
- data/apps/core/components/public/js/tinymce/plugins/template/plugin.js +270 -0
- data/apps/core/components/public/js/tinymce/plugins/template/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/textcolor/plugin.js +282 -0
- data/apps/core/components/public/js/tinymce/plugins/textcolor/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/textpattern/plugin.js +268 -0
- data/apps/core/components/public/js/tinymce/plugins/textpattern/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/css/visualblocks.css +135 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/address.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/article.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/aside.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/blockquote.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/div.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/dl.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/figure.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/h1.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/h2.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/h3.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/h4.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/h5.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/h6.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/hgroup.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/ol.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/p.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/pre.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/section.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/img/ul.gif +0 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/plugin.js +86 -0
- data/apps/core/components/public/js/tinymce/plugins/visualblocks/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/visualchars/plugin.js +123 -0
- data/apps/core/components/public/js/tinymce/plugins/visualchars/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/plugins/wordcount/plugin.js +69 -0
- data/apps/core/components/public/js/tinymce/plugins/wordcount/plugin.min.js +1 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/AbsoluteLayout.less +17 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Animations.less +10 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Button.less +172 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/ButtonGroup.less +71 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Checkbox.less +49 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/ColorBox.less +6 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/ColorButton.less +72 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/ColorPicker.less +80 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/ComboBox.less +39 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Container.less +9 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Content.Inline.less +4 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Content.Objects.less +166 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Content.less +27 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/CropRect.less +54 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/FieldSet.less +15 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/FitLayout.less +9 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/FloatPanel.less +69 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/FlowLayout.less +36 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Icons.Ie7.less +136 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Icons.less +180 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Iframe.less +6 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/ImagePanel.less +20 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/InfoBox.less +71 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Label.less +38 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/ListBox.less +26 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Menu.less +34 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/MenuBar.less +32 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/MenuButton.less +34 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/MenuItem.less +142 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Mixins.less +54 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Notification.less +144 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Panel.less +7 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Path.less +45 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Progress.less +34 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Radio.less +1 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Reset.less +32 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/ResizeHandle.less +18 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Scrollable.less +44 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/SelectBox.less +6 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Slider.less +29 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Spacer.less +5 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/SplitButton.less +49 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/StackLayout.less +5 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/TabPanel.less +44 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/TextBox.less +41 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Throbber.less +19 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/TinyMCE.less +159 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/ToolTip.less +129 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Variables.less +214 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/Window.less +127 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/content.inline.min.css +1 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/content.min.css +1 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/readme.md +1 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce-small.eot +0 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce-small.json +1277 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce-small.svg +63 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce-small.ttf +0 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce-small.woff +0 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce.eot +0 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce.json +3381 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce.svg +129 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce.ttf +0 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/fonts/tinymce.woff +0 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/img/anchor.gif +0 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/img/loader.gif +0 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/img/object.gif +0 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/img/trans.gif +0 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/skin.dev.less +46 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/skin.ie7.dev.less +46 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/skin.ie7.less +2542 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/skin.ie7.min.css +1 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/skin.less +2586 -0
- data/apps/core/components/public/js/tinymce/skins/lightgray/skin.min.css +1 -0
- data/apps/core/components/public/js/tinymce/themes/modern/theme.js +878 -0
- data/apps/core/components/public/js/tinymce/themes/modern/theme.min.js +1 -0
- data/apps/core/components/public/js/tinymce/tinymce.dev.js +286 -0
- data/apps/core/components/public/js/tinymce/tinymce.js +45811 -0
- data/apps/core/components/public/js/tinymce/tinymce.min.js +13 -0
- data/apps/core/forms/widgets/form/form.rb +3 -1
- data/apps/core/forms/widgets/form/form.shtml +1 -0
- data/apps/core/forms/widgets/inputs/search_select/search_select.rb +1 -1
- data/apps/core/forms/widgets/inputs/select/select.rb +1 -1
- data/apps/core/forms/widgets/inputs/select/select.shtml +0 -1
- data/apps/messenger/backends/sms/skebby.rb +1 -1
- data/apps/messenger/controllers/mixins/messenger_helper.rb +23 -5
- data/lib/spiderfw/config/options/spider.rb +1 -1
- data/lib/spiderfw/controller/mixins/visual.rb +2 -0
- data/lib/spiderfw/http/adapters/rack.rb +10 -5
- data/lib/spiderfw/http/http.rb +4 -2
- data/lib/spiderfw/model/base_model.rb +4 -4
- data/lib/spiderfw/model/mappers/db_mapper.rb +4 -2
- data/lib/spiderfw/model/storage/db/adapters/mysql.rb +2 -1
- data/lib/spiderfw/model/storage/db/db_storage.rb +4 -0
- data/lib/spiderfw/spider.rb +6 -0
- data/lib/spiderfw/templates/layout.rb +1 -1
- data/lib/spiderfw/utils/monkey/object.rb +3 -0
- data/spider.gemspec +6 -2
- metadata +539 -42
- 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
|
+
});
|