tinymce-rails 4.6.5 → 4.6.6

Sign up to get free protection for your applications and to get access to all the features.
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
  },