tinymce-rails 4.6.3 → 4.6.4

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: 93f9f956e1b77cb470a5d45af711fc2a52852b08
4
- data.tar.gz: 181e78dff51dc4ee7b10311beca8ba0e8751f8d6
3
+ metadata.gz: 6ce018de101ce52c7c97b1ec464c50a96f25805a
4
+ data.tar.gz: 31c037f319b428cecdd48ec343fdd5f28f5259bc
5
5
  SHA512:
6
- metadata.gz: 52bcf8174f2c22c67f78a146b3fbdf82d90a2c17a156bebb4538ae09ed3ed1a302bc4ec22f63e2012b89e5a35133555f9dab6bcccb84b75e692c18a5e69817d3
7
- data.tar.gz: 8b50bf67723ea5e42f4b289e0d40161ccc83f43e26cc7743e7ec6a24e736230279d83374062c214933d321ad6604dd3110765d67c7c2e1029a5d27fb2ac6cc97
6
+ metadata.gz: 83fc0e180593e62c25bcf6fc3b9a3d8d5aa19b747a67173045ee8db0237fca26f43ab826cc03187caf1feee8bfaf43067676bc45b450f398637331b2b575139b
7
+ data.tar.gz: ea0d70f73da56bca7beb9a6b0e4beb4862247c137a329c35d0ac9127ba47538ad4a09f7bf218633bff60c0a86a35f4819fd8fe1cff862d9dfc37efc504e09452
@@ -1,4 +1,4 @@
1
- // 4.6.3 (2017-05-30)
1
+ // 4.6.4 (2017-06-13)
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","tinymce.core.api.Tinymce","tinymce.core.Register","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.Formatter","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.api.Compat","tinymce.core.util.Color","tinymce.core.ui.Api","tinymce.core.util.Arr","tinymce.core.dom.Range","tinymce.core.dom.StyleSheetLoader","tinymce.core.dom.NodeType","tinymce.core.caret.CaretContainer","tinymce.core.text.Zwsp","tinymce.core.caret.CaretBookmark","tinymce.core.caret.CaretPosition","tinymce.core.dom.ScrollIntoView","tinymce.core.dom.TridentSelection","tinymce.core.selection.FragmentReader","tinymce.core.dom.ElementUtils","tinymce.core.util.Fun","tinymce.core.fmt.Preview","tinymce.core.fmt.Hooks","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.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","ephox.katamari.api.Arr","ephox.katamari.api.Fun","ephox.katamari.api.Future","ephox.katamari.api.Futures","ephox.katamari.api.Result","tinymce.core.caret.CaretCandidate","tinymce.core.geom.ClientRect","tinymce.core.text.ExtendingChar","ephox.sugar.api.dom.Insert","ephox.sugar.api.dom.Replication","ephox.sugar.api.node.Element","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.InlineBoundaryDelete","tinymce.core.caret.CaretWalker","tinymce.core.dom.RangeNormalizer","tinymce.core.InsertList","tinymce.core.data.ObservableObject","tinymce.core.ui.DomUtils","tinymce.core.ui.BoxUtils","tinymce.core.ui.ClassList","global!window","tinymce.core.init.Init","tinymce.core.PluginManager","tinymce.core.ThemeManager","tinymce.core.content.LinkTargets","tinymce.core.fmt.FontInfo","ephox.katamari.api.Option","global!Array","global!Error","global!String","ephox.katamari.api.LazyValue","ephox.katamari.async.Bounce","ephox.katamari.async.AsyncValues","ephox.sugar.api.search.Traverse","ephox.sugar.api.properties.Attr","global!console","ephox.sugar.api.dom.InsertAll","ephox.sugar.api.dom.Remove","ephox.sugar.api.node.NodeTypes","ephox.sugar.api.dom.Compare","ephox.katamari.api.Options","tinymce.core.undo.Diff","tinymce.core.delete.BlockBoundary","tinymce.core.delete.MergeBlocks","tinymce.core.delete.DeleteUtils","tinymce.core.caret.CaretUtils","tinymce.core.delete.CefDeleteAction","tinymce.core.delete.DeleteElement","tinymce.core.caret.CaretFinder","tinymce.core.keyboard.BoundaryCaret","tinymce.core.keyboard.BoundaryLocation","tinymce.core.keyboard.BoundarySelection","tinymce.core.keyboard.InlineUtils","tinymce.core.data.Binding","tinymce.core.init.InitContentBody","global!Object","global!setTimeout","ephox.katamari.api.Type","ephox.katamari.api.Struct","ephox.sugar.alien.Recurse","ephox.sand.api.Node","ephox.sand.api.PlatformDetection","ephox.sugar.api.search.Selectors","ephox.katamari.api.Obj","ephox.sugar.api.search.PredicateFind","tinymce.core.dom.Empty","ephox.katamari.api.Adt","tinymce.core.text.Bidi","tinymce.core.caret.CaretContainerInline","tinymce.core.caret.CaretContainerRemove","tinymce.core.util.LazyEvaluator","ephox.katamari.api.Cell","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.sand.util.Global","ephox.katamari.api.Thunk","ephox.sand.core.PlatformDetection","global!navigator","ephox.sugar.api.node.Body","ephox.sugar.impl.ClosestOrAncestor","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","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","ephox.sugar.api.search.SelectorFind","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.katamari.api.Global","ephox.sand.detect.Version","ephox.katamari.api.Strings","tinymce.core.caret.LineWalker","ephox.katamari.api.Merger","global!Number","ephox.katamari.str.StrAppend","ephox.katamari.str.StringParts"]
85
+ ["tinymce.core.api.Main","tinymce.core.api.Tinymce","tinymce.core.Register","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.Formatter","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.api.Compat","tinymce.core.util.Color","tinymce.core.ui.Api","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.katamari.api.Fun","tinymce.core.dom.RangePoint","tinymce.core.caret.CaretBookmark","tinymce.core.caret.CaretPosition","ephox.sugar.api.dom.Compare","ephox.sugar.api.node.Element","tinymce.core.dom.ScrollIntoView","tinymce.core.dom.TridentSelection","tinymce.core.selection.FragmentReader","tinymce.core.dom.ElementUtils","tinymce.core.util.Fun","tinymce.core.fmt.Preview","tinymce.core.fmt.Hooks","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","ephox.katamari.api.Arr","global!Array","global!Error","ephox.katamari.api.Future","ephox.katamari.api.Futures","ephox.katamari.api.Result","tinymce.core.geom.ClientRect","tinymce.core.caret.CaretCandidate","tinymce.core.text.ExtendingChar","ephox.sand.api.Node","ephox.sand.api.PlatformDetection","ephox.sugar.api.search.Selectors","global!console","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.InlineBoundaryDelete","tinymce.core.caret.CaretWalker","tinymce.core.dom.RangeNormalizer","tinymce.core.InsertList","tinymce.core.data.ObservableObject","tinymce.core.ui.DomUtils","tinymce.core.ui.BoxUtils","tinymce.core.ui.ClassList","global!window","tinymce.core.init.Init","tinymce.core.PluginManager","tinymce.core.ThemeManager","tinymce.core.content.LinkTargets","tinymce.core.fmt.FontInfo","ephox.katamari.api.Option","global!String","ephox.katamari.api.LazyValue","ephox.katamari.async.Bounce","ephox.katamari.async.AsyncValues","ephox.sand.util.Global","ephox.katamari.api.Thunk","ephox.sand.core.PlatformDetection","global!navigator","ephox.sugar.api.node.NodeTypes","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","tinymce.core.delete.DeleteUtils","tinymce.core.caret.CaretUtils","tinymce.core.delete.CefDeleteAction","tinymce.core.delete.DeleteElement","tinymce.core.caret.CaretFinder","tinymce.core.keyboard.BoundaryCaret","tinymce.core.keyboard.BoundaryLocation","tinymce.core.keyboard.BoundarySelection","tinymce.core.keyboard.InlineUtils","tinymce.core.data.Binding","tinymce.core.init.InitContentBody","global!Object","global!setTimeout","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.katamari.api.Type","ephox.katamari.api.Struct","ephox.sugar.alien.Recurse","ephox.katamari.api.Obj","ephox.sugar.api.search.PredicateFind","tinymce.core.dom.Empty","ephox.katamari.api.Adt","tinymce.core.text.Bidi","tinymce.core.caret.CaretContainerInline","tinymce.core.caret.CaretContainerRemove","tinymce.core.util.LazyEvaluator","ephox.katamari.api.Cell","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.katamari.api.Strings","ephox.katamari.data.Immutable","ephox.katamari.data.MixedBag","ephox.sugar.api.node.Body","ephox.sugar.impl.ClosestOrAncestor","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","global!Number","ephox.katamari.str.StrAppend","ephox.katamari.str.StringParts","ephox.katamari.util.BagUtils","ephox.sugar.api.search.SelectorFind","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"]
86
86
  jsc*/
