tinymce-rails 4.6.5 → 4.6.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 61b8c36dd05001960f74f23b400eab854e593ce5
4
- data.tar.gz: 41161054993d023058eab3332134377a44a745e9
3
+ metadata.gz: 49d5f0a86536177dcfbc5465afb56ddb6df157c8
4
+ data.tar.gz: 9b797b95e097bff16ee21253aed00aa09cfccae5
5
5
  SHA512:
6
- metadata.gz: 439133c18751e444619fa4d7eeac9180c94bec5fc845d7a1099b959dacca0e0d27db2dd4e31237a7de8120ca40b090979b2249b930340a19084cc7e4733c6f90
7
- data.tar.gz: 80580e1daaf3a578d8ff2cbcbccd4dacf3721fe8e31e02c819b2c6aa8b40271375ac7199ca2714c4eb89ac7b82edc4e35cd173f54712f3a569f5590fa1449ea2
6
+ metadata.gz: d618cda5d83110cd3c856a62e7de9a9267905a07a2ea6c9c774b9d1b637962efa28b1bf3b4db15e01a8e3bde6f6fdd695f2c4fc6db85c9a528125301ff59f509
7
+ data.tar.gz: 65d5aee2bf6f78780111c48b468085ea8104253d9deca61cdd83a38a1cd60fcec60fb7f6b6227187b589286642b52780d5b0ac66f84bde5cefb7953802d87d90
@@ -1,4 +1,4 @@
1
- // 4.6.5 (2017-08-02)
1
+ // 4.6.6 (2017-08-30)
2
2
  (function () {
3
3
 
4
4
  var defs = {}; // id -> {dependencies, definition, instance (possibly undefined)}
@@ -82,7 +82,7 @@ var defineGlobal = function (id, ref) {
82
82
  define(id, [], function () { return ref; });
83
83
  };
84
84
  /*jsc
85
- ["tinymce.core.api.Main","ephox.katamari.api.Fun","tinymce.core.api.Tinymce","global!Array","global!Error","tinymce.core.api.Formatter","tinymce.core.geom.Rect","tinymce.core.util.Promise","tinymce.core.util.Delay","tinymce.core.Env","tinymce.core.dom.EventUtils","tinymce.core.dom.Sizzle","tinymce.core.util.Tools","tinymce.core.dom.DomQuery","tinymce.core.html.Styles","tinymce.core.dom.TreeWalker","tinymce.core.html.Entities","tinymce.core.dom.DOMUtils","tinymce.core.dom.ScriptLoader","tinymce.core.AddOnManager","tinymce.core.dom.RangeUtils","tinymce.core.html.Node","tinymce.core.html.Schema","tinymce.core.html.SaxParser","tinymce.core.html.DomParser","tinymce.core.html.Writer","tinymce.core.html.Serializer","tinymce.core.dom.Serializer","tinymce.core.util.VK","tinymce.core.dom.ControlSelection","tinymce.core.dom.BookmarkManager","tinymce.core.dom.Selection","tinymce.core.UndoManager","tinymce.core.EditorCommands","tinymce.core.util.URI","tinymce.core.util.Class","tinymce.core.util.EventDispatcher","tinymce.core.util.Observable","tinymce.core.WindowManager","tinymce.core.NotificationManager","tinymce.core.EditorObservable","tinymce.core.Shortcuts","tinymce.core.Editor","tinymce.core.util.I18n","tinymce.core.FocusManager","tinymce.core.EditorManager","tinymce.core.util.XHR","tinymce.core.util.JSON","tinymce.core.util.JSONRequest","tinymce.core.util.JSONP","tinymce.core.util.LocalStorage","tinymce.core.util.Color","tinymce.core.ui.Api","ephox.katamari.api.Cell","tinymce.core.fmt.ApplyFormat","tinymce.core.fmt.FormatChanged","tinymce.core.fmt.FormatRegistry","tinymce.core.fmt.MatchFormat","tinymce.core.fmt.Preview","tinymce.core.fmt.RemoveFormat","tinymce.core.fmt.ToggleFormat","tinymce.core.keyboard.FormatShortcuts","tinymce.core.util.Arr","tinymce.core.dom.Range","tinymce.core.dom.StyleSheetLoader","tinymce.core.dom.NodeType","tinymce.core.caret.CaretContainer","tinymce.core.text.Zwsp","ephox.sugar.api.node.Element","ephox.sugar.api.search.Selectors","tinymce.core.dom.RangePoint","tinymce.core.caret.CaretBookmark","tinymce.core.caret.CaretPosition","ephox.sugar.api.dom.Compare","tinymce.core.dom.ScrollIntoView","tinymce.core.dom.TridentSelection","tinymce.core.selection.FragmentReader","tinymce.core.undo.Levels","tinymce.core.delete.DeleteCommands","tinymce.core.InsertContent","global!document","tinymce.core.ui.Window","tinymce.core.ui.MessageBox","tinymce.core.ui.Notification","tinymce.core.EditorSettings","tinymce.core.init.Render","tinymce.core.Mode","tinymce.core.ui.Sidebar","tinymce.core.util.Uuid","tinymce.core.ErrorReporter","tinymce.core.LegacyInput","tinymce.core.ui.Selector","tinymce.core.ui.Collection","tinymce.core.ui.ReflowQueue","tinymce.core.ui.Control","tinymce.core.ui.Factory","tinymce.core.ui.KeyboardNavigation","tinymce.core.ui.Container","tinymce.core.ui.DragHelper","tinymce.core.ui.Scrollable","tinymce.core.ui.Panel","tinymce.core.ui.Movable","tinymce.core.ui.Resizable","tinymce.core.ui.FloatPanel","tinymce.core.ui.Tooltip","tinymce.core.ui.Widget","tinymce.core.ui.Progress","tinymce.core.ui.Layout","tinymce.core.ui.AbsoluteLayout","tinymce.core.ui.Button","tinymce.core.ui.ButtonGroup","tinymce.core.ui.Checkbox","tinymce.core.ui.ComboBox","tinymce.core.ui.ColorBox","tinymce.core.ui.PanelButton","tinymce.core.ui.ColorButton","tinymce.core.ui.ColorPicker","tinymce.core.ui.Path","tinymce.core.ui.ElementPath","tinymce.core.ui.FormItem","tinymce.core.ui.Form","tinymce.core.ui.FieldSet","tinymce.core.ui.FilePicker","tinymce.core.ui.FitLayout","tinymce.core.ui.FlexLayout","tinymce.core.ui.FlowLayout","tinymce.core.ui.FormatControls","tinymce.core.ui.GridLayout","tinymce.core.ui.Iframe","tinymce.core.ui.InfoBox","tinymce.core.ui.Label","tinymce.core.ui.Toolbar","tinymce.core.ui.MenuBar","tinymce.core.ui.MenuButton","tinymce.core.ui.MenuItem","tinymce.core.ui.Throbber","tinymce.core.ui.Menu","tinymce.core.ui.ListBox","tinymce.core.ui.Radio","tinymce.core.ui.ResizeHandle","tinymce.core.ui.SelectBox","tinymce.core.ui.Slider","tinymce.core.ui.Spacer","tinymce.core.ui.SplitButton","tinymce.core.ui.StackLayout","tinymce.core.ui.TabPanel","tinymce.core.ui.TextBox","tinymce.core.ui.DropZone","tinymce.core.ui.BrowseButton","ephox.katamari.api.Arr","ephox.katamari.api.Future","ephox.katamari.api.Futures","ephox.katamari.api.Result","tinymce.core.util.Fun","tinymce.core.caret.CaretCandidate","tinymce.core.geom.ClientRect","tinymce.core.text.ExtendingChar","tinymce.core.dom.ElementUtils","tinymce.core.dom.RangeNormalizer","tinymce.core.fmt.CaretFormat","tinymce.core.fmt.ExpandRange","tinymce.core.fmt.FormatUtils","tinymce.core.fmt.Hooks","tinymce.core.fmt.DefaultFormats","global!console","ephox.katamari.api.Option","ephox.sugar.api.node.NodeTypes","ephox.sand.api.Node","ephox.sand.api.PlatformDetection","ephox.sugar.api.dom.Insert","ephox.sugar.api.dom.Replication","ephox.sugar.api.node.Fragment","ephox.sugar.api.node.Node","tinymce.core.dom.ElementType","tinymce.core.dom.Parents","tinymce.core.selection.SelectionUtils","tinymce.core.undo.Fragments","tinymce.core.delete.BlockBoundaryDelete","tinymce.core.delete.BlockRangeDelete","tinymce.core.delete.CefDelete","tinymce.core.delete.DeleteUtils","tinymce.core.delete.InlineBoundaryDelete","tinymce.core.caret.CaretWalker","tinymce.core.InsertList","tinymce.core.data.ObservableObject","tinymce.core.ui.DomUtils","tinymce.core.ui.BoxUtils","tinymce.core.ui.ClassList","ephox.katamari.api.Type","global!window","tinymce.core.init.Init","tinymce.core.PluginManager","tinymce.core.ThemeManager","tinymce.core.content.LinkTargets","ephox.sugar.api.search.SelectorFind","tinymce.core.fmt.FontInfo","global!RegExp","global!Object","global!String","ephox.katamari.api.LazyValue","ephox.katamari.async.Bounce","ephox.katamari.async.AsyncValues","tinymce.core.caret.CaretFinder","tinymce.core.caret.CaretUtils","tinymce.core.dom.PaddingBr","ephox.sand.util.Global","ephox.katamari.api.Thunk","ephox.sand.core.PlatformDetection","global!navigator","ephox.sugar.api.search.Traverse","ephox.sugar.api.properties.Attr","ephox.sugar.api.dom.InsertAll","ephox.sugar.api.dom.Remove","ephox.katamari.api.Options","tinymce.core.undo.Diff","tinymce.core.delete.BlockBoundary","tinymce.core.delete.MergeBlocks","ephox.sugar.api.search.PredicateFind","ephox.sugar.api.search.SelectorFilter","tinymce.core.delete.CefDeleteAction","tinymce.core.delete.DeleteElement","tinymce.core.keyboard.BoundaryCaret","tinymce.core.keyboard.BoundaryLocation","tinymce.core.keyboard.BoundarySelection","tinymce.core.keyboard.InlineUtils","tinymce.core.data.Binding","tinymce.core.init.InitContentBody","ephox.sugar.impl.ClosestOrAncestor","global!setTimeout","ephox.katamari.api.Struct","ephox.sugar.alien.Recurse","ephox.katamari.api.Resolve","ephox.sand.core.Browser","ephox.sand.core.OperatingSystem","ephox.sand.detect.DeviceType","ephox.sand.detect.UaString","ephox.sand.info.PlatformInfo","ephox.sugar.api.search.PredicateFilter","ephox.katamari.api.Obj","ephox.sugar.api.node.Body","tinymce.core.dom.Empty","ephox.katamari.api.Adt","tinymce.core.caret.CaretContainerInline","tinymce.core.caret.CaretContainerRemove","tinymce.core.text.Bidi","tinymce.core.util.LazyEvaluator","tinymce.core.caret.CaretContainerInput","tinymce.core.EditorUpload","tinymce.core.ForceBlocks","tinymce.core.keyboard.KeyboardOverrides","tinymce.core.NodeChange","tinymce.core.SelectionOverrides","tinymce.core.util.Quirks","ephox.katamari.data.Immutable","ephox.katamari.data.MixedBag","ephox.katamari.api.Global","ephox.sand.detect.Version","ephox.katamari.api.Strings","ephox.sugar.api.search.SelectorExists","tinymce.core.file.Uploader","tinymce.core.file.ImageScanner","tinymce.core.file.BlobCache","tinymce.core.file.UploadStatus","tinymce.core.keyboard.ArrowKeys","tinymce.core.keyboard.DeleteBackspaceKeys","tinymce.core.keyboard.EnterKey","tinymce.core.keyboard.SpaceKey","tinymce.core.caret.FakeCaret","tinymce.core.caret.LineUtils","tinymce.core.DragDropOverrides","tinymce.core.EditorView","tinymce.core.keyboard.CefUtils","tinymce.core.dom.NodePath","ephox.katamari.util.BagUtils","global!Number","ephox.katamari.str.StrAppend","ephox.katamari.str.StringParts","tinymce.core.file.Conversions","global!URL","tinymce.core.keyboard.CefNavigation","tinymce.core.keyboard.MatchKeys","tinymce.core.keyboard.InsertSpace","tinymce.core.dom.Dimensions","tinymce.core.dom.MousePosition","ephox.sugar.api.properties.Css","tinymce.core.caret.LineWalker","ephox.katamari.api.Merger","ephox.sugar.impl.Style"]
85
+ ["tinymce.core.api.Main","ephox.katamari.api.Fun","tinymce.core.api.Tinymce","global!Array","global!Error","tinymce.core.api.Formatter","tinymce.core.geom.Rect","tinymce.core.util.Promise","tinymce.core.util.Delay","tinymce.core.Env","tinymce.core.dom.EventUtils","tinymce.core.dom.Sizzle","tinymce.core.util.Tools","tinymce.core.dom.DomQuery","tinymce.core.html.Styles","tinymce.core.dom.TreeWalker","tinymce.core.html.Entities","tinymce.core.dom.DOMUtils","tinymce.core.dom.ScriptLoader","tinymce.core.AddOnManager","tinymce.core.dom.RangeUtils","tinymce.core.html.Node","tinymce.core.html.Schema","tinymce.core.html.SaxParser","tinymce.core.html.DomParser","tinymce.core.html.Writer","tinymce.core.html.Serializer","tinymce.core.dom.Serializer","tinymce.core.util.VK","tinymce.core.dom.ControlSelection","tinymce.core.dom.BookmarkManager","tinymce.core.dom.Selection","tinymce.core.UndoManager","tinymce.core.EditorCommands","tinymce.core.util.URI","tinymce.core.util.Class","tinymce.core.util.EventDispatcher","tinymce.core.util.Observable","tinymce.core.WindowManager","tinymce.core.NotificationManager","tinymce.core.EditorObservable","tinymce.core.Shortcuts","tinymce.core.Editor","tinymce.core.util.I18n","tinymce.core.FocusManager","tinymce.core.EditorManager","tinymce.core.util.XHR","tinymce.core.util.JSON","tinymce.core.util.JSONRequest","tinymce.core.util.JSONP","tinymce.core.util.LocalStorage","tinymce.core.util.Color","tinymce.core.ui.Api","ephox.katamari.api.Cell","tinymce.core.fmt.ApplyFormat","tinymce.core.fmt.FormatChanged","tinymce.core.fmt.FormatRegistry","tinymce.core.fmt.MatchFormat","tinymce.core.fmt.Preview","tinymce.core.fmt.RemoveFormat","tinymce.core.fmt.ToggleFormat","tinymce.core.keyboard.FormatShortcuts","tinymce.core.util.Arr","tinymce.core.dom.Range","tinymce.core.dom.StyleSheetLoader","tinymce.core.dom.NodeType","tinymce.core.caret.CaretContainer","tinymce.core.text.Zwsp","ephox.sugar.api.node.Element","ephox.sugar.api.search.Selectors","tinymce.core.dom.RangePoint","tinymce.core.caret.CaretBookmark","tinymce.core.caret.CaretPosition","ephox.sugar.api.dom.Compare","tinymce.core.dom.ScrollIntoView","tinymce.core.dom.TridentSelection","tinymce.core.selection.FragmentReader","tinymce.core.undo.Levels","tinymce.core.delete.DeleteCommands","tinymce.core.InsertContent","global!document","tinymce.core.ui.Window","tinymce.core.ui.MessageBox","tinymce.core.EditorView","tinymce.core.ui.DomUtils","tinymce.core.ui.Notification","tinymce.core.EditorSettings","tinymce.core.init.Render","tinymce.core.Mode","tinymce.core.ui.Sidebar","tinymce.core.util.Uuid","ephox.katamari.api.Arr","ephox.katamari.api.Type","tinymce.core.ErrorReporter","tinymce.core.LegacyInput","tinymce.core.ui.Selector","tinymce.core.ui.Collection","tinymce.core.ui.ReflowQueue","tinymce.core.ui.Control","tinymce.core.ui.Factory","tinymce.core.ui.KeyboardNavigation","tinymce.core.ui.Container","tinymce.core.ui.DragHelper","tinymce.core.ui.Scrollable","tinymce.core.ui.Panel","tinymce.core.ui.Movable","tinymce.core.ui.Resizable","tinymce.core.ui.FloatPanel","tinymce.core.ui.Tooltip","tinymce.core.ui.Widget","tinymce.core.ui.Progress","tinymce.core.ui.Layout","tinymce.core.ui.AbsoluteLayout","tinymce.core.ui.Button","tinymce.core.ui.ButtonGroup","tinymce.core.ui.Checkbox","tinymce.core.ui.ComboBox","tinymce.core.ui.ColorBox","tinymce.core.ui.PanelButton","tinymce.core.ui.ColorButton","tinymce.core.ui.ColorPicker","tinymce.core.ui.Path","tinymce.core.ui.ElementPath","tinymce.core.ui.FormItem","tinymce.core.ui.Form","tinymce.core.ui.FieldSet","tinymce.core.ui.FilePicker","tinymce.core.ui.FitLayout","tinymce.core.ui.FlexLayout","tinymce.core.ui.FlowLayout","tinymce.core.ui.FormatControls","tinymce.core.ui.GridLayout","tinymce.core.ui.Iframe","tinymce.core.ui.InfoBox","tinymce.core.ui.Label","tinymce.core.ui.Toolbar","tinymce.core.ui.MenuBar","tinymce.core.ui.MenuButton","tinymce.core.ui.MenuItem","tinymce.core.ui.Throbber","tinymce.core.ui.Menu","tinymce.core.ui.ListBox","tinymce.core.ui.Radio","tinymce.core.ui.ResizeHandle","tinymce.core.ui.SelectBox","tinymce.core.ui.Slider","tinymce.core.ui.Spacer","tinymce.core.ui.SplitButton","tinymce.core.ui.StackLayout","tinymce.core.ui.TabPanel","tinymce.core.ui.TextBox","tinymce.core.ui.DropZone","tinymce.core.ui.BrowseButton","ephox.katamari.api.Option","global!String","ephox.katamari.api.Future","ephox.katamari.api.Futures","ephox.katamari.api.Result","tinymce.core.util.Fun","tinymce.core.caret.CaretCandidate","tinymce.core.geom.ClientRect","tinymce.core.text.ExtendingChar","tinymce.core.dom.RangeNormalizer","tinymce.core.fmt.CaretFormat","tinymce.core.fmt.ExpandRange","tinymce.core.fmt.FormatUtils","tinymce.core.fmt.Hooks","tinymce.core.fmt.MergeFormats","tinymce.core.fmt.DefaultFormats","global!console","ephox.sugar.api.node.NodeTypes","ephox.sand.api.Node","ephox.sand.api.PlatformDetection","ephox.sugar.api.dom.Insert","ephox.sugar.api.dom.Replication","ephox.sugar.api.node.Fragment","ephox.sugar.api.node.Node","ephox.sugar.api.search.SelectorFilter","ephox.sugar.api.search.SelectorFind","tinymce.core.dom.ElementType","tinymce.core.dom.Parents","tinymce.core.selection.SelectionUtils","tinymce.core.selection.SimpleTableModel","tinymce.core.undo.Fragments","tinymce.core.delete.BlockBoundaryDelete","tinymce.core.delete.BlockRangeDelete","tinymce.core.delete.CefDelete","tinymce.core.delete.DeleteUtils","tinymce.core.delete.InlineBoundaryDelete","tinymce.core.delete.TableDelete","tinymce.core.caret.CaretWalker","tinymce.core.dom.ElementUtils","tinymce.core.InsertList","tinymce.core.data.ObservableObject","tinymce.core.ui.BoxUtils","tinymce.core.ui.ClassList","ephox.sugar.api.properties.Css","ephox.sugar.api.search.Traverse","ephox.katamari.api.Obj","ephox.katamari.api.Strings","ephox.katamari.api.Struct","global!window","tinymce.core.init.Init","tinymce.core.PluginManager","tinymce.core.ThemeManager","tinymce.core.content.LinkTargets","tinymce.core.fmt.FontInfo","global!RegExp","global!Object","ephox.katamari.api.LazyValue","ephox.katamari.async.Bounce","ephox.katamari.async.AsyncValues","tinymce.core.caret.CaretFinder","tinymce.core.caret.CaretUtils","tinymce.core.dom.PaddingBr","ephox.sand.util.Global","ephox.katamari.api.Thunk","ephox.sand.core.PlatformDetection","global!navigator","ephox.katamari.data.Immutable","ephox.katamari.data.MixedBag","ephox.sugar.alien.Recurse","ephox.sugar.api.properties.Attr","ephox.sugar.api.dom.InsertAll","ephox.sugar.api.dom.Remove","ephox.sugar.api.search.PredicateFilter","ephox.sugar.api.search.PredicateFind","ephox.sugar.impl.ClosestOrAncestor","ephox.katamari.api.Options","tinymce.core.undo.Diff","tinymce.core.delete.BlockBoundary","tinymce.core.delete.MergeBlocks","tinymce.core.delete.CefDeleteAction","tinymce.core.delete.DeleteElement","tinymce.core.keyboard.BoundaryCaret","tinymce.core.keyboard.BoundaryLocation","tinymce.core.keyboard.BoundarySelection","tinymce.core.keyboard.InlineUtils","ephox.katamari.api.Adt","tinymce.core.delete.TableDeleteAction","tinymce.core.data.Binding","ephox.sugar.api.node.Body","ephox.sugar.impl.Style","ephox.katamari.str.StrAppend","ephox.katamari.str.StringParts","tinymce.core.init.InitContentBody","global!setTimeout","ephox.katamari.util.BagUtils","ephox.katamari.api.Resolve","ephox.sand.core.Browser","ephox.sand.core.OperatingSystem","ephox.sand.detect.DeviceType","ephox.sand.detect.UaString","ephox.sand.info.PlatformInfo","tinymce.core.dom.Empty","tinymce.core.caret.CaretContainerInline","tinymce.core.caret.CaretContainerRemove","tinymce.core.text.Bidi","tinymce.core.util.LazyEvaluator","tinymce.core.caret.CaretContainerInput","tinymce.core.EditorUpload","tinymce.core.ForceBlocks","tinymce.core.keyboard.KeyboardOverrides","tinymce.core.NodeChange","tinymce.core.SelectionOverrides","tinymce.core.util.Quirks","ephox.katamari.api.Global","ephox.sand.detect.Version","ephox.sugar.api.search.SelectorExists","tinymce.core.file.Uploader","tinymce.core.file.ImageScanner","tinymce.core.file.BlobCache","tinymce.core.file.UploadStatus","tinymce.core.keyboard.ArrowKeys","tinymce.core.keyboard.DeleteBackspaceKeys","tinymce.core.keyboard.EnterKey","tinymce.core.keyboard.SpaceKey","tinymce.core.caret.FakeCaret","tinymce.core.caret.LineUtils","tinymce.core.DragDropOverrides","tinymce.core.keyboard.CefUtils","tinymce.core.dom.NodePath","global!Number","tinymce.core.file.Conversions","ephox.sand.api.URL","tinymce.core.keyboard.CefNavigation","tinymce.core.keyboard.MatchKeys","tinymce.core.keyboard.InsertNewLine","tinymce.core.keyboard.InsertSpace","tinymce.core.dom.Dimensions","tinymce.core.dom.MousePosition","ephox.sand.api.Window","tinymce.core.caret.LineWalker","ephox.katamari.api.Merger"]
86
86
  jsc*/
87
87
  defineGlobal("global!Array", Array);
88
88
  defineGlobal("global!Error", Error);
@@ -13866,129 +13866,6 @@ define(
13866
13866
  return BookmarkManager;
13867
13867
  }
13868
13868
  );
13869
- /**
13870
- * ElementUtils.js
13871
- *
13872
- * Released under LGPL License.
13873
- * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
13874
- *
13875
- * License: http://www.tinymce.com/license
13876
- * Contributing: http://www.tinymce.com/contributing
13877
- */
13878
-
13879
- /**
13880
- * Utility class for various element specific functions.
13881
- *
13882
- * @private
13883
- * @class tinymce.dom.ElementUtils
13884
- */
13885
- define(
13886
- 'tinymce.core.dom.ElementUtils',
13887
- [
13888
- "tinymce.core.dom.BookmarkManager",
13889
- "tinymce.core.util.Tools"
13890
- ],
13891
- function (BookmarkManager, Tools) {
13892
- var each = Tools.each;
13893
-
13894
- function ElementUtils(dom) {
13895
- /**
13896
- * Compares two nodes and checks if it's attributes and styles matches.
13897
- * This doesn't compare classes as items since their order is significant.
13898
- *
13899
- * @method compare
13900
- * @param {Node} node1 First node to compare with.
13901
- * @param {Node} node2 Second node to compare with.
13902
- * @return {boolean} True/false if the nodes are the same or not.
13903
- */
13904
- this.compare = function (node1, node2) {
13905
- // Not the same name
13906
- if (node1.nodeName != node2.nodeName) {
13907
- return false;
13908
- }
13909
-
13910
- /**
13911
- * Returns all the nodes attributes excluding internal ones, styles and classes.
13912
- *
13913
- * @private
13914
- * @param {Node} node Node to get attributes from.
13915
- * @return {Object} Name/value object with attributes and attribute values.
13916
- */
13917
- function getAttribs(node) {
13918
- var attribs = {};
13919
-
13920
- each(dom.getAttribs(node), function (attr) {
13921
- var name = attr.nodeName.toLowerCase();
13922
-
13923
- // Don't compare internal attributes or style
13924
- if (name.indexOf('_') !== 0 && name !== 'style' && name.indexOf('data-') !== 0) {
13925
- attribs[name] = dom.getAttrib(node, name);
13926
- }
13927
- });
13928
-
13929
- return attribs;
13930
- }
13931
-
13932
- /**
13933
- * Compares two objects checks if it's key + value exists in the other one.
13934
- *
13935
- * @private
13936
- * @param {Object} obj1 First object to compare.
13937
- * @param {Object} obj2 Second object to compare.
13938
- * @return {boolean} True/false if the objects matches or not.
13939
- */
13940
- function compareObjects(obj1, obj2) {
13941
- var value, name;
13942
-
13943
- for (name in obj1) {
13944
- // Obj1 has item obj2 doesn't have
13945
- if (obj1.hasOwnProperty(name)) {
13946
- value = obj2[name];
13947
-
13948
- // Obj2 doesn't have obj1 item
13949
- if (typeof value == "undefined") {
13950
- return false;
13951
- }
13952
-
13953
- // Obj2 item has a different value
13954
- if (obj1[name] != value) {
13955
- return false;
13956
- }
13957
-
13958
- // Delete similar value
13959
- delete obj2[name];
13960
- }
13961
- }
13962
-
13963
- // Check if obj 2 has something obj 1 doesn't have
13964
- for (name in obj2) {
13965
- // Obj2 has item obj1 doesn't have
13966
- if (obj2.hasOwnProperty(name)) {
13967
- return false;
13968
- }
13969
- }
13970
-
13971
- return true;
13972
- }
13973
-
13974
- // Attribs are not the same
13975
- if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
13976
- return false;
13977
- }
13978
-
13979
- // Styles are not the same
13980
- if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
13981
- return false;
13982
- }
13983
-
13984
- return !BookmarkManager.isBookmarkNode(node1) && !BookmarkManager.isBookmarkNode(node2);
13985
- };
13986
- }
13987
-
13988
- return ElementUtils;
13989
- }
13990
- );
13991
-
13992
13869
  /**
13993
13870
  * CaretUtils.js
13994
13871
  *
@@ -14683,18 +14560,9 @@ define(
14683
14560
  [
14684
14561
  'tinymce.core.caret.CaretFinder',
14685
14562
  'tinymce.core.caret.CaretPosition',
14686
- 'tinymce.core.caret.CaretUtils',
14687
- 'tinymce.core.dom.NodeType'
14563
+ 'tinymce.core.caret.CaretUtils'
14688
14564
  ],
14689
- function (CaretFinder, CaretPosition, CaretUtils, NodeType) {
14690
- var isTextBlockLike = function (elm) {
14691
- return NodeType.isElement(elm) && /^(P|H[1-6]|DIV|LI|DT|DD)$/.test(elm.nodeName);
14692
- };
14693
-
14694
- var matchEndContainer = function (rng, predicate) {
14695
- return predicate(rng.endContainer);
14696
- };
14697
-
14565
+ function (CaretFinder, CaretPosition, CaretUtils) {
14698
14566
  var createRange = function (sc, so, ec, eo) {
14699
14567
  var rng = document.createRange();
14700
14568
  rng.setStart(sc, so);
@@ -14724,12 +14592,8 @@ define(
14724
14592
  }).getOr(rng);
14725
14593
  };
14726
14594
 
14727
- var isEndAtStartOfTextLikeBlock = function (rng) {
14728
- return matchEndContainer(rng, isTextBlockLike) && rng.endOffset === 0;
14729
- };
14730
-
14731
14595
  var normalizeBlockSelection = function (rng) {
14732
- return rng.collapsed && isEndAtStartOfTextLikeBlock(rng) ? rng : normalizeBlockSelectionRange(rng);
14596
+ return rng.collapsed ? rng : normalizeBlockSelectionRange(rng);
14733
14597
  };
14734
14598
 
14735
14599
  var normalize = function (rng) {
@@ -16599,6 +16463,7 @@ define(
16599
16463
  ];
16600
16464
 
16601
16465
  var headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
16466
+ var listItems = ['li', 'dd', 'dt'];
16602
16467
 
16603
16468
  var lazyLookup = function (items) {
16604
16469
  var lookup;
@@ -16625,6 +16490,7 @@ define(
16625
16490
  isInline: isInline,
16626
16491
  isHeading: isHeading,
16627
16492
  isTextBlock: lazyLookup(textBlocks),
16493
+ isListItem: lazyLookup(listItems),
16628
16494
  isVoid: lazyLookup(voids),
16629
16495
  isTableCell: lazyLookup(tableCells),
16630
16496
  isBr: isBr
@@ -17931,6 +17797,129 @@ define(
17931
17797
  }
17932
17798
  );
17933
17799
 
17800
+ /**
17801
+ * ElementUtils.js
17802
+ *
17803
+ * Released under LGPL License.
17804
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
17805
+ *
17806
+ * License: http://www.tinymce.com/license
17807
+ * Contributing: http://www.tinymce.com/contributing
17808
+ */
17809
+
17810
+ /**
17811
+ * Utility class for various element specific functions.
17812
+ *
17813
+ * @private
17814
+ * @class tinymce.dom.ElementUtils
17815
+ */
17816
+ define(
17817
+ 'tinymce.core.dom.ElementUtils',
17818
+ [
17819
+ "tinymce.core.dom.BookmarkManager",
17820
+ "tinymce.core.util.Tools"
17821
+ ],
17822
+ function (BookmarkManager, Tools) {
17823
+ var each = Tools.each;
17824
+
17825
+ function ElementUtils(dom) {
17826
+ /**
17827
+ * Compares two nodes and checks if it's attributes and styles matches.
17828
+ * This doesn't compare classes as items since their order is significant.
17829
+ *
17830
+ * @method compare
17831
+ * @param {Node} node1 First node to compare with.
17832
+ * @param {Node} node2 Second node to compare with.
17833
+ * @return {boolean} True/false if the nodes are the same or not.
17834
+ */
17835
+ this.compare = function (node1, node2) {
17836
+ // Not the same name
17837
+ if (node1.nodeName != node2.nodeName) {
17838
+ return false;
17839
+ }
17840
+
17841
+ /**
17842
+ * Returns all the nodes attributes excluding internal ones, styles and classes.
17843
+ *
17844
+ * @private
17845
+ * @param {Node} node Node to get attributes from.
17846
+ * @return {Object} Name/value object with attributes and attribute values.
17847
+ */
17848
+ function getAttribs(node) {
17849
+ var attribs = {};
17850
+
17851
+ each(dom.getAttribs(node), function (attr) {
17852
+ var name = attr.nodeName.toLowerCase();
17853
+
17854
+ // Don't compare internal attributes or style
17855
+ if (name.indexOf('_') !== 0 && name !== 'style' && name.indexOf('data-') !== 0) {
17856
+ attribs[name] = dom.getAttrib(node, name);
17857
+ }
17858
+ });
17859
+
17860
+ return attribs;
17861
+ }
17862
+
17863
+ /**
17864
+ * Compares two objects checks if it's key + value exists in the other one.
17865
+ *
17866
+ * @private
17867
+ * @param {Object} obj1 First object to compare.
17868
+ * @param {Object} obj2 Second object to compare.
17869
+ * @return {boolean} True/false if the objects matches or not.
17870
+ */
17871
+ function compareObjects(obj1, obj2) {
17872
+ var value, name;
17873
+
17874
+ for (name in obj1) {
17875
+ // Obj1 has item obj2 doesn't have
17876
+ if (obj1.hasOwnProperty(name)) {
17877
+ value = obj2[name];
17878
+
17879
+ // Obj2 doesn't have obj1 item
17880
+ if (typeof value == "undefined") {
17881
+ return false;
17882
+ }
17883
+
17884
+ // Obj2 item has a different value
17885
+ if (obj1[name] != value) {
17886
+ return false;
17887
+ }
17888
+
17889
+ // Delete similar value
17890
+ delete obj2[name];
17891
+ }
17892
+ }
17893
+
17894
+ // Check if obj 2 has something obj 1 doesn't have
17895
+ for (name in obj2) {
17896
+ // Obj2 has item obj1 doesn't have
17897
+ if (obj2.hasOwnProperty(name)) {
17898
+ return false;
17899
+ }
17900
+ }
17901
+
17902
+ return true;
17903
+ }
17904
+
17905
+ // Attribs are not the same
17906
+ if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
17907
+ return false;
17908
+ }
17909
+
17910
+ // Styles are not the same
17911
+ if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
17912
+ return false;
17913
+ }
17914
+
17915
+ return !BookmarkManager.isBookmarkNode(node1) && !BookmarkManager.isBookmarkNode(node2);
17916
+ };
17917
+ }
17918
+
17919
+ return ElementUtils;
17920
+ }
17921
+ );
17922
+
17934
17923
  /**
17935
17924
  * RemoveFormat.js
17936
17925
  *
@@ -18479,7 +18468,7 @@ define(
18479
18468
  }
18480
18469
  );
18481
18470
  /**
18482
- * ApplyFormat.js
18471
+ * MergeFormats.js
18483
18472
  *
18484
18473
  * Released under LGPL License.
18485
18474
  * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
@@ -18489,83 +18478,25 @@ define(
18489
18478
  */
