tinymce-rails 4.6.3 → 4.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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);