87
87
  /**
88
88
  * Rect.js
@@ -8973,7 +8973,7 @@ define(
8973
8973
  textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' +
8974
8974
  'dfn code mark q sup sub samp');
8975
8975
 
8976
- each((settings.special || 'script noscript style textarea').split(' '), function (name) {
8976
+ each((settings.special || 'script noscript noframes noembed title style textarea xmp').split(' '), function (name) {
8977
8977
  specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi');
8978
8978
  });
8979
8979
 
@@ -13672,6 +13672,10 @@ define(
13672
13672
  return name.indexOf('data-') === 0 || name.indexOf('aria-') === 0;
13673
13673
  };
13674
13674
 
13675
+ var trimComments = function (text) {
13676
+ return text.replace(/<!--|-->/g, '');
13677
+ };
13678
+
13675
13679
  /**
13676
13680
  * Returns the index of the end tag for a specific start tag. This can be
13677
13681
  * used to skip all children of a parent element from being processed.
@@ -13840,6 +13844,11 @@ define(
13840
13844
  }
13841
13845
  }
13842
13846
 
13847
+ // Block data or event attributes on elements marked as internal
13848
+ if (isInternalElement && (name in filteredUrlAttrs || name.indexOf('on') === 0)) {
13849
+ return;
13850
+ }
13851
+
13843
13852
  // Add attribute to list and map
13844
13853
  attrList.map[name] = value;
13845
13854
  attrList.push({
@@ -14066,7 +14075,7 @@ define(
14066
14075
 
14067
14076
  self.comment(value);
14068
14077
  } else if ((value = matches[2])) { // CDATA
14069
- self.cdata(value);
14078
+ self.cdata(trimComments(value));
14070
14079
  } else if ((value = matches[3])) { // DOCTYPE
14071
14080
  self.doctype(value);
14072
14081
  } else if ((value = matches[4])) { // PI
@@ -14871,34 +14880,35 @@ define(
14871
14880
  });
14872
14881
  }
14873
14882
 
14874
- if (!settings.allow_unsafe_link_target) {
14875
- self.addAttributeFilter('href', function (nodes) {
14876
- var i = nodes.length, node;
14877
14883
 
14878
- var appendRel = function (rel) {
14879
- var parts = rel.split(' ').filter(function (p) {
14880
- return p.length > 0;
14881
- });
14882
- return parts.concat(['noopener']).join(' ');
14883
- };
14884
+ self.addAttributeFilter('href', function (nodes) {
14885
+ var i = nodes.length, node;
14884
14886
 
14885
- var addNoOpener = function (rel) {
14886
- var newRel = rel ? Tools.trim(rel) : '';
14887
- if (!/\b(noopener)\b/g.test(newRel)) {
14888
- return appendRel(newRel);
14889
- } else {
14890
- return newRel;
14891
- }
14892
- };
14887
+ var appendRel = function (rel) {
14888
+ var parts = rel.split(' ').filter(function (p) {
14889
+ return p.length > 0;
14890
+ });
14891
+ return parts.concat(['noopener']).sort().join(' ');
14892
+ };
14893
+
14894
+ var addNoOpener = function (rel) {
14895
+ var newRel = rel ? Tools.trim(rel) : '';
14896
+ if (!/\b(noopener)\b/g.test(newRel)) {
14897
+ return appendRel(newRel);
14898
+ } else {
14899
+ return newRel;
14900
+ }
14901
+ };
14893
14902
 
14903
+ if (!settings.allow_unsafe_link_target) {
14894
14904
  while (i--) {
14895
14905
  node = nodes[i];
14896
14906
  if (node.name === 'a' && node.attr('target') === '_blank') {
14897
14907
  node.attr('rel', addNoOpener(node.attr('rel')));
14898
14908
  }
14899
14909
  }
14900
- });
14901
- }
14910
+ }
14911
+ });
14902
14912
 
14903
14913
  // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
14904
14914
  if (!settings.allow_html_in_named_anchor) {
@@ -15894,6 +15904,180 @@ define(
15894
15904
  }
15895
15905
  );
15896
15906
 
15907
+ /**
15908
+ * ClientRect.js
15909
+ *
15910
+ * Released under LGPL License.
15911
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
15912
+ *
15913
+ * License: http://www.tinymce.com/license
15914
+ * Contributing: http://www.tinymce.com/contributing
15915
+ */
15916
+
15917
+ /**
15918
+ * Utility functions for working with client rects.
15919
+ *
15920
+ * @private
15921
+ * @class tinymce.geom.ClientRect
15922
+ */
15923
+ define(
15924
+ 'tinymce.core.geom.ClientRect',
15925
+ [
15926
+ ],
15927
+ function () {
15928
+ var round = Math.round;
15929
+
15930
+ function clone(rect) {
15931
+ if (!rect) {
15932
+ return { left: 0, top: 0, bottom: 0, right: 0, width: 0, height: 0 };
15933
+ }
15934
+
15935
+ return {
15936
+ left: round(rect.left),
15937
+ top: round(rect.top),
15938
+ bottom: round(rect.bottom),
15939
+ right: round(rect.right),
15940
+ width: round(rect.width),
15941
+ height: round(rect.height)
15942
+ };
15943
+ }
15944
+
15945
+ function collapse(clientRect, toStart) {
15946
+ clientRect = clone(clientRect);
15947
+
15948
+ if (toStart) {
15949
+ clientRect.right = clientRect.left;
15950
+ } else {
15951
+ clientRect.left = clientRect.left + clientRect.width;
15952
+ clientRect.right = clientRect.left;
15953
+ }
15954
+
15955
+ clientRect.width = 0;
15956
+
15957
+ return clientRect;
15958
+ }
15959
+
15960
+ function isEqual(rect1, rect2) {
15961
+ return (
15962
+ rect1.left === rect2.left &&
15963
+ rect1.top === rect2.top &&
15964
+ rect1.bottom === rect2.bottom &&
15965
+ rect1.right === rect2.right
15966
+ );
15967
+ }
15968
+
15969
+ function isValidOverflow(overflowY, clientRect1, clientRect2) {
15970
+ return overflowY >= 0 && overflowY <= Math.min(clientRect1.height, clientRect2.height) / 2;
15971
+
15972
+ }
15973
+
15974
+ function isAbove(clientRect1, clientRect2) {
15975
+ if ((clientRect1.bottom - clientRect1.height / 2) < clientRect2.top) {
15976
+ return true;
15977
+ }
15978
+
15979
+ if (clientRect1.top > clientRect2.bottom) {
15980
+ return false;
15981
+ }
15982
+
15983
+ return isValidOverflow(clientRect2.top - clientRect1.bottom, clientRect1, clientRect2);
15984
+ }
15985
+
15986
+ function isBelow(clientRect1, clientRect2) {
15987
+ if (clientRect1.top > clientRect2.bottom) {
15988
+ return true;
15989
+ }
15990
+
15991
+ if (clientRect1.bottom < clientRect2.top) {
15992
+ return false;
15993
+ }
15994
+
15995
+ return isValidOverflow(clientRect2.bottom - clientRect1.top, clientRect1, clientRect2);
15996
+ }
15997
+
15998
+ function isLeft(clientRect1, clientRect2) {
15999
+ return clientRect1.left < clientRect2.left;
16000
+ }
16001
+
16002
+ function isRight(clientRect1, clientRect2) {
16003
+ return clientRect1.right > clientRect2.right;
16004
+ }
16005
+
16006
+ function compare(clientRect1, clientRect2) {
16007
+ if (isAbove(clientRect1, clientRect2)) {
16008
+ return -1;
16009
+ }
16010
+
16011
+ if (isBelow(clientRect1, clientRect2)) {
16012
+ return 1;
16013
+ }
16014
+
16015
+ if (isLeft(clientRect1, clientRect2)) {
16016
+ return -1;
16017
+ }
16018
+
16019
+ if (isRight(clientRect1, clientRect2)) {
16020
+ return 1;
16021
+ }
16022
+
16023
+ return 0;
16024
+ }
16025
+
16026
+ function containsXY(clientRect, clientX, clientY) {
16027
+ return (
16028
+ clientX >= clientRect.left &&
16029
+ clientX <= clientRect.right &&
16030
+ clientY >= clientRect.top &&
16031
+ clientY <= clientRect.bottom
16032
+ );
16033
+ }
16034
+
16035
+ return {
16036
+ clone: clone,
16037
+ collapse: collapse,
16038
+ isEqual: isEqual,
16039
+ isAbove: isAbove,
16040
+ isBelow: isBelow,
16041
+ isLeft: isLeft,
16042
+ isRight: isRight,
16043
+ compare: compare,
16044
+ containsXY: containsXY
16045
+ };
16046
+ }
16047
+ );
16048
+
16049
+ /**
16050
+ * RangePoint.js
16051
+ *
16052
+ * Released under LGPL License.
16053
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
16054
+ *
16055
+ * License: http://www.tinymce.com/license
16056
+ * Contributing: http://www.tinymce.com/contributing
16057
+ */
16058
+
16059
+ define(
16060
+ 'tinymce.core.dom.RangePoint',
16061
+ [
16062
+ 'ephox.katamari.api.Arr',
16063
+ 'tinymce.core.geom.ClientRect'
16064
+ ],
16065
+ function (Arr, ClientRect) {
16066
+ var isXYWithinRange = function (clientX, clientY, range) {
16067
+ if (range.collapsed) {
16068
+ return false;
16069
+ }
16070
+
16071
+ return Arr.foldl(range.getClientRects(), function (state, rect) {
16072
+ return state || ClientRect.containsXY(rect, clientX, clientY);
16073
+ }, false);
16074
+ };
16075
+
16076
+ return {
16077
+ isXYWithinRange: isXYWithinRange
16078
+ };
16079
+ }
16080
+ );
15897
16081
  /**
15898
16082
  * ControlSelection.js
15899
16083
  *
@@ -15914,13 +16098,15 @@ define(
15914
16098
  define(
15915
16099
  'tinymce.core.dom.ControlSelection',
15916
16100
  [
15917
- "tinymce.core.util.VK",
15918
- "tinymce.core.util.Tools",
15919
- "tinymce.core.util.Delay",
15920
- "tinymce.core.Env",
15921
- "tinymce.core.dom.NodeType"
16101
+ 'ephox.katamari.api.Fun',
16102
+ 'tinymce.core.dom.NodeType',
16103
+ 'tinymce.core.dom.RangePoint',
16104
+ 'tinymce.core.Env',
16105
+ 'tinymce.core.util.Delay',
16106
+ 'tinymce.core.util.Tools',
16107
+ 'tinymce.core.util.VK'
15922
16108
  ],
15923
- function (VK, Tools, Delay, Env, NodeType) {
16109
+ function (Fun, NodeType, RangePoint, Env, Delay, Tools, VK) {
15924
16110
  var isContentEditableFalse = NodeType.isContentEditableFalse;
15925
16111
  var isContentEditableTrue = NodeType.isContentEditableTrue;
15926
16112
 
@@ -15936,6 +16122,23 @@ define(
15936
16122
  return null;
15937
16123
  }
15938
16124
 
16125
+ var isImage = function (elm) {
16126
+ return elm && elm.nodeName === 'IMG';
16127
+ };
16128
+
16129
+ var isEventOnImageOutsideRange = function (evt, range) {
16130
+ return isImage(evt.target) && !RangePoint.isXYWithinRange(evt.clientX, evt.clientY, range);
16131
+ };
16132
+
16133
+ var contextMenuSelectImage = function (editor, evt) {
16134
+ var target = evt.target;
16135
+
16136
+ if (isEventOnImageOutsideRange(evt, editor.selection.getRng()) && !evt.isDefaultPrevented()) {
16137
+ evt.preventDefault();
16138
+ editor.selection.select(target);
16139
+ }
16140
+ };
16141
+
15939
16142
  return function (selection, editor) {
15940
16143
  var dom = editor.dom, each = Tools.each;
15941
16144
  var selectedElm, selectedElmGhost, resizeHelper, resizeHandles, selectedHandle, lastMouseDownEvent;
@@ -16471,7 +16674,9 @@ define(
16471
16674
  var target = e.target, nodeName = target.nodeName;
16472
16675
 
16473
16676
  if (!resizeStarted && /^(TABLE|IMG|HR)$/.test(nodeName) && !isWithinContentEditableFalse(target)) {
16474
- editor.selection.select(target, nodeName == 'TABLE');
16677
+ if (e.button !== 2) {
16678
+ editor.selection.select(target, nodeName == 'TABLE');
16679
+ }
16475
16680
 
16476
16681
  // Only fire once since nodeChange is expensive
16477
16682
  if (e.type == 'mousedown') {
@@ -16523,6 +16728,7 @@ define(
16523
16728
  });
16524
16729
 
16525
16730
  editor.on('hide blur', hideResizeRect);
16731
+ editor.on('contextmenu', Fun.curry(contextMenuSelectImage, editor));
16526
16732
 
16527
16733
  // Hide rect on focusout since it would float on top of windows otherwise
16528
16734
  //editor.on('focusout', hideResizeRect);
@@ -16737,148 +16943,6 @@ define(
16737
16943
  };
16738
16944
  }
16739
16945
  );
16740
- /**
16741
- * ClientRect.js
16742
- *
16743
- * Released under LGPL License.
16744
- * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
16745
- *
16746
- * License: http://www.tinymce.com/license
16747
- * Contributing: http://www.tinymce.com/contributing
16748
- */
16749
-
16750
- /**
16751
- * Utility functions for working with client rects.
16752
- *
16753
- * @private
16754
- * @class tinymce.geom.ClientRect
16755
- */
16756
- define(
16757
- 'tinymce.core.geom.ClientRect',
16758
- [
16759
- ],
16760
- function () {
16761
- var round = Math.round;
16762
-
16763
- function clone(rect) {
16764
- if (!rect) {
16765
- return { left: 0, top: 0, bottom: 0, right: 0, width: 0, height: 0 };
16766
- }
16767
-
16768
- return {
16769
- left: round(rect.left),
16770
- top: round(rect.top),
16771
- bottom: round(rect.bottom),
16772
- right: round(rect.right),
16773
- width: round(rect.width),
16774
- height: round(rect.height)
16775
- };
16776
- }
16777
-
16778
- function collapse(clientRect, toStart) {
16779
- clientRect = clone(clientRect);
16780
-
16781
- if (toStart) {
16782
- clientRect.right = clientRect.left;
16783
- } else {
16784
- clientRect.left = clientRect.left + clientRect.width;
16785
- clientRect.right = clientRect.left;
16786
- }
16787
-
16788
- clientRect.width = 0;
16789
-
16790
- return clientRect;
16791
- }
16792
-
16793
- function isEqual(rect1, rect2) {
16794
- return (
16795
- rect1.left === rect2.left &&
16796
- rect1.top === rect2.top &&
16797
- rect1.bottom === rect2.bottom &&
16798
- rect1.right === rect2.right
16799
- );
16800
- }
16801
-
16802
- function isValidOverflow(overflowY, clientRect1, clientRect2) {
16803
- return overflowY >= 0 && overflowY <= Math.min(clientRect1.height, clientRect2.height) / 2;
16804
-
16805
- }
16806
-
16807
- function isAbove(clientRect1, clientRect2) {
16808
- if ((clientRect1.bottom - clientRect1.height / 2) < clientRect2.top) {
16809
- return true;
16810
- }
16811
-
16812
- if (clientRect1.top > clientRect2.bottom) {
16813
- return false;
16814
- }
16815
-
16816
- return isValidOverflow(clientRect2.top - clientRect1.bottom, clientRect1, clientRect2);
16817
- }
16818
-
16819
- function isBelow(clientRect1, clientRect2) {
16820
- if (clientRect1.top > clientRect2.bottom) {
16821
- return true;
16822
- }
16823
-
16824
- if (clientRect1.bottom < clientRect2.top) {
16825
- return false;
16826
- }
16827
-
16828
- return isValidOverflow(clientRect2.bottom - clientRect1.top, clientRect1, clientRect2);
16829
- }
16830
-
16831
- function isLeft(clientRect1, clientRect2) {
16832
- return clientRect1.left < clientRect2.left;
16833
- }
16834
-
16835
- function isRight(clientRect1, clientRect2) {
16836
- return clientRect1.right > clientRect2.right;
16837
- }
16838
-
16839
- function compare(clientRect1, clientRect2) {
16840
- if (isAbove(clientRect1, clientRect2)) {
16841
- return -1;
16842
- }
16843
-
16844
- if (isBelow(clientRect1, clientRect2)) {
16845
- return 1;
16846
- }
16847
-
16848
- if (isLeft(clientRect1, clientRect2)) {
16849
- return -1;
16850
- }
16851
-
16852
- if (isRight(clientRect1, clientRect2)) {
16853
- return 1;
16854
- }
16855
-
16856
- return 0;
16857
- }
16858
-
16859
- function containsXY(clientRect, clientX, clientY) {
16860
- return (
16861
- clientX >= clientRect.left &&
16862
- clientX <= clientRect.right &&
16863
- clientY >= clientRect.top &&
16864
- clientY <= clientRect.bottom
16865
- );
16866
- }
16867
-
16868
- return {
16869
- clone: clone,
16870
- collapse: collapse,
16871
- isEqual: isEqual,
16872
- isAbove: isAbove,
16873
- isBelow: isBelow,
16874
- isLeft: isLeft,
16875
- isRight: isRight,
16876
- compare: compare,
16877
- containsXY: containsXY
16878
- };
16879
- }
16880
- );
16881
-
16882
16946
  /**
16883
16947
  * ExtendingChar.js
16884
16948
  *
@@ -18081,1921 +18145,1921 @@ define(
18081
18145
  return BookmarkManager;
18082
18146
  }
18083
18147
  );
18084
- /**
18085
- * ScrollIntoView.js
18086
- *
18087
- * Released under LGPL License.
18088
- * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
18089
- *
18090
- * License: http://www.tinymce.com/license
18091
- * Contributing: http://www.tinymce.com/contributing
18092
- */
18093
-
18094
18148
  define(
18095
- 'tinymce.core.dom.ScrollIntoView',
18149
+ 'ephox.katamari.api.Global',
18150
+
18096
18151
  [
18097
- 'tinymce.core.dom.NodeType'
18098
18152
  ],
18099
- function (NodeType) {
18100
- var getPos = function (elm) {
18101
- var x = 0, y = 0;
18102
-
18103
- var offsetParent = elm;
18104
- while (offsetParent && offsetParent.nodeType) {
18105
- x += offsetParent.offsetLeft || 0;
18106
- y += offsetParent.offsetTop || 0;
18107
- offsetParent = offsetParent.offsetParent;
18108
- }
18109
18153
 
18110
- return { x: x, y: y };
18111
- };
18154
+ function () {
18155
+ // Use window object as the global if it's available since CSP will block script evals
18156
+ if (typeof window !== 'undefined') {
18157
+ return window;
18158
+ } else {
18159
+ return Function('return this;')();
18160
+ }
18161
+ }
18162
+ );
18112
18163
 
18113
- var fireScrollIntoViewEvent = function (editor, elm, alignToTop) {
18114
- var scrollEvent = { elm: elm, alignToTop: alignToTop };
18115
- editor.fire('scrollIntoView', scrollEvent);
18116
- return scrollEvent.isDefaultPrevented();
18117
- };
18118
18164
 
18119
- var scrollIntoView = function (editor, elm, alignToTop) {
18120
- var y, viewPort, dom = editor.dom, root = dom.getRoot(), viewPortY, viewPortH, offsetY = 0;
18165
+ define(
18166
+ 'ephox.katamari.api.Resolve',
18121
18167
 
18122
- if (fireScrollIntoViewEvent(editor, elm, alignToTop)) {
18123
- return;
18124
- }
18168
+ [
18169
+ 'ephox.katamari.api.Global'
18170
+ ],
18125
18171
 
18126
- if (!NodeType.isElement(elm)) {
18127
- return;
18128
- }
18172
+ function (Global) {
18173
+ /** path :: ([String], JsObj?) -> JsObj */
18174
+ var path = function (parts, scope) {
18175
+ var o = scope !== undefined ? scope : Global;
18176
+ for (var i = 0; i < parts.length && o !== undefined && o !== null; ++i)
18177
+ o = o[parts[i]];
18178
+ return o;
18179
+ };
18129
18180
 
18130
- if (alignToTop === false) {
18131
- offsetY = elm.offsetHeight;
18132
- }
18181
+ /** resolve :: (String, JsObj?) -> JsObj */
18182
+ var resolve = function (p, scope) {
18183
+ var parts = p.split('.');
18184
+ return path(parts, scope);
18185
+ };
18133
18186
 
18134
- if (root.nodeName !== 'BODY') {
18135
- var scrollContainer = editor.selection.getScrollContainer();
18136
- if (scrollContainer) {
18137
- y = getPos(elm).y - getPos(scrollContainer).y + offsetY;
18138
- viewPortH = scrollContainer.clientHeight;
18139
- viewPortY = scrollContainer.scrollTop;
18140
- if (y < viewPortY || y + 25 > viewPortY + viewPortH) {
18141
- scrollContainer.scrollTop = y < viewPortY ? y : y - viewPortH + 25;
18142
- }
18187
+ /** step :: (JsObj, String) -> JsObj */
18188
+ var step = function (o, part) {
18189
+ if (o[part] === undefined || o[part] === null)
18190
+ o[part] = {};
18191
+ return o[part];
18192
+ };
18143
18193
 
18144
- return;
18145
- }
18146
- }
18194
+ /** forge :: ([String], JsObj?) -> JsObj */
18195
+ var forge = function (parts, target) {
18196
+ var o = target !== undefined ? target : Global;
18197
+ for (var i = 0; i < parts.length; ++i)
18198
+ o = step(o, parts[i]);
18199
+ return o;
18200
+ };
18147
18201
 
18148
- viewPort = dom.getViewPort(editor.getWin());
18149
- y = dom.getPos(elm).y + offsetY;
18150
- viewPortY = viewPort.y;
18151
- viewPortH = viewPort.h;
18152
- if (y < viewPort.y || y + 25 > viewPortY + viewPortH) {
18153
- editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25);
18154
- }
18202
+ /** namespace :: (String, JsObj?) -> JsObj */
18203
+ var namespace = function (name, target) {
18204
+ var parts = name.split('.');
18205
+ return forge(parts, target);
18155
18206
  };
18156
18207
 
18157
18208
  return {
18158
- scrollIntoView: scrollIntoView
18209
+ path: path,
18210
+ resolve: resolve,
18211
+ forge: forge,
18212
+ namespace: namespace
18159
18213
  };
18160
18214
  }
18161
18215
  );
18162
18216
 
18163
- /**
18164
- * TridentSelection.js
18165
- *
18166
- * Released under LGPL License.
18167
- * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
18168
- *
18169
- * License: http://www.tinymce.com/license
18170
- * Contributing: http://www.tinymce.com/contributing
18171
- */
18172
18217
 
18173
- /**
18174
- * Selection class for old explorer versions. This one fakes the
18175
- * native selection object available on modern browsers.
18176
- *
18177
- * @private
18178
- * @class tinymce.dom.TridentSelection
18179
- */
18180
18218
  define(
18181
- 'tinymce.core.dom.TridentSelection',
18219
+ 'ephox.sand.util.Global',
18220
+
18182
18221
  [
18222
+ 'ephox.katamari.api.Resolve'
18183
18223
  ],
18184
- function () {
18185
- function Selection(selection) {
18186
- var self = this, dom = selection.dom, FALSE = false;
18187
18224
 
18188
- function getPosition(rng, start) {
18189
- var checkRng, startIndex = 0, endIndex, inside,
18190
- children, child, offset, index, position = -1, parent;
18225
+ function (Resolve) {
18226
+ var unsafe = function (name, scope) {
18227
+ return Resolve.resolve(name, scope);
18228
+ };
18191
18229
 
18192
- // Setup test range, collapse it and get the parent
18193
- checkRng = rng.duplicate();
18194
- checkRng.collapse(start);
18195
- parent = checkRng.parentElement();
18230
+ var getOrDie = function (name, scope) {
18231
+ var actual = unsafe(name, scope);
18196
18232
 
18197
- // Check if the selection is within the right document
18198
- if (parent.ownerDocument !== selection.dom.doc) {
18199
- return;
18200
- }
18233
+ if (actual === undefined) throw name + ' not available on this browser';
18234
+ return actual;
18235
+ };
18201
18236
 
18202
- // IE will report non editable elements as it's parent so look for an editable one
18203
- while (parent.contentEditable === "false") {
18204
- parent = parent.parentNode;
18205
- }
18237
+ return {
18238
+ getOrDie: getOrDie
18239
+ };
18240
+ }
18241
+ );
18242
+ define(
18243
+ 'ephox.sand.api.Node',
18206
18244
 
18207
- // If parent doesn't have any children then return that we are inside the element
18208
- if (!parent.hasChildNodes()) {
18209
- return { node: parent, inside: 1 };
18210
- }
18245
+ [
18246
+ 'ephox.sand.util.Global'
18247
+ ],
18211
18248
 
18212
- // Setup node list and endIndex
18213
- children = parent.children;
18214
- endIndex = children.length - 1;
18249
+ function (Global) {
18250
+ /*
18251
+ * MDN says (yes) for IE, but it's undefined on IE8
18252
+ */
18253
+ var node = function () {
18254
+ var f = Global.getOrDie('Node');
18255
+ return f;
18256
+ };
18215
18257
 
18216
- // Perform a binary search for the position
18217
- while (startIndex <= endIndex) {
18218
- index = Math.floor((startIndex + endIndex) / 2);
18258
+ /*
18259
+ * Most of numerosity doesn't alter the methods on the object.
18260
+ * We're making an exception for Node, because bitwise and is so easy to get wrong.
18261
+ *
18262
+ * Might be nice to ADT this at some point instead of having individual methods.
18263
+ */
18219
18264
 
18220
- // Move selection to node and compare the ranges
18221
- child = children[index];
18222
- checkRng.moveToElementText(child);
18223
- position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
18265
+ var compareDocumentPosition = function (a, b, match) {
18266
+ // Returns: 0 if e1 and e2 are the same node, or a bitmask comparing the positions
18267
+ // of nodes e1 and e2 in their documents. See the URL below for bitmask interpretation
18268
+ // https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition
18269
+ return (a.compareDocumentPosition(b) & match) !== 0;
18270
+ };
18224
18271
 
18225
- // Before/after or an exact match
18226
- if (position > 0) {
18227
- endIndex = index - 1;
18228
- } else if (position < 0) {
18229
- startIndex = index + 1;
18230
- } else {
18231
- return { node: child };
18232
- }
18233
- }
18272
+ var documentPositionPreceding = function (a, b) {
18273
+ return compareDocumentPosition(a, b, node().DOCUMENT_POSITION_PRECEDING);
18274
+ };
18234
18275
 
18235
- // Check if child position is before or we didn't find a position
18236
- if (position < 0) {
18237
- // No element child was found use the parent element and the offset inside that
18238
- if (!child) {
18239
- checkRng.moveToElementText(parent);
18240
- checkRng.collapse(true);
18241
- child = parent;
18242
- inside = true;
18243
- } else {
18244
- checkRng.collapse(false);
18245
- }
18276
+ var documentPositionContainedBy = function (a, b) {
18277
+ return compareDocumentPosition(a, b, node().DOCUMENT_POSITION_CONTAINED_BY);
18278
+ };
18246
18279
 
18247
- // Walk character by character in text node until we hit the selected range endpoint,
18248
- // hit the end of document or parent isn't the right one
18249
- // We need to walk char by char since rng.text or rng.htmlText will trim line endings
18250
- offset = 0;
18251
- while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
18252
- if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
18253
- break;
18254
- }
18280
+ return {
18281
+ documentPositionPreceding: documentPositionPreceding,
18282
+ documentPositionContainedBy: documentPositionContainedBy
18283
+ };
18284
+ }
18285
+ );
18286
+ define(
18287
+ 'ephox.katamari.api.Thunk',
18255
18288
 
18256
- offset++;
18257
- }
18258
- } else {
18259
- // Child position is after the selection endpoint
18260
- checkRng.collapse(true);
18289
+ [
18290
+ ],
18261
18291
 
18262
- // Walk character by character in text node until we hit the selected range endpoint, hit
18263
- // the end of document or parent isn't the right one
18264
- offset = 0;
18265
- while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
18266
- if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
18267
- break;
18268
- }
18292
+ function () {
18269
18293
 
18270
- offset++;
18271
- }
18294
+ var cached = function (f) {
18295
+ var called = false;
18296
+ var r;
18297
+ return function() {
18298
+ if (!called) {
18299
+ called = true;
18300
+ r = f.apply(null, arguments);
18272
18301
  }
18302
+ return r;
18303
+ };
18304
+ };
18273
18305
 
18274
- return { node: child, position: position, offset: offset, inside: inside };
18275
- }
18306
+ return {
18307
+ cached: cached
18308
+ };
18309
+ }
18310
+ );
18276
18311
 