18490
18479
 
18491
18480
  define(
18492
- 'tinymce.core.fmt.ApplyFormat',
18481
+ 'tinymce.core.fmt.MergeFormats',
18493
18482
  [
18483
+ 'ephox.katamari.api.Fun',
18494
18484
  'tinymce.core.dom.BookmarkManager',
18495
18485
  'tinymce.core.dom.ElementUtils',
18496
18486
  'tinymce.core.dom.NodeType',
18497
- 'tinymce.core.dom.RangeNormalizer',
18498
- 'tinymce.core.dom.RangeUtils',
18499
- 'tinymce.core.dom.TreeWalker',
18500
18487
  'tinymce.core.fmt.CaretFormat',
18501
- 'tinymce.core.fmt.ExpandRange',
18502
18488
  'tinymce.core.fmt.FormatUtils',
18503
- 'tinymce.core.fmt.Hooks',
18504
18489
  'tinymce.core.fmt.MatchFormat',
18505
18490
  'tinymce.core.fmt.RemoveFormat',
18506
- 'tinymce.core.util.Fun',
18507
18491
  'tinymce.core.util.Tools'
18508
18492
  ],
18509
- function (
18510
- BookmarkManager, ElementUtils, NodeType, RangeNormalizer, RangeUtils, TreeWalker, CaretFormat, ExpandRange, FormatUtils, Hooks, MatchFormat, RemoveFormat,
18511
- Fun, Tools
18512
- ) {
18493
+ function (Fun, BookmarkManager, ElementUtils, NodeType, CaretFormat, FormatUtils, MatchFormat, RemoveFormat, Tools) {
18513
18494
  var each = Tools.each;
18514
18495
 
18515
18496
  var isElementNode = function (node) {
18516
18497
  return node && node.nodeType === 1 && !BookmarkManager.isBookmarkNode(node) && !CaretFormat.isCaretNode(node) && !NodeType.isBogus(node);
18517
18498
  };
18518
18499
 
18519
- var processChildElements = function (node, filter, process) {
18520
- each(node.childNodes, function (node) {
18521
- if (isElementNode(node)) {
18522
- if (filter(node)) {
18523
- process(node);
18524
- }
18525
- if (node.hasChildNodes()) {
18526
- processChildElements(node, filter, process);
18527
- }
18528
- }
18529
- });
18530
- };
18531
-
18532
- var clearChildStyles = function (dom, format, node) {
18533
- if (format.clear_child_styles) {
18534
- var selector = format.links ? '*:not(a)' : '*';
18535
- each(dom.select(selector, node), function (node) {
18536
- if (isElementNode(node)) {
18537
- each(format.styles, function (value, name) {
18538
- dom.setStyle(node, name, '');
18539
- });
18540
- }
18541
- });
18542
- }
18543
- };
18544
-
18545
- var processUnderlineAndColor = function (dom, node) {
18546
- var textDecoration;
18547
- if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
18548
- textDecoration = FormatUtils.getTextDecoration(dom, node.parentNode);
18549
- if (dom.getStyle(node, 'color') && textDecoration) {
18550
- dom.setStyle(node, 'text-decoration', textDecoration);
18551
- } else if (dom.getStyle(node, 'text-decoration') === textDecoration) {
18552
- dom.setStyle(node, 'text-decoration', null);
18553
- }
18554
- }
18555
- };
18556
-
18557
- var hasStyle = function (dom, name) {
18558
- return Fun.curry(function (name, node) {
18559
- return !!(node && FormatUtils.getStyle(dom, node, name));
18560
- }, name);
18561
- };
18562
-
18563
- var applyStyle = function (dom, name, value) {
18564
- return Fun.curry(function (name, value, node) {
18565
- dom.setStyle(node, name, value);
18566
- }, name, value);
18567
- };
18568
-
18569
18500
  var findElementSibling = function (node, siblingName) {
18570
18501
  var sibling;
18571
18502
 
@@ -18582,15 +18513,7 @@ define(
18582
18513
  return node;
18583
18514
  };
18584
18515
 
18585
- /**
18586
- * Merges the next/previous sibling element if they match.
18587
- *
18588
- * @private
18589
- * @param {Node} prev Previous node to compare/merge.
18590
- * @param {Node} next Next node to compare/merge.
18591
- * @return {Node} Next node if we didn't merge and prev node if we did.
18592
- */
18593
- var mergeSiblings = function (dom, prev, next) {
18516
+ var mergeSiblingsNodes = function (dom, prev, next) {
18594
18517
  var sibling, tmpSibling, elementUtils = new ElementUtils(dom);
18595
18518
 
18596
18519
  // Check if next/prev exists and that they are elements
@@ -18610,7 +18533,7 @@ define(
18610
18533
 
18611
18534
  dom.remove(next);
18612
18535
 
18613
- each(Tools.grep(next.childNodes), function (node) {
18536
+ Tools.each(Tools.grep(next.childNodes), function (node) {
18614
18537
  prev.appendChild(node);
18615
18538
  });
18616
18539
 
@@ -18621,36 +18544,196 @@ define(
18621
18544
  return next;
18622
18545
  };
18623
18546
 
18624
- var findSelectionEnd = function (start, end) {
18625
- var walker = new TreeWalker(end), node;
18547
+ var processChildElements = function (node, filter, process) {
18548
+ each(node.childNodes, function (node) {
18549
+ if (isElementNode(node)) {
18550
+ if (filter(node)) {
18551
+ process(node);
18552
+ }
18553
+ if (node.hasChildNodes()) {
18554
+ processChildElements(node, filter, process);
18555
+ }
18556
+ }
18557
+ });
18558
+ };
18626
18559
 
18627
- for (node = walker.prev2(); node; node = walker.prev2()) {
18628
- if (node.nodeType === 3 && node.data.length > 0) {
18629
- return node;
18560
+ var hasStyle = function (dom, name) {
18561
+ return Fun.curry(function (name, node) {
18562
+ return !!(node && FormatUtils.getStyle(dom, node, name));
18563
+ }, name);
18564
+ };
18565
+
18566
+ var applyStyle = function (dom, name, value) {
18567
+ return Fun.curry(function (name, value, node) {
18568
+ dom.setStyle(node, name, value);
18569
+
18570
+ if (node.getAttribute('style') === '') {
18571
+ node.removeAttribute('style');
18630
18572
  }
18631
18573
 
18632
- if (node.childNodes.length > 1 || node === start || node.tagName === 'BR') {
18633
- return node;
18574
+ unwrapEmptySpan(dom, node);
18575
+ }, name, value);
18576
+ };
18577
+
18578
+ var unwrapEmptySpan = function (dom, node) {
18579
+ if (node.nodeName === 'SPAN' && dom.getAttribs(node).length === 0) {
18580
+ dom.remove(node, true);
18581
+ }
18582
+ };
18583
+
18584
+ var processUnderlineAndColor = function (dom, node) {
18585
+ var textDecoration;
18586
+ if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
18587
+ textDecoration = FormatUtils.getTextDecoration(dom, node.parentNode);
18588
+ if (dom.getStyle(node, 'color') && textDecoration) {
18589
+ dom.setStyle(node, 'text-decoration', textDecoration);
18590
+ } else if (dom.getStyle(node, 'text-decoration') === textDecoration) {
18591
+ dom.setStyle(node, 'text-decoration', null);
18634
18592
  }
18635
18593
  }
18636
18594
  };
18637
18595
 
18638
- // This converts: <p>[a</p><p>]b</p> -> <p>[a]</p><p>b</p>
18639
- var adjustSelectionToVisibleSelection = function (ed) {
18640
- // Adjust selection so that a end container with a end offset of zero is not included in the selection
18641
- // as this isn't visible to the user.
18642
- var rng = RangeNormalizer.normalize(ed.selection.getRng());
18643
- var start = rng.startContainer;
18644
- var end = rng.endContainer;
18596
+ var mergeUnderlineAndColor = function (dom, format, vars, node) {
18597
+ // Colored nodes should be underlined so that the color of the underline matches the text color.
18598
+ if (format.styles.color || format.styles.textDecoration) {
18599
+ Tools.walk(node, Fun.curry(processUnderlineAndColor, dom), 'childNodes');
18600
+ processUnderlineAndColor(dom, node);
18601
+ }
18602
+ };
18603
+
18604
+ var mergeBackgroundColorAndFontSize = function (dom, format, vars, node) {
18605
+ // nodes with font-size should have their own background color as well to fit the line-height (see TINY-882)
18606
+ if (format.styles && format.styles.backgroundColor) {
18607
+ processChildElements(node,
18608
+ hasStyle(dom, 'fontSize'),
18609
+ applyStyle(dom, 'backgroundColor', FormatUtils.replaceVars(format.styles.backgroundColor, vars))
18610
+ );
18611
+ }
18612
+ };
18645
18613
 
18646
- if (start !== end && rng.endOffset === 0) {
18647
- var newEnd = findSelectionEnd(start, end);
18648
- var endOffset = newEnd.nodeType === 3 ? newEnd.data.length : newEnd.childNodes.length;
18614
+ var mergeSubSup = function (dom, format, vars, node) {
18615
+ // Remove font size on all chilren of a sub/sup and remove the inverse element
18616
+ if (format.inline === 'sub' || format.inline === 'sup') {
18617
+ processChildElements(node,
18618
+ hasStyle(dom, 'fontSize'),
18619
+ applyStyle(dom, 'fontSize', '')
18620
+ );
18649
18621
 
18650
- rng.setEnd(newEnd, endOffset);
18622
+ dom.remove(dom.select(format.inline === 'sup' ? 'sub' : 'sup', node), true);
18651
18623
  }
18624
+ };
18652
18625
 
18653
- return rng;
18626
+ var mergeSiblings = function (dom, format, vars, node) {
18627
+ // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
18628
+ if (node && format.merge_siblings !== false) {
18629
+ node = mergeSiblingsNodes(dom, FormatUtils.getNonWhiteSpaceSibling(node), node);
18630
+ node = mergeSiblingsNodes(dom, node, FormatUtils.getNonWhiteSpaceSibling(node, true));
18631
+ }
18632
+ };
18633
+
18634
+ var clearChildStyles = function (dom, format, node) {
18635
+ if (format.clear_child_styles) {
18636
+ var selector = format.links ? '*:not(a)' : '*';
18637
+ each(dom.select(selector, node), function (node) {
18638
+ if (isElementNode(node)) {
18639
+ each(format.styles, function (value, name) {
18640
+ dom.setStyle(node, name, '');
18641
+ });
18642
+ }
18643
+ });
18644
+ }
18645
+ };
18646
+
18647
+ var mergeWithChildren = function (editor, formatList, vars, node) {
18648
+ // Remove/merge children
18649
+ each(formatList, function (format) {
18650
+ // Merge all children of similar type will move styles from child to parent
18651
+ // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
18652
+ // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
18653
+ each(editor.dom.select(format.inline, node), function (child) {
18654
+ if (!isElementNode(child)) {
18655
+ return;
18656
+ }
18657
+
18658
+ RemoveFormat.removeFormat(editor, format, vars, child, format.exact ? child : null);
18659
+ });
18660
+
18661
+ clearChildStyles(editor.dom, format, node);
18662
+ });
18663
+ };
18664
+
18665
+ var mergeWithParents = function (editor, format, name, vars, node) {
18666
+ // Remove format if direct parent already has the same format
18667
+ if (MatchFormat.matchNode(editor, node.parentNode, name, vars)) {
18668
+ if (RemoveFormat.removeFormat(editor, format, vars, node)) {
18669
+ return;
18670
+ }
18671
+ }
18672
+
18673
+ // Remove format if any ancestor already has the same format
18674
+ if (format.merge_with_parents) {
18675
+ editor.dom.getParent(node.parentNode, function (parent) {
18676
+ if (MatchFormat.matchNode(editor, parent, name, vars)) {
18677
+ RemoveFormat.removeFormat(editor, format, vars, node);
18678
+ return true;
18679
+ }
18680
+ });
18681
+ }
18682
+ };
18683
+
18684
+ return {
18685
+ mergeWithChildren: mergeWithChildren,
18686
+ mergeUnderlineAndColor: mergeUnderlineAndColor,
18687
+ mergeBackgroundColorAndFontSize: mergeBackgroundColorAndFontSize,
18688
+ mergeSubSup: mergeSubSup,
18689
+ mergeSiblings: mergeSiblings,
18690
+ mergeWithParents: mergeWithParents
18691
+ };
18692
+ }
18693
+ );
18694
+ /**
18695
+ * ApplyFormat.js
18696
+ *
18697
+ * Released under LGPL License.
18698
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
18699
+ *
18700
+ * License: http://www.tinymce.com/license
18701
+ * Contributing: http://www.tinymce.com/contributing
18702
+ */
18703
+
18704
+ define(
18705
+ 'tinymce.core.fmt.ApplyFormat',
18706
+ [
18707
+ 'tinymce.core.dom.BookmarkManager',
18708
+ 'tinymce.core.dom.NodeType',
18709
+ 'tinymce.core.dom.RangeNormalizer',
18710
+ 'tinymce.core.dom.RangeUtils',
18711
+ 'tinymce.core.fmt.CaretFormat',
18712
+ 'tinymce.core.fmt.ExpandRange',
18713
+ 'tinymce.core.fmt.FormatUtils',
18714
+ 'tinymce.core.fmt.Hooks',
18715
+ 'tinymce.core.fmt.MatchFormat',
18716
+ 'tinymce.core.fmt.MergeFormats',
18717
+ 'tinymce.core.util.Tools'
18718
+ ],
18719
+ function (BookmarkManager, NodeType, RangeNormalizer, RangeUtils, CaretFormat, ExpandRange, FormatUtils, Hooks, MatchFormat, MergeFormats, Tools) {
18720
+ var each = Tools.each;
18721
+
18722
+ var isElementNode = function (node) {
18723
+ return node && node.nodeType === 1 && !BookmarkManager.isBookmarkNode(node) && !CaretFormat.isCaretNode(node) && !NodeType.isBogus(node);
18724
+ };
18725
+
18726
+ var processChildElements = function (node, filter, process) {
18727
+ each(node.childNodes, function (node) {
18728
+ if (isElementNode(node)) {
18729
+ if (filter(node)) {
18730
+ process(node);
18731
+ }
18732
+ if (node.hasChildNodes()) {
18733
+ processChildElements(node, filter, process);
18734
+ }
18735
+ }
18736
+ });
18654
18737
  };
18655
18738
 
18656
18739
  var applyFormat = function (ed, name, vars, node) {
@@ -18863,23 +18946,6 @@ define(
18863
18946
  return child;
18864
18947
  };
18865
18948
 
18866
- var matchNestedWrapper = function (node, filter) {
18867
- do {
18868
- if (getChildCount(node) !== 1) {
18869
- break;
18870
- }
18871
-
18872
- node = getChildElementNode(node);
18873
- if (!node) {
18874
- break;
18875
- } else if (filter(node)) {
18876
- return node;
18877
- }
18878
- } while (node);
18879
-
18880
- return null;
18881
- };
18882
-
18883
18949
  var mergeStyles = function (node) {
18884
18950
  var child, clone;
18885
18951
 
@@ -18913,55 +18979,11 @@ define(
18913
18979
  node = mergeStyles(node);
18914
18980
  }
18915
18981
 
18916
- // Remove/merge children
18917
- each(formatList, function (format) {
18918
- // Merge all children of similar type will move styles from child to parent
18919
- // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
18920
- // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
18921
- each(dom.select(format.inline, node), function (child) {
18922
- if (!isElementNode(child)) {
18923
- return;
18924
- }
18925
-
18926
- RemoveFormat.removeFormat(ed, format, vars, child, format.exact ? child : null);
18927
- });
18928
-
18929
- clearChildStyles(dom, format, node);
18930
- });
18931
-
18932
- // Remove format if direct parent already has the same format
18933
- if (MatchFormat.matchNode(ed, node.parentNode, name, vars)) {
18934
- if (RemoveFormat.removeFormat(ed, format, vars, node)) {
18935
- node = 0;
18936
- }
18937
- }
18938
-
18939
- // Remove format if any ancestor already has the same format
18940
- if (format.merge_with_parents) {
18941
- dom.getParent(node.parentNode, function (parent) {
18942
- if (MatchFormat.matchNode(ed, parent, name, vars)) {
18943
- if (RemoveFormat.removeFormat(ed, format, vars, node)) {
18944
- node = 0;
18945
- }
18946
- return true;
18947
- }
18948
- });
18949
- }
18950
-
18951
- // fontSize defines the line height for the whole branch of nested style wrappers,
18952
- // therefore it should be set on the outermost wrapper
18953
- if (node && !dom.isBlock(node) && !FormatUtils.getStyle(dom, node, 'fontSize')) {
18954
- var styleNode = matchNestedWrapper(node, hasStyle(dom, 'fontSize'));
18955
- if (styleNode) {
18956
- applyFormat(ed, 'fontsize', { value: FormatUtils.getStyle(dom, styleNode, 'fontSize') }, node);
18957
- }
18958
- }
18959
-
18960
- // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
18961
- if (node && format.merge_siblings !== false) {
18962
- node = mergeSiblings(dom, FormatUtils.getNonWhiteSpaceSibling(node), node);
18963
- node = mergeSiblings(dom, node, FormatUtils.getNonWhiteSpaceSibling(node, true));
18964
- }
18982
+ MergeFormats.mergeWithChildren(ed, formatList, vars, node);
18983
+ MergeFormats.mergeWithParents(ed, format, name, vars, node);
18984
+ MergeFormats.mergeBackgroundColorAndFontSize(dom, format, vars, node);
18985
+ MergeFormats.mergeSubSup(dom, format, vars, node);
18986
+ MergeFormats.mergeSiblings(dom, format, vars, node);
18965
18987
  }
18966
18988
  });
18967
18989
  };
@@ -19003,24 +19025,12 @@ define(
19003
19025
  }
19004
19026
 
19005
19027
  // Apply formatting to selection
19006
- ed.selection.setRng(adjustSelectionToVisibleSelection(ed));
19028
+ ed.selection.setRng(RangeNormalizer.normalize(ed.selection.getRng()));
19007
19029
  bookmark = selection.getBookmark();
19008
19030
  applyRngStyle(dom, ExpandRange.expandRng(ed, selection.getRng(true), formatList), bookmark);
19009
19031
 
19010
19032
  if (format.styles) {
19011
- // Colored nodes should be underlined so that the color of the underline matches the text color.
19012
- if (format.styles.color || format.styles.textDecoration) {
19013
- Tools.walk(curSelNode, Fun.curry(processUnderlineAndColor, dom), 'childNodes');
19014
- processUnderlineAndColor(dom, curSelNode);
19015
- }
19016
-
19017
- // nodes with font-size should have their own background color as well to fit the line-height (see TINY-882)
19018
- if (format.styles.backgroundColor) {
19019
- processChildElements(curSelNode,
19020
- hasStyle(dom, 'fontSize'),
19021
- applyStyle(dom, 'backgroundColor', FormatUtils.replaceVars(format.styles.backgroundColor, vars))
19022
- );
19023
- }
19033
+ MergeFormats.mergeUnderlineAndColor(dom, format, vars, curSelNode);
19024
19034
  }
19025
19035
 
19026
19036
  selection.moveToBookmark(bookmark);
@@ -19185,7 +19195,14 @@ define(
19185
19195
  preview: false,
19186
19196
  defaultBlock: 'div'
19187
19197
  },
19188
- { selector: 'img,table', collapsed: false, styles: { 'float': 'left' }, preview: 'font-family font-size' }
19198
+ {
19199
+ selector: 'img,table',
19200
+ collapsed: false,
19201
+ styles: {
19202
+ 'float': 'left'
19203
+ },
19204
+ preview: 'font-family font-size'
19205
+ }
19189
19206
  ],
19190
19207
 
19191
19208
  aligncenter: [
@@ -19195,7 +19212,7 @@ define(
19195
19212
  textAlign: 'center'
19196
19213
  },
19197
19214
  inherit: false,
19198
- preview: false,
19215
+ preview: 'font-family font-size',
19199
19216
  defaultBlock: 'div'
19200
19217
  },
19201
19218
  {
@@ -25126,6 +25143,161 @@ define(
25126
25143
  }
25127
25144
  );
25128
25145
 
25146
+ define(
25147
+ 'ephox.sugar.impl.ClosestOrAncestor',
25148
+
25149
+ [
25150
+ 'ephox.katamari.api.Type',
25151
+ 'ephox.katamari.api.Option'
25152
+ ],
25153
+
25154
+ function (Type, Option) {
25155
+ return function (is, ancestor, scope, a, isRoot) {
25156
+ return is(scope, a) ?
25157
+ Option.some(scope) :
25158
+ Type.isFunction(isRoot) && isRoot(scope) ?
25159
+ Option.none() :
25160
+ ancestor(scope, a, isRoot);
25161
+ };
25162
+ }
25163
+ );
25164
+ define(
25165
+ 'ephox.sugar.api.search.PredicateFind',
25166
+
25167
+ [
25168
+ 'ephox.katamari.api.Type',
25169
+ 'ephox.katamari.api.Arr',
25170
+ 'ephox.katamari.api.Fun',
25171
+ 'ephox.katamari.api.Option',
25172
+ 'ephox.sugar.api.node.Body',
25173
+ 'ephox.sugar.api.dom.Compare',
25174
+ 'ephox.sugar.api.node.Element',
25175
+ 'ephox.sugar.impl.ClosestOrAncestor'
25176
+ ],
25177
+
25178
+ function (Type, Arr, Fun, Option, Body, Compare, Element, ClosestOrAncestor) {
25179
+ var first = function (predicate) {
25180
+ return descendant(Body.body(), predicate);
25181
+ };
25182
+
25183
+ var ancestor = function (scope, predicate, isRoot) {
25184
+ var element = scope.dom();
25185
+ var stop = Type.isFunction(isRoot) ? isRoot : Fun.constant(false);
25186
+
25187
+ while (element.parentNode) {
25188
+ element = element.parentNode;
25189
+ var el = Element.fromDom(element);
25190
+
25191
+ if (predicate(el)) return Option.some(el);
25192
+ else if (stop(el)) break;
25193
+ }
25194
+ return Option.none();
25195
+ };
25196
+
25197
+ var closest = function (scope, predicate, isRoot) {
25198
+ // This is required to avoid ClosestOrAncestor passing the predicate to itself
25199
+ var is = function (scope) {
25200
+ return predicate(scope);
25201
+ };
25202
+ return ClosestOrAncestor(is, ancestor, scope, predicate, isRoot);
25203
+ };
25204
+
25205
+ var sibling = function (scope, predicate) {
25206
+ var element = scope.dom();
25207
+ if (!element.parentNode) return Option.none();
25208
+
25209
+ return child(Element.fromDom(element.parentNode), function (x) {
25210
+ return !Compare.eq(scope, x) && predicate(x);
25211
+ });
25212
+ };
25213
+
25214
+ var child = function (scope, predicate) {
25215
+ var result = Arr.find(scope.dom().childNodes,
25216
+ Fun.compose(predicate, Element.fromDom));
25217
+ return result.map(Element.fromDom);
25218
+ };
25219
+
25220
+ var descendant = function (scope, predicate) {
25221
+ var descend = function (element) {
25222
+ for (var i = 0; i < element.childNodes.length; i++) {
25223
+ if (predicate(Element.fromDom(element.childNodes[i])))
25224
+ return Option.some(Element.fromDom(element.childNodes[i]));
25225
+
25226
+ var res = descend(element.childNodes[i]);
25227
+ if (res.isSome())
25228
+ return res;
25229
+ }
25230
+
25231
+ return Option.none();
25232
+ };
25233
+
25234
+ return descend(scope.dom());
25235
+ };
25236
+
25237
+ return {
25238
+ first: first,
25239
+ ancestor: ancestor,
25240
+ closest: closest,
25241
+ sibling: sibling,
25242
+ child: child,
25243
+ descendant: descendant
25244
+ };
25245
+ }
25246
+ );
25247
+
25248
+ define(
25249
+ 'ephox.sugar.api.search.SelectorFind',
25250
+
25251
+ [
25252
+ 'ephox.sugar.api.search.PredicateFind',
25253
+ 'ephox.sugar.api.search.Selectors',
25254
+ 'ephox.sugar.impl.ClosestOrAncestor'
25255
+ ],
25256
+
25257
+ function (PredicateFind, Selectors, ClosestOrAncestor) {
25258
+ // TODO: An internal SelectorFilter module that doesn't Element.fromDom() everything
25259
+
25260
+ var first = function (selector) {
25261
+ return Selectors.one(selector);
25262
+ };
25263
+
25264
+ var ancestor = function (scope, selector, isRoot) {
25265
+ return PredicateFind.ancestor(scope, function (e) {
25266
+ return Selectors.is(e, selector);
25267
+ }, isRoot);
25268
+ };
25269
+
25270
+ var sibling = function (scope, selector) {
25271
+ return PredicateFind.sibling(scope, function (e) {
25272
+ return Selectors.is(e, selector);
25273
+ });
25274
+ };
25275
+
25276
+ var child = function (scope, selector) {
25277
+ return PredicateFind.child(scope, function (e) {
25278
+ return Selectors.is(e, selector);
25279
+ });
25280
+ };
25281
+
25282
+ var descendant = function (scope, selector) {
25283
+ return Selectors.one(selector, scope);
25284
+ };
25285
+
25286
+ var closest = function (scope, selector, isRoot) {
25287
+ return ClosestOrAncestor(Selectors.is, ancestor, scope, selector, isRoot);
25288
+ };
25289
+
25290
+ return {
25291
+ first: first,
25292
+ ancestor: ancestor,
25293
+ sibling: sibling,
25294
+ child: child,
25295
+ descendant: descendant,
25296
+ closest: closest
25297
+ };
25298
+ }
25299
+ );
25300
+
25129
25301
  /**
25130
25302
  * Parents.js
25131
25303
  *
@@ -25310,6 +25482,172 @@ define(
25310
25482
  }
25311
25483
  );
25312
25484
 
25485
+ /**
25486
+ * SimpleTableModel.js
25487
+ *
25488
+ * Released under LGPL License.
25489
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
25490
+ *
25491
+ * License: http://www.tinymce.com/license
25492
+ * Contributing: http://www.tinymce.com/contributing
25493
+ */
25494
+
25495
+ define(
25496
+ 'tinymce.core.selection.SimpleTableModel',
25497
+ [
25498
+ 'ephox.katamari.api.Arr',
25499
+ 'ephox.katamari.api.Option',
25500
+ 'ephox.katamari.api.Struct',
25501
+ 'ephox.sugar.api.dom.Compare',
25502
+ 'ephox.sugar.api.dom.Insert',
25503
+ 'ephox.sugar.api.dom.InsertAll',
25504
+ 'ephox.sugar.api.dom.Replication',
25505
+ 'ephox.sugar.api.node.Element',
25506
+ 'ephox.sugar.api.properties.Attr',
25507
+ 'ephox.sugar.api.search.SelectorFilter'
25508
+ ],
25509
+ function (Arr, Option, Struct, Compare, Insert, InsertAll, Replication, Element, Attr, SelectorFilter) {
25510
+ var tableModel = Struct.immutable('element', 'width', 'rows');
25511
+ var tableRow = Struct.immutable('element', 'cells');
25512
+ var cellPosition = Struct.immutable('x', 'y');
25513
+
25514
+ var getSpan = function (td, key) {
25515
+ var value = parseInt(Attr.get(td, key), 10);
25516
+ return isNaN(value) ? 1 : value;
25517
+ };
25518
+
25519
+ var fillout = function (table, x, y, tr, td) {
25520
+ var rowspan = getSpan(td, 'rowspan');
25521
+ var colspan = getSpan(td, 'colspan');
25522
+ var rows = table.rows();
25523
+
25524
+ for (var y2 = y; y2 < y + rowspan; y2++) {
25525
+ if (!rows[y2]) {
25526
+ rows[y2] = tableRow(Replication.deep(tr), []);
25527
+ }
25528
+
25529
+ for (var x2 = x; x2 < x + colspan; x2++) {
25530
+ var cells = rows[y2].cells();
25531
+
25532
+ // not filler td:s are purposely not cloned so that we can
25533
+ // find cells in the model by element object references
25534
+ cells[x2] = y2 == y && x2 == x ? td : Replication.shallow(td);
25535
+ }
25536
+ }
25537
+ };
25538
+
25539
+ var cellExists = function (table, x, y) {
25540
+ var rows = table.rows();
25541
+ var cells = rows[y] ? rows[y].cells() : [];
25542
+ return !!cells[x];
25543
+ };
25544
+
25545
+ var skipCellsX = function (table, x, y) {
25546
+ while (cellExists(table, x, y)) {
25547
+ x++;
25548
+ }
25549
+
25550
+ return x;
25551
+ };
25552
+
25553
+ var getWidth = function (rows) {
25554
+ return Arr.foldl(rows, function (acc, row) {
25555
+ return row.cells().length > acc ? row.cells().length : acc;
25556
+ }, 0);
25557
+ };
25558
+
25559
+ var findElementPos = function (table, element) {
25560
+ var rows = table.rows();
25561
+ for (var y = 0; y < rows.length; y++) {
25562
+ var cells = rows[y].cells();
25563
+ for (var x = 0; x < cells.length; x++) {
25564
+ if (Compare.eq(cells[x], element)) {
25565
+ return Option.some(cellPosition(x, y));
25566
+ }
25567
+ }
25568
+ }
25569
+
25570
+ return Option.none();
25571
+ };
25572
+
25573
+ var extractRows = function (table, sx, sy, ex, ey) {
25574
+ var newRows = [];
25575
+ var rows = table.rows();
25576
+
25577
+ for (var y = sy; y <= ey; y++) {
25578
+ var cells = rows[y].cells();
25579
+ var slice = sx < ex ? cells.slice(sx, ex + 1) : cells.slice(ex, sx + 1);
25580
+ newRows.push(tableRow(rows[y].element(), slice));
25581
+ }
25582
+
25583
+ return newRows;
25584
+ };
25585
+
25586
+ var subTable = function (table, startPos, endPos) {
25587
+ var sx = startPos.x(), sy = startPos.y();
25588
+ var ex = endPos.x(), ey = endPos.y();
25589
+ var newRows = sy < ey ? extractRows(table, sx, sy, ex, ey) : extractRows(table, sx, ey, ex, sy);
25590
+
25591
+ return tableModel(table.element(), getWidth(newRows), newRows);
25592
+ };
25593
+
25594
+ var createDomTable = function (table, rows) {
25595
+ var tableElement = Replication.shallow(table.element());
25596
+ var tableBody = Element.fromTag('tbody');
25597
+
25598
+ InsertAll.append(tableBody, rows);
25599
+ Insert.append(tableElement, tableBody);
25600
+
25601
+ return tableElement;
25602
+ };
25603
+
25604
+ var modelRowsToDomRows = function (table) {
25605
+ return Arr.map(table.rows(), function (row) {
25606
+ var cells = Arr.map(row.cells(), function (cell) {
25607
+ var td = Replication.deep(cell);
25608
+ Attr.remove(td, 'colspan');
25609
+ Attr.remove(td, 'rowspan');
25610
+ return td;
25611
+ });
25612
+
25613
+ var tr = Replication.shallow(row.element());
25614
+ InsertAll.append(tr, cells);
25615
+ return tr;
25616
+ });
25617
+ };
25618
+
25619
+ var fromDom = function (tableElm) {
25620
+ var table = tableModel(Replication.shallow(tableElm), 0, []);
25621
+
25622
+ Arr.each(SelectorFilter.descendants(tableElm, 'tr'), function (tr, y) {
25623
+ Arr.each(SelectorFilter.descendants(tr, 'td,th'), function (td, x) {
25624
+ fillout(table, skipCellsX(table, x, y), y, tr, td);
25625
+ });
25626
+ });
25627
+
25628
+ return tableModel(table.element(), getWidth(table.rows()), table.rows());
25629
+ };
25630
+
25631
+ var toDom = function (table) {
25632
+ return createDomTable(table, modelRowsToDomRows(table));
25633
+ };
25634
+
25635
+ var subsection = function (table, startElement, endElement) {
25636
+ return findElementPos(table, startElement).bind(function (startPos) {
25637
+ return findElementPos(table, endElement).map(function (endPos) {
25638
+ return subTable(table, startPos, endPos);
25639
+ });
25640
+ });
25641
+ };
25642
+
25643
+ return {
25644
+ fromDom: fromDom,
25645
+ toDom: toDom,
25646
+ subsection: subsection
25647
+ };
25648
+ }
25649
+ );
25650
+
25313
25651
  /**
25314
25652
  * FragmentReader.js
25315
25653
  *
@@ -25325,16 +25663,20 @@ define(
25325
25663
  [
25326
25664
  'ephox.katamari.api.Arr',
25327
25665
  'ephox.katamari.api.Fun',
25666
+ 'ephox.sugar.api.dom.Compare',
25328
25667
  'ephox.sugar.api.dom.Insert',
25329
25668
  'ephox.sugar.api.dom.Replication',
25330
25669
  'ephox.sugar.api.node.Element',
25331
25670
  'ephox.sugar.api.node.Fragment',
25332
25671
  'ephox.sugar.api.node.Node',
25672
+ 'ephox.sugar.api.search.SelectorFilter',
25673
+ 'ephox.sugar.api.search.SelectorFind',
25333
25674
  'tinymce.core.dom.ElementType',
25334
25675
  'tinymce.core.dom.Parents',
25335
- 'tinymce.core.selection.SelectionUtils'
25676
+ 'tinymce.core.selection.SelectionUtils',
25677
+ 'tinymce.core.selection.SimpleTableModel'
25336
25678
  ],
25337
- function (Arr, Fun, Insert, Replication, Element, Fragment, Node, ElementType, Parents, SelectionUtils) {
25679
+ function (Arr, Fun, Compare, Insert, Replication, Element, Fragment, Node, SelectorFilter, SelectorFind, ElementType, Parents, SelectionUtils, SimpleTableModel) {
25338
25680
  var findParentListContainer = function (parents) {
25339
25681
  return Arr.find(parents, function (elm) {
25340
25682
  return Node.name(elm) === 'ul' || Node.name(elm) === 'ol';
@@ -25366,7 +25708,7 @@ define(
25366
25708
  };
25367
25709
 
25368
25710
  var getWrapElements = function (rootNode, rng) {
25369
- var parents = Parents.parentsAndSelf(Element.fromDom(rng.commonAncestorContainer), Element.fromDom(rootNode));
25711
+ var parents = Parents.parentsAndSelf(Element.fromDom(rng.commonAncestorContainer), rootNode);
25370
25712
  var wrapElements = Arr.filter(parents, function (elm) {
25371
25713
  return ElementType.isInline(elm) || ElementType.isHeading(elm);
25372
25714
  });
@@ -25374,12 +25716,41 @@ define(
25374
25716
  return Arr.map(wrapElements.concat(fullWrappers), Replication.shallow);
25375
25717
  };
25376
25718
 
25719
+ var emptyFragment = function () {
25720
+ return Fragment.fromElements([]);
25721
+ };
25722
+
25377
25723
  var getFragmentFromRange = function (rootNode, rng) {
25378
25724
  return wrap(Element.fromDom(rng.cloneContents()), getWrapElements(rootNode, rng));
25379
25725
  };
25380
25726
 
25727
+ var getTableCellSelection = function (rootNode) {
25728
+ return SelectorFilter.descendants(rootNode, 'td[data-mce-selected],th[data-mce-selected]');
25729
+ };
25730
+
25731
+ var getParentTable = function (rootElm, cell) {
25732
+ return SelectorFind.ancestor(cell, 'table', Fun.curry(Compare.eq, rootElm));
25733
+ };
25734
+
25735
+ var getTableFragment = function (rootNode, selectedTableCells) {
25736
+ return getParentTable(rootNode, selectedTableCells[0]).bind(function (tableElm) {
25737
+ var firstCell = selectedTableCells[0];
25738
+ var lastCell = selectedTableCells[selectedTableCells.length - 1];
25739
+ var fullTableModel = SimpleTableModel.fromDom(tableElm);
25740
+
25741
+ return SimpleTableModel.subsection(fullTableModel, firstCell, lastCell).map(function (sectionedTableModel) {
25742
+ return Fragment.fromElements([SimpleTableModel.toDom(sectionedTableModel)]);
25743
+ });
25744
+ }).getOrThunk(emptyFragment);
25745
+ };
25746
+
25747
+ var getSelectionFragment = function (rootNode, rng) {
25748
+ return rng.collapsed ? emptyFragment() : getFragmentFromRange(rootNode, rng);
25749
+ };
25750
+
25381
25751
  var read = function (rootNode, rng) {
25382
- return rng.collapsed ? Fragment.fromElements([]) : getFragmentFromRange(rootNode, rng);
25752
+ var selectedTableCells = getTableCellSelection(rootNode, rng);
25753
+ return selectedTableCells.length > 0 ? getTableFragment(rootNode, selectedTableCells) : getSelectionFragment(rootNode, rng);
25383
25754
  };
25384
25755
 
25385
25756
  return {
@@ -25524,7 +25895,7 @@ define(
25524
25895
  }
25525
25896
 
25526
25897
  if (rng.cloneContents) {
25527
- fragment = args.contextual ? FragmentReader.read(self.editor.getBody(), rng).dom() : rng.cloneContents();
25898
+ fragment = args.contextual ? FragmentReader.read(Element.fromDom(self.editor.getBody()), rng).dom() : rng.cloneContents();
25528
25899
  if (fragment) {
25529
25900
  tmpElm.appendChild(fragment);
25530
25901
  }
@@ -27146,108 +27517,6 @@ define(
27146
27517
  }
27147
27518
  );
27148
27519
 
27149
- define(
27150
- 'ephox.sugar.impl.ClosestOrAncestor',
27151
-
27152
- [
27153
- 'ephox.katamari.api.Type',
27154
- 'ephox.katamari.api.Option'
27155
- ],
27156
-
27157
- function (Type, Option) {
27158
- return function (is, ancestor, scope, a, isRoot) {
27159
- return is(scope, a) ?
27160
- Option.some(scope) :
27161
- Type.isFunction(isRoot) && isRoot(scope) ?
27162
- Option.none() :
27163
- ancestor(scope, a, isRoot);
27164
- };
27165
- }
27166
- );
27167
- define(
27168
- 'ephox.sugar.api.search.PredicateFind',
27169
-
27170
- [
27171
- 'ephox.katamari.api.Type',
27172
- 'ephox.katamari.api.Arr',
27173
- 'ephox.katamari.api.Fun',
27174
- 'ephox.katamari.api.Option',
27175
- 'ephox.sugar.api.node.Body',
27176
- 'ephox.sugar.api.dom.Compare',
27177
- 'ephox.sugar.api.node.Element',
27178
- 'ephox.sugar.impl.ClosestOrAncestor'
27179
- ],
27180
-
27181
- function (Type, Arr, Fun, Option, Body, Compare, Element, ClosestOrAncestor) {
27182
- var first = function (predicate) {
27183
- return descendant(Body.body(), predicate);
27184
- };
27185
-
27186
- var ancestor = function (scope, predicate, isRoot) {
27187
- var element = scope.dom();
27188
- var stop = Type.isFunction(isRoot) ? isRoot : Fun.constant(false);
27189
-
27190
- while (element.parentNode) {
27191
- element = element.parentNode;
27192
- var el = Element.fromDom(element);
27193
-
27194
- if (predicate(el)) return Option.some(el);
27195
- else if (stop(el)) break;
27196
- }
27197
- return Option.none();
27198
- };
27199
-
27200
- var closest = function (scope, predicate, isRoot) {
27201
- // This is required to avoid ClosestOrAncestor passing the predicate to itself
27202
- var is = function (scope) {
27203
- return predicate(scope);
27204
- };
27205
- return ClosestOrAncestor(is, ancestor, scope, predicate, isRoot);
27206
- };
27207
-
27208
- var sibling = function (scope, predicate) {
27209
- var element = scope.dom();
27210
- if (!element.parentNode) return Option.none();
27211
-
27212
- return child(Element.fromDom(element.parentNode), function (x) {
27213
- return !Compare.eq(scope, x) && predicate(x);
27214
- });
27215
- };
27216
-
27217
- var child = function (scope, predicate) {
27218
- var result = Arr.find(scope.dom().childNodes,
27219
- Fun.compose(predicate, Element.fromDom));
27220
- return result.map(Element.fromDom);
27221
- };
27222
-
27223
- var descendant = function (scope, predicate) {
27224
- var descend = function (element) {
27225
- for (var i = 0; i < element.childNodes.length; i++) {
27226
- if (predicate(Element.fromDom(element.childNodes[i])))
27227
- return Option.some(Element.fromDom(element.childNodes[i]));
27228
-
27229
- var res = descend(element.childNodes[i]);
27230
- if (res.isSome())
27231
- return res;
27232
- }
27233
-
27234
- return Option.none();
27235
- };
27236
-
27237
- return descend(scope.dom());
27238
- };
27239
-
27240
- return {
27241
- first: first,
27242
- ancestor: ancestor,
27243
- closest: closest,
27244
- sibling: sibling,
27245
- child: child,
27246
- descendant: descendant
27247
- };
27248
- }
27249
- );
27250
-
27251
27520
  /**
27252
27521
  * DeleteUtils.js
27253
27522
  *
@@ -27261,38 +27530,23 @@ define(
27261
27530
  define(
27262
27531
  'tinymce.core.delete.DeleteUtils',
27263
27532
  [
27264
- 'ephox.katamari.api.Arr',
27265
27533
  'ephox.katamari.api.Option',
27266
27534
  'ephox.sugar.api.dom.Compare',
27267
27535
  'ephox.sugar.api.node.Element',
27268
- 'ephox.sugar.api.node.Node',
27269
- 'ephox.sugar.api.search.PredicateFind'
27536
+ 'ephox.sugar.api.search.PredicateFind',
27537
+ 'tinymce.core.dom.ElementType'
27270
27538
  ],
27271
- function (Arr, Option, Compare, Element, Node, PredicateFind) {
27272
- var toLookup = function (names) {
27273
- var lookup = Arr.foldl(names, function (acc, name) {
27274
- acc[name] = true;
27275
- return acc;
27276
- }, { });
27277
-
27278
- return function (elm) {
27279
- return lookup[Node.name(elm)] === true;
27280
- };
27281
- };
27282
-
27283
- var isTextBlock = toLookup([
27284
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div', 'address', 'pre', 'form', 'blockquote', 'center',
27285
- 'dir', 'fieldset', 'header', 'footer', 'article', 'section', 'hgroup', 'aside', 'nav', 'figure'
27286
- ]);
27287
-
27539
+ function (Option, Compare, Element, PredicateFind, ElementType) {
27288
27540
  var isBeforeRoot = function (rootNode) {
27289
27541
  return function (elm) {
27290
27542
  return Compare.eq(rootNode, Element.fromDom(elm.dom().parentNode));
27291
27543
  };
27292
27544
  };
27293
27545
 
27294
- var getParentTextBlock = function (rootNode, elm) {
27295
- return Compare.contains(rootNode, elm) ? PredicateFind.closest(elm, isTextBlock, isBeforeRoot(rootNode)) : Option.none();
27546
+ var getParentBlock = function (rootNode, elm) {
27547
+ return Compare.contains(rootNode, elm) ? PredicateFind.closest(elm, function (element) {
27548
+ return ElementType.isTextBlock(element) || ElementType.isListItem(element);
27549
+ }, isBeforeRoot(rootNode)) : Option.none();
27296
27550
  };
27297
27551
 
27298
27552
  var placeCaretInEmptyBody = function (editor) {
@@ -27309,65 +27563,12 @@ define(
27309
27563
  };
27310
27564
 
27311
27565
  return {
27312
- getParentTextBlock: getParentTextBlock,
27566
+ getParentBlock: getParentBlock,
27313
27567
  paddEmptyBody: paddEmptyBody
27314
27568
  };
27315
27569
  }
27316
27570
  );
27317
27571
 
27318
- define(
27319
- 'ephox.sugar.api.search.SelectorFind',
27320
-
27321
- [
27322
- 'ephox.sugar.api.search.PredicateFind',
27323
- 'ephox.sugar.api.search.Selectors',
27324
- 'ephox.sugar.impl.ClosestOrAncestor'
27325
- ],
27326
-
27327
- function (PredicateFind, Selectors, ClosestOrAncestor) {
27328
- // TODO: An internal SelectorFilter module that doesn't Element.fromDom() everything
27329
-
27330
- var first = function (selector) {
27331
- return Selectors.one(selector);
27332
- };
27333
-
27334
- var ancestor = function (scope, selector, isRoot) {
27335
- return PredicateFind.ancestor(scope, function (e) {
27336
- return Selectors.is(e, selector);
27337
- }, isRoot);
27338
- };
27339
-
27340
- var sibling = function (scope, selector) {
27341
- return PredicateFind.sibling(scope, function (e) {
27342
- return Selectors.is(e, selector);
27343
- });
27344
- };
27345
-
27346
- var child = function (scope, selector) {
27347
- return PredicateFind.child(scope, function (e) {
27348
- return Selectors.is(e, selector);
27349
- });
27350
- };
27351
-
27352
- var descendant = function (scope, selector) {
27353
- return Selectors.one(selector, scope);
27354
- };
27355
-
27356
- var closest = function (scope, selector, isRoot) {
27357
- return ClosestOrAncestor(Selectors.is, ancestor, scope, selector, isRoot);
27358
- };
27359
-
27360
- return {
27361
- first: first,
27362
- ancestor: ancestor,
27363
- sibling: sibling,
27364
- child: child,
27365
- descendant: descendant,
27366
- closest: closest
27367
- };
27368
- }
27369
- );
27370
-
27371
27572
  define(
27372
27573
  'ephox.sugar.api.search.SelectorExists',
27373
27574
 
@@ -27541,7 +27742,7 @@ define(
27541
27742
  var getBlockPosition = function (rootNode, pos) {
27542
27743
  var rootElm = Element.fromDom(rootNode);
27543
27744
  var containerElm = Element.fromDom(pos.container());
27544
- return DeleteUtils.getParentTextBlock(rootElm, containerElm).map(function (block) {
27745
+ return DeleteUtils.getParentBlock(rootElm, containerElm).map(function (block) {
27545
27746
  return BlockPosition(block, pos);
27546
27747
  });
27547
27748
  };
@@ -27618,59 +27819,114 @@ define(
27618
27819
  [
27619
27820
  'ephox.katamari.api.Arr',
27620
27821
  'ephox.katamari.api.Option',
27822
+ 'ephox.sugar.api.dom.Compare',
27621
27823
  'ephox.sugar.api.dom.Insert',
27622
27824
  'ephox.sugar.api.dom.Remove',
27623
27825
  'ephox.sugar.api.node.Element',
27624
27826
  'ephox.sugar.api.search.Traverse',
27625
27827
  'tinymce.core.caret.CaretFinder',
27626
27828
  'tinymce.core.caret.CaretPosition',
27829
+ 'tinymce.core.dom.ElementType',
27627
27830
  'tinymce.core.dom.Empty',
27628
- 'tinymce.core.dom.NodeType'
27831
+ 'tinymce.core.dom.NodeType',
27832
+ 'tinymce.core.dom.Parents'
27629
27833
  ],
27630
- function (Arr, Option, Insert, Remove, Element, Traverse, CaretFinder, CaretPosition, Empty, NodeType) {
27631
- var mergeBlocksAndReposition = function (forward, fromBlock, toBlock, toPosition) {
27632
- var children = Traverse.children(fromBlock);
27633
-
27634
- if (NodeType.isBr(toPosition.getNode())) {
27635
- Remove.remove(Element.fromDom(toPosition.getNode()));
27636
- toPosition = CaretFinder.positionIn(false, toBlock.dom()).getOr(toPosition);
27637
- }
27834
+ function (Arr, Option, Compare, Insert, Remove, Element, Traverse, CaretFinder, CaretPosition, ElementType, Empty, NodeType, Parents) {
27835
+ var getChildrenUntilBlockBoundary = function (block) {
27836
+ var children = Traverse.children(block);
27837
+ return Arr.findIndex(children, ElementType.isBlock).fold(
27838
+ function () {
27839
+ return children;
27840
+ },
27841
+ function (index) {
27842
+ return children.slice(0, index);
27843
+ }
27844
+ );
27845
+ };
27638
27846
 
27639
- if (Empty.isEmpty(fromBlock) === false) {
27640
- Arr.each(children, function (node) {
27641
- Insert.append(toBlock, node);
27642
- });
27643
- }
27847
+ var extractChildren = function (block) {
27848
+ var children = getChildrenUntilBlockBoundary(block);
27644
27849
 
27645
- if (Empty.isEmpty(fromBlock)) {
27646
- Remove.remove(fromBlock);
27647
- }
27850
+ Arr.each(children, function (node) {
27851
+ Remove.remove(node);
27852
+ });
27648
27853
 
27649
- return children.length > 0 ? Option.from(toPosition) : Option.none();
27854
+ return children;
27650
27855
  };
27651
27856
 
27652
- var mergeBlocks = function (forward, block1, block2) {
27653
- if (forward) {
27654
- if (Empty.isEmpty(block1)) {
27655
- Remove.remove(block1);
27656
- return CaretFinder.positionIn(true, block2.dom());
27657
- } else {
27658
- return CaretFinder.positionIn(false, block1.dom()).bind(function (toPosition) {
27659
- return mergeBlocksAndReposition(forward, block2, block1, toPosition);
27660
- });
27857
+ var trimBr = function (first, block) {
27858
+ CaretFinder.positionIn(first, block.dom()).each(function (position) {
27859
+ var node = position.getNode();
27860
+ if (NodeType.isBr(node)) {
27861
+ Remove.remove(Element.fromDom(node));
27661
27862
  }
27863
+ });
27864
+ };
27865
+
27866
+ var removeEmptyRoot = function (rootNode, block) {
27867
+ var parents = Parents.parentsAndSelf(block, rootNode);
27868
+ return Arr.find(parents.reverse(), Empty.isEmpty).each(Remove.remove);
27869
+ };
27870
+
27871
+ var findParentInsertPoint = function (toBlock, block) {
27872
+ var parents = Traverse.parents(block, function (elm) {
27873
+ return Compare.eq(elm, toBlock);
27874
+ });
27875
+
27876
+ return Option.from(parents[parents.length - 2]);
27877
+ };
27878
+
27879
+ var getInsertionPoint = function (fromBlock, toBlock) {
27880
+ if (Compare.contains(toBlock, fromBlock)) {
27881
+ return Traverse.parent(fromBlock).bind(function (parent) {
27882
+ return Compare.eq(parent, toBlock) ? Option.some(fromBlock) : findParentInsertPoint(toBlock, fromBlock);
27883
+ });
27662
27884
  } else {
27663
- if (Empty.isEmpty(block2)) {
27664
- Remove.remove(block2);
27665
- return CaretFinder.positionIn(true, block1.dom());
27666
- } else {
27667
- return CaretFinder.positionIn(false, block2.dom()).bind(function (toPosition) {
27668
- return mergeBlocksAndReposition(forward, block1, block2, toPosition);
27669
- });
27670
- }
27885
+ return Option.none();
27671
27886
  }
27672
27887
  };
27673
27888
 
27889
+ var mergeBlockInto = function (rootNode, fromBlock, toBlock) {
27890
+ if (Empty.isEmpty(toBlock)) {
27891
+ Remove.remove(toBlock);
27892
+ return CaretFinder.firstPositionIn(fromBlock.dom());
27893
+ } else {
27894
+ trimBr(true, fromBlock);
27895
+ trimBr(false, toBlock);
27896
+
27897
+ var children = extractChildren(fromBlock);
27898
+
27899
+ return getInsertionPoint(fromBlock, toBlock).fold(
27900
+ function () {
27901
+ removeEmptyRoot(rootNode, fromBlock);
27902
+
27903
+ var position = CaretFinder.lastPositionIn(toBlock.dom());
27904
+
27905
+ Arr.each(children, function (node) {
27906
+ Insert.append(toBlock, node);
27907
+ });
27908
+
27909
+ return position;
27910
+ },
27911
+ function (target) {
27912
+ var position = CaretFinder.prevPosition(toBlock.dom(), CaretPosition.before(target.dom()));
27913
+
27914
+ Arr.each(children, function (node) {
27915
+ Insert.before(target, node);
27916
+ });
27917
+
27918
+ removeEmptyRoot(rootNode, fromBlock);
27919
+
27920
+ return position;
27921
+ }
27922
+ );
27923
+ }
27924
+ };
27925
+
27926
+ var mergeBlocks = function (rootNode, forward, block1, block2) {
27927
+ return forward ? mergeBlockInto(rootNode, block2, block1) : mergeBlockInto(rootNode, block1, block2);
27928
+ };
27929
+
27674
27930
  return {
27675
27931
  mergeBlocks: mergeBlocks
27676
27932
  };
@@ -27690,15 +27946,16 @@ define(
27690
27946
  define(
27691
27947
  'tinymce.core.delete.BlockBoundaryDelete',
27692
27948
  [
27949
+ 'ephox.sugar.api.node.Element',
27693
27950
  'tinymce.core.delete.BlockBoundary',
27694
27951
  'tinymce.core.delete.MergeBlocks'
27695
27952
  ],
27696
- function (BlockBoundary, MergeBlocks) {
27953
+ function (Element, BlockBoundary, MergeBlocks) {
27697
27954
  var backspaceDelete = function (editor, forward) {
27698
- var position;
27955
+ var position, rootNode = Element.fromDom(editor.getBody());
27699
27956
 
27700
- position = BlockBoundary.read(editor.getBody(), forward, editor.selection.getRng()).bind(function (blockBoundary) {
27701
- return MergeBlocks.mergeBlocks(forward, blockBoundary.from().block(), blockBoundary.to().block());
27957
+ position = BlockBoundary.read(rootNode.dom(), forward, editor.selection.getRng()).bind(function (blockBoundary) {
27958
+ return MergeBlocks.mergeBlocks(rootNode, forward, blockBoundary.from().block(), blockBoundary.to().block());
27702
27959
  });
27703
27960
 
27704
27961
  position.each(function (pos) {
@@ -27740,13 +27997,13 @@ define(
27740
27997
  var rng = selection.getRng();
27741
27998
 
27742
27999
  return Options.liftN([
27743
- DeleteUtils.getParentTextBlock(rootNode, Element.fromDom(rng.startContainer)),
27744
- DeleteUtils.getParentTextBlock(rootNode, Element.fromDom(rng.endContainer))
28000
+ DeleteUtils.getParentBlock(rootNode, Element.fromDom(rng.startContainer)),
28001
+ DeleteUtils.getParentBlock(rootNode, Element.fromDom(rng.endContainer))
27745
28002
  ], function (block1, block2) {
27746
28003
  if (Compare.eq(block1, block2) === false) {
27747
28004
  rng.deleteContents();
27748
28005
 
27749
- MergeBlocks.mergeBlocks(true, block1, block2).each(function (pos) {
28006
+ MergeBlocks.mergeBlocks(rootNode, true, block1, block2).each(function (pos) {
27750
28007
  selection.setRng(pos.toRange());
27751
28008
  });
27752
28009
 
@@ -27939,7 +28196,7 @@ define(
27939
28196
 
27940
28197
  var deleteEmptyBlockOrMoveToCef = function (rootNode, forward, from, to) {
27941
28198
  var toCefElm = to.getNode(forward === false);
27942
- return DeleteUtils.getParentTextBlock(Element.fromDom(rootNode), Element.fromDom(from.getNode())).map(function (blockElm) {
28199
+ return DeleteUtils.getParentBlock(Element.fromDom(rootNode), Element.fromDom(from.getNode())).map(function (blockElm) {
27943
28200
  return Empty.isEmpty(blockElm) ? DeleteAction.remove(blockElm.dom()) : DeleteAction.moveToElement(toCefElm);
27944
28201
  }).orThunk(function () {
27945
28202
  return Option.some(DeleteAction.moveToElement(toCefElm));
@@ -28206,15 +28463,12 @@ define(
28206
28463
  'ephox.sugar.api.node.Element',
28207
28464
  'ephox.sugar.api.search.SelectorFilter',
28208
28465
  'tinymce.core.caret.CaretPosition',
28209
- 'tinymce.core.caret.CaretUtils',
28210
- 'tinymce.core.delete.BlockBoundary',
28211
28466
  'tinymce.core.delete.CefDeleteAction',
28212
28467
  'tinymce.core.delete.DeleteElement',
28213
28468
  'tinymce.core.delete.DeleteUtils',
28214
- 'tinymce.core.delete.MergeBlocks',
28215
28469
  'tinymce.core.dom.NodeType'
28216
28470
  ],
28217
- function (Arr, Remove, Element, SelectorFilter, CaretPosition, CaretUtils, BlockBoundary, CefDeleteAction, DeleteElement, DeleteUtils, MergeBlocks, NodeType) {
28471
+ function (Arr, Remove, Element, SelectorFilter, CaretPosition, CefDeleteAction, DeleteElement, DeleteUtils, NodeType) {
28218
28472
  var deleteElement = function (editor, forward) {
28219
28473
  return function (element) {
28220
28474
  DeleteElement.deleteElement(editor, forward, Element.fromDom(element));
@@ -28522,73 +28776,133 @@ define(
28522
28776
  define(
28523
28777
  'tinymce.core.EditorSettings',
28524
28778
  [
28779
+ 'ephox.katamari.api.Arr',
28525
28780
  'ephox.katamari.api.Fun',
28781
+ 'ephox.katamari.api.Obj',
28526
28782
  'ephox.katamari.api.Option',
28783
+ 'ephox.katamari.api.Strings',
28784
+ 'ephox.katamari.api.Struct',
28527
28785
  'ephox.katamari.api.Type',
28786
+ 'ephox.sand.api.PlatformDetection',
28528
28787
  'tinymce.core.util.Tools'
28529
28788
  ],
28530
- function (Fun, Option, Type, Tools) {
28531
- var getEditorSettings = function (editor, id, documentBaseUrl, defaultOverrideSettings, settings) {
28532
- settings = Tools.extend(
28789
+ function (Arr, Fun, Obj, Option, Strings, Struct, Type, PlatformDetection, Tools) {
28790
+ var sectionResult = Struct.immutable('sections', 'settings');
28791
+ var detection = PlatformDetection.detect();
28792
+ var isTouch = detection.deviceType.isTouch();
28793
+ var mobilePlugins = [ 'lists', 'autolink', 'autosave' ];
28794
+
28795
+ var normalizePlugins = function (plugins) {
28796
+ return Type.isArray(plugins) ? plugins.join(' ') : plugins;
28797
+ };
28798
+
28799
+ var filterMobilePlugins = function (plugins) {
28800
+ var trimmedPlugins = Arr.map(normalizePlugins(plugins).split(' '), Strings.trim);
28801
+ return Arr.filter(trimmedPlugins, Fun.curry(Arr.contains, mobilePlugins)).join(' ');
28802
+ };
28803
+
28804
+ var extractSections = function (keys, settings) {
28805
+ var result = Obj.bifilter(settings, function (value, key) {
28806
+ return Arr.contains(keys, key);
28807
+ });
28808
+
28809
+ return sectionResult(result.t, result.f);
28810
+ };
28811
+
28812
+ var getSection = function (sectionResult, name) {
28813
+ var sections = sectionResult.sections();
28814
+ return sections.hasOwnProperty(name) ? sections[name] : { };
28815
+ };
28816
+
28817
+ var hasSection = function (sectionResult, name) {
28818
+ return sectionResult.sections().hasOwnProperty(name);
28819
+ };
28820
+
28821
+ var getDefaultSettings = function (id, documentBaseUrl, editor) {
28822
+ return {
28823
+ id: id,
28824
+ theme: 'modern',
28825
+ delta_width: 0,
28826
+ delta_height: 0,
28827
+ popup_css: '',
28828
+ plugins: '',
28829
+ document_base_url: documentBaseUrl,
28830
+ add_form_submit_trigger: true,
28831
+ submit_patch: true,
28832
+ add_unload_trigger: true,
28833
+ convert_urls: true,
28834
+ relative_urls: true,
28835
+ remove_script_host: true,
28836
+ object_resizing: true,
28837
+ doctype: '<!DOCTYPE html>',
28838
+ visual: true,
28839
+ font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
28840
+
28841
+ // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
28842
+ font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
28843
+ forced_root_block: 'p',
28844
+ hidden_input: true,
28845
+ padd_empty_editor: true,
28846
+ render_ui: true,
28847
+ indentation: '30px',
28848
+ inline_styles: true,
28849
+ convert_fonts_to_spans: true,
28850
+ indent: 'simple',
28851
+ indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
28852
+ 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
28853
+ indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
28854
+ 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
28855
+ entity_encoding: 'named',
28856
+ url_converter: editor.convertURL,
28857
+ url_converter_scope: editor,
28858
+ ie7_compat: true
28859
+ };
28860
+ };
28861
+
28862
+ var getExternalPlugins = function (overrideSettings, settings) {
28863
+ var userDefinedExternalPlugins = settings.external_plugins ? settings.external_plugins : { };
28864
+
28865
+ if (overrideSettings && overrideSettings.external_plugins) {
28866
+ return Tools.extend({}, overrideSettings.external_plugins, userDefinedExternalPlugins);
28867
+ } else {
28868
+ return userDefinedExternalPlugins;
28869
+ }
28870
+ };
28871
+
28872
+ var combineSettings = function (defaultSettings, defaultOverrideSettings, settings) {
28873
+ var sectionResult = extractSections(['mobile'], settings);
28874
+ var plugins = sectionResult.settings().plugins;
28875
+
28876
+ var extendedSettings = Tools.extend(
28533
28877
  // Default settings
28534
- {
28535
- id: id,
28536
- theme: 'modern',
28537
- delta_width: 0,
28538
- delta_height: 0,
28539
- popup_css: '',
28540
- plugins: '',
28541
- document_base_url: documentBaseUrl,
28542
- add_form_submit_trigger: true,
28543
- submit_patch: true,
28544
- add_unload_trigger: true,
28545
- convert_urls: true,
28546
- relative_urls: true,
28547
- remove_script_host: true,
28548
- object_resizing: true,
28549
- doctype: '<!DOCTYPE html>',
28550
- visual: true,
28551
- font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
28552
-
28553
- // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
28554
- font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
28555
- forced_root_block: 'p',
28556
- hidden_input: true,
28557
- padd_empty_editor: true,
28558
- render_ui: true,
28559
- indentation: '30px',
28560
- inline_styles: true,
28561
- convert_fonts_to_spans: true,
28562
- indent: 'simple',
28563
- indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
28564
- 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
28565
- indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
28566
- 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
28567
- entity_encoding: 'named',
28568
- url_converter: editor.convertURL,
28569
- url_converter_scope: editor,
28570
- ie7_compat: true
28571
- },
28878
+ defaultSettings,
28572
28879
 
28573
28880
  // tinymce.overrideDefaults settings
28574
28881
  defaultOverrideSettings,
28575
28882
 
28576
28883
  // User settings
28577
- settings,
28884
+ sectionResult.settings(),
28885
+
28886
+ // Sections
28887
+ isTouch ? getSection(sectionResult, 'mobile') : { },
28578
28888
 
28579
28889
  // Forced settings
28580
28890
  {
28581
28891
  validate: true,
28582
- content_editable: settings.inline
28583
- }
28892
+ content_editable: sectionResult.settings().inline,
28893
+ external_plugins: getExternalPlugins(defaultOverrideSettings, sectionResult.settings())
28894
+ },
28895
+
28896
+ // TODO: Remove this once we fix each plugin with a mobile version
28897
+ isTouch && plugins && hasSection(sectionResult, 'mobile') ? { plugins: filterMobilePlugins(plugins) } : { }
28584
28898
  );
28585
28899
 
28586
- // Merge external_plugins
28587
- if (defaultOverrideSettings && defaultOverrideSettings.external_plugins && settings.external_plugins) {
28588
- settings.external_plugins = Tools.extend({}, defaultOverrideSettings.external_plugins, settings.external_plugins);
28589
- }
28900
+ return extendedSettings;
28901
+ };
28590
28902
 
28591
- return settings;
28903
+ var getEditorSettings = function (editor, id, documentBaseUrl, defaultOverrideSettings, settings) {
28904
+ var defaultSettings = getDefaultSettings(id, documentBaseUrl, editor);
28905
+ return combineSettings(defaultSettings, defaultOverrideSettings, settings);
28592
28906
  };
28593
28907
 
28594
28908
  var get = function (editor, name) {
@@ -28602,7 +28916,10 @@ define(
28602
28916
  return {
28603
28917
  getEditorSettings: getEditorSettings,
28604
28918
  get: get,
28605
- getString: Fun.curry(getFiltered, Type.isString)
28919
+ getString: Fun.curry(getFiltered, Type.isString),
28920
+
28921
+ // TODO: Remove this once we have proper mobile plugins
28922
+ filterMobilePlugins: filterMobilePlugins
28606
28923
  };
28607
28924
  }
28608
28925
  );
@@ -29341,6 +29658,184 @@ define(
29341
29658
  };
29342
29659
  }
29343
29660
  );
29661
+ define(
29662
+ 'tinymce.core.delete.TableDeleteAction',
29663
+
29664
+ [
29665
+ 'ephox.katamari.api.Adt',
29666
+ 'ephox.katamari.api.Arr',
29667
+ 'ephox.katamari.api.Fun',
29668
+ 'ephox.katamari.api.Option',
29669
+ 'ephox.katamari.api.Options',
29670
+ 'ephox.katamari.api.Struct',
29671
+ 'ephox.sugar.api.dom.Compare',
29672
+ 'ephox.sugar.api.node.Element',
29673
+ 'ephox.sugar.api.search.SelectorFilter',
29674
+ 'ephox.sugar.api.search.SelectorFind'
29675
+ ],
29676
+
29677
+ function (Adt, Arr, Fun, Option, Options, Struct, Compare, Element, SelectorFilter, SelectorFind) {
29678
+ var tableCellRng = Struct.immutable('start', 'end');
29679
+ var tableSelection = Struct.immutable('rng', 'table', 'cells');
29680
+ var deleteAction = Adt.generate([
29681
+ { removeTable: [ 'element' ] },
29682
+ { emptyCells: [ 'cells' ] }
29683
+ ]);
29684
+
29685
+ var getClosestCell = function (container, isRoot) {
29686
+ return SelectorFind.closest(Element.fromDom(container), 'td,th', isRoot);
29687
+ };
29688
+
29689
+ var getClosestTable = function (cell, isRoot) {
29690
+ return SelectorFind.ancestor(cell, 'table', isRoot);
29691
+ };
29692
+
29693
+ var isExpandedCellRng = function (cellRng) {
29694
+ return Compare.eq(cellRng.start(), cellRng.end()) === false;
29695
+ };
29696
+
29697
+ var getTableFromCellRng = function (cellRng, isRoot) {
29698
+ return getClosestTable(cellRng.start(), isRoot)
29699
+ .bind(function (startParentTable) {
29700
+ return getClosestTable(cellRng.end(), isRoot)
29701
+ .bind(function (endParentTable) {
29702
+ return Compare.eq(startParentTable, endParentTable) ? Option.some(startParentTable) : Option.none();
29703
+ });
29704
+ });
29705
+ };
29706
+
29707
+ var getCellRng = function (rng, isRoot) {
29708
+ return Options.liftN([ // get start and end cell
29709
+ getClosestCell(rng.startContainer, isRoot),
29710
+ getClosestCell(rng.endContainer, isRoot)
29711
+ ], tableCellRng)
29712
+ .filter(isExpandedCellRng);
29713
+ };
29714
+
29715
+ var getTableSelectionFromCellRng = function (cellRng, isRoot) {
29716
+ return getTableFromCellRng(cellRng, isRoot)
29717
+ .bind(function (table) {
29718
+ var cells = SelectorFilter.descendants(table, 'td,th');
29719
+
29720
+ return tableSelection(cellRng, table, cells);
29721
+ });
29722
+ };
29723
+
29724
+ var getTableSelectionFromRng = function (rootNode, rng) {
29725
+ var isRoot = Fun.curry(Compare.eq, rootNode);
29726
+
29727
+ return getCellRng(rng, isRoot)
29728
+ .map(function (cellRng) {
29729
+ return getTableSelectionFromCellRng(cellRng, isRoot);
29730
+ });
29731
+ };
29732
+
29733
+ var getCellIndex = function (cellArray, cell) {
29734
+ return Arr.findIndex(cellArray, function (x) {
29735
+ return Compare.eq(x, cell);
29736
+ });
29737
+ };
29738
+
29739
+ var getSelectedCells = function (tableSelection) {
29740
+ return Options.liftN([
29741
+ getCellIndex(tableSelection.cells(), tableSelection.rng().start()),
29742
+ getCellIndex(tableSelection.cells(), tableSelection.rng().end())
29743
+ ], function (startIndex, endIndex) {
29744
+ return tableSelection.cells().slice(startIndex, endIndex + 1);
29745
+ });
29746
+ };
29747
+
29748
+ var getAction = function (tableSelection) {
29749
+ return getSelectedCells(tableSelection)
29750
+ .bind(function (selected) {
29751
+ var cells = tableSelection.cells();
29752
+
29753
+ return selected.length === cells.length ? deleteAction.removeTable(tableSelection.table()) : deleteAction.emptyCells(selected);
29754
+ });
29755
+ };
29756
+
29757
+ var getActionFromCells = function (cells) {
29758
+ return deleteAction.emptyCells(cells);
29759
+ };
29760
+
29761
+ var getActionFromRange = function (rootNode, rng) {
29762
+ return getTableSelectionFromRng(rootNode, rng)
29763
+ .map(getAction);
29764
+ };
29765
+
29766
+ return {
29767
+ getActionFromRange: getActionFromRange,
29768
+ getActionFromCells: getActionFromCells
29769
+ };
29770
+ }
29771
+ );
29772
+
29773
+ /**
29774
+ * TableDelete.js
29775
+ *
29776
+ * Released under LGPL License.
29777
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
29778
+ *
29779
+ * License: http://www.tinymce.com/license
29780
+ * Contributing: http://www.tinymce.com/contributing
29781
+ */
29782
+
29783
+ define(
29784
+ 'tinymce.core.delete.TableDelete',
29785
+
29786
+ [
29787
+ 'ephox.katamari.api.Adt',
29788
+ 'ephox.katamari.api.Arr',
29789
+ 'ephox.katamari.api.Fun',
29790
+ 'ephox.sugar.api.node.Element',
29791
+ 'ephox.sugar.api.search.SelectorFilter',
29792
+ 'tinymce.core.delete.DeleteElement',
29793
+ 'tinymce.core.delete.TableDeleteAction',
29794
+ 'tinymce.core.dom.PaddingBr'
29795
+ ],
29796
+
29797
+ function (Adt, Arr, Fun, Element, SelectorFilter, DeleteElement, TableDeleteAction, PaddingBr) {
29798
+ var emptyCells = function (editor, cells) {
29799
+ Arr.each(cells, PaddingBr.fillWithPaddingBr);
29800
+ editor.selection.setCursorLocation(cells[0].dom(), 0);
29801
+
29802
+ return true;
29803
+ };
29804
+
29805
+ var deleteTableElement = function (editor, table) {
29806
+ DeleteElement.deleteElement(editor, false, table);
29807
+
29808
+ return true;
29809
+ };
29810
+
29811
+ var handleCellRange = function (editor, rootNode, rng) {
29812
+ return TableDeleteAction.getActionFromRange(rootNode, rng)
29813
+ .map(function (action) {
29814
+ return action.fold(
29815
+ Fun.curry(deleteTableElement, editor),
29816
+ Fun.curry(emptyCells, editor)
29817
+ );
29818
+ }).getOr(false);
29819
+ };
29820
+
29821
+ var deleteRange = function (editor) {
29822
+ var rootNode = Element.fromDom(editor.getBody());
29823
+ var rng = editor.selection.getRng();
29824
+ var selectedCells = SelectorFilter.descendants(rootNode, 'td[data-mce-selected],th[data-mce-selected]');
29825
+
29826
+ return selectedCells.length !== 0 ? emptyCells(editor, selectedCells) : handleCellRange(editor, rootNode, rng);
29827
+ };
29828
+
29829
+ var backspaceDelete = function (editor) {
29830
+ return editor.selection.isCollapsed() ? false : deleteRange(editor);
29831
+ };
29832
+
29833
+ return {
29834
+ backspaceDelete: backspaceDelete
29835
+ };
29836
+ }
29837
+ );
29838
+
29344
29839
  /**
29345
29840
  * Commands.js
29346
29841
  *
@@ -29358,9 +29853,10 @@ define(
29358
29853
  'tinymce.core.delete.BlockRangeDelete',
29359
29854
  'tinymce.core.delete.CefDelete',
29360
29855
  'tinymce.core.delete.DeleteUtils',
29361
- 'tinymce.core.delete.InlineBoundaryDelete'
29856
+ 'tinymce.core.delete.InlineBoundaryDelete',
29857
+ 'tinymce.core.delete.TableDelete'
29362
29858
  ],
29363
- function (BlockBoundaryDelete, BlockRangeDelete, CefDelete, DeleteUtils, BoundaryDelete) {
29859
+ function (BlockBoundaryDelete, BlockRangeDelete, CefDelete, DeleteUtils, BoundaryDelete, TableDelete) {
29364
29860
  var nativeCommand = function (editor, command) {
29365
29861
  editor.getDoc().execCommand(command, false, null);
29366
29862
  };
@@ -29372,6 +29868,8 @@ define(
29372
29868
  return;
29373
29869
  } else if (BlockBoundaryDelete.backspaceDelete(editor, false)) {
29374
29870
  return;
29871
+ } else if (TableDelete.backspaceDelete(editor)) {
29872
+ return;
29375
29873
  } else if (BlockRangeDelete.backspaceDelete(editor, false)) {
29376
29874
  return;
29377
29875
  } else {
@@ -29387,6 +29885,8 @@ define(
29387
29885
  return;
29388
29886
  } else if (BlockBoundaryDelete.backspaceDelete(editor, true)) {
29389
29887
  return;
29888
+ } else if (TableDelete.backspaceDelete(editor)) {
29889
+ return;
29390
29890
  } else if (BlockRangeDelete.backspaceDelete(editor, true)) {
29391
29891
  return;
29392
29892
  } else {
@@ -37723,6 +38223,296 @@ define(
37723
38223
  }
37724
38224
  );
37725
38225
 
38226
+ define(
38227
+ 'ephox.sugar.impl.Style',
38228
+
38229
+ [
38230
+
38231
+ ],
38232
+
38233
+ function () {
38234
+ // some elements, such as mathml, don't have style attributes
38235
+ var isSupported = function (dom) {
38236
+ return dom.style !== undefined;
38237
+ };
38238
+
38239
+ return {
38240
+ isSupported: isSupported
38241
+ };
38242
+ }
38243
+ );
38244
+ defineGlobal("global!window", window);
38245
+ define(
38246
+ 'ephox.sugar.api.properties.Css',
38247
+
38248
+ [
38249
+ 'ephox.katamari.api.Type',
38250
+ 'ephox.katamari.api.Arr',
38251
+ 'ephox.katamari.api.Obj',
38252
+ 'ephox.katamari.api.Option',
38253
+ 'ephox.sugar.api.properties.Attr',
38254
+ 'ephox.sugar.api.node.Body',
38255
+ 'ephox.sugar.api.node.Element',
38256
+ 'ephox.sugar.api.node.Node',
38257
+ 'ephox.sugar.impl.Style',
38258
+ 'ephox.katamari.api.Strings',
38259
+ 'global!Error',
38260
+ 'global!console',
38261
+ 'global!window'
38262
+ ],
38263
+
38264
+ function (Type, Arr, Obj, Option, Attr, Body, Element, Node, Style, Strings, Error, console, window) {
38265
+ var internalSet = function (dom, property, value) {
38266
+ // This is going to hurt. Apologies.
38267
+ // JQuery coerces numbers to pixels for certain property names, and other times lets numbers through.
38268
+ // we're going to be explicit; strings only.
38269
+ if (!Type.isString(value)) {
38270
+ console.error('Invalid call to CSS.set. Property ', property, ':: Value ', value, ':: Element ', dom);
38271
+ throw new Error('CSS value must be a string: ' + value);
38272
+ }
38273
+
38274
+ // removed: support for dom().style[property] where prop is camel case instead of normal property name
38275
+ if (Style.isSupported(dom)) dom.style.setProperty(property, value);
38276
+ };
38277
+
38278
+ var internalRemove = function (dom, property) {
38279
+ /*
38280
+ * IE9 and above - MDN doesn't have details, but here's a couple of random internet claims
38281
+ *
38282
+ * http://help.dottoro.com/ljopsjck.php
38283
+ * http://stackoverflow.com/a/7901886/7546
38284
+ */
38285
+ if (Style.isSupported(dom)) dom.style.removeProperty(property);
38286
+ };
38287
+
38288
+ var set = function (element, property, value) {
38289
+ var dom = element.dom();
38290
+ internalSet(dom, property, value);
38291
+ };
38292
+
38293
+ var setAll = function (element, css) {
38294
+ var dom = element.dom();
38295
+
38296
+ Obj.each(css, function (v, k) {
38297
+ internalSet(dom, k, v);
38298
+ });
38299
+ };
38300
+
38301
+ var setOptions = function(element, css) {
38302
+ var dom = element.dom();
38303
+
38304
+ Obj.each(css, function (v, k) {
38305
+ v.fold(function () {
38306
+ internalRemove(dom, k);
38307
+ }, function (value) {
38308
+ internalSet(dom, k, value);
38309
+ });
38310
+ });
38311
+ };
38312
+
38313
+ /*
38314
+ * NOTE: For certain properties, this returns the "used value" which is subtly different to the "computed value" (despite calling getComputedStyle).
38315
+ * Blame CSS 2.0.
38316
+ *
38317
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
38318
+ */
38319
+ var get = function (element, property) {
38320
+ var dom = element.dom();
38321
+ /*
38322
+ * IE9 and above per
38323
+ * https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle
38324
+ *
38325
+ * Not in numerosity, because it doesn't memoize and looking this up dynamically in performance critical code would be horrendous.
38326
+ *
38327
+ * JQuery has some magic here for IE popups, but we don't really need that.
38328
+ * It also uses element.ownerDocument.defaultView to handle iframes but that hasn't been required since FF 3.6.
38329
+ */
38330
+ var styles = window.getComputedStyle(dom);
38331
+ var r = styles.getPropertyValue(property);
38332
+
38333
+ // jquery-ism: If r is an empty string, check that the element is not in a document. If it isn't, return the raw value.
38334
+ // Turns out we do this a lot.
38335
+ var v = (r === '' && !Body.inBody(element)) ? getUnsafeProperty(dom, property) : r;
38336
+
38337
+ // undefined is the more appropriate value for JS. JQuery coerces to an empty string, but screw that!
38338
+ return v === null ? undefined : v;
38339
+ };
38340
+
38341
+ var getUnsafeProperty = function (dom, property) {
38342
+ // removed: support for dom().style[property] where prop is camel case instead of normal property name
38343
+ // empty string is what the browsers (IE11 and Chrome) return when the propertyValue doesn't exists.
38344
+ return Style.isSupported(dom) ? dom.style.getPropertyValue(property) : '';
38345
+ };
38346
+
38347
+ /*
38348
+ * Gets the raw value from the style attribute. Useful for retrieving "used values" from the DOM:
38349
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
38350
+ *
38351
+ * Returns NONE if the property isn't set, or the value is an empty string.
38352
+ */
38353
+ var getRaw = function (element, property) {
38354
+ var dom = element.dom();
38355
+ var raw = getUnsafeProperty(dom, property);
38356
+
38357
+ return Option.from(raw).filter(function (r) { return r.length > 0; });
38358
+ };
38359
+
38360
+ var isValidValue = function (tag, property, value) {
38361
+ var element = Element.fromTag(tag);
38362
+ set(element, property, value);
38363
+ var style = getRaw(element, property);
38364
+ return style.isSome();
38365
+ };
38366
+
38367
+ var remove = function (element, property) {
38368
+ var dom = element.dom();
38369
+
38370
+ internalRemove(dom, property);
38371
+
38372
+ if (Attr.has(element, 'style') && Strings.trim(Attr.get(element, 'style')) === '') {
38373
+ // No more styles left, remove the style attribute as well
38374
+ Attr.remove(element, 'style');
38375
+ }
38376
+ };
38377
+
38378
+ var preserve = function (element, f) {
38379
+ var oldStyles = Attr.get(element, 'style');
38380
+ var result = f(element);
38381
+ var restore = oldStyles === undefined ? Attr.remove : Attr.set;
38382
+ restore(element, 'style', oldStyles);
38383
+ return result;
38384
+ };
38385
+
38386
+ var copy = function (source, target) {
38387
+ var sourceDom = source.dom();
38388
+ var targetDom = target.dom();
38389
+ if (Style.isSupported(sourceDom) && Style.isSupported(targetDom)) {
38390
+ targetDom.style.cssText = sourceDom.style.cssText;
38391
+ }
38392
+ };
38393
+
38394
+ var reflow = function (e) {
38395
+ /* NOTE:
38396
+ * do not rely on this return value.
38397
+ * It's here so the closure compiler doesn't optimise the property access away.
38398
+ */
38399
+ return e.dom().offsetWidth;
38400
+ };
38401
+
38402
+ var transferOne = function (source, destination, style) {
38403
+ getRaw(source, style).each(function (value) {
38404
+ // NOTE: We don't want to clobber any existing inline styles.
38405
+ if (getRaw(destination, style).isNone()) set(destination, style, value);
38406
+ });
38407
+ };
38408
+
38409
+ var transfer = function (source, destination, styles) {
38410
+ if (!Node.isElement(source) || !Node.isElement(destination)) return;
38411
+ Arr.each(styles, function (style) {
38412
+ transferOne(source, destination, style);
38413
+ });
38414
+ };
38415
+
38416
+ return {
38417
+ copy: copy,
38418
+ set: set,
38419
+ preserve: preserve,
38420
+ setAll: setAll,
38421
+ setOptions: setOptions,
38422
+ remove: remove,
38423
+ get: get,
38424
+ getRaw: getRaw,
38425
+ isValidValue: isValidValue,
38426
+ reflow: reflow,
38427
+ transfer: transfer
38428
+ };
38429
+ }
38430
+ );
38431
+
38432
+ /**
38433
+ * EditorView.js
38434
+ *
38435
+ * Released under LGPL License.
38436
+ * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
38437
+ *
38438
+ * License: http://www.tinymce.com/license
38439
+ * Contributing: http://www.tinymce.com/contributing
38440
+ */
38441
+
38442
+ define(
38443
+ 'tinymce.core.EditorView',
38444
+ [
38445
+ 'ephox.katamari.api.Fun',
38446
+ 'ephox.katamari.api.Option',
38447
+ 'ephox.sugar.api.dom.Compare',
38448
+ 'ephox.sugar.api.node.Element',
38449
+ 'ephox.sugar.api.properties.Css',
38450
+ 'ephox.sugar.api.search.Traverse'
38451
+ ],
38452
+ function (Fun, Option, Compare, Element, Css, Traverse) {
38453
+ var getProp = function (propName, elm) {
38454
+ var rawElm = elm.dom();
38455
+ return rawElm[propName];
38456
+ };
38457
+
38458
+ var getComputedSizeProp = function (propName, elm) {
38459
+ return parseInt(Css.get(elm, propName), 10);
38460
+ };
38461
+
38462
+ var getClientWidth = Fun.curry(getProp, 'clientWidth');
38463
+ var getClientHeight = Fun.curry(getProp, 'clientHeight');
38464
+ var getMarginTop = Fun.curry(getComputedSizeProp, 'margin-top');
38465
+ var getMarginLeft = Fun.curry(getComputedSizeProp, 'margin-left');
38466
+
38467
+ var getBoundingClientRect = function (elm) {
38468
+ return elm.dom().getBoundingClientRect();
38469
+ };
38470
+
38471
+ var isInsideElementContentArea = function (bodyElm, clientX, clientY) {
38472
+ var clientWidth = getClientWidth(bodyElm);
38473
+ var clientHeight = getClientHeight(bodyElm);
38474
+
38475
+ return clientX >= 0 && clientY >= 0 && clientX <= clientWidth && clientY <= clientHeight;
38476
+ };
38477
+
38478
+ var transpose = function (inline, elm, clientX, clientY) {
38479
+ var clientRect = getBoundingClientRect(elm);
38480
+ var deltaX = inline ? clientRect.left + elm.dom().clientLeft + getMarginLeft(elm) : 0;
38481
+ var deltaY = inline ? clientRect.top + elm.dom().clientTop + getMarginTop(elm) : 0;
38482
+ var x = clientX - deltaX;
38483
+ var y = clientY - deltaY;
38484
+
38485
+ return { x: x, y: y };
38486
+ };
38487
+
38488
+ // Checks if the specified coordinate is within the visual content area excluding the scrollbars
38489
+ var isXYInContentArea = function (editor, clientX, clientY) {
38490
+ var bodyElm = Element.fromDom(editor.getBody());
38491
+ var targetElm = editor.inline ? bodyElm : Traverse.documentElement(bodyElm);
38492
+ var transposedPoint = transpose(editor.inline, targetElm, clientX, clientY);
38493
+
38494
+ return isInsideElementContentArea(targetElm, transposedPoint.x, transposedPoint.y);
38495
+ };
38496
+
38497
+ var fromDomSafe = function (node) {
38498
+ return Option.from(node).map(Element.fromDom);
38499
+ };
38500
+
38501
+ var isEditorAttachedToDom = function (editor) {
38502
+ var rawContainer = editor.inline ? editor.getBody() : editor.getContentAreaContainer();
38503
+
38504
+ return fromDomSafe(rawContainer).map(function (container) {
38505
+ return Compare.contains(Traverse.owner(container), container);
38506
+ }).getOr(false);
38507
+ };
38508
+
38509
+ return {
38510
+ isXYInContentArea: isXYInContentArea,
38511
+ isEditorAttachedToDom: isEditorAttachedToDom
38512
+ };
38513
+ }
38514
+ );
38515
+
37726
38516
  /**
37727
38517
  * Tooltip.js
37728
38518
  *
@@ -38084,6 +38874,8 @@ define(
38084
38874
 
38085
38875
  self._super(settings);
38086
38876
 
38877
+ self.maxWidth = settings.maxWidth;
38878
+
38087
38879
  if (settings.text) {
38088
38880
  self.text(settings.text);
38089
38881
  }
@@ -38131,9 +38923,7 @@ define(
38131
38923
  icon = '<i class="' + prefix + 'ico' + ' ' + prefix + 'i-' + self.icon + '"></i>';
38132
38924
  }
38133
38925
 
38134
- if (self.color) {
38135
- notificationStyle = ' style="background-color: ' + self.color + '"';
38136
- }
38926
+ notificationStyle = ' style="max-width: ' + self.maxWidth + 'px;' + (self.color ? 'background-color: ' + self.color + ';"' : '"');
38137
38927
 
38138
38928
  if (self.closeButton) {
38139
38929
  closeButton = '<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>';
@@ -38237,20 +39027,31 @@ define(
38237
39027
  define(
38238
39028
  'tinymce.core.NotificationManager',
38239
39029
  [
38240
- "tinymce.core.ui.Notification",
38241
- "tinymce.core.util.Delay",
38242
- "tinymce.core.util.Tools"
39030
+ 'tinymce.core.EditorView',
39031
+ 'tinymce.core.ui.DomUtils',
39032
+ 'tinymce.core.ui.Notification',
39033
+ 'tinymce.core.util.Delay',
39034
+ 'tinymce.core.util.Tools'
38243
39035
  ],
38244
- function (Notification, Delay, Tools) {
39036
+ function (EditorView, DomUtils, Notification, Delay, Tools) {
38245
39037
  return function (editor) {
38246
39038
  var self = this, notifications = [];
38247
39039
 
39040
+ var getContainerWidth = function () {
39041
+ var container = editor.inline ? editor.getElement() : editor.getContentAreaContainer();
39042
+ return DomUtils.getSize(container).width;
39043
+ };
39044
+
38248
39045
  function getLastNotification() {
38249
39046
  if (notifications.length) {
38250
39047
  return notifications[notifications.length - 1];
38251
39048
  }
38252
39049
  }
38253
39050
 
39051
+ function getEditorContainer(editor) {
39052
+ return editor.inline ? editor.getElement() : editor.getContentAreaContainer();
39053
+ }
39054
+
38254
39055
  self.notifications = notifications;
38255
39056
 
38256
39057
  function resizeWindowEvent() {
@@ -38271,7 +39072,7 @@ define(
38271
39072
  function positionNotifications() {
38272
39073
  if (notifications.length > 0) {
38273
39074
  var firstItem = notifications.slice(0, 1)[0];
38274
- var container = editor.inline ? editor.getElement() : editor.getContentAreaContainer();
39075
+ var container = getEditorContainer(editor);
38275
39076
  firstItem.moveRel(container, 'tc-tc');
38276
39077
  if (notifications.length > 1) {
38277
39078
  for (var i = 1; i < notifications.length; i++) {
@@ -38300,7 +39101,7 @@ define(
38300
39101
  */
38301
39102
  self.open = function (args) {
38302
39103
  // Never open notification if editor has been removed.
38303
- if (editor.removed) {
39104
+ if (editor.removed || !EditorView.isEditorAttachedToDom(editor)) {
38304
39105
  return;
38305
39106
  }
38306
39107
 
@@ -38311,6 +39112,9 @@ define(
38311
39112
  var duplicate = findDuplicateMessage(notifications, args);
38312
39113
 
38313
39114
  if (duplicate === null) {
39115
+
39116
+ args = Tools.extend(args, { maxWidth: getContainerWidth() });
39117
+
38314
39118
  notif = new Notification(args);
38315
39119
  notifications.push(notif);
38316
39120
 
@@ -38536,7 +39340,7 @@ define(
38536
39340
  }
38537
39341
 
38538
39342
  delegate = function (e) {
38539
- var target = e.target, editors = editor.editorManager.editors, i = editors.length;
39343
+ var target = e.target, editors = editor.editorManager.get(), i = editors.length;
38540
39344
 
38541
39345
  while (i--) {
38542
39346
  var body = editors[i].getBody();
@@ -38861,7 +39665,6 @@ define(
38861
39665
  }
38862
39666
  );
38863
39667
 
38864
- defineGlobal("global!window", window);
38865
39668
  /**
38866
39669
  * ErrorReporter.js
38867
39670
  *
@@ -38881,7 +39684,7 @@ defineGlobal("global!window", window);
38881
39684
  define(
38882
39685
  'tinymce.core.ErrorReporter',
38883
39686
  [
38884
- "tinymce.core.AddOnManager"
39687
+ 'tinymce.core.AddOnManager'
38885
39688
  ],
38886
39689
  function (AddOnManager) {
38887
39690
  var PluginManager = AddOnManager.PluginManager;
@@ -38929,10 +39732,6 @@ define(
38929
39732
  displayError(editor, pluginUrlToMessage(editor, url));
38930
39733
  };
38931
39734
 
38932
- var contentCssError = function (editor, urls) {
38933
- displayError(editor, 'Failed to load content css: ' + urls[0]);
38934
- };
38935
-
38936
39735
  var initError = function (message) {
38937
39736
  var console = window.console;
38938
39737
  if (console && !window.test) { // Skip test env
@@ -38948,7 +39747,6 @@ define(
38948
39747
  pluginLoadError: pluginLoadError,
38949
39748
  uploadError: uploadError,
38950
39749
  displayError: displayError,
38951
- contentCssError: contentCssError,
38952
39750
  initError: initError
38953
39751
  };
38954
39752
  }
@@ -39229,6 +40027,45 @@ define(
39229
40027
  };
39230
40028
  }
39231
40029
  );
40030
+ define(
40031
+ 'ephox.sand.api.Window',
40032
+
40033
+ [
40034
+ 'ephox.sand.util.Global'
40035
+ ],
40036
+
40037
+ function (Global) {
40038
+ /******************************************************************************************
40039
+ * BIG BIG WARNING: Don't put anything other than top-level window functions in here.
40040
+ *
40041
+ * Objects that are technically available as window.X should be in their own module X (e.g. Blob, FileReader, URL).
40042
+ ******************************************************************************************
40043
+ */
40044
+
40045
+ /*
40046
+ * IE10 and above per
40047
+ * https://developer.mozilla.org/en/docs/Web/API/window.requestAnimationFrame
40048
+ */
40049
+ var requestAnimationFrame = function (callback) {
40050
+ var f = Global.getOrDie('requestAnimationFrame');
40051
+ f(callback);
40052
+ };
40053
+
40054
+ /*
40055
+ * IE10 and above per
40056
+ * https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64.atob
40057
+ */
40058
+ var atob = function (base64) {
40059
+ var f = Global.getOrDie('atob');
40060
+ return f(base64);
40061
+ };
40062
+
40063
+ return {
40064
+ atob: atob,
40065
+ requestAnimationFrame: requestAnimationFrame
40066
+ };
40067
+ }
40068
+ );
39232
40069
  /**
39233
40070
  * Conversions.js
39234
40071
  *
@@ -39248,9 +40085,10 @@ define(
39248
40085
  define(
39249
40086
  'tinymce.core.file.Conversions',
39250
40087
  [
39251
- "tinymce.core.util.Promise"
40088
+ 'ephox.sand.api.Window',
40089
+ 'tinymce.core.util.Promise'
39252
40090
  ],
39253
- function (Promise) {
40091
+ function (Window, Promise) {
39254
40092
  function blobUriToBlob(url) {
39255
40093
  return new Promise(function (resolve, reject) {
39256
40094
 
@@ -39308,7 +40146,7 @@ define(
39308
40146
 
39309
40147
  // Might throw error if data isn't proper base64
39310
40148
  try {
39311
- str = atob(uri.data);
40149
+ str = Window.atob(uri.data);
39312
40150
  } catch (e) {
39313
40151
  resolve(new Blob([]));
39314
40152
  return;
@@ -39530,6 +40368,40 @@ define(
39530
40368
  };
39531
40369
  }
39532
40370
  );
40371
+ define(
40372
+ 'ephox.sand.api.URL',
40373
+
40374
+ [
40375
+ 'ephox.sand.util.Global'
40376
+ ],
40377
+
40378
+ function (Global) {
40379
+ /*
40380
+ * IE10 and above per
40381
+ * https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL
40382
+ *
40383
+ * Also Safari 6.1+
40384
+ * Safari 6.0 has 'webkitURL' instead, but doesn't support flexbox so we
40385
+ * aren't supporting it anyway
40386
+ */
40387
+ var url = function () {
40388
+ return Global.getOrDie('URL');
40389
+ };
40390
+
40391
+ var createObjectURL = function (blob) {
40392
+ return url().createObjectURL(blob);
40393
+ };
40394
+
40395
+ var revokeObjectURL = function (u) {
40396
+ url().revokeObjectURL(u);
40397
+ };
40398
+
40399
+ return {
40400
+ createObjectURL: createObjectURL,
40401
+ revokeObjectURL: revokeObjectURL
40402
+ };
40403
+ }
40404
+ );
39533
40405
  /**
39534
40406
  * Uuid.js
39535
40407
  *
@@ -39572,7 +40444,6 @@ define(
39572
40444
  }
39573
40445
  );
39574
40446
 
39575
- defineGlobal("global!URL", URL);
39576
40447
  /**
39577
40448
  * BlobCache.js
39578
40449
  *
@@ -39592,12 +40463,12 @@ defineGlobal("global!URL", URL);
39592
40463
  define(
39593
40464
  'tinymce.core.file.BlobCache',
39594
40465
  [
40466
+ 'ephox.sand.api.URL',
39595
40467
  'tinymce.core.util.Arr',
39596
40468
  'tinymce.core.util.Fun',
39597
- 'tinymce.core.util.Uuid',
39598
- 'global!URL'
40469
+ 'tinymce.core.util.Uuid'
39599
40470
  ],
39600
- function (Arr, Fun, Uuid, URL) {
40471
+ function (URL, Arr, Fun, Uuid) {
39601
40472
  return function () {
39602
40473
  var cache = [], constant = Fun.constant;
39603
40474
 
@@ -39968,7 +40839,7 @@ define(
39968
40839
  var blobInfo = blobCache.getByUri(blobUri);
39969
40840
 
39970
40841
  if (!blobInfo) {
39971
- blobInfo = Arr.reduce(editor.editorManager.editors, function (result, editor) {
40842
+ blobInfo = Arr.reduce(editor.editorManager.get(), function (result, editor) {
39972
40843
  return result || editor.editorUpload && editor.editorUpload.blobCache.getByUri(blobUri);
39973
40844
  }, null);
39974
40845
  }
@@ -41145,7 +42016,7 @@ define(
41145
42016
  );
41146
42017
 
41147
42018
  /**
41148
- * EnterKey.js
42019
+ * InsertNewLine.js
41149
42020
  *
41150
42021
  * Released under LGPL License.
41151
42022
  * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
@@ -41154,23 +42025,17 @@ define(
41154
42025
  * Contributing: http://www.tinymce.com/contributing
41155
42026
  */
41156
42027
 
41157
- /**
41158
- * Contains logic for handling the enter key to split/generate block elements.
41159
- */
41160
42028
  define(
41161
- 'tinymce.core.keyboard.EnterKey',
42029
+ 'tinymce.core.keyboard.InsertNewLine',
41162
42030
  [
41163
42031
  'tinymce.core.caret.CaretContainer',
41164
42032
  'tinymce.core.dom.NodeType',
41165
42033
  'tinymce.core.dom.RangeUtils',
41166
42034
  'tinymce.core.dom.TreeWalker',
41167
- 'tinymce.core.Env',
41168
42035
  'tinymce.core.text.Zwsp',
41169
42036
  'tinymce.core.util.Tools'
41170
42037
  ],
41171
- function (CaretContainer, NodeType, RangeUtils, TreeWalker, Env, Zwsp, Tools) {
41172
- var isIE = Env.ie && Env.ie < 11;
41173
-
42038
+ function (CaretContainer, NodeType, RangeUtils, TreeWalker, Zwsp, Tools) {
41174
42039
  var isEmptyAnchor = function (elm) {
41175
42040
  return elm && elm.nodeName === "A" && Tools.trim(Zwsp.trim(elm.innerText || elm.textContent)).length === 0;
41176
42041
  };
@@ -41188,8 +42053,7 @@ define(
41188
42053
  };
41189
42054
 
41190
42055
  var emptyBlock = function (elm) {
41191
- // BR is needed in empty blocks on non IE browsers
41192
- elm.innerHTML = !isIE ? '<br data-mce-bogus="1">' : '';
42056
+ elm.innerHTML = '<br data-mce-bogus="1">';
41193
42057
  };
41194
42058
 
41195
42059
  var containerAndSiblingName = function (container, nodeName) {
@@ -41213,19 +42077,6 @@ define(
41213
42077
  dom.getContentEditable(node) !== "true";
41214
42078
  };
41215
42079
 
41216
- // Renders empty block on IE
41217
- var renderBlockOnIE = function (dom, selection, block) {
41218
- var oldRng;
41219
-
41220
- if (dom.isBlock(block)) {
41221
- oldRng = selection.getRng();
41222
- block.appendChild(dom.create('span', null, '\u00a0'));
41223
- selection.select(block);
41224
- block.lastChild.outerHTML = '';
41225
- selection.setRng(oldRng);
41226
- }
41227
- };
41228
-
41229
42080
  // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
41230
42081
  var trimInlineElementsOnLeftSideOfBlock = function (dom, nonEmptyElementsMap, block) {
41231
42082
  var node = block, firstChilds = [], i;
@@ -41285,600 +42136,593 @@ define(
41285
42136
  }
41286
42137
  };
41287
42138
 
41288
- var setup = function (editor) {
41289
- var dom = editor.dom, selection = editor.selection, settings = editor.settings;
41290
- var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements(),
41291
- moveCaretBeforeOnEnterElementsMap = schema.getMoveCaretBeforeOnEnterElements();
41292
-
41293
- function handleEnterKey(evt) {
41294
- var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
41295
- newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
41296
-
41297
- // Moves the caret to a suitable position within the root for example in the first non
41298
- // pure whitespace text node or before an image
41299
- function moveToCaretPosition(root) {
41300
- var walker, node, rng, lastNode = root, tempElm;
41301
-
41302
- if (!root) {
41303
- return;
41304
- }
41305
-
41306
- // Old IE versions doesn't properly render blocks with br elements in them
41307
- // For example <p><br></p> wont be rendered correctly in a contentEditable area
41308
- // until you remove the br producing <p></p>
41309
- if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) {
41310
- if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') {
41311
- dom.remove(parentBlock.firstChild);
41312
- }
41313
- }
42139
+ // Inserts a BR element if the forced_root_block option is set to false or empty string
42140
+ var insertBr = function (editor, evt) {
42141
+ editor.execCommand("InsertLineBreak", false, evt);
42142
+ };
41314
42143
 
41315
- if (/^(LI|DT|DD)$/.test(root.nodeName)) {
41316
- var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
42144
+ // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
42145
+ var trimLeadingLineBreaks = function (node) {
42146
+ do {
42147
+ if (node.nodeType === 3) {
42148
+ node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
42149
+ }
41317
42150
 
41318
- if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
41319
- root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
41320
- }
41321
- }
42151
+ node = node.firstChild;
42152
+ } while (node);
42153
+ };
41322
42154
 
41323
- rng = dom.createRng();
42155
+ var getEditableRoot = function (dom, node) {
42156
+ var root = dom.getRoot(), parent, editableRoot;
41324
42157
 
41325
- // Normalize whitespace to remove empty text nodes. Fix for: #6904
41326
- // Gecko will be able to place the caret in empty text nodes but it won't render propery
41327
- // Older IE versions will sometimes crash so for now ignore all IE versions
41328
- if (!Env.ie) {
41329
- root.normalize();
41330
- }
42158
+ // Get all parents until we hit a non editable parent or the root
42159
+ parent = node;
42160
+ while (parent !== root && dom.getContentEditable(parent) !== "false") {
42161
+ if (dom.getContentEditable(parent) === "true") {
42162
+ editableRoot = parent;
42163
+ }
41331
42164
 
41332
- if (root.hasChildNodes()) {
41333
- walker = new TreeWalker(root, root);
42165
+ parent = parent.parentNode;
42166
+ }
41334
42167
 
41335
- while ((node = walker.current())) {
41336
- if (node.nodeType == 3) {
41337
- rng.setStart(node, 0);
41338
- rng.setEnd(node, 0);
41339
- break;
41340
- }
42168
+ return parent !== root ? editableRoot : root;
42169
+ };
41341
42170
 
41342
- if (moveCaretBeforeOnEnterElementsMap[node.nodeName.toLowerCase()]) {
41343
- rng.setStartBefore(node);
41344
- rng.setEndBefore(node);
41345
- break;
41346
- }
42171
+ var setForcedBlockAttrs = function (editor, node) {
42172
+ var forcedRootBlockName = editor.settings.forced_root_block;
41347
42173
 
41348
- lastNode = node;
41349
- node = walker.next();
41350
- }
42174
+ if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
42175
+ editor.dom.setAttribs(node, editor.settings.forced_root_block_attrs);
42176
+ }
42177
+ };
41351
42178
 
41352
- if (!node) {
41353
- rng.setStart(lastNode, 0);
41354
- rng.setEnd(lastNode, 0);
41355
- }
41356
- } else {
41357
- if (root.nodeName == 'BR') {
41358
- if (root.nextSibling && dom.isBlock(root.nextSibling)) {
41359
- // Trick on older IE versions to render the caret before the BR between two lists
41360
- if (!documentMode || documentMode < 9) {
41361
- tempElm = dom.create('br');
41362
- root.parentNode.insertBefore(tempElm, root);
41363
- }
42179
+ // Wraps any text nodes or inline elements in the specified forced root block name
42180
+ var wrapSelfAndSiblingsInDefaultBlock = function (editor, newBlockName, rng, container, offset) {
42181
+ var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P';
42182
+ var dom = editor.dom, editableRoot = getEditableRoot(dom, container);
41364
42183
 
41365
- rng.setStartBefore(root);
41366
- rng.setEndBefore(root);
41367
- } else {
41368
- rng.setStartAfter(root);
41369
- rng.setEndAfter(root);
41370
- }
41371
- } else {
41372
- rng.setStart(root, 0);
41373
- rng.setEnd(root, 0);
41374
- }
41375
- }
42184
+ // Not in a block element or in a table cell or caption
42185
+ parentBlock = dom.getParent(container, dom.isBlock);
42186
+ if (!parentBlock || !canSplitBlock(dom, parentBlock)) {
42187
+ parentBlock = parentBlock || editableRoot;
41376
42188
 
41377
- selection.setRng(rng);
42189
+ if (parentBlock == editor.getBody() || isTableCell(parentBlock)) {
42190
+ rootBlockName = parentBlock.nodeName.toLowerCase();
42191
+ } else {
42192
+ rootBlockName = parentBlock.parentNode.nodeName.toLowerCase();
42193
+ }
41378
42194
 
41379
- // Remove tempElm created for old IE:s
41380
- dom.remove(tempElm);
41381
- selection.scrollIntoView(root);
42195
+ if (!parentBlock.hasChildNodes()) {
42196
+ newBlock = dom.create(blockName);
42197
+ setForcedBlockAttrs(editor, newBlock);
42198
+ parentBlock.appendChild(newBlock);
42199
+ rng.setStart(newBlock, 0);
42200
+ rng.setEnd(newBlock, 0);
42201
+ return newBlock;
41382
42202
  }
41383
42203
 
41384
- function setForcedBlockAttrs(node) {
41385
- var forcedRootBlockName = settings.forced_root_block;
42204
+ // Find parent that is the first child of parentBlock
42205
+ node = container;
42206
+ while (node.parentNode != parentBlock) {
42207
+ node = node.parentNode;
42208
+ }
41386
42209
 
41387
- if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
41388
- dom.setAttribs(node, settings.forced_root_block_attrs);
41389
- }
42210
+ // Loop left to find start node start wrapping at
42211
+ while (node && !dom.isBlock(node)) {
42212
+ startNode = node;
42213
+ node = node.previousSibling;
41390
42214
  }
41391
42215
 
41392
- // Creates a new block element by cloning the current one or creating a new one if the name is specified
41393
- // This function will also copy any text formatting from the parent block and add it to the new one
41394
- function createNewBlock(name) {
41395
- var node = container, block, clonedNode, caretNode, textInlineElements = schema.getTextInlineElements();
42216
+ if (startNode && editor.schema.isValidChild(rootBlockName, blockName.toLowerCase())) {
42217
+ newBlock = dom.create(blockName);
42218
+ setForcedBlockAttrs(editor, newBlock);
42219
+ startNode.parentNode.insertBefore(newBlock, startNode);
41396
42220
 
41397
- if (name || parentBlockName == "TABLE" || parentBlockName == "HR") {
41398
- block = dom.create(name || newBlockName);
41399
- setForcedBlockAttrs(block);
41400
- } else {
41401
- block = parentBlock.cloneNode(false);
42221
+ // Start wrapping until we hit a block
42222
+ node = startNode;
42223
+ while (node && !dom.isBlock(node)) {
42224
+ next = node.nextSibling;
42225
+ newBlock.appendChild(node);
42226
+ node = next;
41402
42227
  }
41403
42228
 
41404
- caretNode = block;
42229
+ // Restore range to it's past location
42230
+ rng.setStart(container, offset);
42231
+ rng.setEnd(container, offset);
42232
+ }
42233
+ }
41405
42234
 
41406
- if (settings.keep_styles === false) {
41407
- dom.setAttrib(block, 'style', null); // wipe out any styles that came over with the block
41408
- dom.setAttrib(block, 'class', null);
41409
- } else {
41410
- // Clone any parent styles
41411
- do {
41412
- if (textInlineElements[node.nodeName]) {
41413
- // Never clone a caret containers
41414
- if (node.id == '_mce_caret') {
41415
- continue;
41416
- }
42235
+ return container;
42236
+ };
41417
42237
 
41418
- clonedNode = node.cloneNode(false);
41419
- dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
42238
+ // Adds a BR at the end of blocks that only contains an IMG or INPUT since
42239
+ // these might be floated and then they won't expand the block
42240
+ var addBrToBlockIfNeeded = function (dom, block) {
42241
+ var lastChild;
41420
42242
 
41421
- if (block.hasChildNodes()) {
41422
- clonedNode.appendChild(block.firstChild);
41423
- block.appendChild(clonedNode);
41424
- } else {
41425
- caretNode = clonedNode;
41426
- block.appendChild(clonedNode);
41427
- }
41428
- }
41429
- } while ((node = node.parentNode) && node != editableRoot);
41430
- }
42243
+ // IE will render the blocks correctly other browsers needs a BR
42244
+ block.normalize(); // Remove empty text nodes that got left behind by the extract
41431
42245
 
41432
- // BR is needed in empty blocks on non IE browsers
41433
- if (!isIE) {
41434
- caretNode.innerHTML = '<br data-mce-bogus="1">';
41435
- }
42246
+ // Check if the block is empty or contains a floated last child
42247
+ lastChild = block.lastChild;
42248
+ if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
42249
+ dom.add(block, 'br');
42250
+ }
42251
+ };
41436
42252
 
41437
- return block;
41438
- }
42253
+ var getContainerBlock = function (containerBlock) {
42254
+ var containerBlockParent = containerBlock.parentNode;
42255
+
42256
+ if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) {
42257
+ return containerBlockParent;
42258
+ }
41439
42259
 
41440
- // Returns true/false if the caret is at the start/end of the parent block element
41441
- function isCaretAtStartOrEndOfBlock(start) {
41442
- var walker, node, name, normalizedOffset;
42260
+ return containerBlock;
42261
+ };
41443
42262
 
41444
- normalizedOffset = normalizeZwspOffset(start, container, offset);
42263
+ var isFirstOrLastLi = function (containerBlock, parentBlock, first) {
42264
+ var node = containerBlock[first ? 'firstChild' : 'lastChild'];
41445
42265
 
41446
- // Caret is in the middle of a text node like "a|b"
41447
- if (container.nodeType == 3 && (start ? normalizedOffset > 0 : normalizedOffset < container.nodeValue.length)) {
41448
- return false;
41449
- }
42266
+ // Find first/last element since there might be whitespace there
42267
+ while (node) {
42268
+ if (node.nodeType == 1) {
42269
+ break;
42270
+ }
41450
42271
 
41451
- // If after the last element in block node edge case for #5091
41452
- if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
41453
- return true;
41454
- }
42272
+ node = node[first ? 'nextSibling' : 'previousSibling'];
42273
+ }
41455
42274
 
41456
- // If the caret if before the first element in parentBlock
41457
- if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
41458
- return true;
41459
- }
42275
+ return node === parentBlock;
42276
+ };
41460
42277
 
41461
- // Caret can be before/after a table or a hr
41462
- if (containerAndSiblingName(container, 'TABLE') || containerAndSiblingName(container, 'HR')) {
41463
- return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
41464
- }
42278
+ var insert = function (editor, evt) {
42279
+ var tmpRng, editableRoot, container, offset, parentBlock, shiftKey;
42280
+ var newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
42281
+ var dom = editor.dom, selection = editor.selection, settings = editor.settings;
42282
+ var schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements();
42283
+ var rng = editor.selection.getRng();
41465
42284
 
41466
- // Walk the DOM and look for text nodes or non empty elements
41467
- walker = new TreeWalker(container, parentBlock);
42285
+ // Moves the caret to a suitable position within the root for example in the first non
42286
+ // pure whitespace text node or before an image
42287
+ function moveToCaretPosition(root) {
42288
+ var walker, node, rng, lastNode = root, tempElm;
42289
+ var moveCaretBeforeOnEnterElementsMap = schema.getMoveCaretBeforeOnEnterElements();
41468
42290
 
41469
- // If caret is in beginning or end of a text block then jump to the next/previous node
41470
- if (container.nodeType == 3) {
41471
- if (start && normalizedOffset === 0) {
41472
- walker.prev();
41473
- } else if (!start && normalizedOffset == container.nodeValue.length) {
41474
- walker.next();
41475
- }
41476
- }
42291
+ if (!root) {
42292
+ return;
42293
+ }
41477
42294
 
41478
- while ((node = walker.current())) {
41479
- if (node.nodeType === 1) {
41480
- // Ignore bogus elements
41481
- if (!node.getAttribute('data-mce-bogus')) {
41482
- // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
41483
- name = node.nodeName.toLowerCase();
41484
- if (nonEmptyElementsMap[name] && name !== 'br') {
41485
- return false;
41486
- }
41487
- }
41488
- } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
41489
- return false;
41490
- }
42295
+ if (/^(LI|DT|DD)$/.test(root.nodeName)) {
42296
+ var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
41491
42297
 
41492
- if (start) {
41493
- walker.prev();
41494
- } else {
41495
- walker.next();
41496
- }
42298
+ if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
42299
+ root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
41497
42300
  }
41498
-
41499
- return true;
41500
42301
  }
41501
42302
 
41502
- // Wraps any text nodes or inline elements in the specified forced root block name
41503
- function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
41504
- var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P';
42303
+ rng = dom.createRng();
42304
+ root.normalize();
41505
42305
 
41506
- // Not in a block element or in a table cell or caption
41507
- parentBlock = dom.getParent(container, dom.isBlock);
41508
- if (!parentBlock || !canSplitBlock(dom, parentBlock)) {
41509
- parentBlock = parentBlock || editableRoot;
42306
+ if (root.hasChildNodes()) {
42307
+ walker = new TreeWalker(root, root);
41510
42308
 
41511
- if (parentBlock == editor.getBody() || isTableCell(parentBlock)) {
41512
- rootBlockName = parentBlock.nodeName.toLowerCase();
41513
- } else {
41514
- rootBlockName = parentBlock.parentNode.nodeName.toLowerCase();
42309
+ while ((node = walker.current())) {
42310
+ if (node.nodeType == 3) {
42311
+ rng.setStart(node, 0);
42312
+ rng.setEnd(node, 0);
42313
+ break;
41515
42314
  }
41516
42315
 
41517
- if (!parentBlock.hasChildNodes()) {
41518
- newBlock = dom.create(blockName);
41519
- setForcedBlockAttrs(newBlock);
41520
- parentBlock.appendChild(newBlock);
41521
- rng.setStart(newBlock, 0);
41522
- rng.setEnd(newBlock, 0);
41523
- return newBlock;
42316
+ if (moveCaretBeforeOnEnterElementsMap[node.nodeName.toLowerCase()]) {
42317
+ rng.setStartBefore(node);
42318
+ rng.setEndBefore(node);
42319
+ break;
41524
42320
  }
41525
42321
 
41526
- // Find parent that is the first child of parentBlock
41527
- node = container;
41528
- while (node.parentNode != parentBlock) {
41529
- node = node.parentNode;
41530
- }
42322
+ lastNode = node;
42323
+ node = walker.next();
42324
+ }
41531
42325
 
41532
- // Loop left to find start node start wrapping at
41533
- while (node && !dom.isBlock(node)) {
41534
- startNode = node;
41535
- node = node.previousSibling;
42326
+ if (!node) {
42327
+ rng.setStart(lastNode, 0);
42328
+ rng.setEnd(lastNode, 0);
42329
+ }
42330
+ } else {
42331
+ if (root.nodeName == 'BR') {
42332
+ if (root.nextSibling && dom.isBlock(root.nextSibling)) {
42333
+ rng.setStartBefore(root);
42334
+ rng.setEndBefore(root);
42335
+ } else {
42336
+ rng.setStartAfter(root);
42337
+ rng.setEndAfter(root);
41536
42338
  }
42339
+ } else {
42340
+ rng.setStart(root, 0);
42341
+ rng.setEnd(root, 0);
42342
+ }
42343
+ }
41537
42344
 
41538
- if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) {
41539
- newBlock = dom.create(blockName);
41540
- setForcedBlockAttrs(newBlock);
41541
- startNode.parentNode.insertBefore(newBlock, startNode);
42345
+ selection.setRng(rng);
41542
42346
 
41543
- // Start wrapping until we hit a block
41544
- node = startNode;
41545
- while (node && !dom.isBlock(node)) {
41546
- next = node.nextSibling;
41547
- newBlock.appendChild(node);
41548
- node = next;
41549
- }
42347
+ // Remove tempElm created for old IE:s
42348
+ dom.remove(tempElm);
42349
+ selection.scrollIntoView(root);
42350
+ }
41550
42351
 
41551
- // Restore range to it's past location
41552
- rng.setStart(container, offset);
41553
- rng.setEnd(container, offset);
41554
- }
41555
- }
42352
+ // Creates a new block element by cloning the current one or creating a new one if the name is specified
42353
+ // This function will also copy any text formatting from the parent block and add it to the new one
42354
+ function createNewBlock(name) {
42355
+ var node = container, block, clonedNode, caretNode, textInlineElements = schema.getTextInlineElements();
41556
42356
 
41557
- return container;
42357
+ if (name || parentBlockName == "TABLE" || parentBlockName == "HR") {
42358
+ block = dom.create(name || newBlockName);
42359
+ setForcedBlockAttrs(editor, block);
42360
+ } else {
42361
+ block = parentBlock.cloneNode(false);
41558
42362
  }
41559
42363
 
41560
- // Inserts a block or br before/after or in the middle of a split list of the LI is empty
41561
- function handleEmptyListItem() {
41562
- function isFirstOrLastLi(first) {
41563
- var node = containerBlock[first ? 'firstChild' : 'lastChild'];
42364
+ caretNode = block;
41564
42365
 
41565
- // Find first/last element since there might be whitespace there
41566
- while (node) {
41567
- if (node.nodeType == 1) {
41568
- break;
42366
+ if (settings.keep_styles === false) {
42367
+ dom.setAttrib(block, 'style', null); // wipe out any styles that came over with the block
42368
+ dom.setAttrib(block, 'class', null);
42369
+ } else {
42370
+ // Clone any parent styles
42371
+ do {
42372
+ if (textInlineElements[node.nodeName]) {
42373
+ // Never clone a caret containers
42374
+ if (node.id == '_mce_caret') {
42375
+ continue;
41569
42376
  }
41570
42377
 
41571
- node = node[first ? 'nextSibling' : 'previousSibling'];
41572
- }
41573
-
41574
- return node === parentBlock;
41575
- }
41576
-
41577
- function getContainerBlock() {
41578
- var containerBlockParent = containerBlock.parentNode;
42378
+ clonedNode = node.cloneNode(false);
42379
+ dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
41579
42380
 
41580
- if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) {
41581
- return containerBlockParent;
42381
+ if (block.hasChildNodes()) {
42382
+ clonedNode.appendChild(block.firstChild);
42383
+ block.appendChild(clonedNode);
42384
+ } else {
42385
+ caretNode = clonedNode;
42386
+ block.appendChild(clonedNode);
42387
+ }
41582
42388
  }
42389
+ } while ((node = node.parentNode) && node != editableRoot);
42390
+ }
41583
42391
 
41584
- return containerBlock;
41585
- }
41586
-
41587
- if (containerBlock == editor.getBody()) {
41588
- return;
41589
- }
42392
+ emptyBlock(caretNode);
41590
42393
 
41591
- if (isNestedList(containerBlock)) {
41592
- newBlockName = 'LI';
41593
- }
42394
+ return block;
42395
+ }
41594
42396
 
41595
- newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
42397
+ // Returns true/false if the caret is at the start/end of the parent block element
42398
+ function isCaretAtStartOrEndOfBlock(start) {
42399
+ var walker, node, name, normalizedOffset;
41596
42400
 
41597
- if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
41598
- if (hasParent(containerBlock, 'LI')) {
41599
- // Nested list is inside a LI
41600
- dom.insertAfter(newBlock, getContainerBlock());
41601
- } else {
41602
- // Is first and last list item then replace the OL/UL with a text block
41603
- dom.replace(newBlock, containerBlock);
41604
- }
41605
- } else if (isFirstOrLastLi(true)) {
41606
- if (hasParent(containerBlock, 'LI')) {
41607
- // List nested in an LI then move the list to a new sibling LI
41608
- dom.insertAfter(newBlock, getContainerBlock());
41609
- newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed
41610
- newBlock.appendChild(containerBlock);
41611
- } else {
41612
- // First LI in list then remove LI and add text block before list
41613
- containerBlock.parentNode.insertBefore(newBlock, containerBlock);
41614
- }
41615
- } else if (isFirstOrLastLi()) {
41616
- // Last LI in list then remove LI and add text block after list
41617
- dom.insertAfter(newBlock, getContainerBlock());
41618
- renderBlockOnIE(dom, selection, newBlock);
41619
- } else {
41620
- // Middle LI in list the split the list and insert a text block in the middle
41621
- // Extract after fragment and insert it after the current block
41622
- containerBlock = getContainerBlock();
41623
- tmpRng = rng.cloneRange();
41624
- tmpRng.setStartAfter(parentBlock);
41625
- tmpRng.setEndAfter(containerBlock);
41626
- fragment = tmpRng.extractContents();
41627
-
41628
- if (newBlockName === 'LI' && hasFirstChild(fragment, 'LI')) {
41629
- newBlock = fragment.firstChild;
41630
- dom.insertAfter(fragment, containerBlock);
41631
- } else {
41632
- dom.insertAfter(fragment, containerBlock);
41633
- dom.insertAfter(newBlock, containerBlock);
41634
- }
41635
- }
42401
+ normalizedOffset = normalizeZwspOffset(start, container, offset);
41636
42402
 
41637
- dom.remove(parentBlock);
41638
- moveToCaretPosition(newBlock);
41639
- undoManager.add();
42403
+ // Caret is in the middle of a text node like "a|b"
42404
+ if (container.nodeType == 3 && (start ? normalizedOffset > 0 : normalizedOffset < container.nodeValue.length)) {
42405
+ return false;
41640
42406
  }
41641
42407
 
41642
- // Inserts a BR element if the forced_root_block option is set to false or empty string
41643
- function insertBr() {
41644
- editor.execCommand("InsertLineBreak", false, evt);
42408
+ // If after the last element in block node edge case for #5091
42409
+ if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
42410
+ return true;
41645
42411
  }
41646
42412
 
41647
- // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
41648
- function trimLeadingLineBreaks(node) {
41649
- do {
41650
- if (node.nodeType === 3) {
41651
- node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
41652
- }
41653
-
41654
- node = node.firstChild;
41655
- } while (node);
42413
+ // If the caret if before the first element in parentBlock
42414
+ if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
42415
+ return true;
41656
42416
  }
41657
42417
 
41658
- function getEditableRoot(node) {
41659
- var root = dom.getRoot(), parent, editableRoot;
41660
-
41661
- // Get all parents until we hit a non editable parent or the root
41662
- parent = node;
41663
- while (parent !== root && dom.getContentEditable(parent) !== "false") {
41664
- if (dom.getContentEditable(parent) === "true") {
41665
- editableRoot = parent;
41666
- }
41667
-
41668
- parent = parent.parentNode;
41669
- }
41670
-
41671
- return parent !== root ? editableRoot : root;
42418
+ // Caret can be before/after a table or a hr
42419
+ if (containerAndSiblingName(container, 'TABLE') || containerAndSiblingName(container, 'HR')) {
42420
+ return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
41672
42421
  }
41673
42422
 
41674
- // Adds a BR at the end of blocks that only contains an IMG or INPUT since
41675
- // these might be floated and then they won't expand the block
41676
- function addBrToBlockIfNeeded(block) {
41677
- var lastChild;
41678
-
41679
- // IE will render the blocks correctly other browsers needs a BR
41680
- if (!isIE) {
41681
- block.normalize(); // Remove empty text nodes that got left behind by the extract
42423
+ // Walk the DOM and look for text nodes or non empty elements
42424
+ walker = new TreeWalker(container, parentBlock);
41682
42425
 
41683
- // Check if the block is empty or contains a floated last child
41684
- lastChild = block.lastChild;
41685
- if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
41686
- dom.add(block, 'br');
41687
- }
42426
+ // If caret is in beginning or end of a text block then jump to the next/previous node
42427
+ if (container.nodeType == 3) {
42428
+ if (start && normalizedOffset === 0) {
42429
+ walker.prev();
42430
+ } else if (!start && normalizedOffset == container.nodeValue.length) {
42431
+ walker.next();
41688
42432
  }
41689
42433
  }
41690
42434
 
41691
- function insertNewBlockAfter() {
41692
- // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
41693
- if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
41694
- newBlock = createNewBlock(newBlockName);
41695
- } else {
41696
- newBlock = createNewBlock();
42435
+ while ((node = walker.current())) {
42436
+ if (node.nodeType === 1) {
42437
+ // Ignore bogus elements
42438
+ if (!node.getAttribute('data-mce-bogus')) {
42439
+ // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
42440
+ name = node.nodeName.toLowerCase();
42441
+ if (nonEmptyElementsMap[name] && name !== 'br') {
42442
+ return false;
42443
+ }
42444
+ }
42445
+ } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
42446
+ return false;
41697
42447
  }
41698
42448
 
41699
- // Split the current container block element if enter is pressed inside an empty inner block element
41700
- if (settings.end_container_on_empty_block && canSplitBlock(dom, containerBlock) && dom.isEmpty(parentBlock)) {
41701
- // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
41702
- newBlock = dom.split(containerBlock, parentBlock);
42449
+ if (start) {
42450
+ walker.prev();
41703
42451
  } else {
41704
- dom.insertAfter(newBlock, parentBlock);
42452
+ walker.next();
41705
42453
  }
41706
-
41707
- moveToCaretPosition(newBlock);
41708
42454
  }
41709
42455
 
41710
- rng = selection.getRng(true);
42456
+ return true;
42457
+ }
41711
42458
 
41712
- // Event is blocked by some other handler for example the lists plugin
41713
- if (evt.isDefaultPrevented()) {
42459
+ // Inserts a block or br before/after or in the middle of a split list of the LI is empty
42460
+ function handleEmptyListItem() {
42461
+ if (containerBlock == editor.getBody()) {
41714
42462
  return;
41715
42463
  }
41716
42464
 
41717
- // Delete any selected contents
41718
- if (!rng.collapsed) {
41719
- editor.execCommand('Delete');
41720
- return;
42465
+ if (isNestedList(containerBlock)) {
42466
+ newBlockName = 'LI';
41721
42467
  }
41722
42468
 
41723
- // Setup range items and newBlockName
41724
- new RangeUtils(dom).normalize(rng);
41725
- container = rng.startContainer;
41726
- offset = rng.startOffset;
41727
- newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
41728
- newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
41729
- documentMode = dom.doc.documentMode;
41730
- shiftKey = evt.shiftKey;
41731
-
41732
- // Resolve node index
41733
- if (container.nodeType == 1 && container.hasChildNodes()) {
41734
- isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
41735
-
41736
- container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
41737
- if (isAfterLastNodeInContainer && container.nodeType == 3) {
41738
- offset = container.nodeValue.length;
42469
+ newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
42470
+
42471
+ if (isFirstOrLastLi(containerBlock, parentBlock, true) && isFirstOrLastLi(containerBlock, parentBlock, false)) {
42472
+ if (hasParent(containerBlock, 'LI')) {
42473
+ // Nested list is inside a LI
42474
+ dom.insertAfter(newBlock, getContainerBlock(containerBlock));
41739
42475
  } else {
41740
- offset = 0;
42476
+ // Is first and last list item then replace the OL/UL with a text block
42477
+ dom.replace(newBlock, containerBlock);
42478
+ }
42479
+ } else if (isFirstOrLastLi(containerBlock, parentBlock, true)) {
42480
+ if (hasParent(containerBlock, 'LI')) {
42481
+ // List nested in an LI then move the list to a new sibling LI
42482
+ dom.insertAfter(newBlock, getContainerBlock(containerBlock));
42483
+ newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed
42484
+ newBlock.appendChild(containerBlock);
42485
+ } else {
42486
+ // First LI in list then remove LI and add text block before list
42487
+ containerBlock.parentNode.insertBefore(newBlock, containerBlock);
42488
+ }
42489
+ } else if (isFirstOrLastLi(containerBlock, parentBlock, false)) {
42490
+ // Last LI in list then remove LI and add text block after list
42491
+ dom.insertAfter(newBlock, getContainerBlock(containerBlock));
42492
+ } else {
42493
+ // Middle LI in list the split the list and insert a text block in the middle
42494
+ // Extract after fragment and insert it after the current block
42495
+ containerBlock = getContainerBlock(containerBlock);
42496
+ tmpRng = rng.cloneRange();
42497
+ tmpRng.setStartAfter(parentBlock);
42498
+ tmpRng.setEndAfter(containerBlock);
42499
+ fragment = tmpRng.extractContents();
42500
+
42501
+ if (newBlockName === 'LI' && hasFirstChild(fragment, 'LI')) {
42502
+ newBlock = fragment.firstChild;
42503
+ dom.insertAfter(fragment, containerBlock);
42504
+ } else {
42505
+ dom.insertAfter(fragment, containerBlock);
42506
+ dom.insertAfter(newBlock, containerBlock);
41741
42507
  }
41742
42508
  }
41743
42509
 
41744
- // Get editable root node, normally the body element but sometimes a div or span
41745
- editableRoot = getEditableRoot(container);
42510
+ dom.remove(parentBlock);
42511
+ moveToCaretPosition(newBlock);
42512
+ }
41746
42513
 
41747
- // If there is no editable root then enter is done inside a contentEditable false element
41748
- if (!editableRoot) {
41749
- return;
42514
+ function insertNewBlockAfter() {
42515
+ // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
42516
+ if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
42517
+ newBlock = createNewBlock(newBlockName);
42518
+ } else {
42519
+ newBlock = createNewBlock();
41750
42520
  }
41751
42521
 
41752
- undoManager.beforeChange();
42522
+ // Split the current container block element if enter is pressed inside an empty inner block element
42523
+ if (settings.end_container_on_empty_block && canSplitBlock(dom, containerBlock) && dom.isEmpty(parentBlock)) {
42524
+ // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
42525
+ newBlock = dom.split(containerBlock, parentBlock);
42526
+ } else {
42527
+ dom.insertAfter(newBlock, parentBlock);
42528
+ }
41753
42529
 
41754
- // If editable root isn't block nor the root of the editor
41755
- if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
41756
- if (!newBlockName || shiftKey) {
41757
- insertBr();
41758
- }
42530
+ moveToCaretPosition(newBlock);
42531
+ }
41759
42532
 
41760
- return;
41761
- }
42533
+ // Setup range items and newBlockName
42534
+ new RangeUtils(dom).normalize(rng);
42535
+ container = rng.startContainer;
42536
+ offset = rng.startOffset;
42537
+ newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
42538
+ newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
42539
+ shiftKey = evt.shiftKey;
42540
+
42541
+ // Resolve node index
42542
+ if (container.nodeType == 1 && container.hasChildNodes()) {
42543
+ isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
41762
42544
 
41763
- // Wrap the current node and it's sibling in a default block if it's needed.
41764
- // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
41765
- // This won't happen if root blocks are disabled or the shiftKey is pressed
41766
- if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
41767
- container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
42545
+ container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
42546
+ if (isAfterLastNodeInContainer && container.nodeType == 3) {
42547
+ offset = container.nodeValue.length;
42548
+ } else {
42549
+ offset = 0;
41768
42550
  }
42551
+ }
41769
42552
 
41770
- // Find parent block and setup empty block paddings
41771
- parentBlock = dom.getParent(container, dom.isBlock);
41772
- containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
42553
+ // Get editable root node, normally the body element but sometimes a div or span
42554
+ editableRoot = getEditableRoot(dom, container);
41773
42555
 
41774
- // Setup block names
41775
- parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
41776
- containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
42556
+ // If there is no editable root then enter is done inside a contentEditable false element
42557
+ if (!editableRoot) {
42558
+ return;
42559
+ }
41777
42560
 
41778
- // Enter inside block contained within a LI then split or insert before/after LI
41779
- if (containerBlockName == 'LI' && !evt.ctrlKey) {
41780
- parentBlock = containerBlock;
41781
- containerBlock = containerBlock.parentNode;
41782
- parentBlockName = containerBlockName;
42561
+ // If editable root isn't block nor the root of the editor
42562
+ if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
42563
+ if (!newBlockName || shiftKey) {
42564
+ insertBr(editor, evt);
41783
42565
  }
41784
42566
 
41785
- if (editor.undoManager.typing) {
41786
- editor.undoManager.typing = false;
41787
- editor.undoManager.add();
41788
- }
42567
+ return;
42568
+ }
41789
42569
 
41790
- // Handle enter in list item
41791
- if (/^(LI|DT|DD)$/.test(parentBlockName)) {
41792
- if (!newBlockName && shiftKey) {
41793
- insertBr();
41794
- return;
41795
- }
42570
+ // Wrap the current node and it's sibling in a default block if it's needed.
42571
+ // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
42572
+ // This won't happen if root blocks are disabled or the shiftKey is pressed
42573
+ if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
42574
+ container = wrapSelfAndSiblingsInDefaultBlock(editor, newBlockName, rng, container, offset);
42575
+ }
41796
42576
 
41797
- // Handle enter inside an empty list item
41798
- if (dom.isEmpty(parentBlock)) {
41799
- handleEmptyListItem();
41800
- return;
41801
- }
42577
+ // Find parent block and setup empty block paddings
42578
+ parentBlock = dom.getParent(container, dom.isBlock);
42579
+ containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
42580
+
42581
+ // Setup block names
42582
+ parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
42583
+ containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
42584
+
42585
+ // Enter inside block contained within a LI then split or insert before/after LI
42586
+ if (containerBlockName == 'LI' && !evt.ctrlKey) {
42587
+ parentBlock = containerBlock;
42588
+ containerBlock = containerBlock.parentNode;
42589
+ parentBlockName = containerBlockName;
42590
+ }
42591
+
42592
+ // Handle enter in list item
42593
+ if (/^(LI|DT|DD)$/.test(parentBlockName)) {
42594
+ if (!newBlockName && shiftKey) {
42595
+ insertBr(editor, evt);
42596
+ return;
41802
42597
  }
41803
42598
 
41804
- // Don't split PRE tags but insert a BR instead easier when writing code samples etc
41805
- if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
41806
- if (!shiftKey) {
41807
- insertBr();
41808
- return;
41809
- }
41810
- } else {
41811
- // If no root block is configured then insert a BR by default or if the shiftKey is pressed
41812
- if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
41813
- insertBr();
41814
- return;
41815
- }
42599
+ // Handle enter inside an empty list item
42600
+ if (dom.isEmpty(parentBlock)) {
42601
+ handleEmptyListItem();
42602
+ return;
41816
42603
  }
42604
+ }
41817
42605
 
41818
- // If parent block is root then never insert new blocks
41819
- if (newBlockName && parentBlock === editor.getBody()) {
42606
+ // Don't split PRE tags but insert a BR instead easier when writing code samples etc
42607
+ if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
42608
+ if (!shiftKey) {
42609
+ insertBr(editor, evt);
42610
+ return;
42611
+ }
42612
+ } else {
42613
+ // If no root block is configured then insert a BR by default or if the shiftKey is pressed
42614
+ if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
42615
+ insertBr(editor, evt);
41820
42616
  return;
41821
42617
  }
42618
+ }
41822
42619
 
41823
- // Default block name if it's not configured
41824
- newBlockName = newBlockName || 'P';
42620
+ // If parent block is root then never insert new blocks
42621
+ if (newBlockName && parentBlock === editor.getBody()) {
42622
+ return;
42623
+ }
41825
42624
 
41826
- // Insert new block before/after the parent block depending on caret location
41827
- if (CaretContainer.isCaretContainerBlock(parentBlock)) {
41828
- newBlock = CaretContainer.showCaretContainerBlock(parentBlock);
41829
- if (dom.isEmpty(parentBlock)) {
41830
- emptyBlock(parentBlock);
41831
- }
41832
- moveToCaretPosition(newBlock);
41833
- } else if (isCaretAtStartOrEndOfBlock()) {
41834
- insertNewBlockAfter();
41835
- } else if (isCaretAtStartOrEndOfBlock(true)) {
41836
- // Insert new block before
41837
- newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
41838
- renderBlockOnIE(dom, selection, newBlock);
42625
+ // Default block name if it's not configured
42626
+ newBlockName = newBlockName || 'P';
41839
42627
 
41840
- // Adjust caret position if HR
41841
- containerAndSiblingName(parentBlock, 'HR') ? moveToCaretPosition(newBlock) : moveToCaretPosition(parentBlock);
41842
- } else {
41843
- // Extract after fragment and insert it after the current block
41844
- tmpRng = includeZwspInRange(rng).cloneRange();
41845
- tmpRng.setEndAfter(parentBlock);
41846
- fragment = tmpRng.extractContents();
41847
- trimLeadingLineBreaks(fragment);
41848
- newBlock = fragment.firstChild;
41849
- dom.insertAfter(fragment, parentBlock);
41850
- trimInlineElementsOnLeftSideOfBlock(dom, nonEmptyElementsMap, newBlock);
41851
- addBrToBlockIfNeeded(parentBlock);
42628
+ // Insert new block before/after the parent block depending on caret location
42629
+ if (CaretContainer.isCaretContainerBlock(parentBlock)) {
42630
+ newBlock = CaretContainer.showCaretContainerBlock(parentBlock);
42631
+ if (dom.isEmpty(parentBlock)) {
42632
+ emptyBlock(parentBlock);
42633
+ }
42634
+ moveToCaretPosition(newBlock);
42635
+ } else if (isCaretAtStartOrEndOfBlock()) {
42636
+ insertNewBlockAfter();
42637
+ } else if (isCaretAtStartOrEndOfBlock(true)) {
42638
+ // Insert new block before
42639
+ newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
41852
42640
 
41853
- if (dom.isEmpty(parentBlock)) {
41854
- emptyBlock(parentBlock);
41855
- }
42641
+ // Adjust caret position if HR
42642
+ containerAndSiblingName(parentBlock, 'HR') ? moveToCaretPosition(newBlock) : moveToCaretPosition(parentBlock);
42643
+ } else {
42644
+ // Extract after fragment and insert it after the current block
42645
+ tmpRng = includeZwspInRange(rng).cloneRange();
42646
+ tmpRng.setEndAfter(parentBlock);
42647
+ fragment = tmpRng.extractContents();
42648
+ trimLeadingLineBreaks(fragment);
42649
+ newBlock = fragment.firstChild;
42650
+ dom.insertAfter(fragment, parentBlock);
42651
+ trimInlineElementsOnLeftSideOfBlock(dom, nonEmptyElementsMap, newBlock);
42652
+ addBrToBlockIfNeeded(dom, parentBlock);
41856
42653
 
41857
- newBlock.normalize();
42654
+ if (dom.isEmpty(parentBlock)) {
42655
+ emptyBlock(parentBlock);
42656
+ }
41858
42657
 
41859
- // New block might become empty if it's <p><b>a |</b></p>
41860
- if (dom.isEmpty(newBlock)) {
41861
- dom.remove(newBlock);
41862
- insertNewBlockAfter();
41863
- } else {
41864
- moveToCaretPosition(newBlock);
41865
- }
42658
+ newBlock.normalize();
42659
+
42660
+ // New block might become empty if it's <p><b>a |</b></p>
42661
+ if (dom.isEmpty(newBlock)) {
42662
+ dom.remove(newBlock);
42663
+ insertNewBlockAfter();
42664
+ } else {
42665
+ moveToCaretPosition(newBlock);
41866
42666
  }
42667
+ }
41867
42668
 
41868
- dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
42669
+ dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
41869
42670
 
41870
- // Allow custom handling of new blocks
41871
- editor.fire('NewBlock', { newBlock: newBlock });
42671
+ // Allow custom handling of new blocks
42672
+ editor.fire('NewBlock', { newBlock: newBlock });
42673
+ };
42674
+
42675
+ return {
42676
+ insert: insert
42677
+ };
42678
+ }
42679
+ );
41872
42680
 
42681
+ /**
42682
+ * EnterKey.js
42683
+ *
42684
+ * Released under LGPL License.
42685
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
42686
+ *
42687
+ * License: http://www.tinymce.com/license
42688
+ * Contributing: http://www.tinymce.com/contributing
42689
+ */
42690
+
42691
+ define(
42692
+ 'tinymce.core.keyboard.EnterKey',
42693
+ [
42694
+ 'tinymce.core.keyboard.InsertNewLine',
42695
+ 'tinymce.core.util.VK'
42696
+ ],
42697
+ function (InsertNewLine, VK) {
42698
+ var endTypingLevel = function (undoManager) {
42699
+ if (undoManager.typing) {
41873
42700
  undoManager.typing = false;
41874
42701
  undoManager.add();
41875
42702
  }
42703
+ };
41876
42704
 
41877
- editor.on('keydown', function (evt) {
41878
- if (evt.keyCode == 13) {
41879
- if (handleEnterKey(evt) !== false) {
41880
- evt.preventDefault();
41881
- }
42705
+ var handleEnterKeyEvent = function (editor, event) {
42706
+ if (event.isDefaultPrevented()) {
42707
+ return;
42708
+ }
42709
+
42710
+ event.preventDefault();
42711
+
42712
+ endTypingLevel(editor.undoManager);
42713
+ editor.undoManager.transact(function () {
42714
+ if (editor.selection.isCollapsed() === false) {
42715
+ editor.execCommand('Delete');
42716
+ }
42717
+
42718
+ InsertNewLine.insert(editor, event);
42719
+ });
42720
+ };
42721
+
42722
+ var setup = function (editor) {
42723
+ editor.on('keydown', function (event) {
42724
+ if (event.keyCode === VK.ENTER) {
42725
+ handleEnterKeyEvent(editor, event);
41882
42726
  }
41883
42727
  });
41884
42728
  };
@@ -42763,280 +43607,6 @@ define(
42763
43607
  }
42764
43608
  );
42765
43609
 
42766
- define(
42767
- 'ephox.sugar.impl.Style',
42768
-
42769
- [
42770
-
42771
- ],
42772
-
42773
- function () {
42774
- // some elements, such as mathml, don't have style attributes
42775
- var isSupported = function (dom) {
42776
- return dom.style !== undefined;
42777
- };
42778
-
42779
- return {
42780
- isSupported: isSupported
42781
- };
42782
- }
42783
- );
42784
- define(
42785
- 'ephox.sugar.api.properties.Css',
42786
-
42787
- [
42788
- 'ephox.katamari.api.Type',
42789
- 'ephox.katamari.api.Arr',
42790
- 'ephox.katamari.api.Obj',
42791
- 'ephox.katamari.api.Option',
42792
- 'ephox.sugar.api.properties.Attr',
42793
- 'ephox.sugar.api.node.Body',
42794
- 'ephox.sugar.api.node.Element',
42795
- 'ephox.sugar.api.node.Node',
42796
- 'ephox.sugar.impl.Style',
42797
- 'ephox.katamari.api.Strings',
42798
- 'global!Error',
42799
- 'global!console',
42800
- 'global!window'
42801
- ],
42802
-
42803
- function (Type, Arr, Obj, Option, Attr, Body, Element, Node, Style, Strings, Error, console, window) {
42804
- var internalSet = function (dom, property, value) {
42805
- // This is going to hurt. Apologies.
42806
- // JQuery coerces numbers to pixels for certain property names, and other times lets numbers through.
42807
- // we're going to be explicit; strings only.
42808
- if (!Type.isString(value)) {
42809
- console.error('Invalid call to CSS.set. Property ', property, ':: Value ', value, ':: Element ', dom);
42810
- throw new Error('CSS value must be a string: ' + value);
42811
- }
42812
-
42813
- // removed: support for dom().style[property] where prop is camel case instead of normal property name
42814
- if (Style.isSupported(dom)) dom.style.setProperty(property, value);
42815
- };
42816
-
42817
- var internalRemove = function (dom, property) {
42818
- /*
42819
- * IE9 and above - MDN doesn't have details, but here's a couple of random internet claims
42820
- *
42821
- * http://help.dottoro.com/ljopsjck.php
42822
- * http://stackoverflow.com/a/7901886/7546
42823
- */
42824
- if (Style.isSupported(dom)) dom.style.removeProperty(property);
42825
- };
42826
-
42827
- var set = function (element, property, value) {
42828
- var dom = element.dom();
42829
- internalSet(dom, property, value);
42830
- };
42831
-
42832
- var setAll = function (element, css) {
42833
- var dom = element.dom();
42834
-
42835
- Obj.each(css, function (v, k) {
42836
- internalSet(dom, k, v);
42837
- });
42838
- };
42839
-
42840
- var setOptions = function(element, css) {
42841
- var dom = element.dom();
42842
-
42843
- Obj.each(css, function (v, k) {
42844
- v.fold(function () {
42845
- internalRemove(dom, k);
42846
- }, function (value) {
42847
- internalSet(dom, k, value);
42848
- });
42849
- });
42850
- };
42851
-
42852
- /*
42853
- * NOTE: For certain properties, this returns the "used value" which is subtly different to the "computed value" (despite calling getComputedStyle).
42854
- * Blame CSS 2.0.
42855
- *
42856
- * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
42857
- */
42858
- var get = function (element, property) {
42859
- var dom = element.dom();
42860
- /*
42861
- * IE9 and above per
42862
- * https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle
42863
- *
42864
- * Not in numerosity, because it doesn't memoize and looking this up dynamically in performance critical code would be horrendous.
42865
- *
42866
- * JQuery has some magic here for IE popups, but we don't really need that.
42867
- * It also uses element.ownerDocument.defaultView to handle iframes but that hasn't been required since FF 3.6.
42868
- */
42869
- var styles = window.getComputedStyle(dom);
42870
- var r = styles.getPropertyValue(property);
42871
-
42872
- // jquery-ism: If r is an empty string, check that the element is not in a document. If it isn't, return the raw value.
42873
- // Turns out we do this a lot.
42874
- var v = (r === '' && !Body.inBody(element)) ? getUnsafeProperty(dom, property) : r;
42875
-
42876
- // undefined is the more appropriate value for JS. JQuery coerces to an empty string, but screw that!
42877
- return v === null ? undefined : v;
42878
- };
42879
-
42880
- var getUnsafeProperty = function (dom, property) {
42881
- // removed: support for dom().style[property] where prop is camel case instead of normal property name
42882
- // empty string is what the browsers (IE11 and Chrome) return when the propertyValue doesn't exists.
42883
- return Style.isSupported(dom) ? dom.style.getPropertyValue(property) : '';
42884
- };
42885
-
42886
- /*
42887
- * Gets the raw value from the style attribute. Useful for retrieving "used values" from the DOM:
42888
- * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
42889
- *
42890
- * Returns NONE if the property isn't set, or the value is an empty string.
42891
- */
42892
- var getRaw = function (element, property) {
42893
- var dom = element.dom();
42894
- var raw = getUnsafeProperty(dom, property);
42895
-
42896
- return Option.from(raw).filter(function (r) { return r.length > 0; });
42897
- };
42898
-
42899
- var isValidValue = function (tag, property, value) {
42900
- var element = Element.fromTag(tag);
42901
- set(element, property, value);
42902
- var style = getRaw(element, property);
42903
- return style.isSome();
42904
- };
42905
-
42906
- var remove = function (element, property) {
42907
- var dom = element.dom();
42908
-
42909
- internalRemove(dom, property);
42910
-
42911
- if (Attr.has(element, 'style') && Strings.trim(Attr.get(element, 'style')) === '') {
42912
- // No more styles left, remove the style attribute as well
42913
- Attr.remove(element, 'style');
42914
- }
42915
- };
42916
-
42917
- var preserve = function (element, f) {
42918
- var oldStyles = Attr.get(element, 'style');
42919
- var result = f(element);
42920
- var restore = oldStyles === undefined ? Attr.remove : Attr.set;
42921
- restore(element, 'style', oldStyles);
42922
- return result;
42923
- };
42924
-
42925
- var copy = function (source, target) {
42926
- var sourceDom = source.dom();
42927
- var targetDom = target.dom();
42928
- if (Style.isSupported(sourceDom) && Style.isSupported(targetDom)) {
42929
- targetDom.style.cssText = sourceDom.style.cssText;
42930
- }
42931
- };
42932
-
42933
- var reflow = function (e) {
42934
- /* NOTE:
42935
- * do not rely on this return value.
42936
- * It's here so the closure compiler doesn't optimise the property access away.
42937
- */
42938
- return e.dom().offsetWidth;
42939
- };
42940
-
42941
- var transferOne = function (source, destination, style) {
42942
- getRaw(source, style).each(function (value) {
42943
- // NOTE: We don't want to clobber any existing inline styles.
42944
- if (getRaw(destination, style).isNone()) set(destination, style, value);
42945
- });
42946
- };
42947
-
42948
- var transfer = function (source, destination, styles) {
42949
- if (!Node.isElement(source) || !Node.isElement(destination)) return;
42950
- Arr.each(styles, function (style) {
42951
- transferOne(source, destination, style);
42952
- });
42953
- };
42954
-
42955
- return {
42956
- copy: copy,
42957
- set: set,
42958
- preserve: preserve,
42959
- setAll: setAll,
42960
- setOptions: setOptions,
42961
- remove: remove,
42962
- get: get,
42963
- getRaw: getRaw,
42964
- isValidValue: isValidValue,
42965
- reflow: reflow,
42966
- transfer: transfer
42967
- };
42968
- }
42969
- );
42970
-
42971
- /**
42972
- * EditorView.js
42973
- *
42974
- * Released under LGPL License.
42975
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
42976
- *
42977
- * License: http://www.tinymce.com/license
42978
- * Contributing: http://www.tinymce.com/contributing
42979
- */
42980
-
42981
- define(
42982
- 'tinymce.core.EditorView',
42983
- [
42984
- 'ephox.katamari.api.Fun',
42985
- 'ephox.sugar.api.node.Element',
42986
- 'ephox.sugar.api.properties.Css',
42987
- 'ephox.sugar.api.search.Traverse'
42988
- ],
42989
- function (Fun, Element, Css, Traverse) {
42990
- var getProp = function (propName, elm) {
42991
- var rawElm = elm.dom();
42992
- return rawElm[propName];
42993
- };
42994
-
42995
- var getComputedSizeProp = function (propName, elm) {
42996
- return parseInt(Css.get(elm, propName), 10);
42997
- };
42998
-
42999
- var getClientWidth = Fun.curry(getProp, 'clientWidth');
43000
- var getClientHeight = Fun.curry(getProp, 'clientHeight');
43001
- var getMarginTop = Fun.curry(getComputedSizeProp, 'margin-top');
43002
- var getMarginLeft = Fun.curry(getComputedSizeProp, 'margin-left');
43003
-
43004
- var getBoundingClientRect = function (elm) {
43005
- return elm.dom().getBoundingClientRect();
43006
- };
43007
-
43008
- var isInsideElementContentArea = function (bodyElm, clientX, clientY) {
43009
- var clientWidth = getClientWidth(bodyElm);
43010
- var clientHeight = getClientHeight(bodyElm);
43011
-
43012
- return clientX >= 0 && clientY >= 0 && clientX <= clientWidth && clientY <= clientHeight;
43013
- };
43014
-
43015
- var transpose = function (inline, elm, clientX, clientY) {
43016
- var clientRect = getBoundingClientRect(elm);
43017
- var deltaX = inline ? clientRect.left + elm.dom().clientLeft + getMarginLeft(elm) : 0;
43018
- var deltaY = inline ? clientRect.top + elm.dom().clientTop + getMarginTop(elm) : 0;
43019
- var x = clientX - deltaX;
43020
- var y = clientY - deltaY;
43021
-
43022
- return { x: x, y: y };
43023
- };
43024
-
43025
- // Checks if the specified coordinate is within the visual content area excluding the scrollbars
43026
- var isXYInContentArea = function (editor, clientX, clientY) {
43027
- var bodyElm = Element.fromDom(editor.getBody());
43028
- var targetElm = editor.inline ? bodyElm : Traverse.documentElement(bodyElm);
43029
- var transposedPoint = transpose(editor.inline, targetElm, clientX, clientY);
43030
-
43031
- return isInsideElementContentArea(targetElm, transposedPoint.x, transposedPoint.y);
43032
- };
43033
-
43034
- return {
43035
- isXYInContentArea: isXYInContentArea
43036
- };
43037
- }
43038
- );
43039
-
43040
43610
  /**
43041
43611
  * SelectionOverrides.js
43042
43612
  *
@@ -44427,7 +44997,11 @@ define(
44427
44997
  // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
44428
44998
  editor.on('keyup focusin mouseup', function (e) {
44429
44999
  if (e.keyCode != 65 || !VK.metaKeyPressed(e)) {
44430
- selection.normalize();
45000
+ // We can't normalize on non collapsed ranges on keyboard events since that would cause
45001
+ // issues with moving the selection over empty paragraphs. See #TINY-1130
45002
+ if (e.type !== 'keyup' || editor.selection.isCollapsed()) {
45003
+ selection.normalize();
45004
+ }
44431
45005
  }
44432
45006
  }, true);
44433
45007
  }
@@ -44754,6 +45328,9 @@ define(
44754
45328
  define(
44755
45329
  'tinymce.core.init.InitContentBody',
44756
45330
  [
45331
+ 'ephox.sugar.api.dom.Insert',
45332
+ 'ephox.sugar.api.node.Element',
45333
+ 'ephox.sugar.api.properties.Attr',
44757
45334
  'global!document',
44758
45335
  'global!window',
44759
45336
  'tinymce.core.api.Formatter',
@@ -44776,11 +45353,19 @@ define(
44776
45353
  'tinymce.core.util.Tools'
44777
45354
  ],
44778
45355
  function (
44779
- document, window, Formatter, CaretContainerInput, DOMUtils, Selection, Serializer, EditorUpload, ErrorReporter, ForceBlocks, DomParser, Node, Schema, KeyboardOverrides,
44780
- NodeChange, SelectionOverrides, UndoManager, Delay, Quirks, Tools
45356
+ Insert, Element, Attr, document, window, Formatter, CaretContainerInput, DOMUtils, Selection, Serializer, EditorUpload, ErrorReporter, ForceBlocks, DomParser,
45357
+ Node, Schema, KeyboardOverrides, NodeChange, SelectionOverrides, UndoManager, Delay, Quirks, Tools
44781
45358
  ) {
44782
45359
  var DOM = DOMUtils.DOM;
44783
45360
 
45361
+ var appendStyle = function (editor, text) {
45362
+ var head = Element.fromDom(editor.getDoc().head);
45363
+ var tag = Element.fromTag('style');
45364
+ Attr.set(tag, 'type', 'text/css');
45365
+ Insert.append(tag, Element.fromText(text));
45366
+ Insert.append(head, tag);
45367
+ };
45368
+
44784
45369
  var createParser = function (editor) {
44785
45370
  var parser = new DomParser(editor.settings, editor.schema);
44786
45371
 
@@ -45034,9 +45619,13 @@ define(
45034
45619
  },
45035
45620
  function (urls) {
45036
45621
  initEditor(editor);
45037
- ErrorReporter.contentCssError(editor, urls);
45038
45622
  }
45039
45623
  );
45624
+
45625
+ // Append specified content CSS last
45626
+ if (settings.content_style) {
45627
+ appendStyle(editor, settings.content_style);
45628
+ }
45040
45629
  };
45041
45630
 
45042
45631
  return {
@@ -45136,11 +45725,16 @@ define(
45136
45725
  }
45137
45726
  };
45138
45727
 
45728
+ var trimLegacyPrefix = function (name) {
45729
+ // Themes and plugins can be prefixed with - to prevent them from being lazy loaded
45730
+ return name.replace(/^\-/, '');
45731
+ };
45732
+
45139
45733
  var initPlugins = function (editor) {
45140
45734
  var initializedPlugins = [];
45141
45735
 
45142
- Tools.each(editor.settings.plugins.replace(/\-/g, '').split(/[ ,]/), function (name) {
45143
- initPlugin(editor, initializedPlugins, name);
45736
+ Tools.each(editor.settings.plugins.split(/[ ,]/), function (name) {
45737
+ initPlugin(editor, initializedPlugins, trimLegacyPrefix(name));
45144
45738
  });
45145
45739
  };
45146
45740
 
@@ -45149,7 +45743,8 @@ define(
45149
45743
 
45150
45744
  if (settings.theme) {
45151
45745
  if (typeof settings.theme != "function") {
45152
- settings.theme = settings.theme.replace(/-/, '');
45746
+ settings.theme = trimLegacyPrefix(settings.theme);
45747
+
45153
45748
  Theme = ThemeManager.get(settings.theme);
45154
45749
  editor.theme = new Theme(editor, ThemeManager.urls[settings.theme]);
45155
45750
 
@@ -45334,11 +45929,6 @@ define(
45334
45929
  });
45335
45930
  }
45336
45931
 
45337
- // Load specified content CSS last
45338
- if (settings.content_style) {
45339
- editor.contentStyles.push(settings.content_style);
45340
- }
45341
-
45342
45932
  // Content editable mode ends here
45343
45933
  if (settings.content_editable) {
45344
45934
  return InitContentBody.initContentBody(editor);
@@ -47628,6 +48218,8 @@ define(
47628
48218
  define(
47629
48219
  'tinymce.core.EditorManager',
47630
48220
  [
48221
+ 'ephox.katamari.api.Arr',
48222
+ 'ephox.katamari.api.Type',
47631
48223
  'tinymce.core.AddOnManager',
47632
48224
  'tinymce.core.dom.DomQuery',
47633
48225
  'tinymce.core.dom.DOMUtils',
@@ -47642,13 +48234,14 @@ define(
47642
48234
  'tinymce.core.util.Tools',
47643
48235
  'tinymce.core.util.URI'
47644
48236
  ],
47645
- function (AddOnManager, DomQuery, DOMUtils, Editor, Env, ErrorReporter, FocusManager, LegacyInput, I18n, Observable, Promise, Tools, URI) {
48237
+ function (Arr, Type, AddOnManager, DomQuery, DOMUtils, Editor, Env, ErrorReporter, FocusManager, LegacyInput, I18n, Observable, Promise, Tools, URI) {
47646
48238
  var DOM = DOMUtils.DOM;
47647
48239
  var explode = Tools.explode, each = Tools.each, extend = Tools.extend;
47648
48240
  var instanceCounter = 0, beforeUnloadDelegate, EditorManager, boundGlobalEvents = false;
48241
+ var legacyEditors = [], editors = [];
47649
48242
 
47650
48243
  function globalEventDelegate(e) {
47651
- each(EditorManager.editors, function (editor) {
48244
+ each(EditorManager.get(), function (editor) {
47652
48245
  if (e.type === 'scroll') {
47653
48246
  editor.fire('ScrollWindow', e);
47654
48247
  } else {
@@ -47657,7 +48250,7 @@ define(
47657
48250
  });
47658
48251
  }
47659
48252
 
47660
- function toggleGlobalEvents(editors, state) {
48253
+ function toggleGlobalEvents(state) {
47661
48254
  if (state !== boundGlobalEvents) {
47662
48255
  if (state) {
47663
48256
  DomQuery(window).on('resize scroll', globalEventDelegate);
@@ -47669,30 +48262,32 @@ define(
47669
48262
  }
47670
48263
  }
47671
48264
 
47672
- function removeEditorFromList(editor) {
47673
- var editors = EditorManager.editors, removedFromList;
48265
+ function removeEditorFromList(targetEditor) {
48266
+ var oldEditors = editors;
47674
48267
 
47675
- delete editors[editor.id];
47676
-
47677
- for (var i = 0; i < editors.length; i++) {
47678
- if (editors[i] == editor) {
47679
- editors.splice(i, 1);
47680
- removedFromList = true;
48268
+ delete legacyEditors[targetEditor.id];
48269
+ for (var i = 0; i < legacyEditors.length; i++) {
48270
+ if (legacyEditors[i] === targetEditor) {
48271
+ legacyEditors.splice(i, 1);
47681
48272
  break;
47682
48273
  }
47683
48274
  }
47684
48275
 
48276
+ editors = Arr.filter(editors, function (editor) {
48277
+ return targetEditor !== editor;
48278
+ });
48279
+
47685
48280
  // Select another editor since the active one was removed
47686
- if (EditorManager.activeEditor == editor) {
47687
- EditorManager.activeEditor = editors[0];
48281
+ if (EditorManager.activeEditor === targetEditor) {
48282
+ EditorManager.activeEditor = editors.length > 0 ? editors[0] : null;
47688
48283
  }
47689
48284
 
47690
48285
  // Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor
47691
- if (EditorManager.focusedEditor == editor) {
48286
+ if (EditorManager.focusedEditor === targetEditor) {
47692
48287
  EditorManager.focusedEditor = null;
47693
48288
  }
47694
48289
 
47695
- return removedFromList;
48290
+ return oldEditors.length !== editors.length;
47696
48291
  }
47697
48292
 
47698
48293
  function purgeDestroyedEditor(editor) {
@@ -47731,7 +48326,7 @@ define(
47731
48326
  * @property minorVersion
47732
48327
  * @type String
47733
48328
  */
47734
- minorVersion: '6.5',
48329
+ minorVersion: '6.6',
47735
48330
 
47736
48331
  /**
47737
48332
  * Release date of TinyMCE build.
@@ -47739,18 +48334,15 @@ define(
47739
48334
  * @property releaseDate
47740
48335
  * @type String
47741
48336
  */
47742
- releaseDate: '2017-08-02',
48337
+ releaseDate: '2017-08-30',
47743
48338
 
47744
48339
  /**
47745
- * Collection of editor instances.
48340
+ * Collection of editor instances. Deprecated use tinymce.get() instead.
47746
48341
  *
47747
48342
  * @property editors
47748
48343
  * @type Object
47749
- * @example
47750
- * for (edId in tinymce.editors)
47751
- * tinymce.editors[edId].save();
47752
48344
  */
47753
- editors: [],
48345
+ editors: legacyEditors,
47754
48346
 
47755
48347
  /**
47756
48348
  * Collection of language pack data.
@@ -48106,24 +48698,35 @@ define(
48106
48698
  *
48107
48699
  * @method get
48108
48700
  * @param {String/Number} id Editor instance id or index to return.
48109
- * @return {tinymce.Editor} Editor instance to return.
48701
+ * @return {tinymce.Editor/Array} Editor instance to return or array of editor instances.
48110
48702
  * @example
48111
- * // Adds an onclick event to an editor by id (shorter version)
48703
+ * // Adds an onclick event to an editor by id
48112
48704
  * tinymce.get('mytextbox').on('click', function(e) {
48113
48705
  * ed.windowManager.alert('Hello world!');
48114
48706
  * });
48115
48707
  *
48708
+ * // Adds an onclick event to an editor by index
48709
+ * tinymce.get(0).on('click', function(e) {
48710
+ * ed.windowManager.alert('Hello world!');
48711
+ * });
48712
+ *
48116
48713
  * // Adds an onclick event to an editor by id (longer version)
48117
48714
  * tinymce.EditorManager.get('mytextbox').on('click', function(e) {
48118
48715
  * ed.windowManager.alert('Hello world!');
48119
48716
  * });
48120
48717
  */
48121
48718
  get: function (id) {
48122
- if (!arguments.length) {
48123
- return this.editors;
48719
+ if (arguments.length === 0) {
48720
+ return editors.slice(0);
48721
+ } else if (Type.isString(id)) {
48722
+ return Arr.find(editors, function (editor) {
48723
+ return editor.id === id;
48724
+ }).getOr(null);
48725
+ } else if (Type.isNumber(id)) {
48726
+ return editors[id] ? editors[id] : null;
48727
+ } else {
48728
+ return null;
48124
48729
  }
48125
-
48126
- return id in this.editors ? this.editors[id] : null;
48127
48730
  },
48128
48731
 
48129
48732
  /**
@@ -48134,13 +48737,25 @@ define(
48134
48737
  * @return {tinymce.Editor} The same instance that got passed in.
48135
48738
  */
48136
48739
  add: function (editor) {
48137
- var self = this, editors = self.editors;
48740
+ var self = this, existingEditor;
48138
48741
 
48139
- // Add named and index editor instance
48140
- editors[editor.id] = editor;
48141
- editors.push(editor);
48742
+ // Prevent existing editors from beeing added again this could happen
48743
+ // if a user calls createEditor then render or add multiple times.
48744
+ existingEditor = legacyEditors[editor.id];
48745
+ if (existingEditor === editor) {
48746
+ return editor;
48747
+ }
48748
+
48749
+ if (self.get(editor.id) === null) {
48750
+ // Add to legacy editors array, this is what breaks in HTML5 where ID:s with numbers are valid
48751
+ // We can't get rid of this strange object and array at the same time since it seems to be used all over the web
48752
+ legacyEditors[editor.id] = editor;
48753
+ legacyEditors.push(editor);
48754
+
48755
+ editors.push(editor);
48756
+ }
48142
48757
 
48143
- toggleGlobalEvents(editors, true);
48758
+ toggleGlobalEvents(true);
48144
48759
 
48145
48760
  // Doesn't call setActive method since we don't want
48146
48761
  // to fire a bunch of activate/deactivate calls while initializing
@@ -48192,7 +48807,7 @@ define(
48192
48807
  * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
48193
48808
  */
48194
48809
  remove: function (selector) {
48195
- var self = this, i, editors = self.editors, editor;
48810
+ var self = this, i, editor;
48196
48811
 
48197
48812
  // Remove all editors
48198
48813
  if (!selector) {
@@ -48204,11 +48819,11 @@ define(
48204
48819
  }
48205
48820
 
48206
48821
  // Remove editors by selector
48207
- if (typeof selector == "string") {
48822
+ if (Type.isString(selector)) {
48208
48823
  selector = selector.selector || selector;
48209
48824
 
48210
48825
  each(DOM.select(selector), function (elm) {
48211
- editor = editors[elm.id];
48826
+ editor = self.get(elm.id);
48212
48827
 
48213
48828
  if (editor) {
48214
48829
  self.remove(editor);
@@ -48222,7 +48837,7 @@ define(
48222
48837
  editor = selector;
48223
48838
 
48224
48839
  // Not in the collection
48225
- if (!editors[editor.id]) {
48840
+ if (Type.isNull(self.get(editor.id))) {
48226
48841
  return null;
48227
48842
  }
48228
48843
 
@@ -48230,13 +48845,13 @@ define(
48230
48845
  self.fire('RemoveEditor', { editor: editor });
48231
48846
  }
48232
48847
 
48233
- if (!editors.length) {
48848
+ if (editors.length === 0) {
48234
48849
  DOM.unbind(window, 'beforeunload', beforeUnloadDelegate);
48235
48850
  }
48236
48851
 
48237
48852
  editor.remove();
48238
48853
 
48239
- toggleGlobalEvents(editors, editors.length > 0);
48854
+ toggleGlobalEvents(editors.length > 0);
48240
48855
 
48241
48856
  return editor;
48242
48857
  },
@@ -48301,7 +48916,7 @@ define(
48301
48916
  * tinyMCE.triggerSave();
48302
48917
  */
48303
48918
  triggerSave: function () {
48304
- each(this.editors, function (editor) {
48919
+ each(editors, function (editor) {
48305
48920
  editor.save();
48306
48921
  });
48307
48922
  },