18277
- // Returns a W3C DOM compatible range object by using the IE Range API
18278
- function getRange() {
18279
- var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark;
18312
+ defineGlobal("global!Number", Number);
18313
+ define(
18314
+ 'ephox.sand.detect.Version',
18280
18315
 
18281
- // If selection is outside the current document just return an empty range
18282
- element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
18283
- if (element.ownerDocument != dom.doc) {
18284
- return domRange;
18285
- }
18316
+ [
18317
+ 'ephox.katamari.api.Arr',
18318
+ 'global!Number',
18319
+ 'global!String'
18320
+ ],
18286
18321
 
18287
- collapsed = selection.isCollapsed();
18322
+ function (Arr, Number, String) {
18323
+ var firstMatch = function (regexes, s) {
18324
+ for (var i = 0; i < regexes.length; i++) {
18325
+ var x = regexes[i];
18326
+ if (x.test(s)) return x;
18327
+ }
18328
+ return undefined;
18329
+ };
18288
18330
 
18289
- // Handle control selection
18290
- if (ieRange.item) {
18291
- domRange.setStart(element.parentNode, dom.nodeIndex(element));
18292
- domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
18331
+ var find = function (regexes, agent) {
18332
+ var r = firstMatch(regexes, agent);
18333
+ if (!r) return { major : 0, minor : 0 };
18334
+ var group = function(i) {
18335
+ return Number(agent.replace(r, '$' + i));
18336
+ };
18337
+ return nu(group(1), group(2));
18338
+ };
18293
18339
 
18294
- return domRange;
18295
- }
18340
+ var detect = function (versionRegexes, agent) {
18341
+ var cleanedAgent = String(agent).toLowerCase();
18296
18342
 
18297
- function findEndPoint(start) {
18298
- var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
18343
+ if (versionRegexes.length === 0) return unknown();
18344
+ return find(versionRegexes, cleanedAgent);
18345
+ };
18299
18346
 
18300
- container = endPoint.node;
18301
- offset = endPoint.offset;
18347
+ var unknown = function () {
18348
+ return nu(0, 0);
18349
+ };
18302
18350
 
18303
- if (endPoint.inside && !container.hasChildNodes()) {
18304
- domRange[start ? 'setStart' : 'setEnd'](container, 0);
18305
- return;
18306
- }
18351
+ var nu = function (major, minor) {
18352
+ return { major: major, minor: minor };
18353
+ };
18307
18354
 
18308
- if (offset === undef) {
18309
- domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
18310
- return;
18311
- }
18355
+ return {
18356
+ nu: nu,
18357
+ detect: detect,
18358
+ unknown: unknown
18359
+ };
18360
+ }
18361
+ );
18362
+ define(
18363
+ 'ephox.sand.core.Browser',
18312
18364
 
18313
- if (endPoint.position < 0) {
18314
- sibling = endPoint.inside ? container.firstChild : container.nextSibling;
18365
+ [
18366
+ 'ephox.katamari.api.Fun',
18367
+ 'ephox.sand.detect.Version'
18368
+ ],
18315
18369
 
18316
- if (!sibling) {
18317
- domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
18318
- return;
18319
- }
18370
+ function (Fun, Version) {
18371
+ var edge = 'Edge';
18372
+ var chrome = 'Chrome';
18373
+ var ie = 'IE';
18374
+ var opera = 'Opera';
18375
+ var firefox = 'Firefox';
18376
+ var safari = 'Safari';
18320
18377
 
18321
- if (!offset) {
18322
- if (sibling.nodeType == 3) {
18323
- domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
18324
- } else {
18325
- domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
18326
- }
18378
+ var isBrowser = function (name, current) {
18379
+ return function () {
18380
+ return current === name;
18381
+ };
18382
+ };
18327
18383
 
18328
- return;
18329
- }
18384
+ var unknown = function () {
18385
+ return nu({
18386
+ current: undefined,
18387
+ version: Version.unknown()
18388
+ });
18389
+ };
18330
18390
 
18331
- // Find the text node and offset
18332
- while (sibling) {
18333
- if (sibling.nodeType == 3) {
18334
- nodeValue = sibling.nodeValue;
18335
- textNodeOffset += nodeValue.length;
18391
+ var nu = function (info) {
18392
+ var current = info.current;
18393
+ var version = info.version;
18336
18394
 
18337
- // We are at or passed the position we where looking for
18338
- if (textNodeOffset >= offset) {
18339
- container = sibling;
18340
- textNodeOffset -= offset;
18341
- textNodeOffset = nodeValue.length - textNodeOffset;
18342
- break;
18343
- }
18344
- }
18395
+ return {
18396
+ current: current,
18397
+ version: version,
18345
18398
 
18346
- sibling = sibling.nextSibling;
18347
- }
18348
- } else {
18349
- // Find the text node and offset
18350
- sibling = container.previousSibling;
18399
+ // INVESTIGATE: Rename to Edge ?
18400
+ isEdge: isBrowser(edge, current),
18401
+ isChrome: isBrowser(chrome, current),
18402
+ // NOTE: isIe just looks too weird
18403
+ isIE: isBrowser(ie, current),
18404
+ isOpera: isBrowser(opera, current),
18405
+ isFirefox: isBrowser(firefox, current),
18406
+ isSafari: isBrowser(safari, current)
18407
+ };
18408
+ };
18351
18409
 
18352
- if (!sibling) {
18353
- return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
18354
- }
18410
+ return {
18411
+ unknown: unknown,
18412
+ nu: nu,
18413
+ edge: Fun.constant(edge),
18414
+ chrome: Fun.constant(chrome),
18415
+ ie: Fun.constant(ie),
18416
+ opera: Fun.constant(opera),
18417
+ firefox: Fun.constant(firefox),
18418
+ safari: Fun.constant(safari)
18419
+ };
18420
+ }
18421
+ );
18422
+ define(
18423
+ 'ephox.sand.core.OperatingSystem',
18355
18424
 
18356
- // If there isn't any text to loop then use the first position
18357
- if (!offset) {
18358
- if (container.nodeType == 3) {
18359
- domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
18360
- } else {
18361
- domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
18362
- }
18425
+ [
18426
+ 'ephox.katamari.api.Fun',
18427
+ 'ephox.sand.detect.Version'
18428
+ ],
18363
18429
 
18364
- return;
18365
- }
18430
+ function (Fun, Version) {
18431
+ var windows = 'Windows';
18432
+ var ios = 'iOS';
18433
+ var android = 'Android';
18434
+ var linux = 'Linux';
18435
+ var osx = 'OSX';
18436
+ var solaris = 'Solaris';
18437
+ var freebsd = 'FreeBSD';
18366
18438
 
18367
- while (sibling) {
18368
- if (sibling.nodeType == 3) {
18369
- textNodeOffset += sibling.nodeValue.length;
18439
+ // Though there is a bit of dupe with this and Browser, trying to
18440
+ // reuse code makes it much harder to follow and change.
18441
+ var isOS = function (name, current) {
18442
+ return function () {
18443
+ return current === name;
18444
+ };
18445
+ };
18370
18446
 
18371
- // We are at or passed the position we where looking for
18372
- if (textNodeOffset >= offset) {
18373
- container = sibling;
18374
- textNodeOffset -= offset;
18375
- break;
18376
- }
18377
- }
18447
+ var unknown = function () {
18448
+ return nu({
18449
+ current: undefined,
18450
+ version: Version.unknown()
18451
+ });
18452
+ };
18378
18453
 
18379
- sibling = sibling.previousSibling;
18380
- }
18381
- }
18454
+ var nu = function (info) {
18455
+ var current = info.current;
18456
+ var version = info.version;
18382
18457
 
18383
- domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
18384
- }
18458
+ return {
18459
+ current: current,
18460
+ version: version,
18385
18461
 
18386
- try {
18387
- // Find start point
18388
- findEndPoint(true);
18462
+ isWindows: isOS(windows, current),
18463
+ // TODO: Fix capitalisation
18464
+ isiOS: isOS(ios, current),
18465
+ isAndroid: isOS(android, current),
18466
+ isOSX: isOS(osx, current),
18467
+ isLinux: isOS(linux, current),
18468
+ isSolaris: isOS(solaris, current),
18469
+ isFreeBSD: isOS(freebsd, current)
18470
+ };
18471
+ };
18389
18472
 
18390
- // Find end point if needed
18391
- if (!collapsed) {
18392
- findEndPoint();
18393
- }
18394
- } catch (ex) {
18395
- // IE has a nasty bug where text nodes might throw "invalid argument" when you
18396
- // access the nodeValue or other properties of text nodes. This seems to happen when
18397
- // text nodes are split into two nodes by a delete/backspace call.
18398
- // So let us detect and try to fix it.
18399
- if (ex.number == -2147024809) {
18400
- // Get the current selection
18401
- bookmark = self.getBookmark(2);
18402
-
18403
- // Get start element
18404
- tmpRange = ieRange.duplicate();
18405
- tmpRange.collapse(true);
18406
- element = tmpRange.parentElement();
18407
-
18408
- // Get end element
18409
- if (!collapsed) {
18410
- tmpRange = ieRange.duplicate();
18411
- tmpRange.collapse(false);
18412
- element2 = tmpRange.parentElement();
18413
- element2.innerHTML = element2.innerHTML;
18414
- }
18415
-
18416
- // Remove the broken elements
18417
- element.innerHTML = element.innerHTML;
18418
-
18419
- // Restore the selection
18420
- self.moveToBookmark(bookmark);
18421
-
18422
- // Since the range has moved we need to re-get it
18423
- ieRange = selection.getRng();
18473
+ return {
18474
+ unknown: unknown,
18475
+ nu: nu,
18424
18476
 
18425
- // Find start point
18426
- findEndPoint(true);
18477
+ windows: Fun.constant(windows),
18478
+ ios: Fun.constant(ios),
18479
+ android: Fun.constant(android),
18480
+ linux: Fun.constant(linux),
18481
+ osx: Fun.constant(osx),
18482
+ solaris: Fun.constant(solaris),
18483
+ freebsd: Fun.constant(freebsd)
18484
+ };
18485
+ }
18486
+ );
18487
+ define(
18488
+ 'ephox.sand.detect.DeviceType',
18427
18489
 
18428
- // Find end point if needed
18429
- if (!collapsed) {
18430
- findEndPoint();
18431
- }
18432
- } else {
18433
- throw ex; // Throw other errors
18434
- }
18435
- }
18490
+ [
18491
+ 'ephox.katamari.api.Fun'
18492
+ ],
18436
18493
 
18437
- return domRange;
18438
- }
18494
+ function (Fun) {
18495
+ return function (os, browser, userAgent) {
18496
+ var isiPad = os.isiOS() && /ipad/i.test(userAgent) === true;
18497
+ var isiPhone = os.isiOS() && !isiPad;
18498
+ var isAndroid3 = os.isAndroid() && os.version.major === 3;
18499
+ var isAndroid4 = os.isAndroid() && os.version.major === 4;
18500
+ var isTablet = isiPad || isAndroid3 || ( isAndroid4 && /mobile/i.test(userAgent) === true );
18501
+ var isTouch = os.isiOS() || os.isAndroid();
18502
+ var isPhone = isTouch && !isTablet;
18439
18503
 
18440
- this.getBookmark = function (type) {
18441
- var rng = selection.getRng(), bookmark = {};
18504
+ var iOSwebview = browser.isSafari() && os.isiOS() && /safari/i.test(userAgent) === false;
18442
18505
 
18443
- function getIndexes(node) {
18444
- var parent, root, children, i, indexes = [];
18506
+ return {
18507
+ isiPad : Fun.constant(isiPad),
18508
+ isiPhone: Fun.constant(isiPhone),
18509
+ isTablet: Fun.constant(isTablet),
18510
+ isPhone: Fun.constant(isPhone),
18511
+ isTouch: Fun.constant(isTouch),
18512
+ isAndroid: os.isAndroid,
18513
+ isiOS: os.isiOS,
18514
+ isWebView: Fun.constant(iOSwebview)
18515
+ };
18516
+ };
18517
+ }
18518
+ );
18519
+ define(
18520
+ 'ephox.sand.detect.UaString',
18445
18521
 
18446
- parent = node.parentNode;
18447
- root = dom.getRoot().parentNode;
18522
+ [
18523
+ 'ephox.katamari.api.Arr',
18524
+ 'ephox.sand.detect.Version',
18525
+ 'global!String'
18526
+ ],
18448
18527
 
18449
- while (parent != root && parent.nodeType !== 9) {
18450
- children = parent.children;
18528
+ function (Arr, Version, String) {
18529
+ var detect = function (candidates, userAgent) {
18530
+ var agent = String(userAgent).toLowerCase();
18531
+ return Arr.find(candidates, function (candidate) {
18532
+ return candidate.search(agent);
18533
+ });
18534
+ };
18451
18535
 
18452
- i = children.length;
18453
- while (i--) {
18454
- if (node === children[i]) {
18455
- indexes.push(i);
18456
- break;
18457
- }
18458
- }
18536
+ // They (browser and os) are the same at the moment, but they might
18537
+ // not stay that way.
18538
+ var detectBrowser = function (browsers, userAgent) {
18539
+ return detect(browsers, userAgent).map(function (browser) {
18540
+ var version = Version.detect(browser.versionRegexes, userAgent);
18541
+ return {
18542
+ current: browser.name,
18543
+ version: version
18544
+ };
18545
+ });
18546
+ };
18459
18547
 
18460
- node = parent;
18461
- parent = parent.parentNode;
18462
- }
18548
+ var detectOs = function (oses, userAgent) {
18549
+ return detect(oses, userAgent).map(function (os) {
18550
+ var version = Version.detect(os.versionRegexes, userAgent);
18551
+ return {
18552
+ current: os.name,
18553
+ version: version
18554
+ };
18555
+ });
18556
+ };
18463
18557
 
18464
- return indexes;
18465
- }
18558
+ return {
18559
+ detectBrowser: detectBrowser,
18560
+ detectOs: detectOs
18561
+ };
18562
+ }
18563
+ );
18564
+ define(
18565
+ 'ephox.katamari.str.StrAppend',
18466
18566
 
18467
- function getBookmarkEndPoint(start) {
18468
- var position;
18567
+ [
18469
18568
 
18470
- position = getPosition(rng, start);
18471
- if (position) {
18472
- return {
18473
- position: position.position,
18474
- offset: position.offset,
18475
- indexes: getIndexes(position.node),
18476
- inside: position.inside
18477
- };
18478
- }
18479
- }
18569
+ ],
18480
18570
 
18481
- // Non ubstructive bookmark
18482
- if (type === 2) {
18483
- // Handle text selection
18484
- if (!rng.item) {
18485
- bookmark.start = getBookmarkEndPoint(true);
18571
+ function () {
18572
+ var addToStart = function (str, prefix) {
18573
+ return prefix + str;
18574
+ };
18486
18575
 
18487
- if (!selection.isCollapsed()) {
18488
- bookmark.end = getBookmarkEndPoint();
18489
- }
18490
- } else {
18491
- bookmark.start = { ctrl: true, indexes: getIndexes(rng.item(0)) };
18492
- }
18493
- }
18576
+ var addToEnd = function (str, suffix) {
18577
+ return str + suffix;
18578
+ };
18494
18579
 
18495
- return bookmark;
18496
- };
18580
+ var removeFromStart = function (str, numChars) {
18581
+ return str.substring(numChars);
18582
+ };
18497
18583
 
18498
- this.moveToBookmark = function (bookmark) {
18499
- var rng, body = dom.doc.body;
18584
+ var removeFromEnd = function (str, numChars) {
18585
+ return str.substring(0, str.length - numChars);
18586
+ };
18587
+
18588
+ return {
18589
+ addToStart: addToStart,
18590
+ addToEnd: addToEnd,
18591
+ removeFromStart: removeFromStart,
18592
+ removeFromEnd: removeFromEnd
18593
+ };
18594
+ }
18595
+ );
18596
+ define(
18597
+ 'ephox.katamari.str.StringParts',
18500
18598
 
18501
- function resolveIndexes(indexes) {
18502
- var node, i, idx, children;
18599
+ [
18600
+ 'ephox.katamari.api.Option',
18601
+ 'global!Error'
18602
+ ],
18503
18603
 
18504
- node = dom.getRoot();
18505
- for (i = indexes.length - 1; i >= 0; i--) {
18506
- children = node.children;
18507
- idx = indexes[i];
18604
+ function (Option, Error) {
18605
+ /** Return the first 'count' letters from 'str'.
18606
+ - * e.g. first("abcde", 2) === "ab"
18607
+ - */
18608
+ var first = function(str, count) {
18609
+ return str.substr(0, count);
18610
+ };
18508
18611
 
18509
- if (idx <= children.length - 1) {
18510
- node = children[idx];
18511
- }
18512
- }
18612
+ /** Return the last 'count' letters from 'str'.
18613
+ * e.g. last("abcde", 2) === "de"
18614
+ */
18615
+ var last = function(str, count) {
18616
+ return str.substr(str.length - count, str.length);
18617
+ };
18513
18618
 
18514
- return node;
18515
- }
18619
+ var head = function(str) {
18620
+ return str === '' ? Option.none() : Option.some(str.substr(0, 1));
18621
+ };
18516
18622
 
18517
- function setBookmarkEndPoint(start) {
18518
- var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset;
18623
+ var tail = function(str) {
18624
+ return str === '' ? Option.none() : Option.some(str.substring(1));
18625
+ };
18519
18626
 
18520
- if (endPoint) {
18521
- moveLeft = endPoint.position > 0;
18627
+ return {
18628
+ first: first,
18629
+ last: last,
18630
+ head: head,
18631
+ tail: tail
18632
+ };
18633
+ }
18634
+ );
18635
+ define(
18636
+ 'ephox.katamari.api.Strings',
18522
18637
 
18523
- moveRng = body.createTextRange();
18524
- moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
18638
+ [
18639
+ 'ephox.katamari.str.StrAppend',
18640
+ 'ephox.katamari.str.StringParts',
18641
+ 'global!Error'
18642
+ ],
18525
18643
 
18526
- offset = endPoint.offset;
18527
- if (offset !== undef) {
18528
- moveRng.collapse(endPoint.inside || moveLeft);
18529
- moveRng.moveStart('character', moveLeft ? -offset : offset);
18530
- } else {
18531
- moveRng.collapse(start);
18532
- }
18644
+ function (StrAppend, StringParts, Error) {
18645
+ var checkRange = function(str, substr, start) {
18646
+ if (substr === '') return true;
18647
+ if (str.length < substr.length) return false;
18648
+ var x = str.substr(start, start + substr.length);
18649
+ return x === substr;
18650
+ };
18533
18651
 
18534
- rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
18652
+ /** Given a string and object, perform template-replacements on the string, as specified by the object.
18653
+ * Any template fields of the form ${name} are replaced by the string or number specified as obj["name"]
18654
+ * Based on Douglas Crockford's 'supplant' method for template-replace of strings. Uses different template format.
18655
+ */
18656
+ var supplant = function(str, obj) {
18657
+ var isStringOrNumber = function(a) {
18658
+ var t = typeof a;
18659
+ return t === 'string' || t === 'number';
18660
+ };
18535
18661
 
18536
- if (start) {
18537
- rng.collapse(true);
18538
- }
18539
- }
18662
+ return str.replace(/\${([^{}]*)}/g,
18663
+ function (a, b) {
18664
+ var value = obj[b];
18665
+ return isStringOrNumber(value) ? value : a;
18540
18666
  }
18667
+ );
18668
+ };
18541
18669
 
18542
- if (bookmark.start) {
18543
- if (bookmark.start.ctrl) {
18544
- rng = body.createControlRange();
18545
- rng.addElement(resolveIndexes(bookmark.start.indexes));
18546
- rng.select();
18547
- } else {
18548
- rng = body.createTextRange();
18549
- setBookmarkEndPoint(true);
18550
- setBookmarkEndPoint();
18551
- rng.select();
18552
- }
18553
- }
18554
- };
18670
+ var removeLeading = function (str, prefix) {
18671
+ return startsWith(str, prefix) ? StrAppend.removeFromStart(str, prefix.length) : str;
18672
+ };
18555
18673
 
18556
- this.addRange = function (rng) {
18557
- var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
18558
- doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
18674
+ var removeTrailing = function (str, prefix) {
18675
+ return endsWith(str, prefix) ? StrAppend.removeFromEnd(str, prefix.length) : str;
18676
+ };
18559
18677
 
18560
- function setEndPoint(start) {
18561
- var container, offset, marker, tmpRng, nodes;
18678
+ var ensureLeading = function (str, prefix) {
18679
+ return startsWith(str, prefix) ? str : StrAppend.addToStart(str, prefix);
18680
+ };
18562
18681
 
18563
- marker = dom.create('a');
18564
- container = start ? startContainer : endContainer;
18565
- offset = start ? startOffset : endOffset;
18566
- tmpRng = ieRng.duplicate();
18682
+ var ensureTrailing = function (str, prefix) {
18683
+ return endsWith(str, prefix) ? str : StrAppend.addToEnd(str, prefix);
18684
+ };
18685
+
18686
+ var contains = function(str, substr) {
18687
+ return str.indexOf(substr) !== -1;
18688
+ };
18567
18689
 
18568
- if (container == doc || container == doc.documentElement) {
18569
- container = body;
18570
- offset = 0;
18571
- }
18690
+ var capitalize = function(str) {
18691
+ return StringParts.head(str).bind(function (head) {
18692
+ return StringParts.tail(str).map(function (tail) {
18693
+ return head.toUpperCase() + tail;
18694
+ });
18695
+ }).getOr(str);
18696
+ };
18572
18697
 
18573
- if (container.nodeType == 3) {
18574
- container.parentNode.insertBefore(marker, container);
18575
- tmpRng.moveToElementText(marker);
18576
- tmpRng.moveStart('character', offset);
18577
- dom.remove(marker);
18578
- ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
18579
- } else {
18580
- nodes = container.childNodes;
18698
+ /** Does 'str' start with 'prefix'?
18699
+ * Note: all strings start with the empty string.
18700
+ * More formally, for all strings x, startsWith(x, "").
18701
+ * This is so that for all strings x and y, startsWith(y + x, y)
18702
+ */
18703
+ var startsWith = function(str, prefix) {
18704
+ return checkRange(str, prefix, 0);
18705
+ };
18581
18706
 
18582
- if (nodes.length) {
18583
- if (offset >= nodes.length) {
18584
- dom.insertAfter(marker, nodes[nodes.length - 1]);
18585
- } else {
18586
- container.insertBefore(marker, nodes[offset]);
18587
- }
18707
+ /** Does 'str' end with 'suffix'?
18708
+ * Note: all strings end with the empty string.
18709
+ * More formally, for all strings x, endsWith(x, "").
18710
+ * This is so that for all strings x and y, endsWith(x + y, y)
18711
+ */
18712
+ var endsWith = function(str, suffix) {
18713
+ return checkRange(str, suffix, str.length - suffix.length);
18714
+ };
18588
18715
 
18589
- tmpRng.moveToElementText(marker);
18590
- } else if (container.canHaveHTML) {
18591
- // Empty node selection for example <div>|</div>
18592
- // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
18593
- container.innerHTML = '<span>&#xFEFF;</span>';
18594
- marker = container.firstChild;
18595
- tmpRng.moveToElementText(marker);
18596
- tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
18597
- }
18716
+
18717
+ /** removes all leading and trailing spaces */
18718
+ var trim = function(str) {
18719
+ return str.replace(/^\s+|\s+$/g, '');
18720
+ };
18598
18721
 
18599
- ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
18600
- dom.remove(marker);
18601
- }
18602
- }
18722
+ var lTrim = function(str) {
18723
+ return str.replace(/^\s+/g, '');
18724
+ };
18603
18725
 
18604
- // Setup some shorter versions
18605
- startContainer = rng.startContainer;
18606
- startOffset = rng.startOffset;
18607
- endContainer = rng.endContainer;
18608
- endOffset = rng.endOffset;
18609
- ieRng = body.createTextRange();
18726
+ var rTrim = function(str) {
18727
+ return str.replace(/\s+$/g, '');
18728
+ };
18610
18729
 
18611
- // If single element selection then try making a control selection out of it
18612
- if (startContainer == endContainer && startContainer.nodeType == 1) {
18613
- // Trick to place the caret inside an empty block element like <p></p>
18614
- if (startOffset == endOffset && !startContainer.hasChildNodes()) {
18615
- if (startContainer.canHaveHTML) {
18616
- // Check if previous sibling is an empty block if it is then we need to render it
18617
- // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
18618
- // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
18619
- sibling = startContainer.previousSibling;
18620
- if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
18621
- sibling.innerHTML = '&#xFEFF;';
18622
- } else {
18623
- sibling = null;
18624
- }
18730
+ return {
18731
+ supplant: supplant,
18732
+ startsWith: startsWith,
18733
+ removeLeading: removeLeading,
18734
+ removeTrailing: removeTrailing,
18735
+ ensureLeading: ensureLeading,
18736
+ ensureTrailing: ensureTrailing,
18737
+ endsWith: endsWith,
18738
+ contains: contains,
18739
+ trim: trim,
18740
+ lTrim: lTrim,
18741
+ rTrim: rTrim,
18742
+ capitalize: capitalize
18743
+ };
18744
+ }
18745
+ );
18625
18746
 
18626
- startContainer.innerHTML = '<span>&#xFEFF;</span><span>&#xFEFF;</span>';
18627
- ieRng.moveToElementText(startContainer.lastChild);
18628
- ieRng.select();
18629
- dom.doc.selection.clear();
18630
- startContainer.innerHTML = '';
18747
+ define(
18748
+ 'ephox.sand.info.PlatformInfo',
18631
18749
 
18632
- if (sibling) {
18633
- sibling.innerHTML = '';
18634
- }
18635
- return;
18636
- }
18750
+ [
18751
+ 'ephox.katamari.api.Fun',
18752
+ 'ephox.katamari.api.Strings'
18753
+ ],
18637
18754
 
18638
- startOffset = dom.nodeIndex(startContainer);
18639
- startContainer = startContainer.parentNode;
18640
- }
18755
+ function (Fun, Strings) {
18756
+ var normalVersionRegex = /.*?version\/\ ?([0-9]+)\.([0-9]+).*/;
18641
18757
 
18642
- if (startOffset == endOffset - 1) {
18643
- try {
18644
- ctrlElm = startContainer.childNodes[startOffset];
18645
- ctrlRng = body.createControlRange();
18646
- ctrlRng.addElement(ctrlElm);
18647
- ctrlRng.select();
18758
+ var checkContains = function (target) {
18759
+ return function (uastring) {
18760
+ return Strings.contains(uastring, target);
18761
+ };
18762
+ };
18648
18763
 
18649
- // Check if the range produced is on the correct element and is a control range
18650
- // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
18651
- nativeRng = selection.getRng();
18652
- if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
18653
- return;
18654
- }
18655
- } catch (ex) {
18656
- // Ignore
18657
- }
18658
- }
18764
+ var browsers = [
18765
+ {
18766
+ name : 'Edge',
18767
+ versionRegexes: [/.*?edge\/ ?([0-9]+)\.([0-9]+)$/],
18768
+ search: function (uastring) {
18769
+ var monstrosity = Strings.contains(uastring, 'edge/') && Strings.contains(uastring, 'chrome') && Strings.contains(uastring, 'safari') && Strings.contains(uastring, 'applewebkit');
18770
+ return monstrosity;
18659
18771
  }
18772
+ },
18773
+ {
18774
+ name : 'Chrome',
18775
+ versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/, normalVersionRegex],
18776
+ search : function (uastring) {
18777
+ return Strings.contains(uastring, 'chrome') && !Strings.contains(uastring, 'chromeframe');
18778
+ }
18779
+ },
18780
+ {
18781
+ name : 'IE',
18782
+ versionRegexes: [/.*?msie\ ?([0-9]+)\.([0-9]+).*/, /.*?rv:([0-9]+)\.([0-9]+).*/],
18783
+ search: function (uastring) {
18784
+ return Strings.contains(uastring, 'msie') || Strings.contains(uastring, 'trident');
18785
+ }
18786
+ },
18787
+ // INVESTIGATE: Is this still the Opera user agent?
18788
+ {
18789
+ name : 'Opera',
18790
+ versionRegexes: [normalVersionRegex, /.*?opera\/([0-9]+)\.([0-9]+).*/],
18791
+ search : checkContains('opera')
18792
+ },
18793
+ {
18794
+ name : 'Firefox',
18795
+ versionRegexes: [/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/],
18796
+ search : checkContains('firefox')
18797
+ },
18798
+ {
18799
+ name : 'Safari',
18800
+ versionRegexes: [normalVersionRegex, /.*?cpu os ([0-9]+)_([0-9]+).*/],
18801
+ search : function (uastring) {
18802
+ return (Strings.contains(uastring, 'safari') || Strings.contains(uastring, 'mobile/')) && Strings.contains(uastring, 'applewebkit');
18803
+ }
18804
+ }
18805
+ ];
18660
18806
 
18661
- // Set start/end point of selection
18662
- setEndPoint(true);
18663
- setEndPoint();
18664
-
18665
- // Select the new range and scroll it into view
18666
- ieRng.select();
18667
- };
18668
-
18669
- // Expose range method
18670
- this.getRangeAt = getRange;
18671
- }
18807
+ var oses = [
18808
+ {
18809
+ name : 'Windows',
18810
+ search : checkContains('win'),
18811
+ versionRegexes: [/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/]
18812
+ },
18813
+ {
18814
+ name : 'iOS',
18815
+ search : function (uastring) {
18816
+ return Strings.contains(uastring, 'iphone') || Strings.contains(uastring, 'ipad');
18817
+ },
18818
+ versionRegexes: [/.*?version\/\ ?([0-9]+)\.([0-9]+).*/, /.*cpu os ([0-9]+)_([0-9]+).*/, /.*cpu iphone os ([0-9]+)_([0-9]+).*/]
18819
+ },
18820
+ {
18821
+ name : 'Android',
18822
+ search : checkContains('android'),
18823
+ versionRegexes: [/.*?android\ ?([0-9]+)\.([0-9]+).*/]
18824
+ },
18825
+ {
18826
+ name : 'OSX',
18827
+ search : checkContains('os x'),
18828
+ versionRegexes: [/.*?os\ x\ ?([0-9]+)_([0-9]+).*/]
18829
+ },
18830
+ {
18831
+ name : 'Linux',
18832
+ search : checkContains('linux'),
18833
+ versionRegexes: [ ]
18834
+ },
18835
+ { name : 'Solaris',
18836
+ search : checkContains('sunos'),
18837
+ versionRegexes: [ ]
18838
+ },
18839
+ {
18840
+ name : 'FreeBSD',
18841
+ search : checkContains('freebsd'),
18842
+ versionRegexes: [ ]
18843
+ }
18844
+ ];
18672
18845
 
18673
- return Selection;
18846
+ return {
18847
+ browsers: Fun.constant(browsers),
18848
+ oses: Fun.constant(oses)
18849
+ };
18674
18850
  }
18675
18851
  );
18676
-
18677
18852
  define(
18678
- 'ephox.katamari.api.Type',
18853
+ 'ephox.sand.core.PlatformDetection',
18679
18854
 
18680
18855
  [
18681
- 'global!Array',
18682
- 'global!String'
18856
+ 'ephox.sand.core.Browser',
18857
+ 'ephox.sand.core.OperatingSystem',
18858
+ 'ephox.sand.detect.DeviceType',
18859
+ 'ephox.sand.detect.UaString',
18860
+ 'ephox.sand.info.PlatformInfo'
18683
18861
  ],
18684
18862
 
18685
- function (Array, String) {
18686
- var typeOf = function(x) {
18687
- if (x === null) return 'null';
18688
- var t = typeof x;
18689
- if (t === 'object' && Array.prototype.isPrototypeOf(x)) return 'array';
18690
- if (t === 'object' && String.prototype.isPrototypeOf(x)) return 'string';
18691
- return t;
18692
- };
18863
+ function (Browser, OperatingSystem, DeviceType, UaString, PlatformInfo) {
18864
+ var detect = function (userAgent) {
18865
+ var browsers = PlatformInfo.browsers();
18866
+ var oses = PlatformInfo.oses();
18693
18867
 
18694
- var isType = function (type) {
18695
- return function (value) {
18696
- return typeOf(value) === type;
18868
+ var browser = UaString.detectBrowser(browsers, userAgent).fold(
18869
+ Browser.unknown,
18870
+ Browser.nu
18871
+ );
18872
+ var os = UaString.detectOs(oses, userAgent).fold(
18873
+ OperatingSystem.unknown,
18874
+ OperatingSystem.nu
18875
+ );
18876
+ var deviceType = DeviceType(os, browser, userAgent);
18877
+
18878
+ return {
18879
+ browser: browser,
18880
+ os: os,
18881
+ deviceType: deviceType
18697
18882
  };
18698
18883
  };
18699
18884
 
18700
18885
  return {
18701
- isString: isType('string'),
18702
- isObject: isType('object'),
18703
- isArray: isType('array'),
18704
- isNull: isType('null'),
18705
- isBoolean: isType('boolean'),
18706
- isUndefined: isType('undefined'),
18707
- isFunction: isType('function'),
18708
- isNumber: isType('number')
18886
+ detect: detect
18709
18887
  };
18710
18888
  }
18711
18889
  );
18712
-
18713
-
18890
+ defineGlobal("global!navigator", navigator);
18714
18891
  define(
18715
- 'ephox.katamari.data.Immutable',
18892
+ 'ephox.sand.api.PlatformDetection',
18716
18893
 
18717
18894
  [
18718
- 'ephox.katamari.api.Arr',
18719
- 'ephox.katamari.api.Fun',
18720
- 'global!Array',
18721
- 'global!Error'
18895
+ 'ephox.katamari.api.Thunk',
18896
+ 'ephox.sand.core.PlatformDetection',
18897
+ 'global!navigator'
18722
18898
  ],
18723
18899
 
18724
- function (Arr, Fun, Array, Error) {
18725
- return function () {
18726
- var fields = arguments;
18727
- return function(/* values */) {
18728
- // Don't use array slice(arguments), makes the whole function unoptimisable on Chrome
18729
- var values = new Array(arguments.length);
18730
- for (var i = 0; i < values.length; i++) values[i] = arguments[i];
18731
-
18732
- if (fields.length !== values.length)
18733
- throw new Error('Wrong number of arguments to struct. Expected "[' + fields.length + ']", got ' + values.length + ' arguments');
18900
+ function (Thunk, PlatformDetection, navigator) {
18901
+ var detect = Thunk.cached(function () {
18902
+ var userAgent = navigator.userAgent;
18903
+ return PlatformDetection.detect(userAgent);
18904
+ });
18734
18905
 
18735
- var struct = {};
18736
- Arr.each(fields, function (name, i) {
18737
- struct[name] = Fun.constant(values[i]);
18738
- });
18739
- return struct;
18740
- };
18906
+ return {
18907
+ detect: detect
18741
18908
  };
18742
18909
  }
18743
18910
  );
18744
-
18911
+ define("global!console", [], function () { if (typeof console === "undefined") console = { log: function () {} }; return console; });
18912
+ defineGlobal("global!document", document);
18745
18913
  define(
18746
- 'ephox.katamari.api.Obj',
18914
+ 'ephox.sugar.api.node.Element',
18747
18915
 
18748
18916
  [
18749
- 'ephox.katamari.api.Option',
18750
- 'global!Object'
18917
+ 'ephox.katamari.api.Fun',
18918
+ 'global!Error',
18919
+ 'global!console',
18920
+ 'global!document'
18751
18921
  ],
18752
18922
 
18753
- function (Option, Object) {
18754
- // There are many variations of Object iteration that are faster than the 'for-in' style:
18755
- // http://jsperf.com/object-keys-iteration/107
18756
- //
18757
- // Use the native keys if it is available (IE9+), otherwise fall back to manually filtering
18758
- var keys = (function () {
18759
- var fastKeys = Object.keys;
18760
-
18761
- // This technically means that 'each' and 'find' on IE8 iterate through the object twice.
18762
- // This code doesn't run on IE8 much, so it's an acceptable tradeoff.
18763
- // If it becomes a problem we can always duplicate the feature detection inside each and find as well.
18764
- var slowKeys = function (o) {
18765
- var r = [];
18766
- for (var i in o) {
18767
- if (o.hasOwnProperty(i)) {
18768
- r.push(i);
18769
- }
18770
- }
18771
- return r;
18772
- };
18773
-
18774
- return fastKeys === undefined ? slowKeys : fastKeys;
18775
- })();
18776
-
18777
-
18778
- var each = function (obj, f) {
18779
- var props = keys(obj);
18780
- for (var k = 0, len = props.length; k < len; k++) {
18781
- var i = props[k];
18782
- var x = obj[i];
18783
- f(x, i, obj);
18923
+ function (Fun, Error, console, document) {
18924
+ var fromHtml = function (html, scope) {
18925
+ var doc = scope || document;
18926
+ var div = doc.createElement('div');
18927
+ div.innerHTML = html;
18928
+ if (!div.hasChildNodes() || div.childNodes.length > 1) {
18929
+ console.error('HTML does not have a single root node', html);
18930
+ throw 'HTML must have a single root node';
18784
18931
  }
18932
+ return fromDom(div.childNodes[0]);
18785
18933
  };
18786
18934
 
18787
- /** objectMap :: (JsObj(k, v), (v, k, JsObj(k, v) -> x)) -> JsObj(k, x) */
18788
- var objectMap = function (obj, f) {
18789
- return tupleMap(obj, function (x, i, obj) {
18790
- return {
18791
- k: i,
18792
- v: f(x, i, obj)
18793
- };
18794
- });
18935
+ var fromTag = function (tag, scope) {
18936
+ var doc = scope || document;
18937
+ var node = doc.createElement(tag);
18938
+ return fromDom(node);
18795
18939
  };
18796
18940
 
18797
- /** tupleMap :: (JsObj(k, v), (v, k, JsObj(k, v) -> { k: x, v: y })) -> JsObj(x, y) */
18798
- var tupleMap = function (obj, f) {
18799
- var r = {};
18800
- each(obj, function (x, i) {
18801
- var tuple = f(x, i, obj);
18802
- r[tuple.k] = tuple.v;
18803
- });
18804
- return r;
18941
+ var fromText = function (text, scope) {
18942
+ var doc = scope || document;
18943
+ var node = doc.createTextNode(text);
18944
+ return fromDom(node);
18805
18945
  };
18806
18946
 
18807
- /** bifilter :: (JsObj(k, v), (v, k -> Bool)) -> { t: JsObj(k, v), f: JsObj(k, v) } */
18808
- var bifilter = function (obj, pred) {
18809
- var t = {};
18810
- var f = {};
18811
- each(obj, function(x, i) {
18812
- var branch = pred(x, i) ? t : f;
18813
- branch[i] = x;
18814
- });
18947
+ var fromDom = function (node) {
18948
+ if (node === null || node === undefined) throw new Error('Node cannot be null or undefined');
18815
18949
  return {
18816
- t: t,
18817
- f: f
18950
+ dom: Fun.constant(node)
18818
18951
  };
18819
18952
  };
18820
18953
 
18821
- /** mapToArray :: (JsObj(k, v), (v, k -> a)) -> [a] */
18822
- var mapToArray = function (obj, f) {
18823
- var r = [];
18824
- each(obj, function(value, name) {
18825
- r.push(f(value, name));
18826
- });
18827
- return r;
18954
+ return {
18955
+ fromHtml: fromHtml,
18956
+ fromTag: fromTag,
18957
+ fromText: fromText,
18958
+ fromDom: fromDom
18828
18959
  };
18960
+ }
18961
+ );
18829
18962
 
18830
- /** find :: (JsObj(k, v), (v, k, JsObj(k, v) -> Bool)) -> Option v */
18831
- var find = function (obj, pred) {
18832
- var props = keys(obj);
18833
- for (var k = 0, len = props.length; k < len; k++) {
18834
- var i = props[k];
18835
- var x = obj[i];
18836
- if (pred(x, i, obj)) {
18837
- return Option.some(x);
18838
- }
18839
- }
18840
- return Option.none();
18841
- };
18963
+ define(
18964
+ 'ephox.sugar.api.node.NodeTypes',
18842
18965
 
18843
- /** values :: JsObj(k, v) -> [v] */
18844
- var values = function (obj) {
18845
- return mapToArray(obj, function (v) {
18846
- return v;
18847
- });
18848
- };
18966
+ [
18849
18967
 
18850
- var size = function (obj) {
18851
- return values(obj).length;
18852
- };
18968
+ ],
18853
18969
 
18970
+ function () {
18854
18971
  return {
18855
- bifilter: bifilter,
18856
- each: each,
18857
- map: objectMap,
18858
- mapToArray: mapToArray,
18859
- tupleMap: tupleMap,
18860
- find: find,
18861
- keys: keys,
18862
- values: values,
18863
- size: size
18972
+ ATTRIBUTE: 2,
18973
+ CDATA_SECTION: 4,
18974
+ COMMENT: 8,
18975
+ DOCUMENT: 9,
18976
+ DOCUMENT_TYPE: 10,
18977
+ DOCUMENT_FRAGMENT: 11,
18978
+ ELEMENT: 1,
18979
+ TEXT: 3,
18980
+ PROCESSING_INSTRUCTION: 7,
18981
+ ENTITY_REFERENCE: 5,
18982
+ ENTITY: 6,
18983
+ NOTATION: 12
18864
18984
  };
18865
18985
  }
18866
18986
  );
18867
18987
  define(
18868
- 'ephox.katamari.util.BagUtils',
18988
+ 'ephox.sugar.api.search.Selectors',
18869
18989
 
18870
18990
  [
18871
18991
  'ephox.katamari.api.Arr',
18872
- 'ephox.katamari.api.Type',
18873
- 'global!Error'
18992
+ 'ephox.katamari.api.Option',
18993
+ 'ephox.sugar.api.node.Element',
18994
+ 'ephox.sugar.api.node.NodeTypes',
18995
+ 'global!Error',
18996
+ 'global!document'
18874
18997
  ],
18875
18998
 
18876
- function (Arr, Type, Error) {
18877
- var sort = function (arr) {
18878
- return arr.slice(0).sort();
18879
- };
18999
+ function (Arr, Option, Element, NodeTypes, Error, document) {
19000
+ /*
19001
+ * There's a lot of code here; the aim is to allow the browser to optimise constant comparisons,
19002
+ * instead of doing object lookup feature detection on every call
19003
+ */
19004
+ var STANDARD = 0;
19005
+ var MSSTANDARD = 1;
19006
+ var WEBKITSTANDARD = 2;
19007
+ var FIREFOXSTANDARD = 3;
18880
19008
 
18881
- var reqMessage = function (required, keys) {
18882
- throw new Error('All required keys (' + sort(required).join(', ') + ') were not specified. Specified keys were: ' + sort(keys).join(', ') + '.');
18883
- };
19009
+ var selectorType = (function () {
19010
+ var test = document.createElement('span');
19011
+ // As of Chrome 34 / Safari 7.1 / FireFox 34, everyone except IE has the unprefixed function.
19012
+ // Still check for the others, but do it last.
19013
+ return test.matches !== undefined ? STANDARD :
19014
+ test.msMatchesSelector !== undefined ? MSSTANDARD :
19015
+ test.webkitMatchesSelector !== undefined ? WEBKITSTANDARD :
19016
+ test.mozMatchesSelector !== undefined ? FIREFOXSTANDARD :
19017
+ -1;
19018
+ })();
18884
19019
 
18885
- var unsuppMessage = function (unsupported) {
18886
- throw new Error('Unsupported keys for object: ' + sort(unsupported).join(', '));
18887
- };
18888
19020
 
18889
- var validateStrArr = function (label, array) {
18890
- if (!Type.isArray(array)) throw new Error('The ' + label + ' fields must be an array. Was: ' + array + '.');
18891
- Arr.each(array, function (a) {
18892
- if (!Type.isString(a)) throw new Error('The value ' + a + ' in the ' + label + ' fields was not a string.');
18893
- });
19021
+ var ELEMENT = NodeTypes.ELEMENT;
19022
+ var DOCUMENT = NodeTypes.DOCUMENT;
19023
+
19024
+ var is = function (element, selector) {
19025
+ var elem = element.dom();
19026
+ if (elem.nodeType !== ELEMENT) return false; // documents have querySelector but not matches
19027
+
19028
+ // As of Chrome 34 / Safari 7.1 / FireFox 34, everyone except IE has the unprefixed function.
19029
+ // Still check for the others, but do it last.
19030
+ else if (selectorType === STANDARD) return elem.matches(selector);
19031
+ else if (selectorType === MSSTANDARD) return elem.msMatchesSelector(selector);
19032
+ else if (selectorType === WEBKITSTANDARD) return elem.webkitMatchesSelector(selector);
19033
+ else if (selectorType === FIREFOXSTANDARD) return elem.mozMatchesSelector(selector);
19034
+ else throw new Error('Browser lacks native selectors'); // unfortunately we can't throw this on startup :(
18894
19035
  };
18895
19036
 
18896
- var invalidTypeMessage = function (incorrect, type) {
18897
- throw new Error('All values need to be of type: ' + type + '. Keys (' + sort(incorrect).join(', ') + ') were not.');
19037
+ var bypassSelector = function (dom) {
19038
+ // Only elements and documents support querySelector
19039
+ return dom.nodeType !== ELEMENT && dom.nodeType !== DOCUMENT ||
19040
+ // IE fix for complex queries on empty nodes: http://jsfiddle.net/spyder/fv9ptr5L/
19041
+ dom.childElementCount === 0;
18898
19042
  };
18899
19043
 
18900
- var checkDupes = function (everything) {
18901
- var sorted = sort(everything);
18902
- var dupe = Arr.find(sorted, function (s, i) {
18903
- return i < sorted.length -1 && s === sorted[i + 1];
18904
- });
19044
+ var all = function (selector, scope) {
19045
+ var base = scope === undefined ? document : scope.dom();
19046
+ return bypassSelector(base) ? [] : Arr.map(base.querySelectorAll(selector), Element.fromDom);
19047
+ };
18905
19048
 
18906
- dupe.each(function (d) {
18907
- throw new Error('The field: ' + d + ' occurs more than once in the combined fields: [' + sorted.join(', ') + '].');
18908
- });
19049
+ var one = function (selector, scope) {
19050
+ var base = scope === undefined ? document : scope.dom();
19051
+ return bypassSelector(base) ? Option.none() : Option.from(base.querySelector(selector)).map(Element.fromDom);
18909
19052
  };
18910
19053
 
18911
19054
  return {
18912
- sort: sort,
18913
- reqMessage: reqMessage,
18914
- unsuppMessage: unsuppMessage,
18915
- validateStrArr: validateStrArr,
18916
- invalidTypeMessage: invalidTypeMessage,
18917
- checkDupes: checkDupes
19055
+ all: all,
19056
+ is: is,
19057
+ one: one
18918
19058
  };
18919
19059
  }
18920
19060
  );
19061
+
18921
19062
  define(
18922
- 'ephox.katamari.data.MixedBag',
19063
+ 'ephox.sugar.api.dom.Compare',
18923
19064
 
18924
19065
  [
18925
19066
  'ephox.katamari.api.Arr',
18926
19067
  'ephox.katamari.api.Fun',
18927
- 'ephox.katamari.api.Obj',
18928
- 'ephox.katamari.api.Option',
18929
- 'ephox.katamari.util.BagUtils',
18930
- 'global!Error',
18931
- 'global!Object'
19068
+ 'ephox.sand.api.Node',
19069
+ 'ephox.sand.api.PlatformDetection',
19070
+ 'ephox.sugar.api.search.Selectors'
18932
19071
  ],
18933
19072
 
18934
- function (Arr, Fun, Obj, Option, BagUtils, Error, Object) {
18935
-
18936
- return function (required, optional) {
18937
- var everything = required.concat(optional);
18938
- if (everything.length === 0) throw new Error('You must specify at least one required or optional field.');
18939
-
18940
- BagUtils.validateStrArr('required', required);
18941
- BagUtils.validateStrArr('optional', optional);
19073
+ function (Arr, Fun, Node, PlatformDetection, Selectors) {
18942
19074
 
18943
- BagUtils.checkDupes(everything);
19075
+ var eq = function (e1, e2) {
19076
+ return e1.dom() === e2.dom();
19077
+ };
18944
19078
 
18945
- return function (obj) {
18946
- var keys = Obj.keys(obj);
19079
+ var isEqualNode = function (e1, e2) {
19080
+ return e1.dom().isEqualNode(e2.dom());
19081
+ };
18947
19082
 
18948
- // Ensure all required keys are present.
18949
- var allReqd = Arr.forall(required, function (req) {
18950
- return Arr.contains(keys, req);
18951
- });
19083
+ var member = function (element, elements) {
19084
+ return Arr.exists(elements, Fun.curry(eq, element));
19085
+ };
18952
19086
 
18953
- if (! allReqd) BagUtils.reqMessage(required, keys);
19087
+ // DOM contains() method returns true if e1===e2, we define our contains() to return false (a node does not contain itself).
19088
+ var regularContains = function (e1, e2) {
19089
+ var d1 = e1.dom(), d2 = e2.dom();
19090
+ return d1 === d2 ? false : d1.contains(d2);
19091
+ };
18954
19092
 
18955
- var unsupported = Arr.filter(keys, function (key) {
18956
- return !Arr.contains(everything, key);
18957
- });
19093
+ var ieContains = function (e1, e2) {
19094
+ // IE only implements the contains() method for Element nodes.
19095
+ // It fails for Text nodes, so implement it using compareDocumentPosition()
19096
+ // https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect
19097
+ // Note that compareDocumentPosition returns CONTAINED_BY if 'e2 *is_contained_by* e1':
19098
+ // Also, compareDocumentPosition defines a node containing itself as false.
19099
+ return Node.documentPositionContainedBy(e1.dom(), e2.dom());
19100
+ };
18958
19101
 
18959
- if (unsupported.length > 0) BagUtils.unsuppMessage(unsupported);
19102
+ var browser = PlatformDetection.detect().browser;
18960
19103
 
18961
- var r = {};
18962
- Arr.each(required, function (req) {
18963
- r[req] = Fun.constant(obj[req]);
18964
- });
19104
+ // Returns: true if node e1 contains e2, otherwise false.
19105
+ // (returns false if e1===e2: A node does not contain itself).
19106
+ var contains = browser.isIE() ? ieContains : regularContains;
18965
19107
 
18966
- Arr.each(optional, function (opt) {
18967
- r[opt] = Fun.constant(Object.prototype.hasOwnProperty.call(obj, opt) ? Option.some(obj[opt]): Option.none());
18968
- });
19108
+ return {
19109
+ eq: eq,
19110
+ isEqualNode: isEqualNode,
19111
+ member: member,
19112
+ contains: contains,
18969
19113
 
18970
- return r;
18971
- };
19114
+ // Only used by DomUniverse. Remove (or should Selectors.is move here?)
19115
+ is: Selectors.is
18972
19116
  };
18973
19117
  }
18974
19118
  );
18975
- define(
18976
- 'ephox.katamari.api.Struct',
18977
19119
 
19120
+ /**
19121
+ * ScrollIntoView.js
19122
+ *
19123
+ * Released under LGPL License.
19124
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
19125
+ *
19126
+ * License: http://www.tinymce.com/license
19127
+ * Contributing: http://www.tinymce.com/contributing
19128
+ */
19129
+
19130
+ define(
19131
+ 'tinymce.core.dom.ScrollIntoView',
18978
19132
  [
18979
- 'ephox.katamari.data.Immutable',
18980
- 'ephox.katamari.data.MixedBag'
19133
+ 'tinymce.core.dom.NodeType'
18981
19134
  ],
19135
+ function (NodeType) {
19136
+ var getPos = function (elm) {
19137
+ var x = 0, y = 0;
18982
19138
 
18983
- function (Immutable, MixedBag) {
18984
- return {
18985
- immutable: Immutable,
18986
- immutableBag: MixedBag
19139
+ var offsetParent = elm;
19140
+ while (offsetParent && offsetParent.nodeType) {
19141
+ x += offsetParent.offsetLeft || 0;
19142
+ y += offsetParent.offsetTop || 0;
19143
+ offsetParent = offsetParent.offsetParent;
19144
+ }
19145
+
19146
+ return { x: x, y: y };
18987
19147
  };
18988
- }
18989
- );
18990
19148
 
18991
- define(
18992
- 'ephox.sugar.alien.Recurse',
19149
+ var fireScrollIntoViewEvent = function (editor, elm, alignToTop) {
19150
+ var scrollEvent = { elm: elm, alignToTop: alignToTop };
19151
+ editor.fire('scrollIntoView', scrollEvent);
19152
+ return scrollEvent.isDefaultPrevented();
19153
+ };
18993
19154
 
18994
- [
19155
+ var scrollIntoView = function (editor, elm, alignToTop) {
19156
+ var y, viewPort, dom = editor.dom, root = dom.getRoot(), viewPortY, viewPortH, offsetY = 0;
18995
19157
 
18996
- ],
19158
+ if (fireScrollIntoViewEvent(editor, elm, alignToTop)) {
19159
+ return;
19160
+ }
18997
19161
 
18998
- function () {
18999
- /**
19000
- * Applies f repeatedly until it completes (by returning Option.none()).
19001
- *
19002
- * Normally would just use recursion, but JavaScript lacks tail call optimisation.
19003
- *
19004
- * This is what recursion looks like when manually unravelled :)
19005
- */
19006
- var toArray = function (target, f) {
19007
- var r = [];
19162
+ if (!NodeType.isElement(elm)) {
19163
+ return;
19164
+ }
19008
19165
 
19009
- var recurse = function (e) {
19010
- r.push(e);
19011
- return f(e);
19012
- };
19166
+ if (alignToTop === false) {
19167
+ offsetY = elm.offsetHeight;
19168
+ }
19013
19169
 
19014
- var cur = f(target);
19015
- do {
19016
- cur = cur.bind(recurse);
19017
- } while (cur.isSome());
19170
+ if (root.nodeName !== 'BODY') {
19171
+ var scrollContainer = editor.selection.getScrollContainer();
19172
+ if (scrollContainer) {
19173
+ y = getPos(elm).y - getPos(scrollContainer).y + offsetY;
19174
+ viewPortH = scrollContainer.clientHeight;
19175
+ viewPortY = scrollContainer.scrollTop;
19176
+ if (y < viewPortY || y + 25 > viewPortY + viewPortH) {
19177
+ scrollContainer.scrollTop = y < viewPortY ? y : y - viewPortH + 25;
19178
+ }
19018
19179
 
19019
- return r;
19180
+ return;
19181
+ }
19182
+ }
19183
+
19184
+ viewPort = dom.getViewPort(editor.getWin());
19185
+ y = dom.getPos(elm).y + offsetY;
19186
+ viewPortY = viewPort.y;
19187
+ viewPortH = viewPort.h;
19188
+ if (y < viewPort.y || y + 25 > viewPortY + viewPortH) {
19189
+ editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25);
19190
+ }
19020
19191
  };
19021
19192
 
19022
19193
  return {
19023
- toArray: toArray
19194
+ scrollIntoView: scrollIntoView
19024
19195
  };
19025
19196
  }
19026
19197
  );
19198
+
19199
+ /**
19200
+ * TridentSelection.js
19201
+ *
19202
+ * Released under LGPL License.
19203
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
19204
+ *
19205
+ * License: http://www.tinymce.com/license
19206
+ * Contributing: http://www.tinymce.com/contributing
19207
+ */
19208
+
19209
+ /**
19210
+ * Selection class for old explorer versions. This one fakes the
19211
+ * native selection object available on modern browsers.
19212
+ *
19213
+ * @private
19214
+ * @class tinymce.dom.TridentSelection
19215
+ */
19027
19216
  define(
19028
- 'ephox.katamari.api.Global',
19217
+ 'tinymce.core.dom.TridentSelection',
19218
+ [
19219
+ ],
19220
+ function () {
19221
+ function Selection(selection) {
19222
+ var self = this, dom = selection.dom, FALSE = false;
19223
+
19224
+ function getPosition(rng, start) {
19225
+ var checkRng, startIndex = 0, endIndex, inside,
19226
+ children, child, offset, index, position = -1, parent;
19227
+
19228
+ // Setup test range, collapse it and get the parent
19229
+ checkRng = rng.duplicate();
19230
+ checkRng.collapse(start);
19231
+ parent = checkRng.parentElement();
19232
+
19233
+ // Check if the selection is within the right document
19234
+ if (parent.ownerDocument !== selection.dom.doc) {
19235
+ return;
19236
+ }
19237
+
19238
+ // IE will report non editable elements as it's parent so look for an editable one
19239
+ while (parent.contentEditable === "false") {
19240
+ parent = parent.parentNode;
19241
+ }
19242
+
19243
+ // If parent doesn't have any children then return that we are inside the element
19244
+ if (!parent.hasChildNodes()) {
19245
+ return { node: parent, inside: 1 };
19246
+ }
19247
+
19248
+ // Setup node list and endIndex
19249
+ children = parent.children;
19250
+ endIndex = children.length - 1;
19251
+
19252
+ // Perform a binary search for the position
19253
+ while (startIndex <= endIndex) {
19254
+ index = Math.floor((startIndex + endIndex) / 2);
19255
+
19256
+ // Move selection to node and compare the ranges
19257
+ child = children[index];
19258
+ checkRng.moveToElementText(child);
19259
+ position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
19260
+
19261
+ // Before/after or an exact match
19262
+ if (position > 0) {
19263
+ endIndex = index - 1;
19264
+ } else if (position < 0) {
19265
+ startIndex = index + 1;
19266
+ } else {
19267
+ return { node: child };
19268
+ }
19269
+ }
19270
+
19271
+ // Check if child position is before or we didn't find a position
19272
+ if (position < 0) {
19273
+ // No element child was found use the parent element and the offset inside that
19274
+ if (!child) {
19275
+ checkRng.moveToElementText(parent);
19276
+ checkRng.collapse(true);
19277
+ child = parent;
19278
+ inside = true;
19279
+ } else {
19280
+ checkRng.collapse(false);
19281
+ }
19282
+
19283
+ // Walk character by character in text node until we hit the selected range endpoint,
19284
+ // hit the end of document or parent isn't the right one
19285
+ // We need to walk char by char since rng.text or rng.htmlText will trim line endings
19286
+ offset = 0;
19287
+ while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
19288
+ if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
19289
+ break;
19290
+ }
19291
+
19292
+ offset++;
19293
+ }
19294
+ } else {
19295
+ // Child position is after the selection endpoint
19296
+ checkRng.collapse(true);
19297
+
19298
+ // Walk character by character in text node until we hit the selected range endpoint, hit
19299
+ // the end of document or parent isn't the right one
19300
+ offset = 0;
19301
+ while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
19302
+ if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
19303
+ break;
19304
+ }
19305
+
19306
+ offset++;
19307
+ }
19308
+ }
19309
+
19310
+ return { node: child, position: position, offset: offset, inside: inside };
19311
+ }
19312
+
19313
+ // Returns a W3C DOM compatible range object by using the IE Range API
19314
+ function getRange() {
19315
+ var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark;
19316
+
19317
+ // If selection is outside the current document just return an empty range
19318
+ element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
19319
+ if (element.ownerDocument != dom.doc) {
19320
+ return domRange;
19321
+ }
19322
+
19323
+ collapsed = selection.isCollapsed();
19324
+
19325
+ // Handle control selection
19326
+ if (ieRange.item) {
19327
+ domRange.setStart(element.parentNode, dom.nodeIndex(element));
19328
+ domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
19329
+
19330
+ return domRange;
19331
+ }
19332
+
19333
+ function findEndPoint(start) {
19334
+ var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
19335
+
19336
+ container = endPoint.node;
19337
+ offset = endPoint.offset;
19338
+
19339
+ if (endPoint.inside && !container.hasChildNodes()) {
19340
+ domRange[start ? 'setStart' : 'setEnd'](container, 0);
19341
+ return;
19342
+ }
19343
+
19344
+ if (offset === undef) {
19345
+ domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
19346
+ return;
19347
+ }
19348
+
19349
+ if (endPoint.position < 0) {
19350
+ sibling = endPoint.inside ? container.firstChild : container.nextSibling;
19351
+
19352
+ if (!sibling) {
19353
+ domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
19354
+ return;
19355
+ }
19029
19356
 
19030
- [
19031
- ],
19357
+ if (!offset) {
19358
+ if (sibling.nodeType == 3) {
19359
+ domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
19360
+ } else {
19361
+ domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
19362
+ }
19032
19363
 
19033
- function () {
19034
- // Use window object as the global if it's available since CSP will block script evals
19035
- if (typeof window !== 'undefined') {
19036
- return window;
19037
- } else {
19038
- return Function('return this;')();
19039
- }
19040
- }
19041
- );
19364
+ return;
19365
+ }
19042
19366
 
19367
+ // Find the text node and offset
19368
+ while (sibling) {
19369
+ if (sibling.nodeType == 3) {
19370
+ nodeValue = sibling.nodeValue;
19371
+ textNodeOffset += nodeValue.length;
19043
19372
 
19044
- define(
19045
- 'ephox.katamari.api.Resolve',
19373
+ // We are at or passed the position we where looking for
19374
+ if (textNodeOffset >= offset) {
19375
+ container = sibling;
19376
+ textNodeOffset -= offset;
19377
+ textNodeOffset = nodeValue.length - textNodeOffset;
19378
+ break;
19379
+ }
19380
+ }
19046
19381
 
19047
- [
19048
- 'ephox.katamari.api.Global'
19049
- ],
19382
+ sibling = sibling.nextSibling;
19383
+ }
19384
+ } else {
19385
+ // Find the text node and offset
19386
+ sibling = container.previousSibling;
19050
19387
 
19051
- function (Global) {
19052
- /** path :: ([String], JsObj?) -> JsObj */
19053
- var path = function (parts, scope) {
19054
- var o = scope !== undefined ? scope : Global;
19055
- for (var i = 0; i < parts.length && o !== undefined && o !== null; ++i)
19056
- o = o[parts[i]];
19057
- return o;
19058
- };
19388
+ if (!sibling) {
19389
+ return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
19390
+ }
19059
19391
 
19060
- /** resolve :: (String, JsObj?) -> JsObj */
19061
- var resolve = function (p, scope) {
19062
- var parts = p.split('.');
19063
- return path(parts, scope);
19064
- };
19392
+ // If there isn't any text to loop then use the first position
19393
+ if (!offset) {
19394
+ if (container.nodeType == 3) {
19395
+ domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
19396
+ } else {
19397
+ domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
19398
+ }
19065
19399
 
19066
- /** step :: (JsObj, String) -> JsObj */
19067
- var step = function (o, part) {
19068
- if (o[part] === undefined || o[part] === null)
19069
- o[part] = {};
19070
- return o[part];
19071
- };
19400
+ return;
19401
+ }
19072
19402
 
19073
- /** forge :: ([String], JsObj?) -> JsObj */
19074
- var forge = function (parts, target) {
19075
- var o = target !== undefined ? target : Global;
19076
- for (var i = 0; i < parts.length; ++i)
19077
- o = step(o, parts[i]);
19078
- return o;
19079
- };
19403
+ while (sibling) {
19404
+ if (sibling.nodeType == 3) {
19405
+ textNodeOffset += sibling.nodeValue.length;
19080
19406
 
19081
- /** namespace :: (String, JsObj?) -> JsObj */
19082
- var namespace = function (name, target) {
19083
- var parts = name.split('.');
19084
- return forge(parts, target);
19085
- };
19407
+ // We are at or passed the position we where looking for
19408
+ if (textNodeOffset >= offset) {
19409
+ container = sibling;
19410
+ textNodeOffset -= offset;
19411
+ break;
19412
+ }
19413
+ }
19086
19414
 
19087
- return {
19088
- path: path,
19089
- resolve: resolve,
19090
- forge: forge,
19091
- namespace: namespace
19092
- };
19093
- }
19094
- );
19415
+ sibling = sibling.previousSibling;
19416
+ }
19417
+ }
19095
19418
 
19419
+ domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
19420
+ }
19096
19421
 
19097
- define(
19098
- 'ephox.sand.util.Global',
19422
+ try {
19423
+ // Find start point
19424
+ findEndPoint(true);
19099
19425
 
19100
- [
19101
- 'ephox.katamari.api.Resolve'
19102
- ],
19426
+ // Find end point if needed
19427
+ if (!collapsed) {
19428
+ findEndPoint();
19429
+ }
19430
+ } catch (ex) {
19431
+ // IE has a nasty bug where text nodes might throw "invalid argument" when you
19432
+ // access the nodeValue or other properties of text nodes. This seems to happen when
19433
+ // text nodes are split into two nodes by a delete/backspace call.
19434
+ // So let us detect and try to fix it.
19435
+ if (ex.number == -2147024809) {
19436
+ // Get the current selection
19437
+ bookmark = self.getBookmark(2);
19103
19438
 
19104
- function (Resolve) {
19105
- var unsafe = function (name, scope) {
19106
- return Resolve.resolve(name, scope);
19107
- };
19439
+ // Get start element
19440
+ tmpRange = ieRange.duplicate();
19441
+ tmpRange.collapse(true);
19442
+ element = tmpRange.parentElement();
19108
19443
 
19109
- var getOrDie = function (name, scope) {
19110
- var actual = unsafe(name, scope);
19444
+ // Get end element
19445
+ if (!collapsed) {
19446
+ tmpRange = ieRange.duplicate();
19447
+ tmpRange.collapse(false);
19448
+ element2 = tmpRange.parentElement();
19449
+ element2.innerHTML = element2.innerHTML;
19450
+ }
19111
19451
 
19112
- if (actual === undefined) throw name + ' not available on this browser';
19113
- return actual;
19114
- };
19452
+ // Remove the broken elements
19453
+ element.innerHTML = element.innerHTML;
19115
19454
 
19116
- return {
19117
- getOrDie: getOrDie
19118
- };
19119
- }
19120
- );
19121
- define(
19122
- 'ephox.sand.api.Node',
19455
+ // Restore the selection
19456
+ self.moveToBookmark(bookmark);
19123
19457
 
19124
- [
19125
- 'ephox.sand.util.Global'
19126
- ],
19458
+ // Since the range has moved we need to re-get it
19459
+ ieRange = selection.getRng();
19127
19460
 
19128
- function (Global) {
19129
- /*
19130
- * MDN says (yes) for IE, but it's undefined on IE8
19131
- */
19132
- var node = function () {
19133
- var f = Global.getOrDie('Node');
19134
- return f;
19135
- };
19461
+ // Find start point
19462
+ findEndPoint(true);
19136
19463
 
19137
- /*
19138
- * Most of numerosity doesn't alter the methods on the object.
19139
- * We're making an exception for Node, because bitwise and is so easy to get wrong.
19140
- *
19141
- * Might be nice to ADT this at some point instead of having individual methods.
19142
- */
19464
+ // Find end point if needed
19465
+ if (!collapsed) {
19466
+ findEndPoint();
19467
+ }
19468
+ } else {
19469
+ throw ex; // Throw other errors
19470
+ }
19471
+ }
19143
19472
 
19144
- var compareDocumentPosition = function (a, b, match) {
19145
- // Returns: 0 if e1 and e2 are the same node, or a bitmask comparing the positions
19146
- // of nodes e1 and e2 in their documents. See the URL below for bitmask interpretation
19147
- // https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition
19148
- return (a.compareDocumentPosition(b) & match) !== 0;
19149
- };
19473
+ return domRange;
19474
+ }
19150
19475
 
19151
- var documentPositionPreceding = function (a, b) {
19152
- return compareDocumentPosition(a, b, node().DOCUMENT_POSITION_PRECEDING);
19153
- };
19476
+ this.getBookmark = function (type) {
19477
+ var rng = selection.getRng(), bookmark = {};
19154
19478
 
19155
- var documentPositionContainedBy = function (a, b) {
19156
- return compareDocumentPosition(a, b, node().DOCUMENT_POSITION_CONTAINED_BY);
19157
- };
19479
+ function getIndexes(node) {
19480
+ var parent, root, children, i, indexes = [];
19158
19481
 
19159
- return {
19160
- documentPositionPreceding: documentPositionPreceding,
19161
- documentPositionContainedBy: documentPositionContainedBy
19162
- };
19163
- }
19164
- );
19165
- define(
19166
- 'ephox.katamari.api.Thunk',
19482
+ parent = node.parentNode;
19483
+ root = dom.getRoot().parentNode;
19167
19484
 
19168
- [
19169
- ],
19485
+ while (parent != root && parent.nodeType !== 9) {
19486
+ children = parent.children;
19170
19487
 
19171
- function () {
19488
+ i = children.length;
19489
+ while (i--) {
19490
+ if (node === children[i]) {
19491
+ indexes.push(i);
19492
+ break;
19493
+ }
19494
+ }
19172
19495
 
19173
- var cached = function (f) {
19174
- var called = false;
19175
- var r;
19176
- return function() {
19177
- if (!called) {
19178
- called = true;
19179
- r = f.apply(null, arguments);
19496
+ node = parent;
19497
+ parent = parent.parentNode;
19498
+ }
19499
+
19500
+ return indexes;
19180
19501
  }
19181
- return r;
19182
- };
19183
- };
19184
19502
 
19185
- return {
19186
- cached: cached
19187
- };
19188
- }
19189
- );
19503
+ function getBookmarkEndPoint(start) {
19504
+ var position;
19190
19505
 
19191
- defineGlobal("global!Number", Number);
19192
- define(
19193
- 'ephox.sand.detect.Version',
19506
+ position = getPosition(rng, start);
19507
+ if (position) {
19508
+ return {
19509
+ position: position.position,
19510
+ offset: position.offset,
19511
+ indexes: getIndexes(position.node),
19512
+ inside: position.inside
19513
+ };
19514
+ }
19515
+ }
19194
19516
 
19195
- [
19196
- 'ephox.katamari.api.Arr',
19197
- 'global!Number',
19198
- 'global!String'
19199
- ],
19517
+ // Non ubstructive bookmark
19518
+ if (type === 2) {
19519
+ // Handle text selection
19520
+ if (!rng.item) {
19521
+ bookmark.start = getBookmarkEndPoint(true);
19200
19522
 
19201
- function (Arr, Number, String) {
19202
- var firstMatch = function (regexes, s) {
19203
- for (var i = 0; i < regexes.length; i++) {
19204
- var x = regexes[i];
19205
- if (x.test(s)) return x;
19206
- }
19207
- return undefined;
19208
- };
19523
+ if (!selection.isCollapsed()) {
19524
+ bookmark.end = getBookmarkEndPoint();
19525
+ }
19526
+ } else {
19527
+ bookmark.start = { ctrl: true, indexes: getIndexes(rng.item(0)) };
19528
+ }
19529
+ }
19209
19530
 
19210
- var find = function (regexes, agent) {
19211
- var r = firstMatch(regexes, agent);
19212
- if (!r) return { major : 0, minor : 0 };
19213
- var group = function(i) {
19214
- return Number(agent.replace(r, '$' + i));
19531
+ return bookmark;
19215
19532
  };
19216
- return nu(group(1), group(2));
19217
- };
19218
19533
 
19219
- var detect = function (versionRegexes, agent) {
19220
- var cleanedAgent = String(agent).toLowerCase();
19534
+ this.moveToBookmark = function (bookmark) {
19535
+ var rng, body = dom.doc.body;
19221
19536
 
19222
- if (versionRegexes.length === 0) return unknown();
19223
- return find(versionRegexes, cleanedAgent);
19224
- };
19537
+ function resolveIndexes(indexes) {
19538
+ var node, i, idx, children;
19225
19539
 
19226
- var unknown = function () {
19227
- return nu(0, 0);
19228
- };
19540
+ node = dom.getRoot();
19541
+ for (i = indexes.length - 1; i >= 0; i--) {
19542
+ children = node.children;
19543
+ idx = indexes[i];
19229
19544
 
19230
- var nu = function (major, minor) {
19231
- return { major: major, minor: minor };
19232
- };
19545
+ if (idx <= children.length - 1) {
19546
+ node = children[idx];
19547
+ }
19548
+ }
19233
19549
 
19234
- return {
19235
- nu: nu,
19236
- detect: detect,
19237
- unknown: unknown
19238
- };
19239
- }
19240
- );
19241
- define(
19242
- 'ephox.sand.core.Browser',
19550
+ return node;
19551
+ }
19243
19552
 
19244
- [
19245
- 'ephox.katamari.api.Fun',
19246
- 'ephox.sand.detect.Version'
19247
- ],
19553
+ function setBookmarkEndPoint(start) {
19554
+ var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset;
19248
19555
 
19249
- function (Fun, Version) {
19250
- var edge = 'Edge';
19251
- var chrome = 'Chrome';
19252
- var ie = 'IE';
19253
- var opera = 'Opera';
19254
- var firefox = 'Firefox';
19255
- var safari = 'Safari';
19556
+ if (endPoint) {
19557
+ moveLeft = endPoint.position > 0;
19256
19558
 
19257
- var isBrowser = function (name, current) {
19258
- return function () {
19259
- return current === name;
19260
- };
19261
- };
19559
+ moveRng = body.createTextRange();
19560
+ moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
19262
19561
 
19263
- var unknown = function () {
19264
- return nu({
19265
- current: undefined,
19266
- version: Version.unknown()
19267
- });
19268
- };
19562
+ offset = endPoint.offset;
19563
+ if (offset !== undef) {
19564
+ moveRng.collapse(endPoint.inside || moveLeft);
19565
+ moveRng.moveStart('character', moveLeft ? -offset : offset);
19566
+ } else {
19567
+ moveRng.collapse(start);
19568
+ }
19269
19569
 
19270
- var nu = function (info) {
19271
- var current = info.current;
19272
- var version = info.version;
19570
+ rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
19273
19571
 
19274
- return {
19275
- current: current,
19276
- version: version,
19572
+ if (start) {
19573
+ rng.collapse(true);
19574
+ }
19575
+ }
19576
+ }
19277
19577
 
19278
- // INVESTIGATE: Rename to Edge ?
19279
- isEdge: isBrowser(edge, current),
19280
- isChrome: isBrowser(chrome, current),
19281
- // NOTE: isIe just looks too weird
19282
- isIE: isBrowser(ie, current),
19283
- isOpera: isBrowser(opera, current),
19284
- isFirefox: isBrowser(firefox, current),
19285
- isSafari: isBrowser(safari, current)
19578
+ if (bookmark.start) {
19579
+ if (bookmark.start.ctrl) {
19580
+ rng = body.createControlRange();
19581
+ rng.addElement(resolveIndexes(bookmark.start.indexes));
19582
+ rng.select();
19583
+ } else {
19584
+ rng = body.createTextRange();
19585
+ setBookmarkEndPoint(true);
19586
+ setBookmarkEndPoint();
19587
+ rng.select();
19588
+ }
19589
+ }
19286
19590
  };
19287
- };
19288
19591
 
19289
- return {
19290
- unknown: unknown,
19291
- nu: nu,
19292
- edge: Fun.constant(edge),
19293
- chrome: Fun.constant(chrome),
19294
- ie: Fun.constant(ie),
19295
- opera: Fun.constant(opera),
19296
- firefox: Fun.constant(firefox),
19297
- safari: Fun.constant(safari)
19298
- };
19299
- }
19300
- );
19301
- define(
19302
- 'ephox.sand.core.OperatingSystem',
19592
+ this.addRange = function (rng) {
19593
+ var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
19594
+ doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
19303
19595
 
19304
- [
19305
- 'ephox.katamari.api.Fun',
19306
- 'ephox.sand.detect.Version'
19307
- ],
19596
+ function setEndPoint(start) {
19597
+ var container, offset, marker, tmpRng, nodes;
19308
19598
 
19309
- function (Fun, Version) {
19310
- var windows = 'Windows';
19311
- var ios = 'iOS';
19312
- var android = 'Android';
19313
- var linux = 'Linux';
19314
- var osx = 'OSX';
19315
- var solaris = 'Solaris';
19316
- var freebsd = 'FreeBSD';
19599
+ marker = dom.create('a');
19600
+ container = start ? startContainer : endContainer;
19601
+ offset = start ? startOffset : endOffset;
19602
+ tmpRng = ieRng.duplicate();
19317
19603
 
19318
- // Though there is a bit of dupe with this and Browser, trying to
19319
- // reuse code makes it much harder to follow and change.
19320
- var isOS = function (name, current) {
19321
- return function () {
19322
- return current === name;
19323
- };
19324
- };
19604
+ if (container == doc || container == doc.documentElement) {
19605
+ container = body;
19606
+ offset = 0;
19607
+ }
19325
19608
 
19326
- var unknown = function () {
19327
- return nu({
19328
- current: undefined,
19329
- version: Version.unknown()
19330
- });
19331
- };
19609
+ if (container.nodeType == 3) {
19610
+ container.parentNode.insertBefore(marker, container);
19611
+ tmpRng.moveToElementText(marker);
19612
+ tmpRng.moveStart('character', offset);
19613
+ dom.remove(marker);
19614
+ ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
19615
+ } else {
19616
+ nodes = container.childNodes;
19332
19617
 
19333
- var nu = function (info) {
19334
- var current = info.current;
19335
- var version = info.version;
19618
+ if (nodes.length) {
19619
+ if (offset >= nodes.length) {
19620
+ dom.insertAfter(marker, nodes[nodes.length - 1]);
19621
+ } else {
19622
+ container.insertBefore(marker, nodes[offset]);
19623
+ }
19336
19624
 
19337
- return {
19338
- current: current,
19339
- version: version,
19625
+ tmpRng.moveToElementText(marker);
19626
+ } else if (container.canHaveHTML) {
19627
+ // Empty node selection for example <div>|</div>
19628
+ // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
19629
+ container.innerHTML = '<span>&#xFEFF;</span>';
19630
+ marker = container.firstChild;
19631
+ tmpRng.moveToElementText(marker);
19632
+ tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
19633
+ }
19340
19634
 
19341
- isWindows: isOS(windows, current),
19342
- // TODO: Fix capitalisation
19343
- isiOS: isOS(ios, current),
19344
- isAndroid: isOS(android, current),
19345
- isOSX: isOS(osx, current),
19346
- isLinux: isOS(linux, current),
19347
- isSolaris: isOS(solaris, current),
19348
- isFreeBSD: isOS(freebsd, current)
19349
- };
19350
- };
19635
+ ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
19636
+ dom.remove(marker);
19637
+ }
19638
+ }
19639
+
19640
+ // Setup some shorter versions
19641
+ startContainer = rng.startContainer;
19642
+ startOffset = rng.startOffset;
19643
+ endContainer = rng.endContainer;
19644
+ endOffset = rng.endOffset;
19645
+ ieRng = body.createTextRange();
19646
+
19647
+ // If single element selection then try making a control selection out of it
19648
+ if (startContainer == endContainer && startContainer.nodeType == 1) {
19649
+ // Trick to place the caret inside an empty block element like <p></p>
19650
+ if (startOffset == endOffset && !startContainer.hasChildNodes()) {
19651
+ if (startContainer.canHaveHTML) {
19652
+ // Check if previous sibling is an empty block if it is then we need to render it
19653
+ // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
19654
+ // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
19655
+ sibling = startContainer.previousSibling;
19656
+ if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
19657
+ sibling.innerHTML = '&#xFEFF;';
19658
+ } else {
19659
+ sibling = null;
19660
+ }
19351
19661
 
19352
- return {
19353
- unknown: unknown,
19354
- nu: nu,
19662
+ startContainer.innerHTML = '<span>&#xFEFF;</span><span>&#xFEFF;</span>';
19663
+ ieRng.moveToElementText(startContainer.lastChild);
19664
+ ieRng.select();
19665
+ dom.doc.selection.clear();
19666
+ startContainer.innerHTML = '';
19355
19667
 
19356
- windows: Fun.constant(windows),
19357
- ios: Fun.constant(ios),
19358
- android: Fun.constant(android),
19359
- linux: Fun.constant(linux),
19360
- osx: Fun.constant(osx),
19361
- solaris: Fun.constant(solaris),
19362
- freebsd: Fun.constant(freebsd)
19363
- };
19364
- }
19365
- );
19366
- define(
19367
- 'ephox.sand.detect.DeviceType',
19668
+ if (sibling) {
19669
+ sibling.innerHTML = '';
19670
+ }
19671
+ return;
19672
+ }
19368
19673
 
19369
- [
19370
- 'ephox.katamari.api.Fun'
19371
- ],
19674
+ startOffset = dom.nodeIndex(startContainer);
19675
+ startContainer = startContainer.parentNode;
19676
+ }
19372
19677
 
19373
- function (Fun) {
19374
- return function (os, browser, userAgent) {
19375
- var isiPad = os.isiOS() && /ipad/i.test(userAgent) === true;
19376
- var isiPhone = os.isiOS() && !isiPad;
19377
- var isAndroid3 = os.isAndroid() && os.version.major === 3;
19378
- var isAndroid4 = os.isAndroid() && os.version.major === 4;
19379
- var isTablet = isiPad || isAndroid3 || ( isAndroid4 && /mobile/i.test(userAgent) === true );
19380
- var isTouch = os.isiOS() || os.isAndroid();
19381
- var isPhone = isTouch && !isTablet;
19678
+ if (startOffset == endOffset - 1) {
19679
+ try {
19680
+ ctrlElm = startContainer.childNodes[startOffset];
19681
+ ctrlRng = body.createControlRange();
19682
+ ctrlRng.addElement(ctrlElm);
19683
+ ctrlRng.select();
19382
19684
 
19383
- var iOSwebview = browser.isSafari() && os.isiOS() && /safari/i.test(userAgent) === false;
19685
+ // Check if the range produced is on the correct element and is a control range
19686
+ // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
19687
+ nativeRng = selection.getRng();
19688
+ if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
19689
+ return;
19690
+ }
19691
+ } catch (ex) {
19692
+ // Ignore
19693
+ }
19694
+ }
19695
+ }
19384
19696
 
19385
- return {
19386
- isiPad : Fun.constant(isiPad),
19387
- isiPhone: Fun.constant(isiPhone),
19388
- isTablet: Fun.constant(isTablet),
19389
- isPhone: Fun.constant(isPhone),
19390
- isTouch: Fun.constant(isTouch),
19391
- isAndroid: os.isAndroid,
19392
- isiOS: os.isiOS,
19393
- isWebView: Fun.constant(iOSwebview)
19697
+ // Set start/end point of selection
19698
+ setEndPoint(true);
19699
+ setEndPoint();
19700
+
19701
+ // Select the new range and scroll it into view
19702
+ ieRng.select();
19394
19703
  };
19395
- };
19704
+
19705
+ // Expose range method
19706
+ this.getRangeAt = getRange;
19707
+ }
19708
+
19709
+ return Selection;
19396
19710
  }
19397
19711
  );
19712
+
19398
19713
  define(
19399
- 'ephox.sand.detect.UaString',
19714
+ 'ephox.katamari.api.Type',
19400
19715
 
19401
19716
  [
19402
- 'ephox.katamari.api.Arr',
19403
- 'ephox.sand.detect.Version',
19717
+ 'global!Array',
19404
19718
  'global!String'
19405
19719
  ],
19406
19720
 
19407
- function (Arr, Version, String) {
19408
- var detect = function (candidates, userAgent) {
19409
- var agent = String(userAgent).toLowerCase();
19410
- return Arr.find(candidates, function (candidate) {
19411
- return candidate.search(agent);
19412
- });
19413
- };
19414
-
19415
- // They (browser and os) are the same at the moment, but they might
19416
- // not stay that way.
19417
- var detectBrowser = function (browsers, userAgent) {
19418
- return detect(browsers, userAgent).map(function (browser) {
19419
- var version = Version.detect(browser.versionRegexes, userAgent);
19420
- return {
19421
- current: browser.name,
19422
- version: version
19423
- };
19424
- });
19721
+ function (Array, String) {
19722
+ var typeOf = function(x) {
19723
+ if (x === null) return 'null';
19724
+ var t = typeof x;
19725
+ if (t === 'object' && Array.prototype.isPrototypeOf(x)) return 'array';
19726
+ if (t === 'object' && String.prototype.isPrototypeOf(x)) return 'string';
19727
+ return t;
19425
19728
  };
19426
19729
 
19427
- var detectOs = function (oses, userAgent) {
19428
- return detect(oses, userAgent).map(function (os) {
19429
- var version = Version.detect(os.versionRegexes, userAgent);
19430
- return {
19431
- current: os.name,
19432
- version: version
19433
- };
19434
- });
19730
+ var isType = function (type) {
19731
+ return function (value) {
19732
+ return typeOf(value) === type;
19733
+ };
19435
19734
  };
19436
19735
 
19437
19736
  return {
19438
- detectBrowser: detectBrowser,
19439
- detectOs: detectOs
19737
+ isString: isType('string'),
19738
+ isObject: isType('object'),
19739
+ isArray: isType('array'),
19740
+ isNull: isType('null'),
19741
+ isBoolean: isType('boolean'),
19742
+ isUndefined: isType('undefined'),
19743
+ isFunction: isType('function'),
19744
+ isNumber: isType('number')
19440
19745
  };
19441
19746
  }
19442
19747
  );
19443
- define(
19444
- 'ephox.katamari.str.StrAppend',
19445
-
19446
- [
19447
-
19448
- ],
19449
19748
 
19450
- function () {
19451
- var addToStart = function (str, prefix) {
19452
- return prefix + str;
19453
- };
19454
-
19455
- var addToEnd = function (str, suffix) {
19456
- return str + suffix;
19457
- };
19458
19749
 
19459
- var removeFromStart = function (str, numChars) {
19460
- return str.substring(numChars);
19461
- };
19462
-
19463
- var removeFromEnd = function (str, numChars) {
19464
- return str.substring(0, str.length - numChars);
19465
- };
19466
-
19467
- return {
19468
- addToStart: addToStart,
19469
- addToEnd: addToEnd,
19470
- removeFromStart: removeFromStart,
19471
- removeFromEnd: removeFromEnd
19472
- };
19473
- }
19474
- );
19475
19750
  define(
19476
- 'ephox.katamari.str.StringParts',
19751
+ 'ephox.katamari.data.Immutable',
19477
19752
 
19478
19753
  [
19479
- 'ephox.katamari.api.Option',
19754
+ 'ephox.katamari.api.Arr',
19755
+ 'ephox.katamari.api.Fun',
19756
+ 'global!Array',
19480
19757
  'global!Error'
19481
19758
  ],
19482
19759
 
19483
- function (Option, Error) {
19484
- /** Return the first 'count' letters from 'str'.
19485
- - * e.g. first("abcde", 2) === "ab"
19486
- - */
19487
- var first = function(str, count) {
19488
- return str.substr(0, count);
19489
- };
19490
-
19491
- /** Return the last 'count' letters from 'str'.
19492
- * e.g. last("abcde", 2) === "de"
19493
- */
19494
- var last = function(str, count) {
19495
- return str.substr(str.length - count, str.length);
19496
- };
19497
-
19498
- var head = function(str) {
19499
- return str === '' ? Option.none() : Option.some(str.substr(0, 1));
19500
- };
19760
+ function (Arr, Fun, Array, Error) {
19761
+ return function () {
19762
+ var fields = arguments;
19763
+ return function(/* values */) {
19764
+ // Don't use array slice(arguments), makes the whole function unoptimisable on Chrome
19765
+ var values = new Array(arguments.length);
19766
+ for (var i = 0; i < values.length; i++) values[i] = arguments[i];
19501
19767
 
19502
- var tail = function(str) {
19503
- return str === '' ? Option.none() : Option.some(str.substring(1));
19504
- };
19768
+ if (fields.length !== values.length)
19769
+ throw new Error('Wrong number of arguments to struct. Expected "[' + fields.length + ']", got ' + values.length + ' arguments');
19505
19770
 
19506
- return {
19507
- first: first,
19508
- last: last,
19509
- head: head,
19510
- tail: tail
19771
+ var struct = {};
19772
+ Arr.each(fields, function (name, i) {
19773
+ struct[name] = Fun.constant(values[i]);
19774
+ });
19775
+ return struct;
19776
+ };
19511
19777
  };
19512
19778
  }
19513
19779
  );
19780
+
19514
19781
  define(
19515
- 'ephox.katamari.api.Strings',
19782
+ 'ephox.katamari.api.Obj',
19516
19783
 
19517
19784
  [
19518
- 'ephox.katamari.str.StrAppend',
19519
- 'ephox.katamari.str.StringParts',
19520
- 'global!Error'
19785
+ 'ephox.katamari.api.Option',
19786
+ 'global!Object'
19521
19787
  ],
19522
19788
 
19523
- function (StrAppend, StringParts, Error) {
19524
- var checkRange = function(str, substr, start) {
19525
- if (substr === '') return true;
19526
- if (str.length < substr.length) return false;
19527
- var x = str.substr(start, start + substr.length);
19528
- return x === substr;
19529
- };
19530
-
19531
- /** Given a string and object, perform template-replacements on the string, as specified by the object.
19532
- * Any template fields of the form ${name} are replaced by the string or number specified as obj["name"]
19533
- * Based on Douglas Crockford's 'supplant' method for template-replace of strings. Uses different template format.
19534
- */
19535
- var supplant = function(str, obj) {
19536
- var isStringOrNumber = function(a) {
19537
- var t = typeof a;
19538
- return t === 'string' || t === 'number';
19539
- };
19789
+ function (Option, Object) {
19790
+ // There are many variations of Object iteration that are faster than the 'for-in' style:
19791
+ // http://jsperf.com/object-keys-iteration/107
19792
+ //
19793
+ // Use the native keys if it is available (IE9+), otherwise fall back to manually filtering
19794
+ var keys = (function () {
19795
+ var fastKeys = Object.keys;
19540
19796
 
19541
- return str.replace(/\${([^{}]*)}/g,
19542
- function (a, b) {
19543
- var value = obj[b];
19544
- return isStringOrNumber(value) ? value : a;
19797
+ // This technically means that 'each' and 'find' on IE8 iterate through the object twice.
19798
+ // This code doesn't run on IE8 much, so it's an acceptable tradeoff.
19799
+ // If it becomes a problem we can always duplicate the feature detection inside each and find as well.
19800
+ var slowKeys = function (o) {
19801
+ var r = [];
19802
+ for (var i in o) {
19803
+ if (o.hasOwnProperty(i)) {
19804
+ r.push(i);
19805
+ }
19545
19806
  }
19546
- );
19547
- };
19548
-
19549
- var removeLeading = function (str, prefix) {
19550
- return startsWith(str, prefix) ? StrAppend.removeFromStart(str, prefix.length) : str;
19551
- };
19552
-
19553
- var removeTrailing = function (str, prefix) {
19554
- return endsWith(str, prefix) ? StrAppend.removeFromEnd(str, prefix.length) : str;
19555
- };
19556
-
19557
- var ensureLeading = function (str, prefix) {
19558
- return startsWith(str, prefix) ? str : StrAppend.addToStart(str, prefix);
19559
- };
19560
-
19561
- var ensureTrailing = function (str, prefix) {
19562
- return endsWith(str, prefix) ? str : StrAppend.addToEnd(str, prefix);
19563
- };
19564
-
19565
- var contains = function(str, substr) {
19566
- return str.indexOf(substr) !== -1;
19567
- };
19568
-
19569
- var capitalize = function(str) {
19570
- return StringParts.head(str).bind(function (head) {
19571
- return StringParts.tail(str).map(function (tail) {
19572
- return head.toUpperCase() + tail;
19573
- });
19574
- }).getOr(str);
19575
- };
19576
-
19577
- /** Does 'str' start with 'prefix'?
19578
- * Note: all strings start with the empty string.
19579
- * More formally, for all strings x, startsWith(x, "").
19580
- * This is so that for all strings x and y, startsWith(y + x, y)
19581
- */
19582
- var startsWith = function(str, prefix) {
19583
- return checkRange(str, prefix, 0);
19584
- };
19807
+ return r;
19808
+ };
19585
19809
 
19586
- /** Does 'str' end with 'suffix'?
19587
- * Note: all strings end with the empty string.
19588
- * More formally, for all strings x, endsWith(x, "").
19589
- * This is so that for all strings x and y, endsWith(x + y, y)
19590
- */
19591
- var endsWith = function(str, suffix) {
19592
- return checkRange(str, suffix, str.length - suffix.length);
19593
- };
19810
+ return fastKeys === undefined ? slowKeys : fastKeys;
19811
+ })();
19594
19812
 
19595
-
19596
- /** removes all leading and trailing spaces */
19597
- var trim = function(str) {
19598
- return str.replace(/^\s+|\s+$/g, '');
19599
- };
19600
19813
 
19601
- var lTrim = function(str) {
19602
- return str.replace(/^\s+/g, '');
19814
+ var each = function (obj, f) {
19815
+ var props = keys(obj);
19816
+ for (var k = 0, len = props.length; k < len; k++) {
19817
+ var i = props[k];
19818
+ var x = obj[i];
19819
+ f(x, i, obj);
19820
+ }
19603
19821
  };
19604
19822
 
19605
- var rTrim = function(str) {
19606
- return str.replace(/\s+$/g, '');
19823
+ /** objectMap :: (JsObj(k, v), (v, k, JsObj(k, v) -> x)) -> JsObj(k, x) */
19824
+ var objectMap = function (obj, f) {
19825
+ return tupleMap(obj, function (x, i, obj) {
19826
+ return {
19827
+ k: i,
19828
+ v: f(x, i, obj)
19829
+ };
19830
+ });
19607
19831
  };
19608
19832
 
19609
- return {
19610
- supplant: supplant,
19611
- startsWith: startsWith,
19612
- removeLeading: removeLeading,
19613
- removeTrailing: removeTrailing,
19614
- ensureLeading: ensureLeading,
19615
- ensureTrailing: ensureTrailing,
19616
- endsWith: endsWith,
19617
- contains: contains,
19618
- trim: trim,
19619
- lTrim: lTrim,
19620
- rTrim: rTrim,
19621
- capitalize: capitalize
19833
+ /** tupleMap :: (JsObj(k, v), (v, k, JsObj(k, v) -> { k: x, v: y })) -> JsObj(x, y) */
19834
+ var tupleMap = function (obj, f) {
19835
+ var r = {};
19836
+ each(obj, function (x, i) {
19837
+ var tuple = f(x, i, obj);
19838
+ r[tuple.k] = tuple.v;
19839
+ });
19840
+ return r;
19622
19841
  };
19623
- }
19624
- );
19625
-
19626
- define(
19627
- 'ephox.sand.info.PlatformInfo',
19628
-
19629
- [
19630
- 'ephox.katamari.api.Fun',
19631
- 'ephox.katamari.api.Strings'
19632
- ],
19633
-
19634
- function (Fun, Strings) {
19635
- var normalVersionRegex = /.*?version\/\ ?([0-9]+)\.([0-9]+).*/;
19636
19842
 
19637
- var checkContains = function (target) {
19638
- return function (uastring) {
19639
- return Strings.contains(uastring, target);
19843
+ /** bifilter :: (JsObj(k, v), (v, k -> Bool)) -> { t: JsObj(k, v), f: JsObj(k, v) } */
19844
+ var bifilter = function (obj, pred) {
19845
+ var t = {};
19846
+ var f = {};
19847
+ each(obj, function(x, i) {
19848
+ var branch = pred(x, i) ? t : f;
19849
+ branch[i] = x;
19850
+ });
19851
+ return {
19852
+ t: t,
19853
+ f: f
19640
19854
  };
19641
19855
  };
19642
19856
 
19643
- var browsers = [
19644
- {
19645
- name : 'Edge',
19646
- versionRegexes: [/.*?edge\/ ?([0-9]+)\.([0-9]+)$/],
19647
- search: function (uastring) {
19648
- var monstrosity = Strings.contains(uastring, 'edge/') && Strings.contains(uastring, 'chrome') && Strings.contains(uastring, 'safari') && Strings.contains(uastring, 'applewebkit');
19649
- return monstrosity;
19650
- }
19651
- },
19652
- {
19653
- name : 'Chrome',
19654
- versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/, normalVersionRegex],
19655
- search : function (uastring) {
19656
- return Strings.contains(uastring, 'chrome') && !Strings.contains(uastring, 'chromeframe');
19657
- }
19658
- },
19659
- {
19660
- name : 'IE',
19661
- versionRegexes: [/.*?msie\ ?([0-9]+)\.([0-9]+).*/, /.*?rv:([0-9]+)\.([0-9]+).*/],
19662
- search: function (uastring) {
19663
- return Strings.contains(uastring, 'msie') || Strings.contains(uastring, 'trident');
19664
- }
19665
- },
19666
- // INVESTIGATE: Is this still the Opera user agent?
19667
- {
19668
- name : 'Opera',
19669
- versionRegexes: [normalVersionRegex, /.*?opera\/([0-9]+)\.([0-9]+).*/],
19670
- search : checkContains('opera')
19671
- },
19672
- {
19673
- name : 'Firefox',
19674
- versionRegexes: [/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/],
19675
- search : checkContains('firefox')
19676
- },
19677
- {
19678
- name : 'Safari',
19679
- versionRegexes: [normalVersionRegex, /.*?cpu os ([0-9]+)_([0-9]+).*/],
19680
- search : function (uastring) {
19681
- return (Strings.contains(uastring, 'safari') || Strings.contains(uastring, 'mobile/')) && Strings.contains(uastring, 'applewebkit');
19682
- }
19683
- }
19684
- ];
19685
-
19686
- var oses = [
19687
- {
19688
- name : 'Windows',
19689
- search : checkContains('win'),
19690
- versionRegexes: [/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/]
19691
- },
19692
- {
19693
- name : 'iOS',
19694
- search : function (uastring) {
19695
- return Strings.contains(uastring, 'iphone') || Strings.contains(uastring, 'ipad');
19696
- },
19697
- versionRegexes: [/.*?version\/\ ?([0-9]+)\.([0-9]+).*/, /.*cpu os ([0-9]+)_([0-9]+).*/, /.*cpu iphone os ([0-9]+)_([0-9]+).*/]
19698
- },
19699
- {
19700
- name : 'Android',
19701
- search : checkContains('android'),
19702
- versionRegexes: [/.*?android\ ?([0-9]+)\.([0-9]+).*/]
19703
- },
19704
- {
19705
- name : 'OSX',
19706
- search : checkContains('os x'),
19707
- versionRegexes: [/.*?os\ x\ ?([0-9]+)_([0-9]+).*/]
19708
- },
19709
- {
19710
- name : 'Linux',
19711
- search : checkContains('linux'),
19712
- versionRegexes: [ ]
19713
- },
19714
- { name : 'Solaris',
19715
- search : checkContains('sunos'),
19716
- versionRegexes: [ ]
19717
- },
19718
- {
19719
- name : 'FreeBSD',
19720
- search : checkContains('freebsd'),
19721
- versionRegexes: [ ]
19722
- }
19723
- ];
19724
-
19725
- return {
19726
- browsers: Fun.constant(browsers),
19727
- oses: Fun.constant(oses)
19857
+ /** mapToArray :: (JsObj(k, v), (v, k -> a)) -> [a] */
19858
+ var mapToArray = function (obj, f) {
19859
+ var r = [];
19860
+ each(obj, function(value, name) {
19861
+ r.push(f(value, name));
19862
+ });
19863
+ return r;
19728
19864
  };
19729
- }
19730
- );
19731
- define(
19732
- 'ephox.sand.core.PlatformDetection',
19733
-
19734
- [
19735
- 'ephox.sand.core.Browser',
19736
- 'ephox.sand.core.OperatingSystem',
19737
- 'ephox.sand.detect.DeviceType',
19738
- 'ephox.sand.detect.UaString',
19739
- 'ephox.sand.info.PlatformInfo'
19740
- ],
19741
-
19742
- function (Browser, OperatingSystem, DeviceType, UaString, PlatformInfo) {
19743
- var detect = function (userAgent) {
19744
- var browsers = PlatformInfo.browsers();
19745
- var oses = PlatformInfo.oses();
19746
-
19747
- var browser = UaString.detectBrowser(browsers, userAgent).fold(
19748
- Browser.unknown,
19749
- Browser.nu
19750
- );
19751
- var os = UaString.detectOs(oses, userAgent).fold(
19752
- OperatingSystem.unknown,
19753
- OperatingSystem.nu
19754
- );
19755
- var deviceType = DeviceType(os, browser, userAgent);
19756
19865
 
19757
- return {
19758
- browser: browser,
19759
- os: os,
19760
- deviceType: deviceType
19761
- };
19866
+ /** find :: (JsObj(k, v), (v, k, JsObj(k, v) -> Bool)) -> Option v */
19867
+ var find = function (obj, pred) {
19868
+ var props = keys(obj);
19869
+ for (var k = 0, len = props.length; k < len; k++) {
19870
+ var i = props[k];
19871
+ var x = obj[i];
19872
+ if (pred(x, i, obj)) {
19873
+ return Option.some(x);
19874
+ }
19875
+ }
19876
+ return Option.none();
19762
19877
  };
19763
19878
 
19764
- return {
19765
- detect: detect
19879
+ /** values :: JsObj(k, v) -> [v] */
19880
+ var values = function (obj) {
19881
+ return mapToArray(obj, function (v) {
19882
+ return v;
19883
+ });
19766
19884
  };
19767
- }
19768
- );
19769
- defineGlobal("global!navigator", navigator);
19770
- define(
19771
- 'ephox.sand.api.PlatformDetection',
19772
-
19773
- [
19774
- 'ephox.katamari.api.Thunk',
19775
- 'ephox.sand.core.PlatformDetection',
19776
- 'global!navigator'
19777
- ],
19778
19885
 
19779
- function (Thunk, PlatformDetection, navigator) {
19780
- var detect = Thunk.cached(function () {
19781
- var userAgent = navigator.userAgent;
19782
- return PlatformDetection.detect(userAgent);
19783
- });
19886
+ var size = function (obj) {
19887
+ return values(obj).length;
19888
+ };
19784
19889
 
19785
19890
  return {
19786
- detect: detect
19891
+ bifilter: bifilter,
19892
+ each: each,
19893
+ map: objectMap,
19894
+ mapToArray: mapToArray,
19895
+ tupleMap: tupleMap,
19896
+ find: find,
19897
+ keys: keys,
19898
+ values: values,
19899
+ size: size
19787
19900
  };
19788
19901
  }
19789
19902
  );
19790
- define("global!console", [], function () { if (typeof console === "undefined") console = { log: function () {} }; return console; });
19791
- defineGlobal("global!document", document);
19792
19903
  define(
19793
- 'ephox.sugar.api.node.Element',
19904
+ 'ephox.katamari.util.BagUtils',
19794
19905
 
19795
19906
  [
19796
- 'ephox.katamari.api.Fun',
19797
- 'global!Error',
19798
- 'global!console',
19799
- 'global!document'
19907
+ 'ephox.katamari.api.Arr',
19908
+ 'ephox.katamari.api.Type',
19909
+ 'global!Error'
19800
19910
  ],
19801
19911
 
19802
- function (Fun, Error, console, document) {
19803
- var fromHtml = function (html, scope) {
19804
- var doc = scope || document;
19805
- var div = doc.createElement('div');
19806
- div.innerHTML = html;
19807
- if (!div.hasChildNodes() || div.childNodes.length > 1) {
19808
- console.error('HTML does not have a single root node', html);
19809
- throw 'HTML must have a single root node';
19810
- }
19811
- return fromDom(div.childNodes[0]);
19912
+ function (Arr, Type, Error) {
19913
+ var sort = function (arr) {
19914
+ return arr.slice(0).sort();
19812
19915
  };
19813
19916
 
19814
- var fromTag = function (tag, scope) {
19815
- var doc = scope || document;
19816
- var node = doc.createElement(tag);
19817
- return fromDom(node);
19917
+ var reqMessage = function (required, keys) {
19918
+ throw new Error('All required keys (' + sort(required).join(', ') + ') were not specified. Specified keys were: ' + sort(keys).join(', ') + '.');
19818
19919
  };
19819
19920
 
19820
- var fromText = function (text, scope) {
19821
- var doc = scope || document;
19822
- var node = doc.createTextNode(text);
19823
- return fromDom(node);
19921
+ var unsuppMessage = function (unsupported) {
19922
+ throw new Error('Unsupported keys for object: ' + sort(unsupported).join(', '));
19824
19923
  };
19825
19924
 
19826
- var fromDom = function (node) {
19827
- if (node === null || node === undefined) throw new Error('Node cannot be null or undefined');
19828
- return {
19829
- dom: Fun.constant(node)
19830
- };
19925
+ var validateStrArr = function (label, array) {
19926
+ if (!Type.isArray(array)) throw new Error('The ' + label + ' fields must be an array. Was: ' + array + '.');
19927
+ Arr.each(array, function (a) {
19928
+ if (!Type.isString(a)) throw new Error('The value ' + a + ' in the ' + label + ' fields was not a string.');
19929
+ });
19831
19930
  };
19832
19931
 
19833
- return {
19834
- fromHtml: fromHtml,
19835
- fromTag: fromTag,
19836
- fromText: fromText,
19837
- fromDom: fromDom
19932
+ var invalidTypeMessage = function (incorrect, type) {
19933
+ throw new Error('All values need to be of type: ' + type + '. Keys (' + sort(incorrect).join(', ') + ') were not.');
19838
19934
  };
19839
- }
19840
- );
19841
-
19842
- define(
19843
- 'ephox.sugar.api.node.NodeTypes',
19844
19935
 
19845
- [
19936
+ var checkDupes = function (everything) {
19937
+ var sorted = sort(everything);
19938
+ var dupe = Arr.find(sorted, function (s, i) {
19939
+ return i < sorted.length -1 && s === sorted[i + 1];
19940
+ });
19846
19941
 
19847
- ],
19942
+ dupe.each(function (d) {
19943
+ throw new Error('The field: ' + d + ' occurs more than once in the combined fields: [' + sorted.join(', ') + '].');
19944
+ });
19945
+ };
19848
19946
 
19849
- function () {
19850
19947
  return {
19851
- ATTRIBUTE: 2,
19852
- CDATA_SECTION: 4,
19853
- COMMENT: 8,
19854
- DOCUMENT: 9,
19855
- DOCUMENT_TYPE: 10,
19856
- DOCUMENT_FRAGMENT: 11,
19857
- ELEMENT: 1,
19858
- TEXT: 3,
19859
- PROCESSING_INSTRUCTION: 7,
19860
- ENTITY_REFERENCE: 5,
19861
- ENTITY: 6,
19862
- NOTATION: 12
19948
+ sort: sort,
19949
+ reqMessage: reqMessage,
19950
+ unsuppMessage: unsuppMessage,
19951
+ validateStrArr: validateStrArr,
19952
+ invalidTypeMessage: invalidTypeMessage,
19953
+ checkDupes: checkDupes
19863
19954
  };
19864
19955
  }
19865
19956
  );
19866
19957
  define(
19867
- 'ephox.sugar.api.search.Selectors',
19958
+ 'ephox.katamari.data.MixedBag',
19868
19959
 
19869
19960
  [
19870
19961
  'ephox.katamari.api.Arr',
19962
+ 'ephox.katamari.api.Fun',
19963
+ 'ephox.katamari.api.Obj',
19871
19964
  'ephox.katamari.api.Option',
19872
- 'ephox.sugar.api.node.Element',
19873
- 'ephox.sugar.api.node.NodeTypes',
19965
+ 'ephox.katamari.util.BagUtils',
19874
19966
  'global!Error',
19875
- 'global!document'
19967
+ 'global!Object'
19876
19968
  ],
19877
19969
 
19878
- function (Arr, Option, Element, NodeTypes, Error, document) {
19879
- /*
19880
- * There's a lot of code here; the aim is to allow the browser to optimise constant comparisons,
19881
- * instead of doing object lookup feature detection on every call
19882
- */
19883
- var STANDARD = 0;
19884
- var MSSTANDARD = 1;
19885
- var WEBKITSTANDARD = 2;
19886
- var FIREFOXSTANDARD = 3;
19970
+ function (Arr, Fun, Obj, Option, BagUtils, Error, Object) {
19971
+
19972
+ return function (required, optional) {
19973
+ var everything = required.concat(optional);
19974
+ if (everything.length === 0) throw new Error('You must specify at least one required or optional field.');
19887
19975
 
19888
- var selectorType = (function () {
19889
- var test = document.createElement('span');
19890
- // As of Chrome 34 / Safari 7.1 / FireFox 34, everyone except IE has the unprefixed function.
19891
- // Still check for the others, but do it last.
19892
- return test.matches !== undefined ? STANDARD :
19893
- test.msMatchesSelector !== undefined ? MSSTANDARD :
19894
- test.webkitMatchesSelector !== undefined ? WEBKITSTANDARD :
19895
- test.mozMatchesSelector !== undefined ? FIREFOXSTANDARD :
19896
- -1;
19897
- })();
19976
+ BagUtils.validateStrArr('required', required);
19977
+ BagUtils.validateStrArr('optional', optional);
19898
19978
 
19979
+ BagUtils.checkDupes(everything);
19899
19980
 
19900
- var ELEMENT = NodeTypes.ELEMENT;
19901
- var DOCUMENT = NodeTypes.DOCUMENT;
19981
+ return function (obj) {
19982
+ var keys = Obj.keys(obj);
19902
19983
 
19903
- var is = function (element, selector) {
19904
- var elem = element.dom();
19905
- if (elem.nodeType !== ELEMENT) return false; // documents have querySelector but not matches
19984
+ // Ensure all required keys are present.
19985
+ var allReqd = Arr.forall(required, function (req) {
19986
+ return Arr.contains(keys, req);
19987
+ });
19906
19988
 
19907
- // As of Chrome 34 / Safari 7.1 / FireFox 34, everyone except IE has the unprefixed function.
19908
- // Still check for the others, but do it last.
19909
- else if (selectorType === STANDARD) return elem.matches(selector);
19910
- else if (selectorType === MSSTANDARD) return elem.msMatchesSelector(selector);
19911
- else if (selectorType === WEBKITSTANDARD) return elem.webkitMatchesSelector(selector);
19912
- else if (selectorType === FIREFOXSTANDARD) return elem.mozMatchesSelector(selector);
19913
- else throw new Error('Browser lacks native selectors'); // unfortunately we can't throw this on startup :(
19914
- };
19989
+ if (! allReqd) BagUtils.reqMessage(required, keys);
19915
19990
 
19916
- var bypassSelector = function (dom) {
19917
- // Only elements and documents support querySelector
19918
- return dom.nodeType !== ELEMENT && dom.nodeType !== DOCUMENT ||
19919
- // IE fix for complex queries on empty nodes: http://jsfiddle.net/spyder/fv9ptr5L/
19920
- dom.childElementCount === 0;
19921
- };
19991
+ var unsupported = Arr.filter(keys, function (key) {
19992
+ return !Arr.contains(everything, key);
19993
+ });
19922
19994
 
19923
- var all = function (selector, scope) {
19924
- var base = scope === undefined ? document : scope.dom();
19925
- return bypassSelector(base) ? [] : Arr.map(base.querySelectorAll(selector), Element.fromDom);
19926
- };
19995
+ if (unsupported.length > 0) BagUtils.unsuppMessage(unsupported);
19927
19996
 
19928
- var one = function (selector, scope) {
19929
- var base = scope === undefined ? document : scope.dom();
19930
- return bypassSelector(base) ? Option.none() : Option.from(base.querySelector(selector)).map(Element.fromDom);
19931
- };
19997
+ var r = {};
19998
+ Arr.each(required, function (req) {
19999
+ r[req] = Fun.constant(obj[req]);
20000
+ });
19932
20001
 
19933
- return {
19934
- all: all,
19935
- is: is,
19936
- one: one
20002
+ Arr.each(optional, function (opt) {
20003
+ r[opt] = Fun.constant(Object.prototype.hasOwnProperty.call(obj, opt) ? Option.some(obj[opt]): Option.none());
20004
+ });
20005
+
20006
+ return r;
20007
+ };
19937
20008
  };
19938
20009
  }
19939
20010
  );
19940
-
19941
20011
  define(
19942
- 'ephox.sugar.api.dom.Compare',
20012
+ 'ephox.katamari.api.Struct',
19943
20013
 
19944
20014
  [
19945
- 'ephox.katamari.api.Arr',
19946
- 'ephox.katamari.api.Fun',
19947
- 'ephox.sand.api.Node',
19948
- 'ephox.sand.api.PlatformDetection',
19949
- 'ephox.sugar.api.search.Selectors'
20015
+ 'ephox.katamari.data.Immutable',
20016
+ 'ephox.katamari.data.MixedBag'
19950
20017
  ],
19951
20018
 
19952
- function (Arr, Fun, Node, PlatformDetection, Selectors) {
19953
-
19954
- var eq = function (e1, e2) {
19955
- return e1.dom() === e2.dom();
20019
+ function (Immutable, MixedBag) {
20020
+ return {
20021
+ immutable: Immutable,
20022
+ immutableBag: MixedBag
19956
20023
  };
20024
+ }
20025
+ );
19957
20026
 
19958
- var isEqualNode = function (e1, e2) {
19959
- return e1.dom().isEqualNode(e2.dom());
19960
- };
20027
+ define(
20028
+ 'ephox.sugar.alien.Recurse',
19961
20029
 
19962
- var member = function (element, elements) {
19963
- return Arr.exists(elements, Fun.curry(eq, element));
19964
- };
20030
+ [
19965
20031
 
19966
- // DOM contains() method returns true if e1===e2, we define our contains() to return false (a node does not contain itself).
19967
- var regularContains = function (e1, e2) {
19968
- var d1 = e1.dom(), d2 = e2.dom();
19969
- return d1 === d2 ? false : d1.contains(d2);
19970
- };
20032
+ ],
19971
20033
 
19972
- var ieContains = function (e1, e2) {
19973
- // IE only implements the contains() method for Element nodes.
19974
- // It fails for Text nodes, so implement it using compareDocumentPosition()
19975
- // https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect
19976
- // Note that compareDocumentPosition returns CONTAINED_BY if 'e2 *is_contained_by* e1':
19977
- // Also, compareDocumentPosition defines a node containing itself as false.
19978
- return Node.documentPositionContainedBy(e1.dom(), e2.dom());
19979
- };
20034
+ function () {
20035
+ /**
20036
+ * Applies f repeatedly until it completes (by returning Option.none()).
20037
+ *
20038
+ * Normally would just use recursion, but JavaScript lacks tail call optimisation.
20039
+ *
20040
+ * This is what recursion looks like when manually unravelled :)
20041
+ */
20042
+ var toArray = function (target, f) {
20043
+ var r = [];
19980
20044
 
19981
- var browser = PlatformDetection.detect().browser;
20045
+ var recurse = function (e) {
20046
+ r.push(e);
20047
+ return f(e);
20048
+ };
19982
20049
 
19983
- // Returns: true if node e1 contains e2, otherwise false.
19984
- // (returns false if e1===e2: A node does not contain itself).
19985
- var contains = browser.isIE() ? ieContains : regularContains;
20050
+ var cur = f(target);
20051
+ do {
20052
+ cur = cur.bind(recurse);
20053
+ } while (cur.isSome());
19986
20054
 
19987
- return {
19988
- eq: eq,
19989
- isEqualNode: isEqualNode,
19990
- member: member,
19991
- contains: contains,
20055
+ return r;
20056
+ };
19992
20057
 
19993
- // Only used by DomUniverse. Remove (or should Selectors.is move here?)
19994
- is: Selectors.is
20058
+ return {
20059
+ toArray: toArray
19995
20060
  };
19996
20061
  }
19997
20062
  );
19998
-
19999
20063
  define(
20000
20064
  'ephox.sugar.api.search.Traverse',
20001
20065
 
@@ -20891,6 +20955,8 @@ define(
20891
20955
  define(
20892
20956
  'tinymce.core.dom.Selection',
20893
20957
  [
20958
+ 'ephox.sugar.api.dom.Compare',
20959
+ 'ephox.sugar.api.node.Element',
20894
20960
  'tinymce.core.caret.CaretPosition',
20895
20961
  'tinymce.core.dom.BookmarkManager',
20896
20962
  'tinymce.core.dom.ControlSelection',
@@ -20904,18 +20970,24 @@ define(
20904
20970
  'tinymce.core.text.Zwsp',
20905
20971
  'tinymce.core.util.Tools'
20906
20972
  ],
20907
- function (CaretPosition, BookmarkManager, ControlSelection, NodeType, RangeUtils, ScrollIntoView, TreeWalker, TridentSelection, Env, FragmentReader, Zwsp, Tools) {
20973
+ function (
20974
+ Compare, Element, CaretPosition, BookmarkManager, ControlSelection, NodeType, RangeUtils, ScrollIntoView, TreeWalker, TridentSelection, Env, FragmentReader,
20975
+ Zwsp, Tools
20976
+ ) {
20908
20977
  var each = Tools.each, trim = Tools.trim;
20909
20978
  var isIE = Env.ie;
20910
20979
 
20980
+ var isAttachedToDom = function (node) {
20981
+ return !!(node && node.ownerDocument) && Compare.contains(Element.fromDom(node.ownerDocument), Element.fromDom(node));
20982
+ };
20983
+
20911
20984
  var isValidRange = function (rng) {
20912
20985
  if (!rng) {
20913
20986
  return false;
20914
20987
  } else if (rng.select) { // Native IE range still produced by placeCaretAt
20915
20988
  return true;
20916
20989
  } else {
20917
- var sc = rng.startContainer, ec = rng.endContainer;
20918
- return !!(sc && sc.parentNode && ec && ec.parentNode);
20990
+ return isAttachedToDom(rng.startContainer) && isAttachedToDom(rng.endContainer);
20919
20991
  }
20920
20992
  };
20921
20993
 
@@ -37950,20 +38022,20 @@ define(
37950
38022
  */
37951
38023
 
37952
38024
  /**
37953
- * Contains all logic for handling of keyboard shortcuts.
38025
+ * Contains logic for handling keyboard shortcuts.
37954
38026
  *
37955
38027
  * @class tinymce.Shortcuts
37956
38028
  * @example
37957
- * editor.shortcuts.add('ctrl+a', function() {});
37958
- * editor.shortcuts.add('meta+a', function() {}); // "meta" maps to Command on Mac and Ctrl on PC
37959
- * editor.shortcuts.add('ctrl+alt+a', function() {});
37960
- * editor.shortcuts.add('access+a', function() {}); // "access" maps to ctrl+alt on Mac and shift+alt on PC
38029
+ * editor.shortcuts.add('ctrl+a', "description of the shortcut", function() {});
38030
+ * editor.shortcuts.add('meta+a', "description of the shortcut", function() {}); // "meta" maps to Command on Mac and Ctrl on PC
38031
+ * editor.shortcuts.add('ctrl+alt+a', "description of the shortcut", function() {});
38032
+ * editor.shortcuts.add('access+a', "description of the shortcut", function() {}); // "access" maps to ctrl+alt on Mac and shift+alt on PC
37961
38033
  */
37962
38034
  define(
37963
38035
  'tinymce.core.Shortcuts',
37964
38036
  [
37965
- "tinymce.core.util.Tools",
37966
- "tinymce.core.Env"
38037
+ 'tinymce.core.util.Tools',
38038
+ 'tinymce.core.Env'
37967
38039
  ],
37968
38040
  function (Tools, Env) {
37969
38041
  var each = Tools.each, explode = Tools.explode;
@@ -38160,6 +38232,91 @@ define(
38160
38232
  }
38161
38233
  );
38162
38234
 
38235
+ /**
38236
+ * DefaultSettings.js
38237
+ *
38238
+ * Released under LGPL License.
38239
+ * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
38240
+ *
38241
+ * License: http://www.tinymce.com/license
38242
+ * Contributing: http://www.tinymce.com/contributing
38243
+ */
38244
+
38245
+ define(
38246
+ 'tinymce.core.EditorSettings',
38247
+ [
38248
+ 'tinymce.core.util.Tools'
38249
+ ],
38250
+ function (Tools) {
38251
+ var getEditorSettings = function (editor, id, documentBaseUrl, defaultOverrideSettings, settings) {
38252
+ settings = Tools.extend(
38253
+ // Default settings
38254
+ {
38255
+ id: id,
38256
+ theme: 'modern',
38257
+ delta_width: 0,
38258
+ delta_height: 0,
38259
+ popup_css: '',
38260
+ plugins: '',
38261
+ document_base_url: documentBaseUrl,
38262
+ add_form_submit_trigger: true,
38263
+ submit_patch: true,
38264
+ add_unload_trigger: true,
38265
+ convert_urls: true,
38266
+ relative_urls: true,
38267
+ remove_script_host: true,
38268
+ object_resizing: true,
38269
+ doctype: '<!DOCTYPE html>',
38270
+ visual: true,
38271
+ font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
38272
+
38273
+ // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
38274
+ font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
38275
+ forced_root_block: 'p',
38276
+ hidden_input: true,
38277
+ padd_empty_editor: true,
38278
+ render_ui: true,
38279
+ indentation: '30px',
38280
+ inline_styles: true,
38281
+ convert_fonts_to_spans: true,
38282
+ indent: 'simple',
38283
+ 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,' +
38284
+ 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
38285
+ 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,' +
38286
+ 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
38287
+ entity_encoding: 'named',
38288
+ url_converter: editor.convertURL,
38289
+ url_converter_scope: editor,
38290
+ ie7_compat: true
38291
+ },
38292
+
38293
+ // tinymce.overrideDefaults settings
38294
+ defaultOverrideSettings,
38295
+
38296
+ // User settings
38297
+ settings,
38298
+
38299
+ // Forced settings
38300
+ {
38301
+ validate: true,
38302
+ content_editable: settings.inline
38303
+ }
38304
+ );
38305
+
38306
+ // Merge external_plugins
38307
+ if (defaultOverrideSettings && defaultOverrideSettings.external_plugins && settings.external_plugins) {
38308
+ settings.external_plugins = Tools.extend({}, defaultOverrideSettings.external_plugins, settings.external_plugins);
38309
+ }
38310
+
38311
+ return settings;
38312
+ };
38313
+
38314
+ return {
38315
+ getEditorSettings: getEditorSettings
38316
+ };
38317
+ }
38318
+ );
38319
+
38163
38320
  defineGlobal("global!window", window);
38164
38321
  /**
38165
38322
  * ErrorReporter.js
@@ -42053,6 +42210,280 @@ define(
42053
42210
  }
42054
42211
  );
42055
42212
 
42213
+ define(
42214
+ 'ephox.sugar.impl.Style',
42215
+
42216
+ [
42217
+
42218
+ ],
42219
+
42220
+ function () {
42221
+ // some elements, such as mathml, don't have style attributes
42222
+ var isSupported = function (dom) {
42223
+ return dom.style !== undefined;
42224
+ };
42225
+
42226
+ return {
42227
+ isSupported: isSupported
42228
+ };
42229
+ }
42230
+ );
42231
+ define(
42232
+ 'ephox.sugar.api.properties.Css',
42233
+
42234
+ [
42235
+ 'ephox.katamari.api.Type',
42236
+ 'ephox.katamari.api.Arr',
42237
+ 'ephox.katamari.api.Obj',
42238
+ 'ephox.katamari.api.Option',
42239
+ 'ephox.sugar.api.properties.Attr',
42240
+ 'ephox.sugar.api.node.Body',
42241
+ 'ephox.sugar.api.node.Element',
42242
+ 'ephox.sugar.api.node.Node',
42243
+ 'ephox.sugar.impl.Style',
42244
+ 'ephox.katamari.api.Strings',
42245
+ 'global!Error',
42246
+ 'global!console',
42247
+ 'global!window'
42248
+ ],
42249
+
42250
+ function (Type, Arr, Obj, Option, Attr, Body, Element, Node, Style, Strings, Error, console, window) {
42251
+ var internalSet = function (dom, property, value) {
42252
+ // This is going to hurt. Apologies.
42253
+ // JQuery coerces numbers to pixels for certain property names, and other times lets numbers through.
42254
+ // we're going to be explicit; strings only.
42255
+ if (!Type.isString(value)) {
42256
+ console.error('Invalid call to CSS.set. Property ', property, ':: Value ', value, ':: Element ', dom);
42257
+ throw new Error('CSS value must be a string: ' + value);
42258
+ }
42259
+
42260
+ // removed: support for dom().style[property] where prop is camel case instead of normal property name
42261
+ if (Style.isSupported(dom)) dom.style.setProperty(property, value);
42262
+ };
42263
+
42264
+ var internalRemove = function (dom, property) {
42265
+ /*
42266
+ * IE9 and above - MDN doesn't have details, but here's a couple of random internet claims
42267
+ *
42268
+ * http://help.dottoro.com/ljopsjck.php
42269
+ * http://stackoverflow.com/a/7901886/7546
42270
+ */
42271
+ if (Style.isSupported(dom)) dom.style.removeProperty(property);
42272
+ };
42273
+
42274
+ var set = function (element, property, value) {
42275
+ var dom = element.dom();
42276
+ internalSet(dom, property, value);
42277
+ };
42278
+
42279
+ var setAll = function (element, css) {
42280
+ var dom = element.dom();
42281
+
42282
+ Obj.each(css, function (v, k) {
42283
+ internalSet(dom, k, v);
42284
+ });
42285
+ };
42286
+
42287
+ var setOptions = function(element, css) {
42288
+ var dom = element.dom();
42289
+
42290
+ Obj.each(css, function (v, k) {
42291
+ v.fold(function () {
42292
+ internalRemove(dom, k);
42293
+ }, function (value) {
42294
+ internalSet(dom, k, value);
42295
+ });
42296
+ });
42297
+ };
42298
+
42299
+ /*
42300
+ * NOTE: For certain properties, this returns the "used value" which is subtly different to the "computed value" (despite calling getComputedStyle).
42301
+ * Blame CSS 2.0.
42302
+ *
42303
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
42304
+ */
42305
+ var get = function (element, property) {
42306
+ var dom = element.dom();
42307
+ /*
42308
+ * IE9 and above per
42309
+ * https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle
42310
+ *
42311
+ * Not in numerosity, because it doesn't memoize and looking this up dynamically in performance critical code would be horrendous.
42312
+ *
42313
+ * JQuery has some magic here for IE popups, but we don't really need that.
42314
+ * It also uses element.ownerDocument.defaultView to handle iframes but that hasn't been required since FF 3.6.
42315
+ */
42316
+ var styles = window.getComputedStyle(dom);
42317
+ var r = styles.getPropertyValue(property);
42318
+
42319
+ // 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.
42320
+ // Turns out we do this a lot.
42321
+ var v = (r === '' && !Body.inBody(element)) ? getUnsafeProperty(dom, property) : r;
42322
+
42323
+ // undefined is the more appropriate value for JS. JQuery coerces to an empty string, but screw that!
42324
+ return v === null ? undefined : v;
42325
+ };
42326
+
42327
+ var getUnsafeProperty = function (dom, property) {
42328
+ // removed: support for dom().style[property] where prop is camel case instead of normal property name
42329
+ // empty string is what the browsers (IE11 and Chrome) return when the propertyValue doesn't exists.
42330
+ return Style.isSupported(dom) ? dom.style.getPropertyValue(property) : '';
42331
+ };
42332
+
42333
+ /*
42334
+ * Gets the raw value from the style attribute. Useful for retrieving "used values" from the DOM:
42335
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
42336
+ *
42337
+ * Returns NONE if the property isn't set, or the value is an empty string.
42338
+ */
42339
+ var getRaw = function (element, property) {
42340
+ var dom = element.dom();
42341
+ var raw = getUnsafeProperty(dom, property);
42342
+
42343
+ return Option.from(raw).filter(function (r) { return r.length > 0; });
42344
+ };
42345
+
42346
+ var isValidValue = function (tag, property, value) {
42347
+ var element = Element.fromTag(tag);
42348
+ set(element, property, value);
42349
+ var style = getRaw(element, property);
42350
+ return style.isSome();
42351
+ };
42352
+
42353
+ var remove = function (element, property) {
42354
+ var dom = element.dom();
42355
+
42356
+ internalRemove(dom, property);
42357
+
42358
+ if (Attr.has(element, 'style') && Strings.trim(Attr.get(element, 'style')) === '') {
42359
+ // No more styles left, remove the style attribute as well
42360
+ Attr.remove(element, 'style');
42361
+ }
42362
+ };
42363
+
42364
+ var preserve = function (element, f) {
42365
+ var oldStyles = Attr.get(element, 'style');
42366
+ var result = f(element);
42367
+ var restore = oldStyles === undefined ? Attr.remove : Attr.set;
42368
+ restore(element, 'style', oldStyles);
42369
+ return result;
42370
+ };
42371
+
42372
+ var copy = function (source, target) {
42373
+ var sourceDom = source.dom();
42374
+ var targetDom = target.dom();
42375
+ if (Style.isSupported(sourceDom) && Style.isSupported(targetDom)) {
42376
+ targetDom.style.cssText = sourceDom.style.cssText;
42377
+ }
42378
+ };
42379
+
42380
+ var reflow = function (e) {
42381
+ /* NOTE:
42382
+ * do not rely on this return value.
42383
+ * It's here so the closure compiler doesn't optimise the property access away.
42384
+ */
42385
+ return e.dom().offsetWidth;
42386
+ };
42387
+
42388
+ var transferOne = function (source, destination, style) {
42389
+ getRaw(source, style).each(function (value) {
42390
+ // NOTE: We don't want to clobber any existing inline styles.
42391
+ if (getRaw(destination, style).isNone()) set(destination, style, value);
42392
+ });
42393
+ };
42394
+
42395
+ var transfer = function (source, destination, styles) {
42396
+ if (!Node.isElement(source) || !Node.isElement(destination)) return;
42397
+ Arr.each(styles, function (style) {
42398
+ transferOne(source, destination, style);
42399
+ });
42400
+ };
42401
+
42402
+ return {
42403
+ copy: copy,
42404
+ set: set,
42405
+ preserve: preserve,
42406
+ setAll: setAll,
42407
+ setOptions: setOptions,
42408
+ remove: remove,
42409
+ get: get,
42410
+ getRaw: getRaw,
42411
+ isValidValue: isValidValue,
42412
+ reflow: reflow,
42413
+ transfer: transfer
42414
+ };
42415
+ }
42416
+ );
42417
+
42418
+ /**
42419
+ * EditorView.js
42420
+ *
42421
+ * Released under LGPL License.
42422
+ * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
42423
+ *
42424
+ * License: http://www.tinymce.com/license
42425
+ * Contributing: http://www.tinymce.com/contributing
42426
+ */
42427
+
42428
+ define(
42429
+ 'tinymce.core.EditorView',
42430
+ [
42431
+ 'ephox.katamari.api.Fun',
42432
+ 'ephox.sugar.api.node.Element',
42433
+ 'ephox.sugar.api.properties.Css',
42434
+ 'ephox.sugar.api.search.Traverse'
42435
+ ],
42436
+ function (Fun, Element, Css, Traverse) {
42437
+ var getProp = function (propName, elm) {
42438
+ var rawElm = elm.dom();
42439
+ return rawElm[propName];
42440
+ };
42441
+
42442
+ var getComputedSizeProp = function (propName, elm) {
42443
+ return parseInt(Css.get(elm, propName), 10);
42444
+ };
42445
+
42446
+ var getClientWidth = Fun.curry(getProp, 'clientWidth');
42447
+ var getClientHeight = Fun.curry(getProp, 'clientHeight');
42448
+ var getMarginTop = Fun.curry(getComputedSizeProp, 'margin-top');
42449
+ var getMarginLeft = Fun.curry(getComputedSizeProp, 'margin-left');
42450
+
42451
+ var getBoundingClientRect = function (elm) {
42452
+ return elm.dom().getBoundingClientRect();
42453
+ };
42454
+
42455
+ var isInsideElementContentArea = function (bodyElm, clientX, clientY) {
42456
+ var clientWidth = getClientWidth(bodyElm);
42457
+ var clientHeight = getClientHeight(bodyElm);
42458
+
42459
+ return clientX >= 0 && clientY >= 0 && clientX <= clientWidth && clientY <= clientHeight;
42460
+ };
42461
+
42462
+ var transpose = function (inline, elm, clientX, clientY) {
42463
+ var clientRect = getBoundingClientRect(elm);
42464
+ var deltaX = inline ? clientRect.left + elm.dom().clientLeft + getMarginLeft(elm) : 0;
42465
+ var deltaY = inline ? clientRect.top + elm.dom().clientTop + getMarginTop(elm) : 0;
42466
+ var x = clientX - deltaX;
42467
+ var y = clientY - deltaY;
42468
+
42469
+ return { x: x, y: y };
42470
+ };
42471
+
42472
+ // Checks if the specified coordinate is within the visual content area excluding the scrollbars
42473
+ var isXYInContentArea = function (editor, clientX, clientY) {
42474
+ var bodyElm = Element.fromDom(editor.getBody());
42475
+ var targetElm = editor.inline ? bodyElm : Traverse.documentElement(bodyElm);
42476
+ var transposedPoint = transpose(editor.inline, targetElm, clientX, clientY);
42477
+
42478
+ return isInsideElementContentArea(targetElm, transposedPoint.x, transposedPoint.y);
42479
+ };
42480
+
42481
+ return {
42482
+ isXYInContentArea: isXYInContentArea
42483
+ };
42484
+ }
42485
+ );
42486
+
42056
42487
  /**
42057
42488
  * SelectionOverrides.js
42058
42489
  *
@@ -42086,16 +42517,18 @@ define(
42086
42517
  'tinymce.core.caret.FakeCaret',
42087
42518
  'tinymce.core.caret.LineUtils',
42088
42519
  'tinymce.core.dom.NodeType',
42520
+ 'tinymce.core.dom.RangePoint',
42089
42521
  'tinymce.core.DragDropOverrides',
42522
+ 'tinymce.core.EditorView',
42090
42523
  'tinymce.core.Env',
42091
- 'tinymce.core.geom.ClientRect',
42092
42524
  'tinymce.core.keyboard.CefUtils',
42093
- 'tinymce.core.util.Arr',
42094
42525
  'tinymce.core.util.Delay',
42095
- 'tinymce.core.util.Fun',
42096
42526
  'tinymce.core.util.VK'
42097
42527
  ],
42098
- function (CaretContainer, CaretPosition, CaretUtils, CaretWalker, FakeCaret, LineUtils, NodeType, DragDropOverrides, Env, ClientRect, CefUtils, Arr, Delay, Fun, VK) {
42528
+ function (
42529
+ CaretContainer, CaretPosition, CaretUtils, CaretWalker, FakeCaret, LineUtils, NodeType, RangePoint, DragDropOverrides, EditorView, Env, CefUtils, Delay,
42530
+ VK
42531
+ ) {
42099
42532
  var isContentEditableTrue = NodeType.isContentEditableTrue,
42100
42533
  isContentEditableFalse = NodeType.isContentEditableFalse,
42101
42534
  isAfterContentEditableFalse = CaretUtils.isAfterContentEditableFalse,
@@ -42186,22 +42619,12 @@ define(
42186
42619
  return null;
42187
42620
  }
42188
42621
 
42189
- function isXYWithinRange(clientX, clientY, range) {
42190
- if (range.collapsed) {
42191
- return false;
42192
- }
42193
-
42194
- return Arr.reduce(range.getClientRects(), function (state, rect) {
42195
- return state || ClientRect.containsXY(rect, clientX, clientY);
42196
- }, false);
42197
- }
42198
-
42199
42622
  // Some browsers (Chrome) lets you place the caret after a cE=false
42200
42623
  // Make sure we render the caret container in this case
42201
- editor.on('mouseup', function () {
42624
+ editor.on('mouseup', function (e) {
42202
42625
  var range = getRange();
42203
42626
 
42204
- if (range.collapsed) {
42627
+ if (range.collapsed && EditorView.isXYInContentArea(editor, e.clientX, e.clientY)) {
42205
42628
  setRange(CefUtils.renderCaretAtRange(editor, range));
42206
42629
  }
42207
42630
  });
@@ -42288,6 +42711,10 @@ define(
42288
42711
  editor.on('mousedown', function (e) {
42289
42712
  var contentEditableRoot;
42290
42713
 
42714
+ if (EditorView.isXYInContentArea(editor, e.clientX, e.clientY) === false) {
42715
+ return;
42716
+ }
42717
+
42291
42718
  contentEditableRoot = getContentEditableRoot(e.target);
42292
42719
  if (contentEditableRoot) {
42293
42720
  if (isContentEditableFalse(contentEditableRoot)) {
@@ -42297,7 +42724,7 @@ define(
42297
42724
  removeContentEditableSelection();
42298
42725
 
42299
42726
  // Check that we're not attempting a shift + click select within a contenteditable='true' element
42300
- if (!(isContentEditableTrue(contentEditableRoot) && e.shiftKey) && !isXYWithinRange(e.clientX, e.clientY, editor.selection.getRng())) {
42727
+ if (!(isContentEditableTrue(contentEditableRoot) && e.shiftKey) && !RangePoint.isXYWithinRange(e.clientX, e.clientY, editor.selection.getRng())) {
42301
42728
  editor.selection.placeCaretAt(e.clientX, e.clientY);
42302
42729
  }
42303
42730
  }
@@ -43935,6 +44362,10 @@ define(
43935
44362
  autoFocus(editor);
43936
44363
  };
43937
44364
 
44365
+ var getStyleSheetLoader = function (editor) {
44366
+ return editor.inline ? DOM.styleSheetLoader : editor.dom.styleSheetLoader;
44367
+ };
44368
+
43938
44369
  var initContentBody = function (editor, skipWrite) {
43939
44370
  var settings = editor.settings, targetElm = editor.getElement(), doc = editor.getDoc(), body, contentCssText;
43940
44371
 
@@ -44072,7 +44503,7 @@ define(
44072
44503
  editor.dom.addStyle(contentCssText);
44073
44504
  }
44074
44505
 
44075
- editor.dom.styleSheetLoader.loadAll(
44506
+ getStyleSheetLoader(editor).loadAll(
44076
44507
  editor.contentCSS,
44077
44508
  function (_) {
44078
44509
  initEditor(editor);
@@ -44793,6 +45224,7 @@ define(
44793
45224
  'tinymce.core.dom.DOMUtils',
44794
45225
  'tinymce.core.EditorCommands',
44795
45226
  'tinymce.core.EditorObservable',
45227
+ 'tinymce.core.EditorSettings',
44796
45228
  'tinymce.core.Env',
44797
45229
  'tinymce.core.html.Serializer',
44798
45230
  'tinymce.core.init.Render',
@@ -44803,10 +45235,7 @@ define(
44803
45235
  'tinymce.core.util.URI',
44804
45236
  'tinymce.core.util.Uuid'
44805
45237
  ],
44806
- function (
44807
- AddOnManager, DomQuery, DOMUtils, EditorCommands, EditorObservable, Env, Serializer, Render, Mode,
44808
- Shortcuts, Sidebar, Tools, URI, Uuid
44809
- ) {
45238
+ function (AddOnManager, DomQuery, DOMUtils, EditorCommands, EditorObservable, EditorSettings, Env, Serializer, Render, Mode, Shortcuts, Sidebar, Tools, URI, Uuid) {
44810
45239
  // Shorten these names
44811
45240
  var DOM = DOMUtils.DOM;
44812
45241
  var extend = Tools.extend, each = Tools.each;
@@ -44829,11 +45258,10 @@ define(
44829
45258
  * @param {tinymce.EditorManager} editorManager EditorManager instance.
44830
45259
  */
44831
45260
  function Editor(id, settings, editorManager) {
44832
- var self = this, documentBaseUrl, baseUri, defaultSettings;
45261
+ var self = this, documentBaseUrl, baseUri;
44833
45262
 
44834
45263
  documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL;
44835
45264
  baseUri = editorManager.baseURI;
44836
- defaultSettings = editorManager.defaultSettings;
44837
45265
 
44838
45266
  /**
44839
45267
  * Name/value collection with editor settings.
@@ -44844,52 +45272,9 @@ define(
44844
45272
  * // Get the value of the theme setting
44845
45273
  * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme");
44846
45274
  */
44847
- settings = extend({
44848
- id: id,
44849
- theme: 'modern',
44850
- delta_width: 0,
44851
- delta_height: 0,
44852
- popup_css: '',
44853
- plugins: '',
44854
- document_base_url: documentBaseUrl,
44855
- add_form_submit_trigger: true,
44856
- submit_patch: true,
44857
- add_unload_trigger: true,
44858
- convert_urls: true,
44859
- relative_urls: true,
44860
- remove_script_host: true,
44861
- object_resizing: true,
44862
- doctype: '<!DOCTYPE html>',
44863
- visual: true,
44864
- font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
44865
-
44866
- // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
44867
- font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
44868
- forced_root_block: 'p',
44869
- hidden_input: true,
44870
- padd_empty_editor: true,
44871
- render_ui: true,
44872
- indentation: '30px',
44873
- inline_styles: true,
44874
- convert_fonts_to_spans: true,
44875
- indent: 'simple',
44876
- 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,' +
44877
- 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
44878
- 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,' +
44879
- 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
44880
- validate: true,
44881
- entity_encoding: 'named',
44882
- url_converter: self.convertURL,
44883
- url_converter_scope: self,
44884
- ie7_compat: true
44885
- }, defaultSettings, settings);
44886
-
44887
- // Merge external_plugins
44888
- if (defaultSettings && defaultSettings.external_plugins && settings.external_plugins) {
44889
- settings.external_plugins = extend({}, defaultSettings.external_plugins, settings.external_plugins);
44890
- }
44891
-
45275
+ settings = EditorSettings.getEditorSettings(self, id, documentBaseUrl, editorManager.defaultSettings, settings);
44892
45276
  self.settings = settings;
45277
+
44893
45278
  AddOnManager.language = settings.language || 'en';
44894
45279
  AddOnManager.languageLoad = settings.language_load;
44895
45280
  AddOnManager.baseURL = editorManager.baseURL;
@@ -44900,7 +45285,7 @@ define(
44900
45285
  * @property id
44901
45286
  * @type String
44902
45287
  */
44903
- self.id = settings.id = id;
45288
+ self.id = id;
44904
45289
 
44905
45290
  /**
44906
45291
  * State to force the editor to return false on a isDirty call.
@@ -44934,7 +45319,7 @@ define(
44934
45319
  * // Get absolute URL from the location of document_base_url
44935
45320
  * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
44936
45321
  */
44937
- self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, {
45322
+ self.documentBaseURI = new URI(settings.document_base_url, {
44938
45323
  base_uri: baseUri
44939
45324
  });
44940
45325
 
@@ -44975,7 +45360,6 @@ define(
44975
45360
  self.suffix = editorManager.suffix;
44976
45361
  self.editorManager = editorManager;
44977
45362
  self.inline = settings.inline;
44978
- self.settings.content_editable = self.inline;
44979
45363
 
44980
45364
  if (settings.cache_suffix) {
44981
45365
  Env.cacheSuffix = settings.cache_suffix.replace(/^[\?\&]+/, '');
@@ -45077,6 +45461,9 @@ define(
45077
45461
  body.focus();
45078
45462
  }
45079
45463
  } else {
45464
+ // Restore previous selection before focus to prevent Chrome from
45465
+ // jumping to the top of the document in long inline editors
45466
+ self.selection.setRng(self.lastRng);
45080
45467
  body.focus();
45081
45468
  }
45082
45469
 
@@ -45139,16 +45526,14 @@ define(
45139
45526
  * @return {String} Translated string.
45140
45527
  */
45141
45528
  translate: function (text) {
45142
- var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
45529
+ if (text && Tools.is(text, 'string')) {
45530
+ var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
45143
45531
 
45144
- if (!text) {
45145
- return '';
45532
+ text = i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function (a, b) {
45533
+ return i18n.data[lang + '.' + b] || '{#' + b + '}';
45534
+ });
45146
45535
  }
45147
45536
 
45148
- text = i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function (a, b) {
45149
- return i18n.data[lang + '.' + b] || '{#' + b + '}';
45150
- });
45151
-
45152
45537
  return this.editorManager.translate(text);
45153
45538
  },
45154
45539
 
@@ -46838,7 +47223,7 @@ define(
46838
47223
  * @property minorVersion
46839
47224
  * @type String
46840
47225
  */
46841
- minorVersion: '6.3',
47226
+ minorVersion: '6.4',
46842
47227
 
46843
47228
  /**
46844
47229
  * Release date of TinyMCE build.
@@ -46846,7 +47231,7 @@ define(
46846
47231
  * @property releaseDate
46847
47232
  * @type String
46848
47233
  */
46849
- releaseDate: '2017-05-30',
47234
+ releaseDate: '2017-06-13',
46850
47235
 
46851
47236
  /**
46852
47237
  * Collection of editor instances.
@@ -49652,7 +50037,12 @@ define(
49652
50037
  self.panel.show();
49653
50038
  }
49654
50039
 
49655
- self.panel.moveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tr', 'bc-tc'] : ['bc-tl', 'bc-tc']));
50040
+ var rel = self.panel.testMoveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tc', 'bc-tl', 'bc-tr'] : ['bc-tc', 'bc-tr', 'bc-tl']));
50041
+
50042
+ self.panel.classes.toggle('start', rel === 'bc-tl');
50043
+ self.panel.classes.toggle('end', rel === 'bc-tr');
50044
+
50045
+ self.panel.moveRel(self.getEl(), rel);
49656
50046
  },
49657
50047
 
49658
50048
  /**
@@ -51525,7 +51915,7 @@ define(
51525
51915
  return fontFamily ? fontFamily.split(',')[0] : '';
51526
51916
  };
51527
51917
 
51528
- editor.on('nodeChange', function (e) {
51918
+ editor.on('init nodeChange', function (e) {
51529
51919
  var fontFamily, value = null;
51530
51920
 
51531
51921
  fontFamily = FontInfo.getFontFamily(editor.getBody(), e.element);
@@ -51555,7 +51945,7 @@ define(
51555
51945
  return function () {
51556
51946
  var self = this;
51557
51947
 
51558
- editor.on('nodeChange', function (e) {
51948
+ editor.on('init nodeChange', function (e) {
51559
51949
  var px, pt, value = null;
51560
51950
 
51561
51951
  px = FontInfo.getFontSize(editor.getBody(), e.element);