Last active
December 10, 2017 00:38
-
-
Save lbogdan/457227f081c494a315417b9fdb0065dc to your computer and use it in GitHub Desktop.
TinyMCE theme
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function () { | |
var defs = {}; // id -> {dependencies, definition, instance (possibly undefined)} | |
// Used when there is no 'main' module. | |
// The name is probably (hopefully) unique so minification removes for releases. | |
var register_3795 = function (id) { | |
var module = dem(id); | |
var fragments = id.split('.'); | |
var target = Function('return this;')(); | |
for (var i = 0; i < fragments.length - 1; ++i) { | |
if (target[fragments[i]] === undefined) | |
target[fragments[i]] = {}; | |
target = target[fragments[i]]; | |
} | |
target[fragments[fragments.length - 1]] = module; | |
}; | |
var instantiate = function (id) { | |
var actual = defs[id]; | |
var dependencies = actual.deps; | |
var definition = actual.defn; | |
var len = dependencies.length; | |
var instances = new Array(len); | |
for (var i = 0; i < len; ++i) | |
instances[i] = dem(dependencies[i]); | |
var defResult = definition.apply(null, instances); | |
if (defResult === undefined) | |
throw 'module [' + id + '] returned undefined'; | |
actual.instance = defResult; | |
}; | |
var def = function (id, dependencies, definition) { | |
if (typeof id !== 'string') | |
throw 'module id must be a string'; | |
else if (dependencies === undefined) | |
throw 'no dependencies for ' + id; | |
else if (definition === undefined) | |
throw 'no definition function for ' + id; | |
defs[id] = { | |
deps: dependencies, | |
defn: definition, | |
instance: undefined | |
}; | |
}; | |
var dem = function (id) { | |
var actual = defs[id]; | |
if (actual === undefined) | |
throw 'module [' + id + '] was undefined'; | |
else if (actual.instance === undefined) | |
instantiate(id); | |
return actual.instance; | |
}; | |
var req = function (ids, callback) { | |
var len = ids.length; | |
var instances = new Array(len); | |
for (var i = 0; i < len; ++i) | |
instances[i] = dem(ids[i]); | |
callback.apply(null, instances); | |
}; | |
var ephox = {}; | |
ephox.bolt = { | |
module: { | |
api: { | |
define: def, | |
require: req, | |
demand: dem | |
} | |
} | |
}; | |
var define = def; | |
var require = req; | |
var demand = dem; | |
// this helps with minification when using a lot of global references | |
var defineGlobal = function (id, ref) { | |
define(id, [], function () { return ref; }); | |
}; | |
/*jsc | |
["tinymce.themes.modern.Theme","global!window","tinymce.core.ThemeManager","tinymce.themes.modern.api.ThemeApi","tinymce.ui.Api","tinymce.ui.FormatControls","global!tinymce.util.Tools.resolve","tinymce.themes.modern.ui.Render","tinymce.themes.modern.ui.Resize","tinymce.ui.NotificationManagerImpl","tinymce.ui.WindowManagerImpl","tinymce.core.ui.Factory","tinymce.core.util.Tools","tinymce.ui.AbsoluteLayout","tinymce.ui.BrowseButton","tinymce.ui.Button","tinymce.ui.ButtonGroup","tinymce.ui.Checkbox","tinymce.ui.Collection","tinymce.ui.ColorBox","tinymce.ui.ColorButton","tinymce.ui.ColorPicker","tinymce.ui.ComboBox","tinymce.ui.Container","tinymce.ui.Control","tinymce.ui.DragHelper","tinymce.ui.DropZone","tinymce.ui.ElementPath","tinymce.ui.FieldSet","tinymce.ui.FilePicker","tinymce.ui.FitLayout","tinymce.ui.FlexLayout","tinymce.ui.FloatPanel","tinymce.ui.FlowLayout","tinymce.ui.Form","ephox.katamari.api.Fun","ephox.sugar.api.node.Element","ephox.sugar.api.search.SelectorFind","global!document","tinymce.core.EditorManager","tinymce.core.Env","tinymce.ui.Widget","tinymce.ui.editorui.Align","tinymce.ui.editorui.FontSelect","tinymce.ui.editorui.FontSizeSelect","tinymce.ui.editorui.FormatSelect","tinymce.ui.editorui.Formats","tinymce.ui.editorui.InsertButton","tinymce.ui.editorui.SimpleControls","tinymce.ui.editorui.UndoRedo","tinymce.ui.editorui.VisualAid","tinymce.ui.FormItem","tinymce.ui.GridLayout","tinymce.ui.Iframe","tinymce.ui.InfoBox","tinymce.ui.KeyboardNavigation","tinymce.ui.Label","tinymce.ui.Layout","tinymce.ui.ListBox","tinymce.ui.Menu","tinymce.ui.MenuBar","tinymce.ui.MenuButton","tinymce.ui.MenuItem","tinymce.ui.MessageBox","tinymce.ui.Movable","tinymce.ui.Notification","tinymce.ui.Panel","tinymce.ui.PanelButton","tinymce.ui.Path","tinymce.ui.Progress","tinymce.ui.Radio","tinymce.ui.ReflowQueue","tinymce.ui.Resizable","tinymce.ui.ResizeHandle","tinymce.ui.Scrollable","tinymce.ui.SelectBox","tinymce.ui.Selector","tinymce.ui.Slider","tinymce.ui.Spacer","tinymce.ui.SplitButton","tinymce.ui.StackLayout","tinymce.ui.TabPanel","tinymce.ui.TextBox","tinymce.ui.Throbber","tinymce.ui.Toolbar","tinymce.ui.Tooltip","tinymce.ui.Window","tinymce.themes.modern.api.Settings","tinymce.themes.modern.modes.Iframe","tinymce.themes.modern.modes.Inline","tinymce.themes.modern.ui.ProgressState","tinymce.core.dom.DOMUtils","tinymce.themes.modern.api.Events","ephox.katamari.api.Arr","global!setTimeout","tinymce.ui.DomUtils","tinymce.core.dom.DomQuery","tinymce.core.util.Class","tinymce.core.util.EventDispatcher","tinymce.ui.BoxUtils","tinymce.ui.ClassList","tinymce.ui.data.ObservableObject","tinymce.core.util.Delay","global!RegExp","tinymce.core.util.VK","tinymce.core.util.Color","global!Array","global!Error","tinymce.ui.content.LinkTargets","ephox.katamari.api.Option","global!console","ephox.sugar.api.search.PredicateFind","ephox.sugar.api.search.Selectors","ephox.sugar.impl.ClosestOrAncestor","tinymce.ui.editorui.FormatUtils","tinymce.ui.fmt.FontInfo","tinymce.core.util.I18n","tinymce.themes.modern.ui.A11y","tinymce.themes.modern.ui.ContextToolbars","tinymce.themes.modern.ui.Menubar","tinymce.themes.modern.ui.Sidebar","tinymce.themes.modern.ui.SkinLoaded","tinymce.themes.modern.ui.Toolbar","tinymce.ui.data.Binding","tinymce.core.util.Observable","global!Object","global!String","ephox.katamari.api.Id","ephox.sugar.api.search.SelectorFilter","ephox.katamari.api.Type","ephox.sugar.api.node.Body","ephox.sugar.api.dom.Compare","ephox.sugar.api.node.NodeTypes","ephox.sugar.api.node.Node","tinymce.core.geom.Rect","tinymce.themes.modern.alien.UiContainer","global!Date","global!Math","ephox.sugar.api.search.PredicateFilter","ephox.katamari.api.Thunk","ephox.sand.api.Node","ephox.sand.api.PlatformDetection","ephox.sugar.api.search.Traverse","ephox.sand.util.Global","ephox.sand.core.PlatformDetection","global!navigator","ephox.katamari.api.Struct","ephox.sugar.alien.Recurse","ephox.katamari.api.Resolve","ephox.sand.core.Browser","ephox.sand.core.OperatingSystem","ephox.sand.detect.DeviceType","ephox.sand.detect.UaString","ephox.sand.info.PlatformInfo","ephox.katamari.data.Immutable","ephox.katamari.data.MixedBag","ephox.katamari.api.Global","ephox.sand.detect.Version","ephox.katamari.api.Strings","ephox.katamari.api.Obj","ephox.katamari.util.BagUtils","global!Number","ephox.katamari.str.StrAppend","ephox.katamari.str.StringParts"] | |
jsc*/ | |
defineGlobal("global!window", window); | |
defineGlobal("global!tinymce.util.Tools.resolve", tinymce.util.Tools.resolve); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.ThemeManager', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.ThemeManager'); | |
} | |
); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.EditorManager', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.EditorManager'); | |
} | |
); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.util.Tools', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.util.Tools'); | |
} | |
); | |
/** | |
* Settings.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.api.Settings', | |
[ | |
'tinymce.core.EditorManager', | |
'tinymce.core.util.Tools' | |
], | |
function (EditorManager, Tools) { | |
var isBrandingEnabled = function (editor) { | |
return editor.getParam('branding', true); | |
}; | |
var hasMenubar = function (editor) { | |
return getMenubar(editor) !== false; | |
}; | |
var getMenubar = function (editor) { | |
return editor.getParam('menubar'); | |
}; | |
var hasStatusbar = function (editor) { | |
return editor.getParam('statusbar', true); | |
}; | |
var getToolbarSize = function (editor) { | |
return editor.getParam('toolbar_items_size'); | |
}; | |
var getResize = function (editor) { | |
var resize = editor.getParam('resize', 'vertical'); | |
if (resize === false) { | |
return 'none'; | |
} else if (resize === 'both') { | |
return 'both'; | |
} else { | |
return 'vertical'; | |
} | |
}; | |
var isReadOnly = function (editor) { | |
return editor.getParam('readonly', false); | |
}; | |
var getFixedToolbarContainer = function (editor) { | |
return editor.getParam('fixed_toolbar_container'); | |
}; | |
var getInlineToolbarPositionHandler = function (editor) { | |
return editor.getParam('inline_toolbar_position_handler'); | |
}; | |
var getMenu = function (editor) { | |
return editor.getParam('menu'); | |
}; | |
var getRemovedMenuItems = function (editor) { | |
return editor.getParam('removed_menuitems', ''); | |
}; | |
var getMinWidth = function (editor) { | |
return editor.getParam('min_width', 100); | |
}; | |
var getMinHeight = function (editor) { | |
return editor.getParam('min_height', 100); | |
}; | |
var getMaxWidth = function (editor) { | |
return editor.getParam('max_width', 0xFFFF); | |
}; | |
var getMaxHeight = function (editor) { | |
return editor.getParam('max_height', 0xFFFF); | |
}; | |
var getSkinUrl = function (editor) { | |
var settings = editor.settings; | |
var skin = settings.skin; | |
var skinUrl = settings.skin_url; | |
if (skin !== false) { | |
var skinName = skin ? skin : 'lightgray'; | |
if (skinUrl) { | |
skinUrl = editor.documentBaseURI.toAbsolute(skinUrl); | |
} else { | |
skinUrl = EditorManager.baseURL + '/skins/' + skinName; | |
} | |
} | |
return skinUrl; | |
}; | |
var isSkinDisabled = function (editor) { | |
return editor.settings.skin === false; | |
}; | |
var isInline = function (editor) { | |
return editor.getParam('inline', false); | |
}; | |
var getIndexedToolbars = function (settings, defaultToolbar) { | |
var toolbars = []; | |
// Generate toolbar<n> | |
for (var i = 1; i < 10; i++) { | |
var toolbar = settings['toolbar' + i]; | |
if (!toolbar) { | |
break; | |
} | |
toolbars.push(toolbar); | |
} | |
var mainToolbar = settings.toolbar ? [ settings.toolbar ] : [ defaultToolbar ]; | |
return toolbars.length > 0 ? toolbars : mainToolbar; | |
}; | |
var getToolbars = function (editor) { | |
var toolbar = editor.getParam('toolbar'); | |
var defaultToolbar = 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image'; | |
if (toolbar === false) { | |
return []; | |
} else if (Tools.isArray(toolbar)) { | |
return Tools.grep(toolbar, function (toolbar) { | |
return toolbar.length > 0; | |
}); | |
} else { | |
return getIndexedToolbars(editor.settings, defaultToolbar); | |
} | |
}; | |
return { | |
isBrandingEnabled: isBrandingEnabled, | |
hasMenubar: hasMenubar, | |
getMenubar: getMenubar, | |
hasStatusbar: hasStatusbar, | |
getToolbarSize: getToolbarSize, | |
getResize: getResize, | |
isReadOnly: isReadOnly, | |
getFixedToolbarContainer: getFixedToolbarContainer, | |
getInlineToolbarPositionHandler: getInlineToolbarPositionHandler, | |
getMenu: getMenu, | |
getRemovedMenuItems: getRemovedMenuItems, | |
getMinWidth: getMinWidth, | |
getMinHeight: getMinHeight, | |
getMaxWidth: getMaxWidth, | |
getMaxHeight: getMaxHeight, | |
getSkinUrl: getSkinUrl, | |
isSkinDisabled: isSkinDisabled, | |
isInline: isInline, | |
getToolbars: getToolbars | |
}; | |
} | |
); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.dom.DOMUtils', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.dom.DOMUtils'); | |
} | |
); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.ui.Factory', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.ui.Factory'); | |
} | |
); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.util.I18n', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.util.I18n'); | |
} | |
); | |
/** | |
* Events.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.api.Events', | |
[ | |
], | |
function () { | |
var fireSkinLoaded = function (editor) { | |
return editor.fire('SkinLoaded'); | |
}; | |
var fireResizeEditor = function (editor) { | |
return editor.fire('ResizeEditor'); | |
}; | |
var fireBeforeRenderUI = function (editor) { | |
return editor.fire('BeforeRenderUI'); | |
}; | |
return { | |
fireSkinLoaded: fireSkinLoaded, | |
fireResizeEditor: fireResizeEditor, | |
fireBeforeRenderUI: fireBeforeRenderUI | |
}; | |
} | |
); | |
/** | |
* A11y.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.ui.A11y', | |
[ | |
], | |
function () { | |
var focus = function (panel, type) { | |
return function () { | |
var item = panel.find(type)[0]; | |
if (item) { | |
item.focus(true); | |
} | |
}; | |
}; | |
var addKeys = function (editor, panel) { | |
editor.shortcuts.add('Alt+F9', '', focus(panel, 'menubar')); | |
editor.shortcuts.add('Alt+F10,F10', '', focus(panel, 'toolbar')); | |
editor.shortcuts.add('Alt+F11', '', focus(panel, 'elementpath')); | |
panel.on('cancel', function () { | |
editor.focus(); | |
}); | |
}; | |
return { | |
addKeys: addKeys | |
}; | |
} | |
); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.Env', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.Env'); | |
} | |
); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.geom.Rect', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.geom.Rect'); | |
} | |
); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.util.Delay', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.util.Delay'); | |
} | |
); | |
defineGlobal("global!Array", Array); | |
defineGlobal("global!Error", Error); | |
define( | |
'ephox.katamari.api.Fun', | |
[ | |
'global!Array', | |
'global!Error' | |
], | |
function (Array, Error) { | |
var noop = function () { }; | |
var noarg = function (f) { | |
return function () { | |
return f(); | |
}; | |
}; | |
var compose = function (fa, fb) { | |
return function () { | |
return fa(fb.apply(null, arguments)); | |
}; | |
}; | |
var constant = function (value) { | |
return function () { | |
return value; | |
}; | |
}; | |
var identity = function (x) { | |
return x; | |
}; | |
var tripleEquals = function(a, b) { | |
return a === b; | |
}; | |
// Don't use array slice(arguments), makes the whole function unoptimisable on Chrome | |
var curry = function (f) { | |
// equivalent to arguments.slice(1) | |
// starting at 1 because 0 is the f, makes things tricky. | |
// Pay attention to what variable is where, and the -1 magic. | |
// thankfully, we have tests for this. | |
var args = new Array(arguments.length - 1); | |
for (var i = 1; i < arguments.length; i++) args[i-1] = arguments[i]; | |
return function () { | |
var newArgs = new Array(arguments.length); | |
for (var j = 0; j < newArgs.length; j++) newArgs[j] = arguments[j]; | |
var all = args.concat(newArgs); | |
return f.apply(null, all); | |
}; | |
}; | |
var not = function (f) { | |
return function () { | |
return !f.apply(null, arguments); | |
}; | |
}; | |
var die = function (msg) { | |
return function () { | |
throw new Error(msg); | |
}; | |
}; | |
var apply = function (f) { | |
return f(); | |
}; | |
var call = function(f) { | |
f(); | |
}; | |
var never = constant(false); | |
var always = constant(true); | |
return { | |
noop: noop, | |
noarg: noarg, | |
compose: compose, | |
constant: constant, | |
identity: identity, | |
tripleEquals: tripleEquals, | |
curry: curry, | |
not: not, | |
die: die, | |
apply: apply, | |
call: call, | |
never: never, | |
always: always | |
}; | |
} | |
); | |
defineGlobal("global!Object", Object); | |
define( | |
'ephox.katamari.api.Option', | |
[ | |
'ephox.katamari.api.Fun', | |
'global!Object' | |
], | |
function (Fun, Object) { | |
var never = Fun.never; | |
var always = Fun.always; | |
/** | |
Option objects support the following methods: | |
fold :: this Option a -> ((() -> b, a -> b)) -> Option b | |
is :: this Option a -> a -> Boolean | |
isSome :: this Option a -> () -> Boolean | |
isNone :: this Option a -> () -> Boolean | |
getOr :: this Option a -> a -> a | |
getOrThunk :: this Option a -> (() -> a) -> a | |
getOrDie :: this Option a -> String -> a | |
or :: this Option a -> Option a -> Option a | |
- if some: return self | |
- if none: return opt | |
orThunk :: this Option a -> (() -> Option a) -> Option a | |
- Same as "or", but uses a thunk instead of a value | |
map :: this Option a -> (a -> b) -> Option b | |
- "fmap" operation on the Option Functor. | |
- same as 'each' | |
ap :: this Option a -> Option (a -> b) -> Option b | |
- "apply" operation on the Option Apply/Applicative. | |
- Equivalent to <*> in Haskell/PureScript. | |
each :: this Option a -> (a -> b) -> undefined | |
- similar to 'map', but doesn't return a value. | |
- intended for clarity when performing side effects. | |
bind :: this Option a -> (a -> Option b) -> Option b | |
- "bind"/"flatMap" operation on the Option Bind/Monad. | |
- Equivalent to >>= in Haskell/PureScript; flatMap in Scala. | |
flatten :: {this Option (Option a))} -> () -> Option a | |
- "flatten"/"join" operation on the Option Monad. | |
exists :: this Option a -> (a -> Boolean) -> Boolean | |
forall :: this Option a -> (a -> Boolean) -> Boolean | |
filter :: this Option a -> (a -> Boolean) -> Option a | |
equals :: this Option a -> Option a -> Boolean | |
equals_ :: this Option a -> (Option a, a -> Boolean) -> Boolean | |
toArray :: this Option a -> () -> [a] | |
*/ | |
var none = function () { return NONE; }; | |
var NONE = (function () { | |
var eq = function (o) { | |
return o.isNone(); | |
}; | |
// inlined from peanut, maybe a micro-optimisation? | |
var call = function (thunk) { return thunk(); }; | |
var id = function (n) { return n; }; | |
var noop = function () { }; | |
var me = { | |
fold: function (n, s) { return n(); }, | |
is: never, | |
isSome: never, | |
isNone: always, | |
getOr: id, | |
getOrThunk: call, | |
getOrDie: function (msg) { | |
throw new Error(msg || 'error: getOrDie called on none.'); | |
}, | |
or: id, | |
orThunk: call, | |
map: none, | |
ap: none, | |
each: noop, | |
bind: none, | |
flatten: none, | |
exists: never, | |
forall: always, | |
filter: none, | |
equals: eq, | |
equals_: eq, | |
toArray: function () { return []; }, | |
toString: Fun.constant("none()") | |
}; | |
if (Object.freeze) Object.freeze(me); | |
return me; | |
})(); | |
/** some :: a -> Option a */ | |
var some = function (a) { | |
// inlined from peanut, maybe a micro-optimisation? | |
var constant_a = function () { return a; }; | |
var self = function () { | |
// can't Fun.constant this one | |
return me; | |
}; | |
var map = function (f) { | |
return some(f(a)); | |
}; | |
var bind = function (f) { | |
return f(a); | |
}; | |
var me = { | |
fold: function (n, s) { return s(a); }, | |
is: function (v) { return a === v; }, | |
isSome: always, | |
isNone: never, | |
getOr: constant_a, | |
getOrThunk: constant_a, | |
getOrDie: constant_a, | |
or: self, | |
orThunk: self, | |
map: map, | |
ap: function (optfab) { | |
return optfab.fold(none, function(fab) { | |
return some(fab(a)); | |
}); | |
}, | |
each: function (f) { | |
f(a); | |
}, | |
bind: bind, | |
flatten: constant_a, | |
exists: bind, | |
forall: bind, | |
filter: function (f) { | |
return f(a) ? me : NONE; | |
}, | |
equals: function (o) { | |
return o.is(a); | |
}, | |
equals_: function (o, elementEq) { | |
return o.fold( | |
never, | |
function (b) { return elementEq(a, b); } | |
); | |
}, | |
toArray: function () { | |
return [a]; | |
}, | |
toString: function () { | |
return 'some(' + a + ')'; | |
} | |
}; | |
return me; | |
}; | |
/** from :: undefined|null|a -> Option a */ | |
var from = function (value) { | |
return value === null || value === undefined ? NONE : some(value); | |
}; | |
return { | |
some: some, | |
none: none, | |
from: from | |
}; | |
} | |
); | |
/** | |
* UiContainer.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.alien.UiContainer', | |
[ | |
'ephox.katamari.api.Option', | |
'tinymce.core.Env', | |
'tinymce.core.dom.DOMUtils' | |
], | |
function (Option, Env, DOMUtils) { | |
var getUiContainerDelta = function () { | |
var uiContainer = Env.container; | |
if (uiContainer && DOMUtils.DOM.getStyle(uiContainer, 'position', true) !== 'static') { | |
var containerPos = DOMUtils.DOM.getPos(uiContainer); | |
var dx = uiContainer.scrollLeft - containerPos.x; | |
var dy = uiContainer.scrollTop - containerPos.y; | |
return Option.some({ | |
x: dx, | |
y: dy | |
}); | |
} else { | |
return Option.none(); | |
} | |
}; | |
return { | |
getUiContainerDelta: getUiContainerDelta | |
}; | |
} | |
); | |
/** | |
* Toolbar.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.ui.Toolbar', | |
[ | |
'tinymce.core.ui.Factory', | |
'tinymce.core.util.Tools', | |
'tinymce.themes.modern.api.Settings' | |
], | |
function (Factory, Tools, Settings) { | |
var createToolbar = function (editor, items, size) { | |
var toolbarItems = [], buttonGroup; | |
if (!items) { | |
return; | |
} | |
Tools.each(items.split(/[ ,]/), function (item) { | |
var itemName; | |
var bindSelectorChanged = function () { | |
var selection = editor.selection; | |
if (item.settings.stateSelector) { | |
selection.selectorChanged(item.settings.stateSelector, function (state) { | |
item.active(state); | |
}, true); | |
} | |
if (item.settings.disabledStateSelector) { | |
selection.selectorChanged(item.settings.disabledStateSelector, function (state) { | |
item.disabled(state); | |
}); | |
} | |
}; | |
if (item === "|") { | |
buttonGroup = null; | |
} else { | |
if (!buttonGroup) { | |
buttonGroup = { type: 'buttongroup', items: [] }; | |
toolbarItems.push(buttonGroup); | |
} | |
if (editor.buttons[item]) { | |
// TODO: Move control creation to some UI class | |
itemName = item; | |
item = editor.buttons[itemName]; | |
if (typeof item === "function") { | |
item = item(); | |
} | |
item.type = item.type || 'button'; | |
item.size = size; | |
item = Factory.create(item); | |
buttonGroup.items.push(item); | |
if (editor.initialized) { | |
bindSelectorChanged(); | |
} else { | |
editor.on('init', bindSelectorChanged); | |
} | |
} | |
} | |
}); | |
return { | |
type: 'toolbar', | |
layout: 'flow', | |
items: toolbarItems | |
}; | |
}; | |
/** | |
* Creates the toolbars from config and returns a toolbar array. | |
* | |
* @param {String} size Optional toolbar item size. | |
* @return {Array} Array with toolbars. | |
*/ | |
var createToolbars = function (editor, size) { | |
var toolbars = []; | |
var addToolbar = function (items) { | |
if (items) { | |
toolbars.push(createToolbar(editor, items, size)); | |
} | |
}; | |
Tools.each(Settings.getToolbars(editor), function (toolbar) { | |
addToolbar(toolbar); | |
}); | |
if (toolbars.length) { | |
return { | |
type: 'panel', | |
layout: 'stack', | |
classes: "toolbar-grp", | |
ariaRoot: true, | |
ariaRemember: true, | |
items: toolbars | |
}; | |
} | |
}; | |
return { | |
createToolbar: createToolbar, | |
createToolbars: createToolbars | |
}; | |
} | |
); | |
/** | |
* ContextToolbars.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.ui.ContextToolbars', | |
[ | |
'tinymce.core.Env', | |
'tinymce.core.dom.DOMUtils', | |
'tinymce.core.geom.Rect', | |
'tinymce.core.ui.Factory', | |
'tinymce.core.util.Delay', | |
'tinymce.core.util.Tools', | |
'tinymce.themes.modern.alien.UiContainer', | |
'tinymce.themes.modern.api.Settings', | |
'tinymce.themes.modern.ui.Toolbar' | |
], | |
function (Env, DOMUtils, Rect, Factory, Delay, Tools, UiContainer, Settings, Toolbar) { | |
var DOM = DOMUtils.DOM; | |
var toClientRect = function (geomRect) { | |
return { | |
left: geomRect.x, | |
top: geomRect.y, | |
width: geomRect.w, | |
height: geomRect.h, | |
right: geomRect.x + geomRect.w, | |
bottom: geomRect.y + geomRect.h | |
}; | |
}; | |
var hideAllFloatingPanels = function (editor) { | |
Tools.each(editor.contextToolbars, function (toolbar) { | |
if (toolbar.panel) { | |
toolbar.panel.hide(); | |
} | |
}); | |
}; | |
var movePanelTo = function (panel, pos) { | |
panel.moveTo(pos.left, pos.top); | |
}; | |
var togglePositionClass = function (panel, relPos, predicate) { | |
relPos = relPos ? relPos.substr(0, 2) : ''; | |
Tools.each({ | |
t: 'down', | |
b: 'up' | |
}, function (cls, pos) { | |
panel.classes.toggle('arrow-' + cls, predicate(pos, relPos.substr(0, 1))); | |
}); | |
Tools.each({ | |
l: 'left', | |
r: 'right' | |
}, function (cls, pos) { | |
panel.classes.toggle('arrow-' + cls, predicate(pos, relPos.substr(1, 1))); | |
}); | |
}; | |
var userConstrain = function (handler, x, y, elementRect, contentAreaRect, panelRect) { | |
panelRect = toClientRect({ x: x, y: y, w: panelRect.w, h: panelRect.h }); | |
if (handler) { | |
panelRect = handler({ | |
elementRect: toClientRect(elementRect), | |
contentAreaRect: toClientRect(contentAreaRect), | |
panelRect: panelRect | |
}); | |
} | |
return panelRect; | |
}; | |
var addContextualToolbars = function (editor) { | |
var scrollContainer; | |
var getContextToolbars = function () { | |
return editor.contextToolbars || []; | |
}; | |
var getElementRect = function (elm) { | |
var pos, targetRect, root; | |
pos = DOM.getPos(editor.getContentAreaContainer()); | |
targetRect = editor.dom.getRect(elm); | |
root = editor.dom.getRoot(); | |
// Adjust targetPos for scrolling in the editor | |
if (root.nodeName === 'BODY') { | |
targetRect.x -= root.ownerDocument.documentElement.scrollLeft || root.scrollLeft; | |
targetRect.y -= root.ownerDocument.documentElement.scrollTop || root.scrollTop; | |
} | |
targetRect.x += pos.x; | |
targetRect.y += pos.y; | |
return targetRect; | |
}; | |
var reposition = function (match, shouldShow) { | |
var relPos, panelRect, elementRect, contentAreaRect, panel, relRect, testPositions, smallElementWidthThreshold; | |
var handler = Settings.getInlineToolbarPositionHandler(editor); | |
if (editor.removed) { | |
return; | |
} | |
if (!match || !match.toolbar.panel) { | |
hideAllFloatingPanels(editor); | |
return; | |
} | |
testPositions = [ | |
'bc-tc', 'tc-bc', | |
'tl-bl', 'bl-tl', | |
'tr-br', 'br-tr' | |
]; | |
panel = match.toolbar.panel; | |
// Only show the panel on some events not for example nodeChange since that fires when context menu is opened | |
if (shouldShow) { | |
panel.show(); | |
} | |
elementRect = getElementRect(match.element); | |
panelRect = DOM.getRect(panel.getEl()); | |
contentAreaRect = DOM.getRect(editor.getContentAreaContainer() || editor.getBody()); | |
var delta = UiContainer.getUiContainerDelta().getOr({ x: 0, y: 0 }); | |
elementRect.x += delta.x; | |
elementRect.y += delta.y; | |
panelRect.x += delta.x; | |
panelRect.y += delta.y; | |
contentAreaRect.x += delta.x; | |
contentAreaRect.y += delta.y; | |
smallElementWidthThreshold = 25; | |
if (DOM.getStyle(match.element, 'display', true) !== 'inline') { | |
// We need to use these instead of the rect values since the style | |
// size properites might not be the same as the real size for a table if it has a caption | |
var clientRect = match.element.getBoundingClientRect(); | |
elementRect.w = clientRect.width; | |
elementRect.h = clientRect.height; | |
} | |
if (!editor.inline) { | |
contentAreaRect.w = editor.getDoc().documentElement.offsetWidth; | |
} | |
// Inflate the elementRect so it doesn't get placed above resize handles | |
if (editor.selection.controlSelection.isResizable(match.element) && elementRect.w < smallElementWidthThreshold) { | |
elementRect = Rect.inflate(elementRect, 0, 8); | |
} | |
relPos = Rect.findBestRelativePosition(panelRect, elementRect, contentAreaRect, testPositions); | |
elementRect = Rect.clamp(elementRect, contentAreaRect); | |
if (relPos) { | |
relRect = Rect.relativePosition(panelRect, elementRect, relPos); | |
movePanelTo(panel, userConstrain(handler, relRect.x, relRect.y, elementRect, contentAreaRect, panelRect)); | |
} else { | |
// Allow overflow below the editor to avoid placing toolbars ontop of tables | |
contentAreaRect.h += panelRect.h; | |
elementRect = Rect.intersect(contentAreaRect, elementRect); | |
if (elementRect) { | |
relPos = Rect.findBestRelativePosition(panelRect, elementRect, contentAreaRect, [ | |
'bc-tc', 'bl-tl', 'br-tr' | |
]); | |
if (relPos) { | |
relRect = Rect.relativePosition(panelRect, elementRect, relPos); | |
movePanelTo(panel, userConstrain(handler, relRect.x, relRect.y, elementRect, contentAreaRect, panelRect)); | |
} else { | |
movePanelTo(panel, userConstrain(handler, elementRect.x, elementRect.y, elementRect, contentAreaRect, panelRect)); | |
} | |
} else { | |
panel.hide(); | |
} | |
} | |
togglePositionClass(panel, relPos, function (pos1, pos2) { | |
return pos1 === pos2; | |
}); | |
//drawRect(contentAreaRect, 'blue'); | |
//drawRect(elementRect, 'red'); | |
//drawRect(panelRect, 'green'); | |
}; | |
var repositionHandler = function (show) { | |
return function () { | |
var execute = function () { | |
if (editor.selection) { | |
reposition(findFrontMostMatch(editor.selection.getNode()), show); | |
} | |
}; | |
Delay.requestAnimationFrame(execute); | |
}; | |
}; | |
var bindScrollEvent = function () { | |
if (!scrollContainer) { | |
var reposition = repositionHandler(true); | |
scrollContainer = editor.selection.getScrollContainer() || editor.getWin(); | |
DOM.bind(scrollContainer, 'scroll', reposition); | |
DOM.bind(Env.container, 'scroll', reposition); | |
editor.on('remove', function () { | |
DOM.unbind(scrollContainer, 'scroll', reposition); | |
DOM.unbind(Env.container, 'scroll', reposition); | |
}); | |
} | |
}; | |
var showContextToolbar = function (match) { | |
var panel; | |
if (match.toolbar.panel) { | |
match.toolbar.panel.show(); | |
reposition(match); | |
return; | |
} | |
bindScrollEvent(); | |
panel = Factory.create({ | |
type: 'floatpanel', | |
role: 'dialog', | |
classes: 'tinymce tinymce-inline arrow', | |
ariaLabel: 'Inline toolbar', | |
layout: 'flex', | |
direction: 'column', | |
align: 'stretch', | |
autohide: false, | |
autofix: true, | |
fixed: true, | |
border: 1, | |
items: Toolbar.createToolbar(editor, match.toolbar.items), | |
oncancel: function () { | |
editor.focus(); | |
} | |
}); | |
match.toolbar.panel = panel; | |
panel.renderTo().reflow(); | |
reposition(match); | |
}; | |
var hideAllContextToolbars = function () { | |
Tools.each(getContextToolbars(), function (toolbar) { | |
if (toolbar.panel) { | |
toolbar.panel.hide(); | |
} | |
}); | |
}; | |
var findFrontMostMatch = function (targetElm) { | |
var i, y, parentsAndSelf, toolbars = getContextToolbars(); | |
parentsAndSelf = editor.$(targetElm).parents().add(targetElm); | |
for (i = parentsAndSelf.length - 1; i >= 0; i--) { | |
for (y = toolbars.length - 1; y >= 0; y--) { | |
if (toolbars[y].predicate(parentsAndSelf[i])) { | |
return { | |
toolbar: toolbars[y], | |
element: parentsAndSelf[i] | |
}; | |
} | |
} | |
} | |
return null; | |
}; | |
editor.on('click keyup setContent ObjectResized', function (e) { | |
// Only act on partial inserts | |
if (e.type === 'setcontent' && !e.selection) { | |
return; | |
} | |
// Needs to be delayed to avoid Chrome img focus out bug | |
Delay.setEditorTimeout(editor, function () { | |
var match; | |
match = findFrontMostMatch(editor.selection.getNode()); | |
if (match) { | |
hideAllContextToolbars(); | |
showContextToolbar(match); | |
} else { | |
hideAllContextToolbars(); | |
} | |
}); | |
}); | |
editor.on('blur hide contextmenu', hideAllContextToolbars); | |
editor.on('ObjectResizeStart', function () { | |
var match = findFrontMostMatch(editor.selection.getNode()); | |
if (match && match.toolbar.panel) { | |
match.toolbar.panel.hide(); | |
} | |
}); | |
editor.on('ResizeEditor ResizeWindow', repositionHandler(true)); | |
editor.on('nodeChange', repositionHandler(false)); | |
editor.on('remove', function () { | |
Tools.each(getContextToolbars(), function (toolbar) { | |
if (toolbar.panel) { | |
toolbar.panel.remove(); | |
} | |
}); | |
editor.contextToolbars = {}; | |
}); | |
editor.shortcuts.add('ctrl+shift+e > ctrl+shift+p', '', function () { | |
var match = findFrontMostMatch(editor.selection.getNode()); | |
if (match && match.toolbar.panel) { | |
match.toolbar.panel.items()[0].focus(); | |
} | |
}); | |
}; | |
return { | |
addContextualToolbars: addContextualToolbars | |
}; | |
} | |
); | |
defineGlobal("global!String", String); | |
define( | |
'ephox.katamari.api.Arr', | |
[ | |
'ephox.katamari.api.Option', | |
'global!Array', | |
'global!Error', | |
'global!String' | |
], | |
function (Option, Array, Error, String) { | |
// Use the native Array.indexOf if it is available (IE9+) otherwise fall back to manual iteration | |
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf | |
var rawIndexOf = (function () { | |
var pIndexOf = Array.prototype.indexOf; | |
var fastIndex = function (xs, x) { return pIndexOf.call(xs, x); }; | |
var slowIndex = function(xs, x) { return slowIndexOf(xs, x); }; | |
return pIndexOf === undefined ? slowIndex : fastIndex; | |
})(); | |
var indexOf = function (xs, x) { | |
// The rawIndexOf method does not wrap up in an option. This is for performance reasons. | |
var r = rawIndexOf(xs, x); | |
return r === -1 ? Option.none() : Option.some(r); | |
}; | |
var contains = function (xs, x) { | |
return rawIndexOf(xs, x) > -1; | |
}; | |
// Using findIndex is likely less optimal in Chrome (dynamic return type instead of bool) | |
// but if we need that micro-optimisation we can inline it later. | |
var exists = function (xs, pred) { | |
return findIndex(xs, pred).isSome(); | |
}; | |
var range = function (num, f) { | |
var r = []; | |
for (var i = 0; i < num; i++) { | |
r.push(f(i)); | |
} | |
return r; | |
}; | |
// It's a total micro optimisation, but these do make some difference. | |
// Particularly for browsers other than Chrome. | |
// - length caching | |
// http://jsperf.com/browser-diet-jquery-each-vs-for-loop/69 | |
// - not using push | |
// http://jsperf.com/array-direct-assignment-vs-push/2 | |
var chunk = function (array, size) { | |
var r = []; | |
for (var i = 0; i < array.length; i += size) { | |
var s = array.slice(i, i + size); | |
r.push(s); | |
} | |
return r; | |
}; | |
var map = function(xs, f) { | |
// pre-allocating array size when it's guaranteed to be known | |
// http://jsperf.com/push-allocated-vs-dynamic/22 | |
var len = xs.length; | |
var r = new Array(len); | |
for (var i = 0; i < len; i++) { | |
var x = xs[i]; | |
r[i] = f(x, i, xs); | |
} | |
return r; | |
}; | |
// Unwound implementing other functions in terms of each. | |
// The code size is roughly the same, and it should allow for better optimisation. | |
var each = function(xs, f) { | |
for (var i = 0, len = xs.length; i < len; i++) { | |
var x = xs[i]; | |
f(x, i, xs); | |
} | |
}; | |
var eachr = function (xs, f) { | |
for (var i = xs.length - 1; i >= 0; i--) { | |
var x = xs[i]; | |
f(x, i, xs); | |
} | |
}; | |
var partition = function(xs, pred) { | |
var pass = []; | |
var fail = []; | |
for (var i = 0, len = xs.length; i < len; i++) { | |
var x = xs[i]; | |
var arr = pred(x, i, xs) ? pass : fail; | |
arr.push(x); | |
} | |
return { pass: pass, fail: fail }; | |
}; | |
var filter = function(xs, pred) { | |
var r = []; | |
for (var i = 0, len = xs.length; i < len; i++) { | |
var x = xs[i]; | |
if (pred(x, i, xs)) { | |
r.push(x); | |
} | |
} | |
return r; | |
}; | |
/* | |
* Groups an array into contiguous arrays of like elements. Whether an element is like or not depends on f. | |
* | |
* f is a function that derives a value from an element - e.g. true or false, or a string. | |
* Elements are like if this function generates the same value for them (according to ===). | |
* | |
* | |
* Order of the elements is preserved. Arr.flatten() on the result will return the original list, as with Haskell groupBy function. | |
* For a good explanation, see the group function (which is a special case of groupBy) | |
* http://hackage.haskell.org/package/base-4.7.0.0/docs/Data-List.html#v:group | |
*/ | |
var groupBy = function (xs, f) { | |
if (xs.length === 0) { | |
return []; | |
} else { | |
var wasType = f(xs[0]); // initial case for matching | |
var r = []; | |
var group = []; | |
for (var i = 0, len = xs.length; i < len; i++) { | |
var x = xs[i]; | |
var type = f(x); | |
if (type !== wasType) { | |
r.push(group); | |
group = []; | |
} | |
wasType = type; | |
group.push(x); | |
} | |
if (group.length !== 0) { | |
r.push(group); | |
} | |
return r; | |
} | |
}; | |
var foldr = function (xs, f, acc) { | |
eachr(xs, function (x) { | |
acc = f(acc, x); | |
}); | |
return acc; | |
}; | |
var foldl = function (xs, f, acc) { | |
each(xs, function (x) { | |
acc = f(acc, x); | |
}); | |
return acc; | |
}; | |
var find = function (xs, pred) { | |
for (var i = 0, len = xs.length; i < len; i++) { | |
var x = xs[i]; | |
if (pred(x, i, xs)) { | |
return Option.some(x); | |
} | |
} | |
return Option.none(); | |
}; | |
var findIndex = function (xs, pred) { | |
for (var i = 0, len = xs.length; i < len; i++) { | |
var x = xs[i]; | |
if (pred(x, i, xs)) { | |
return Option.some(i); | |
} | |
} | |
return Option.none(); | |
}; | |
var slowIndexOf = function (xs, x) { | |
for (var i = 0, len = xs.length; i < len; ++i) { | |
if (xs[i] === x) { | |
return i; | |
} | |
} | |
return -1; | |
}; | |
var push = Array.prototype.push; | |
var flatten = function (xs) { | |
// Note, this is possible because push supports multiple arguments: | |
// http://jsperf.com/concat-push/6 | |
// Note that in the past, concat() would silently work (very slowly) for array-like objects. | |
// With this change it will throw an error. | |
var r = []; | |
for (var i = 0, len = xs.length; i < len; ++i) { | |
// Ensure that each value is an array itself | |
if (! Array.prototype.isPrototypeOf(xs[i])) throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs); | |
push.apply(r, xs[i]); | |
} | |
return r; | |
}; | |
var bind = function (xs, f) { | |
var output = map(xs, f); | |
return flatten(output); | |
}; | |
var forall = function (xs, pred) { | |
for (var i = 0, len = xs.length; i < len; ++i) { | |
var x = xs[i]; | |
if (pred(x, i, xs) !== true) { | |
return false; | |
} | |
} | |
return true; | |
}; | |
var equal = function (a1, a2) { | |
return a1.length === a2.length && forall(a1, function (x, i) { | |
return x === a2[i]; | |
}); | |
}; | |
var slice = Array.prototype.slice; | |
var reverse = function (xs) { | |
var r = slice.call(xs, 0); | |
r.reverse(); | |
return r; | |
}; | |
var difference = function (a1, a2) { | |
return filter(a1, function (x) { | |
return !contains(a2, x); | |
}); | |
}; | |
var mapToObject = function(xs, f) { | |
var r = {}; | |
for (var i = 0, len = xs.length; i < len; i++) { | |
var x = xs[i]; | |
r[String(x)] = f(x, i); | |
} | |
return r; | |
}; | |
var pure = function(x) { | |
return [x]; | |
}; | |
var sort = function (xs, comparator) { | |
var copy = slice.call(xs, 0); | |
copy.sort(comparator); | |
return copy; | |
}; | |
var head = function (xs) { | |
return xs.length === 0 ? Option.none() : Option.some(xs[0]); | |
}; | |
var last = function (xs) { | |
return xs.length === 0 ? Option.none() : Option.some(xs[xs.length - 1]); | |
}; | |
return { | |
map: map, | |
each: each, | |
eachr: eachr, | |
partition: partition, | |
filter: filter, | |
groupBy: groupBy, | |
indexOf: indexOf, | |
foldr: foldr, | |
foldl: foldl, | |
find: find, | |
findIndex: findIndex, | |
flatten: flatten, | |
bind: bind, | |
forall: forall, | |
exists: exists, | |
contains: contains, | |
equal: equal, | |
reverse: reverse, | |
chunk: chunk, | |
difference: difference, | |
mapToObject: mapToObject, | |
pure: pure, | |
sort: sort, | |
range: range, | |
head: head, | |
last: last | |
}; | |
} | |
); | |
/** | |
* Menubar.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.ui.Menubar', | |
[ | |
'ephox.katamari.api.Arr', | |
'tinymce.core.util.Tools', | |
'tinymce.themes.modern.api.Settings' | |
], | |
function (Arr, Tools, Settings) { | |
var defaultMenus = { | |
file: { title: 'File', items: 'newdocument restoredraft | preview | print' }, | |
edit: { title: 'Edit', items: 'undo redo | cut copy paste pastetext | selectall' }, | |
view: { title: 'View', items: 'code | visualaid visualchars visualblocks | spellchecker | preview fullscreen' }, | |
insert: { title: 'Insert', items: 'image link media template codesample inserttable | charmap hr | pagebreak nonbreaking anchor toc | insertdatetime' }, | |
format: { title: 'Format', items: 'bold italic underline strikethrough superscript subscript codeformat | blockformats align | removeformat' }, | |
tools: { title: 'Tools', items: 'spellchecker spellcheckerlanguage | a11ycheck' }, | |
table: { title: 'Table' }, | |
help: { title: 'Help' } | |
}; | |
var delimiterMenuNamePair = function () { | |
return { name: '|', item: { text: '|' } }; | |
}; | |
var createMenuNameItemPair = function (name, item) { | |
var menuItem = item ? { name: name, item: item } : null; | |
return name === '|' ? delimiterMenuNamePair() : menuItem; | |
}; | |
var hasItemName = function (namedMenuItems, name) { | |
return Arr.findIndex(namedMenuItems, function (namedMenuItem) { | |
return namedMenuItem.name === name; | |
}).isSome(); | |
}; | |
var isSeparator = function (namedMenuItem) { | |
return namedMenuItem && namedMenuItem.item.text === '|'; | |
}; | |
var cleanupMenu = function (namedMenuItems, removedMenuItems) { | |
var menuItemsPass1 = Arr.filter(namedMenuItems, function (namedMenuItem) { | |
return removedMenuItems.hasOwnProperty(namedMenuItem.name) === false; | |
}); | |
var menuItemsPass2 = Arr.filter(menuItemsPass1, function (namedMenuItem, i, namedMenuItems) { | |
return !isSeparator(namedMenuItem) || !isSeparator(namedMenuItems[i - 1]); | |
}); | |
return Arr.filter(menuItemsPass2, function (namedMenuItem, i, namedMenuItems) { | |
return !isSeparator(namedMenuItem) || i > 0 && i < namedMenuItems.length - 1; | |
}); | |
}; | |
var createMenu = function (editorMenuItems, menus, removedMenuItems, context) { | |
var menuButton, menu, namedMenuItems, isUserDefined; | |
// User defined menu | |
if (menus) { | |
menu = menus[context]; | |
isUserDefined = true; | |
} else { | |
menu = defaultMenus[context]; | |
} | |
if (menu) { | |
menuButton = { text: menu.title }; | |
namedMenuItems = []; | |
// Default/user defined items | |
Tools.each((menu.items || '').split(/[ ,]/), function (name) { | |
var namedMenuItem = createMenuNameItemPair(name, editorMenuItems[name]); | |
if (namedMenuItem) { | |
namedMenuItems.push(namedMenuItem); | |
} | |
}); | |
// Added though context | |
if (!isUserDefined) { | |
Tools.each(editorMenuItems, function (item, name) { | |
if (item.context === context && !hasItemName(namedMenuItems, name)) { | |
if (item.separator === 'before') { | |
namedMenuItems.push(delimiterMenuNamePair()); | |
} | |
if (item.prependToContext) { | |
namedMenuItems.unshift(createMenuNameItemPair(name, item)); | |
} else { | |
namedMenuItems.push(createMenuNameItemPair(name, item)); | |
} | |
if (item.separator === 'after') { | |
namedMenuItems.push(delimiterMenuNamePair()); | |
} | |
} | |
}); | |
} | |
menuButton.menu = Arr.map(cleanupMenu(namedMenuItems, removedMenuItems), function (menuItem) { | |
return menuItem.item; | |
}); | |
if (!menuButton.menu.length) { | |
return null; | |
} | |
} | |
return menuButton; | |
}; | |
var getDefaultMenubar = function (editor) { | |
var name, defaultMenuBar = []; | |
var menu = Settings.getMenu(editor); | |
if (menu) { | |
for (name in menu) { | |
defaultMenuBar.push(name); | |
} | |
} else { | |
for (name in defaultMenus) { | |
defaultMenuBar.push(name); | |
} | |
} | |
return defaultMenuBar; | |
}; | |
var createMenuButtons = function (editor) { | |
var menuButtons = []; | |
var defaultMenuBar = getDefaultMenubar(editor); | |
var removedMenuItems = Tools.makeMap(Settings.getRemovedMenuItems(editor).split(/[ ,]/)); | |
var menubar = Settings.getMenubar(editor); | |
var enabledMenuNames = typeof menubar === "string" ? menubar.split(/[ ,]/) : defaultMenuBar; | |
for (var i = 0; i < enabledMenuNames.length; i++) { | |
var menuItems = enabledMenuNames[i]; | |
var menu = createMenu(editor.menuItems, Settings.getMenu(editor), removedMenuItems, menuItems); | |
if (menu) { | |
menuButtons.push(menu); | |
} | |
} | |
return menuButtons; | |
}; | |
return { | |
createMenuButtons: createMenuButtons | |
}; | |
} | |
); | |
/** | |
* Resize.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.ui.Resize', | |
[ | |
'tinymce.core.dom.DOMUtils', | |
'tinymce.themes.modern.api.Events', | |
'tinymce.themes.modern.api.Settings' | |
], | |
function (DOMUtils, Events, Settings) { | |
var DOM = DOMUtils.DOM; | |
var getSize = function (elm) { | |
return { | |
width: elm.clientWidth, | |
height: elm.clientHeight | |
}; | |
}; | |
var resizeTo = function (editor, width, height) { | |
var containerElm, iframeElm, containerSize, iframeSize; | |
containerElm = editor.getContainer(); | |
iframeElm = editor.getContentAreaContainer().firstChild; | |
containerSize = getSize(containerElm); | |
iframeSize = getSize(iframeElm); | |
if (width !== null) { | |
width = Math.max(Settings.getMinWidth(editor), width); | |
width = Math.min(Settings.getMaxWidth(editor), width); | |
DOM.setStyle(containerElm, 'width', width + (containerSize.width - iframeSize.width)); | |
DOM.setStyle(iframeElm, 'width', width); | |
} | |
height = Math.max(Settings.getMinHeight(editor), height); | |
height = Math.min(Settings.getMaxHeight(editor), height); | |
DOM.setStyle(iframeElm, 'height', height); | |
Events.fireResizeEditor(editor); | |
}; | |
var resizeBy = function (editor, dw, dh) { | |
var elm = editor.getContentAreaContainer(); | |
resizeTo(editor, elm.clientWidth + dw, elm.clientHeight + dh); | |
}; | |
return { | |
resizeTo: resizeTo, | |
resizeBy: resizeBy | |
}; | |
} | |
); | |
/** | |
* Sidebar.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.ui.Sidebar', | |
[ | |
'tinymce.core.Env', | |
'tinymce.core.ui.Factory', | |
'tinymce.core.util.Tools', | |
'tinymce.themes.modern.api.Events' | |
], | |
function (Env, Factory, Tools, Events) { | |
var api = function (elm) { | |
return { | |
element: function () { | |
return elm; | |
} | |
}; | |
}; | |
var trigger = function (sidebar, panel, callbackName) { | |
var callback = sidebar.settings[callbackName]; | |
if (callback) { | |
callback(api(panel.getEl('body'))); | |
} | |
}; | |
var hidePanels = function (name, container, sidebars) { | |
Tools.each(sidebars, function (sidebar) { | |
var panel = container.items().filter('#' + sidebar.name)[0]; | |
if (panel && panel.visible() && sidebar.name !== name) { | |
trigger(sidebar, panel, 'onhide'); | |
panel.visible(false); | |
} | |
}); | |
}; | |
var deactivateButtons = function (toolbar) { | |
toolbar.items().each(function (ctrl) { | |
ctrl.active(false); | |
}); | |
}; | |
var findSidebar = function (sidebars, name) { | |
return Tools.grep(sidebars, function (sidebar) { | |
return sidebar.name === name; | |
})[0]; | |
}; | |
var showPanel = function (editor, name, sidebars) { | |
return function (e) { | |
var btnCtrl = e.control; | |
var container = btnCtrl.parents().filter('panel')[0]; | |
var panel = container.find('#' + name)[0]; | |
var sidebar = findSidebar(sidebars, name); | |
hidePanels(name, container, sidebars); | |
deactivateButtons(btnCtrl.parent()); | |
if (panel && panel.visible()) { | |
trigger(sidebar, panel, 'onhide'); | |
panel.hide(); | |
btnCtrl.active(false); | |
} else { | |
if (panel) { | |
panel.show(); | |
trigger(sidebar, panel, 'onshow'); | |
} else { | |
panel = Factory.create({ | |
type: 'container', | |
name: name, | |
layout: 'stack', | |
classes: 'sidebar-panel', | |
html: '' | |
}); | |
container.prepend(panel); | |
trigger(sidebar, panel, 'onrender'); | |
trigger(sidebar, panel, 'onshow'); | |
} | |
btnCtrl.active(true); | |
} | |
Events.fireResizeEditor(editor); | |
}; | |
}; | |
var isModernBrowser = function () { | |
return !Env.ie || Env.ie >= 11; | |
}; | |
var hasSidebar = function (editor) { | |
return isModernBrowser() && editor.sidebars ? editor.sidebars.length > 0 : false; | |
}; | |
var createSidebar = function (editor) { | |
var buttons = Tools.map(editor.sidebars, function (sidebar) { | |
var settings = sidebar.settings; | |
return { | |
type: 'button', | |
icon: settings.icon, | |
image: settings.image, | |
tooltip: settings.tooltip, | |
onclick: showPanel(editor, sidebar.name, editor.sidebars) | |
}; | |
}); | |
return { | |
type: 'panel', | |
name: 'sidebar', | |
layout: 'stack', | |
classes: 'sidebar', | |
items: [ | |
{ | |
type: 'toolbar', | |
layout: 'stack', | |
classes: 'sidebar-toolbar', | |
items: buttons | |
} | |
] | |
}; | |
}; | |
return { | |
hasSidebar: hasSidebar, | |
createSidebar: createSidebar | |
}; | |
} | |
); | |
/** | |
* SkinLoaded.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.ui.SkinLoaded', [ | |
'tinymce.themes.modern.api.Events' | |
], | |
function (Events) { | |
var fireSkinLoaded = function (editor) { | |
var done = function () { | |
editor._skinLoaded = true; | |
Events.fireSkinLoaded(editor); | |
}; | |
return function () { | |
if (editor.initialized) { | |
done(); | |
} else { | |
editor.on('init', done); | |
} | |
}; | |
}; | |
return { | |
fireSkinLoaded: fireSkinLoaded | |
}; | |
} | |
); | |
/** | |
* Iframe.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.modes.Iframe', | |
[ | |
'tinymce.core.dom.DOMUtils', | |
'tinymce.core.ui.Factory', | |
'tinymce.core.util.I18n', | |
'tinymce.core.util.Tools', | |
'tinymce.themes.modern.api.Events', | |
'tinymce.themes.modern.api.Settings', | |
'tinymce.themes.modern.ui.A11y', | |
'tinymce.themes.modern.ui.ContextToolbars', | |
'tinymce.themes.modern.ui.Menubar', | |
'tinymce.themes.modern.ui.Resize', | |
'tinymce.themes.modern.ui.Sidebar', | |
'tinymce.themes.modern.ui.SkinLoaded', | |
'tinymce.themes.modern.ui.Toolbar' | |
], | |
function (DOMUtils, Factory, I18n, Tools, Events, Settings, A11y, ContextToolbars, Menubar, Resize, Sidebar, SkinLoaded, Toolbar) { | |
var DOM = DOMUtils.DOM; | |
var switchMode = function (panel) { | |
return function (e) { | |
panel.find('*').disabled(e.mode === 'readonly'); | |
}; | |
}; | |
var editArea = function (border) { | |
return { | |
type: 'panel', | |
name: 'iframe', | |
layout: 'stack', | |
classes: 'edit-area', | |
border: border, | |
html: '' | |
}; | |
}; | |
var editAreaContainer = function (editor) { | |
return { | |
type: 'panel', | |
layout: 'stack', | |
classes: 'edit-aria-container', | |
border: '1 0 0 0', | |
items: [ | |
editArea('0'), | |
Sidebar.createSidebar(editor) | |
] | |
}; | |
}; | |
var render = function (editor, theme, args) { | |
var panel, resizeHandleCtrl, startSize; | |
if (Settings.isSkinDisabled(editor) === false && args.skinUiCss) { | |
DOM.styleSheetLoader.load(args.skinUiCss, SkinLoaded.fireSkinLoaded(editor)); | |
} else { | |
SkinLoaded.fireSkinLoaded(editor)(); | |
} | |
panel = theme.panel = Factory.create({ | |
type: 'panel', | |
role: 'application', | |
classes: 'tinymce', | |
style: 'visibility: hidden', | |
layout: 'stack', | |
border: 1, | |
items: [ | |
{ | |
type: 'container', | |
classes: 'top-part', | |
items: [ | |
Settings.hasMenubar(editor) === false ? null : { type: 'menubar', border: '0 0 1 0', items: Menubar.createMenuButtons(editor) }, | |
Toolbar.createToolbars(editor, Settings.getToolbarSize(editor)) | |
] | |
}, | |
Sidebar.hasSidebar(editor) ? editAreaContainer(editor) : editArea('1 0 0 0') | |
] | |
}); | |
if (Settings.getResize(editor) !== "none") { | |
resizeHandleCtrl = { | |
type: 'resizehandle', | |
direction: Settings.getResize(editor), | |
onResizeStart: function () { | |
var elm = editor.getContentAreaContainer().firstChild; | |
startSize = { | |
width: elm.clientWidth, | |
height: elm.clientHeight | |
}; | |
}, | |
onResize: function (e) { | |
if (Settings.getResize(editor) === 'both') { | |
Resize.resizeTo(editor, startSize.width + e.deltaX, startSize.height + e.deltaY); | |
} else { | |
Resize.resizeTo(editor, null, startSize.height + e.deltaY); | |
} | |
} | |
}; | |
} | |
if (Settings.hasStatusbar(editor)) { | |
var linkHtml = '<a href="https://www.tinymce.com/?utm_campaign=editor_referral&utm_medium=poweredby&utm_source=tinymce" rel="noopener" target="_blank">tinymce</a>'; | |
var html = I18n.translate(['Powered by {0}', linkHtml]); | |
var brandingLabel = Settings.isBrandingEnabled(editor) ? { type: 'label', classes: 'branding', html: ' ' + html } : null; | |
panel.add({ | |
type: 'panel', name: 'statusbar', classes: 'statusbar', layout: 'flow', border: '1 0 0 0', ariaRoot: true, items: [ | |
{ type: 'elementpath', editor: editor }, | |
resizeHandleCtrl, | |
brandingLabel | |
] | |
}); | |
} | |
Events.fireBeforeRenderUI(editor); | |
editor.on('SwitchMode', switchMode(panel)); | |
panel.renderBefore(args.targetNode).reflow(); | |
if (Settings.isReadOnly(editor)) { | |
editor.setMode('readonly'); | |
} | |
if (args.width) { | |
DOM.setStyle(panel.getEl(), 'width', args.width); | |
} | |
// Remove the panel when the editor is removed | |
editor.on('remove', function () { | |
panel.remove(); | |
panel = null; | |
}); | |
// Add accesibility shortcuts | |
A11y.addKeys(editor, panel); | |
ContextToolbars.addContextualToolbars(editor); | |
return { | |
iframeContainer: panel.find('#iframe')[0].getEl(), | |
editorContainer: panel.getEl() | |
}; | |
}; | |
return { | |
render: render | |
}; | |
} | |
); | |
defineGlobal("global!document", document); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.dom.DomQuery', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.dom.DomQuery'); | |
} | |
); | |
/** | |
* DomUtils.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Private UI DomUtils proxy. | |
* | |
* @private | |
* @class tinymce.ui.DomUtils | |
*/ | |
define( | |
'tinymce.ui.DomUtils', | |
[ | |
'global!document', | |
'tinymce.core.dom.DOMUtils', | |
'tinymce.core.Env', | |
'tinymce.core.util.Tools' | |
], | |
function (document, DOMUtils, Env, Tools) { | |
"use strict"; | |
var count = 0; | |
var funcs = { | |
id: function () { | |
return 'mceu_' + (count++); | |
}, | |
create: function (name, attrs, children) { | |
var elm = document.createElement(name); | |
DOMUtils.DOM.setAttribs(elm, attrs); | |
if (typeof children === 'string') { | |
elm.innerHTML = children; | |
} else { | |
Tools.each(children, function (child) { | |
if (child.nodeType) { | |
elm.appendChild(child); | |
} | |
}); | |
} | |
return elm; | |
}, | |
createFragment: function (html) { | |
return DOMUtils.DOM.createFragment(html); | |
}, | |
getWindowSize: function () { | |
return DOMUtils.DOM.getViewPort(); | |
}, | |
getSize: function (elm) { | |
var width, height; | |
if (elm.getBoundingClientRect) { | |
var rect = elm.getBoundingClientRect(); | |
width = Math.max(rect.width || (rect.right - rect.left), elm.offsetWidth); | |
height = Math.max(rect.height || (rect.bottom - rect.bottom), elm.offsetHeight); | |
} else { | |
width = elm.offsetWidth; | |
height = elm.offsetHeight; | |
} | |
return { width: width, height: height }; | |
}, | |
getPos: function (elm, root) { | |
return DOMUtils.DOM.getPos(elm, root || funcs.getContainer()); | |
}, | |
getContainer: function () { | |
return Env.container ? Env.container : document.body; | |
}, | |
getViewPort: function (win) { | |
return DOMUtils.DOM.getViewPort(win); | |
}, | |
get: function (id) { | |
return document.getElementById(id); | |
}, | |
addClass: function (elm, cls) { | |
return DOMUtils.DOM.addClass(elm, cls); | |
}, | |
removeClass: function (elm, cls) { | |
return DOMUtils.DOM.removeClass(elm, cls); | |
}, | |
hasClass: function (elm, cls) { | |
return DOMUtils.DOM.hasClass(elm, cls); | |
}, | |
toggleClass: function (elm, cls, state) { | |
return DOMUtils.DOM.toggleClass(elm, cls, state); | |
}, | |
css: function (elm, name, value) { | |
return DOMUtils.DOM.setStyle(elm, name, value); | |
}, | |
getRuntimeStyle: function (elm, name) { | |
return DOMUtils.DOM.getStyle(elm, name, true); | |
}, | |
on: function (target, name, callback, scope) { | |
return DOMUtils.DOM.bind(target, name, callback, scope); | |
}, | |
off: function (target, name, callback) { | |
return DOMUtils.DOM.unbind(target, name, callback); | |
}, | |
fire: function (target, name, args) { | |
return DOMUtils.DOM.fire(target, name, args); | |
}, | |
innerHtml: function (elm, html) { | |
// Workaround for <div> in <p> bug on IE 8 #6178 | |
DOMUtils.DOM.setHTML(elm, html); | |
} | |
}; | |
return funcs; | |
} | |
); | |
/** | |
* Movable.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Movable mixin. Makes controls movable absolute and relative to other elements. | |
* | |
* @mixin tinymce.ui.Movable | |
*/ | |
define( | |
'tinymce.ui.Movable', | |
[ | |
'global!document', | |
'global!window', | |
'tinymce.ui.DomUtils' | |
], | |
function (document, window, DomUtils) { | |
"use strict"; | |
function calculateRelativePosition(ctrl, targetElm, rel) { | |
var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport, size; | |
viewport = DomUtils.getViewPort(); | |
// Get pos of target | |
pos = DomUtils.getPos(targetElm); | |
x = pos.x; | |
y = pos.y; | |
if (ctrl.state.get('fixed') && DomUtils.getRuntimeStyle(document.body, 'position') == 'static') { | |
x -= viewport.x; | |
y -= viewport.y; | |
} | |
// Get size of self | |
ctrlElm = ctrl.getEl(); | |
size = DomUtils.getSize(ctrlElm); | |
selfW = size.width; | |
selfH = size.height; | |
// Get size of target | |
size = DomUtils.getSize(targetElm); | |
targetW = size.width; | |
targetH = size.height; | |
// Parse align string | |
rel = (rel || '').split(''); | |
// Target corners | |
if (rel[0] === 'b') { | |
y += targetH; | |
} | |
if (rel[1] === 'r') { | |
x += targetW; | |
} | |
if (rel[0] === 'c') { | |
y += Math.round(targetH / 2); | |
} | |
if (rel[1] === 'c') { | |
x += Math.round(targetW / 2); | |
} | |
// Self corners | |
if (rel[3] === 'b') { | |
y -= selfH; | |
} | |
if (rel[4] === 'r') { | |
x -= selfW; | |
} | |
if (rel[3] === 'c') { | |
y -= Math.round(selfH / 2); | |
} | |
if (rel[4] === 'c') { | |
x -= Math.round(selfW / 2); | |
} | |
return { | |
x: x, | |
y: y, | |
w: selfW, | |
h: selfH | |
}; | |
} | |
return { | |
/** | |
* Tests various positions to get the most suitable one. | |
* | |
* @method testMoveRel | |
* @param {DOMElement} elm Element to position against. | |
* @param {Array} rels Array with relative positions. | |
* @return {String} Best suitable relative position. | |
*/ | |
testMoveRel: function (elm, rels) { | |
var viewPortRect = DomUtils.getViewPort(); | |
for (var i = 0; i < rels.length; i++) { | |
var pos = calculateRelativePosition(this, elm, rels[i]); | |
if (this.state.get('fixed')) { | |
if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) { | |
return rels[i]; | |
} | |
} else { | |
if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x && | |
pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) { | |
return rels[i]; | |
} | |
} | |
} | |
return rels[0]; | |
}, | |
/** | |
* Move relative to the specified element. | |
* | |
* @method moveRel | |
* @param {Element} elm Element to move relative to. | |
* @param {String} rel Relative mode. For example: br-tl. | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
moveRel: function (elm, rel) { | |
if (typeof rel != 'string') { | |
rel = this.testMoveRel(elm, rel); | |
} | |
var pos = calculateRelativePosition(this, elm, rel); | |
return this.moveTo(pos.x, pos.y); | |
}, | |
/** | |
* Move by a relative x, y values. | |
* | |
* @method moveBy | |
* @param {Number} dx Relative x position. | |
* @param {Number} dy Relative y position. | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
moveBy: function (dx, dy) { | |
var self = this, rect = self.layoutRect(); | |
self.moveTo(rect.x + dx, rect.y + dy); | |
return self; | |
}, | |
/** | |
* Move to absolute position. | |
* | |
* @method moveTo | |
* @param {Number} x Absolute x position. | |
* @param {Number} y Absolute y position. | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
moveTo: function (x, y) { | |
var self = this; | |
// TODO: Move this to some global class | |
function constrain(value, max, size) { | |
if (value < 0) { | |
return 0; | |
} | |
if (value + size > max) { | |
value = max - size; | |
return value < 0 ? 0 : value; | |
} | |
return value; | |
} | |
if (self.settings.constrainToViewport) { | |
var viewPortRect = DomUtils.getViewPort(window); | |
var layoutRect = self.layoutRect(); | |
x = constrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w); | |
y = constrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h); | |
} | |
if (self.state.get('rendered')) { | |
self.layoutRect({ x: x, y: y }).repaint(); | |
} else { | |
self.settings.x = x; | |
self.settings.y = y; | |
} | |
self.fire('move', { x: x, y: y }); | |
return self; | |
} | |
}; | |
} | |
); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.util.Class', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.util.Class'); | |
} | |
); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.util.EventDispatcher', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.util.EventDispatcher'); | |
} | |
); | |
/** | |
* BoxUtils.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Utility class for box parsing and measuring. | |
* | |
* @private | |
* @class tinymce.ui.BoxUtils | |
*/ | |
define( | |
'tinymce.ui.BoxUtils', | |
[ | |
], | |
function () { | |
"use strict"; | |
return { | |
/** | |
* Parses the specified box value. A box value contains 1-4 properties in clockwise order. | |
* | |
* @method parseBox | |
* @param {String/Number} value Box value "0 1 2 3" or "0" etc. | |
* @return {Object} Object with top/right/bottom/left properties. | |
* @private | |
*/ | |
parseBox: function (value) { | |
var len, radix = 10; | |
if (!value) { | |
return; | |
} | |
if (typeof value === "number") { | |
value = value || 0; | |
return { | |
top: value, | |
left: value, | |
bottom: value, | |
right: value | |
}; | |
} | |
value = value.split(' '); | |
len = value.length; | |
if (len === 1) { | |
value[1] = value[2] = value[3] = value[0]; | |
} else if (len === 2) { | |
value[2] = value[0]; | |
value[3] = value[1]; | |
} else if (len === 3) { | |
value[3] = value[1]; | |
} | |
return { | |
top: parseInt(value[0], radix) || 0, | |
right: parseInt(value[1], radix) || 0, | |
bottom: parseInt(value[2], radix) || 0, | |
left: parseInt(value[3], radix) || 0 | |
}; | |
}, | |
measureBox: function (elm, prefix) { | |
function getStyle(name) { | |
var defaultView = elm.ownerDocument.defaultView; | |
if (defaultView) { | |
var computedStyle = defaultView.getComputedStyle(elm, null); | |
if (computedStyle) { | |
// Remove camelcase | |
name = name.replace(/[A-Z]/g, function (a) { | |
return '-' + a; | |
}); | |
return computedStyle.getPropertyValue(name); | |
} else { | |
return null; | |
} | |
} | |
return elm.currentStyle[name]; | |
} | |
function getSide(name) { | |
var val = parseFloat(getStyle(name), 10); | |
return isNaN(val) ? 0 : val; | |
} | |
return { | |
top: getSide(prefix + "TopWidth"), | |
right: getSide(prefix + "RightWidth"), | |
bottom: getSide(prefix + "BottomWidth"), | |
left: getSide(prefix + "LeftWidth") | |
}; | |
} | |
}; | |
} | |
); | |
/** | |
* ClassList.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Handles adding and removal of classes. | |
* | |
* @private | |
* @class tinymce.ui.ClassList | |
*/ | |
define( | |
'tinymce.ui.ClassList', | |
[ | |
"tinymce.core.util.Tools" | |
], | |
function (Tools) { | |
"use strict"; | |
function noop() { | |
} | |
/** | |
* Constructs a new class list the specified onchange | |
* callback will be executed when the class list gets modifed. | |
* | |
* @constructor ClassList | |
* @param {function} onchange Onchange callback to be executed. | |
*/ | |
function ClassList(onchange) { | |
this.cls = []; | |
this.cls._map = {}; | |
this.onchange = onchange || noop; | |
this.prefix = ''; | |
} | |
Tools.extend(ClassList.prototype, { | |
/** | |
* Adds a new class to the class list. | |
* | |
* @method add | |
* @param {String} cls Class to be added. | |
* @return {tinymce.ui.ClassList} Current class list instance. | |
*/ | |
add: function (cls) { | |
if (cls && !this.contains(cls)) { | |
this.cls._map[cls] = true; | |
this.cls.push(cls); | |
this._change(); | |
} | |
return this; | |
}, | |
/** | |
* Removes the specified class from the class list. | |
* | |
* @method remove | |
* @param {String} cls Class to be removed. | |
* @return {tinymce.ui.ClassList} Current class list instance. | |
*/ | |
remove: function (cls) { | |
if (this.contains(cls)) { | |
for (var i = 0; i < this.cls.length; i++) { | |
if (this.cls[i] === cls) { | |
break; | |
} | |
} | |
this.cls.splice(i, 1); | |
delete this.cls._map[cls]; | |
this._change(); | |
} | |
return this; | |
}, | |
/** | |
* Toggles a class in the class list. | |
* | |
* @method toggle | |
* @param {String} cls Class to be added/removed. | |
* @param {Boolean} state Optional state if it should be added/removed. | |
* @return {tinymce.ui.ClassList} Current class list instance. | |
*/ | |
toggle: function (cls, state) { | |
var curState = this.contains(cls); | |
if (curState !== state) { | |
if (curState) { | |
this.remove(cls); | |
} else { | |
this.add(cls); | |
} | |
this._change(); | |
} | |
return this; | |
}, | |
/** | |
* Returns true if the class list has the specified class. | |
* | |
* @method contains | |
* @param {String} cls Class to look for. | |
* @return {Boolean} true/false if the class exists or not. | |
*/ | |
contains: function (cls) { | |
return !!this.cls._map[cls]; | |
}, | |
/** | |
* Returns a space separated list of classes. | |
* | |
* @method toString | |
* @return {String} Space separated list of classes. | |
*/ | |
_change: function () { | |
delete this.clsValue; | |
this.onchange.call(this); | |
} | |
}); | |
// IE 8 compatibility | |
ClassList.prototype.toString = function () { | |
var value; | |
if (this.clsValue) { | |
return this.clsValue; | |
} | |
value = ''; | |
for (var i = 0; i < this.cls.length; i++) { | |
if (i > 0) { | |
value += ' '; | |
} | |
value += this.prefix + this.cls[i]; | |
} | |
return value; | |
}; | |
return ClassList; | |
} | |
); | |
/** | |
* Selector.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/*eslint no-nested-ternary:0 */ | |
/** | |
* Selector engine, enables you to select controls by using CSS like expressions. | |
* We currently only support basic CSS expressions to reduce the size of the core | |
* and the ones we support should be enough for most cases. | |
* | |
* @example | |
* Supported expressions: | |
* element | |
* element#name | |
* element.class | |
* element[attr] | |
* element[attr*=value] | |
* element[attr~=value] | |
* element[attr!=value] | |
* element[attr^=value] | |
* element[attr$=value] | |
* element:<state> | |
* element:not(<expression>) | |
* element:first | |
* element:last | |
* element:odd | |
* element:even | |
* element element | |
* element > element | |
* | |
* @class tinymce.ui.Selector | |
*/ | |
define( | |
'tinymce.ui.Selector', | |
[ | |
"tinymce.core.util.Class" | |
], | |
function (Class) { | |
"use strict"; | |
/** | |
* Produces an array with a unique set of objects. It will not compare the values | |
* but the references of the objects. | |
* | |
* @private | |
* @method unqiue | |
* @param {Array} array Array to make into an array with unique items. | |
* @return {Array} Array with unique items. | |
*/ | |
function unique(array) { | |
var uniqueItems = [], i = array.length, item; | |
while (i--) { | |
item = array[i]; | |
if (!item.__checked) { | |
uniqueItems.push(item); | |
item.__checked = 1; | |
} | |
} | |
i = uniqueItems.length; | |
while (i--) { | |
delete uniqueItems[i].__checked; | |
} | |
return uniqueItems; | |
} | |
var expression = /^([\w\\*]+)?(?:#([\w\-\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i; | |
/*jshint maxlen:255 */ | |
/*eslint max-len:0 */ | |
var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, | |
whiteSpace = /^\s*|\s*$/g, | |
Collection; | |
var Selector = Class.extend({ | |
/** | |
* Constructs a new Selector instance. | |
* | |
* @constructor | |
* @method init | |
* @param {String} selector CSS like selector expression. | |
*/ | |
init: function (selector) { | |
var match = this.match; | |
function compileNameFilter(name) { | |
if (name) { | |
name = name.toLowerCase(); | |
return function (item) { | |
return name === '*' || item.type === name; | |
}; | |
} | |
} | |
function compileIdFilter(id) { | |
if (id) { | |
return function (item) { | |
return item._name === id; | |
}; | |
} | |
} | |
function compileClassesFilter(classes) { | |
if (classes) { | |
classes = classes.split('.'); | |
return function (item) { | |
var i = classes.length; | |
while (i--) { | |
if (!item.classes.contains(classes[i])) { | |
return false; | |
} | |
} | |
return true; | |
}; | |
} | |
} | |
function compileAttrFilter(name, cmp, check) { | |
if (name) { | |
return function (item) { | |
var value = item[name] ? item[name]() : ''; | |
return !cmp ? !!check : | |
cmp === "=" ? value === check : | |
cmp === "*=" ? value.indexOf(check) >= 0 : | |
cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 : | |
cmp === "!=" ? value != check : | |
cmp === "^=" ? value.indexOf(check) === 0 : | |
cmp === "$=" ? value.substr(value.length - check.length) === check : | |
false; | |
}; | |
} | |
} | |
function compilePsuedoFilter(name) { | |
var notSelectors; | |
if (name) { | |
name = /(?:not\((.+)\))|(.+)/i.exec(name); | |
if (!name[1]) { | |
name = name[2]; | |
return function (item, index, length) { | |
return name === 'first' ? index === 0 : | |
name === 'last' ? index === length - 1 : | |
name === 'even' ? index % 2 === 0 : | |
name === 'odd' ? index % 2 === 1 : | |
item[name] ? item[name]() : | |
false; | |
}; | |
} | |
// Compile not expression | |
notSelectors = parseChunks(name[1], []); | |
return function (item) { | |
return !match(item, notSelectors); | |
}; | |
} | |
} | |
function compile(selector, filters, direct) { | |
var parts; | |
function add(filter) { | |
if (filter) { | |
filters.push(filter); | |
} | |
} | |
// Parse expression into parts | |
parts = expression.exec(selector.replace(whiteSpace, '')); | |
add(compileNameFilter(parts[1])); | |
add(compileIdFilter(parts[2])); | |
add(compileClassesFilter(parts[3])); | |
add(compileAttrFilter(parts[4], parts[5], parts[6])); | |
add(compilePsuedoFilter(parts[7])); | |
// Mark the filter with pseudo for performance | |
filters.pseudo = !!parts[7]; | |
filters.direct = direct; | |
return filters; | |
} | |
// Parser logic based on Sizzle by John Resig | |
function parseChunks(selector, selectors) { | |
var parts = [], extra, matches, i; | |
do { | |
chunker.exec(""); | |
matches = chunker.exec(selector); | |
if (matches) { | |
selector = matches[3]; | |
parts.push(matches[1]); | |
if (matches[2]) { | |
extra = matches[3]; | |
break; | |
} | |
} | |
} while (matches); | |
if (extra) { | |
parseChunks(extra, selectors); | |
} | |
selector = []; | |
for (i = 0; i < parts.length; i++) { | |
if (parts[i] != '>') { | |
selector.push(compile(parts[i], [], parts[i - 1] === '>')); | |
} | |
} | |
selectors.push(selector); | |
return selectors; | |
} | |
this._selectors = parseChunks(selector, []); | |
}, | |
/** | |
* Returns true/false if the selector matches the specified control. | |
* | |
* @method match | |
* @param {tinymce.ui.Control} control Control to match against the selector. | |
* @param {Array} selectors Optional array of selectors, mostly used internally. | |
* @return {Boolean} true/false state if the control matches or not. | |
*/ | |
match: function (control, selectors) { | |
var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item; | |
selectors = selectors || this._selectors; | |
for (i = 0, l = selectors.length; i < l; i++) { | |
selector = selectors[i]; | |
sl = selector.length; | |
item = control; | |
count = 0; | |
for (si = sl - 1; si >= 0; si--) { | |
filters = selector[si]; | |
while (item) { | |
// Find the index and length since a pseudo filter like :first needs it | |
if (filters.pseudo) { | |
siblings = item.parent().items(); | |
index = length = siblings.length; | |
while (index--) { | |
if (siblings[index] === item) { | |
break; | |
} | |
} | |
} | |
for (fi = 0, fl = filters.length; fi < fl; fi++) { | |
if (!filters[fi](item, index, length)) { | |
fi = fl + 1; | |
break; | |
} | |
} | |
if (fi === fl) { | |
count++; | |
break; | |
} else { | |
// If it didn't match the right most expression then | |
// break since it's no point looking at the parents | |
if (si === sl - 1) { | |
break; | |
} | |
} | |
item = item.parent(); | |
} | |
} | |
// If we found all selectors then return true otherwise continue looking | |
if (count === sl) { | |
return true; | |
} | |
} | |
return false; | |
}, | |
/** | |
* Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container. | |
* | |
* @method find | |
* @param {tinymce.ui.Control} container Container to look for items in. | |
* @return {tinymce.ui.Collection} Collection with matched elements. | |
*/ | |
find: function (container) { | |
var matches = [], i, l, selectors = this._selectors; | |
function collect(items, selector, index) { | |
var i, l, fi, fl, item, filters = selector[index]; | |
for (i = 0, l = items.length; i < l; i++) { | |
item = items[i]; | |
// Run each filter against the item | |
for (fi = 0, fl = filters.length; fi < fl; fi++) { | |
if (!filters[fi](item, i, l)) { | |
fi = fl + 1; | |
break; | |
} | |
} | |
// All filters matched the item | |
if (fi === fl) { | |
// Matched item is on the last expression like: panel toolbar [button] | |
if (index == selector.length - 1) { | |
matches.push(item); | |
} else { | |
// Collect next expression type | |
if (item.items) { | |
collect(item.items(), selector, index + 1); | |
} | |
} | |
} else if (filters.direct) { | |
return; | |
} | |
// Collect child items | |
if (item.items) { | |
collect(item.items(), selector, index); | |
} | |
} | |
} | |
if (container.items) { | |
for (i = 0, l = selectors.length; i < l; i++) { | |
collect(container.items(), selectors[i], 0); | |
} | |
// Unique the matches if needed | |
if (l > 1) { | |
matches = unique(matches); | |
} | |
} | |
// Fix for circular reference | |
if (!Collection) { | |
// TODO: Fix me! | |
Collection = Selector.Collection; | |
} | |
return new Collection(matches); | |
} | |
}); | |
return Selector; | |
} | |
); | |
/** | |
* Collection.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Control collection, this class contains control instances and it enables you to | |
* perform actions on all the contained items. This is very similar to how jQuery works. | |
* | |
* @example | |
* someCollection.show().disabled(true); | |
* | |
* @class tinymce.ui.Collection | |
*/ | |
define( | |
'tinymce.ui.Collection', | |
[ | |
"tinymce.core.util.Tools", | |
"tinymce.ui.Selector", | |
"tinymce.core.util.Class" | |
], | |
function (Tools, Selector, Class) { | |
"use strict"; | |
var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice; | |
proto = { | |
/** | |
* Current number of contained control instances. | |
* | |
* @field length | |
* @type Number | |
*/ | |
length: 0, | |
/** | |
* Constructor for the collection. | |
* | |
* @constructor | |
* @method init | |
* @param {Array} items Optional array with items to add. | |
*/ | |
init: function (items) { | |
if (items) { | |
this.add(items); | |
} | |
}, | |
/** | |
* Adds new items to the control collection. | |
* | |
* @method add | |
* @param {Array} items Array if items to add to collection. | |
* @return {tinymce.ui.Collection} Current collection instance. | |
*/ | |
add: function (items) { | |
var self = this; | |
// Force single item into array | |
if (!Tools.isArray(items)) { | |
if (items instanceof Collection) { | |
self.add(items.toArray()); | |
} else { | |
push.call(self, items); | |
} | |
} else { | |
push.apply(self, items); | |
} | |
return self; | |
}, | |
/** | |
* Sets the contents of the collection. This will remove any existing items | |
* and replace them with the ones specified in the input array. | |
* | |
* @method set | |
* @param {Array} items Array with items to set into the Collection. | |
* @return {tinymce.ui.Collection} Collection instance. | |
*/ | |
set: function (items) { | |
var self = this, len = self.length, i; | |
self.length = 0; | |
self.add(items); | |
// Remove old entries | |
for (i = self.length; i < len; i++) { | |
delete self[i]; | |
} | |
return self; | |
}, | |
/** | |
* Filters the collection item based on the specified selector expression or selector function. | |
* | |
* @method filter | |
* @param {String} selector Selector expression to filter items by. | |
* @return {tinymce.ui.Collection} Collection containing the filtered items. | |
*/ | |
filter: function (selector) { | |
var self = this, i, l, matches = [], item, match; | |
// Compile string into selector expression | |
if (typeof selector === "string") { | |
selector = new Selector(selector); | |
match = function (item) { | |
return selector.match(item); | |
}; | |
} else { | |
// Use selector as matching function | |
match = selector; | |
} | |
for (i = 0, l = self.length; i < l; i++) { | |
item = self[i]; | |
if (match(item)) { | |
matches.push(item); | |
} | |
} | |
return new Collection(matches); | |
}, | |
/** | |
* Slices the items within the collection. | |
* | |
* @method slice | |
* @param {Number} index Index to slice at. | |
* @param {Number} len Optional length to slice. | |
* @return {tinymce.ui.Collection} Current collection. | |
*/ | |
slice: function () { | |
return new Collection(slice.apply(this, arguments)); | |
}, | |
/** | |
* Makes the current collection equal to the specified index. | |
* | |
* @method eq | |
* @param {Number} index Index of the item to set the collection to. | |
* @return {tinymce.ui.Collection} Current collection. | |
*/ | |
eq: function (index) { | |
return index === -1 ? this.slice(index) : this.slice(index, +index + 1); | |
}, | |
/** | |
* Executes the specified callback on each item in collection. | |
* | |
* @method each | |
* @param {function} callback Callback to execute for each item in collection. | |
* @return {tinymce.ui.Collection} Current collection instance. | |
*/ | |
each: function (callback) { | |
Tools.each(this, callback); | |
return this; | |
}, | |
/** | |
* Returns an JavaScript array object of the contents inside the collection. | |
* | |
* @method toArray | |
* @return {Array} Array with all items from collection. | |
*/ | |
toArray: function () { | |
return Tools.toArray(this); | |
}, | |
/** | |
* Finds the index of the specified control or return -1 if it isn't in the collection. | |
* | |
* @method indexOf | |
* @param {Control} ctrl Control instance to look for. | |
* @return {Number} Index of the specified control or -1. | |
*/ | |
indexOf: function (ctrl) { | |
var self = this, i = self.length; | |
while (i--) { | |
if (self[i] === ctrl) { | |
break; | |
} | |
} | |
return i; | |
}, | |
/** | |
* Returns a new collection of the contents in reverse order. | |
* | |
* @method reverse | |
* @return {tinymce.ui.Collection} Collection instance with reversed items. | |
*/ | |
reverse: function () { | |
return new Collection(Tools.toArray(this).reverse()); | |
}, | |
/** | |
* Returns true/false if the class exists or not. | |
* | |
* @method hasClass | |
* @param {String} cls Class to check for. | |
* @return {Boolean} true/false state if the class exists or not. | |
*/ | |
hasClass: function (cls) { | |
return this[0] ? this[0].classes.contains(cls) : false; | |
}, | |
/** | |
* Sets/gets the specific property on the items in the collection. The same as executing control.<property>(<value>); | |
* | |
* @method prop | |
* @param {String} name Property name to get/set. | |
* @param {Object} value Optional object value to set. | |
* @return {tinymce.ui.Collection} Current collection instance or value of the first item on a get operation. | |
*/ | |
prop: function (name, value) { | |
var self = this, undef, item; | |
if (value !== undef) { | |
self.each(function (item) { | |
if (item[name]) { | |
item[name](value); | |
} | |
}); | |
return self; | |
} | |
item = self[0]; | |
if (item && item[name]) { | |
return item[name](); | |
} | |
}, | |
/** | |
* Executes the specific function name with optional arguments an all items in collection if it exists. | |
* | |
* @example collection.exec("myMethod", arg1, arg2, arg3); | |
* @method exec | |
* @param {String} name Name of the function to execute. | |
* @param {Object} ... Multiple arguments to pass to each function. | |
* @return {tinymce.ui.Collection} Current collection. | |
*/ | |
exec: function (name) { | |
var self = this, args = Tools.toArray(arguments).slice(1); | |
self.each(function (item) { | |
if (item[name]) { | |
item[name].apply(item, args); | |
} | |
}); | |
return self; | |
}, | |
/** | |
* Remove all items from collection and DOM. | |
* | |
* @method remove | |
* @return {tinymce.ui.Collection} Current collection. | |
*/ | |
remove: function () { | |
var i = this.length; | |
while (i--) { | |
this[i].remove(); | |
} | |
return this; | |
}, | |
/** | |
* Adds a class to all items in the collection. | |
* | |
* @method addClass | |
* @param {String} cls Class to add to each item. | |
* @return {tinymce.ui.Collection} Current collection instance. | |
*/ | |
addClass: function (cls) { | |
return this.each(function (item) { | |
item.classes.add(cls); | |
}); | |
}, | |
/** | |
* Removes the specified class from all items in collection. | |
* | |
* @method removeClass | |
* @param {String} cls Class to remove from each item. | |
* @return {tinymce.ui.Collection} Current collection instance. | |
*/ | |
removeClass: function (cls) { | |
return this.each(function (item) { | |
item.classes.remove(cls); | |
}); | |
} | |
/** | |
* Fires the specified event by name and arguments on the control. This will execute all | |
* bound event handlers. | |
* | |
* @method fire | |
* @param {String} name Name of the event to fire. | |
* @param {Object} args Optional arguments to pass to the event. | |
* @return {tinymce.ui.Collection} Current collection instance. | |
*/ | |
// fire: function(event, args) {}, -- Generated by code below | |
/** | |
* Binds a callback to the specified event. This event can both be | |
* native browser events like "click" or custom ones like PostRender. | |
* | |
* The callback function gets one parameter: either the browser's native event object or a custom JS object. | |
* | |
* @method on | |
* @param {String} name Name of the event to bind. For example "click". | |
* @param {String/function} callback Callback function to execute once the event occurs. | |
* @return {tinymce.ui.Collection} Current collection instance. | |
*/ | |
// on: function(name, callback) {}, -- Generated by code below | |
/** | |
* Unbinds the specified event and optionally a specific callback. If you omit the name | |
* parameter all event handlers will be removed. If you omit the callback all event handles | |
* by the specified name will be removed. | |
* | |
* @method off | |
* @param {String} name Optional name for the event to unbind. | |
* @param {function} callback Optional callback function to unbind. | |
* @return {tinymce.ui.Collection} Current collection instance. | |
*/ | |
// off: function(name, callback) {}, -- Generated by code below | |
/** | |
* Shows the items in the current collection. | |
* | |
* @method show | |
* @return {tinymce.ui.Collection} Current collection instance. | |
*/ | |
// show: function() {}, -- Generated by code below | |
/** | |
* Hides the items in the current collection. | |
* | |
* @method hide | |
* @return {tinymce.ui.Collection} Current collection instance. | |
*/ | |
// hide: function() {}, -- Generated by code below | |
/** | |
* Sets/gets the text contents of the items in the current collection. | |
* | |
* @method text | |
* @return {tinymce.ui.Collection} Current collection instance or text value of the first item on a get operation. | |
*/ | |
// text: function(value) {}, -- Generated by code below | |
/** | |
* Sets/gets the name contents of the items in the current collection. | |
* | |
* @method name | |
* @return {tinymce.ui.Collection} Current collection instance or name value of the first item on a get operation. | |
*/ | |
// name: function(value) {}, -- Generated by code below | |
/** | |
* Sets/gets the disabled state on the items in the current collection. | |
* | |
* @method disabled | |
* @return {tinymce.ui.Collection} Current collection instance or disabled state of the first item on a get operation. | |
*/ | |
// disabled: function(state) {}, -- Generated by code below | |
/** | |
* Sets/gets the active state on the items in the current collection. | |
* | |
* @method active | |
* @return {tinymce.ui.Collection} Current collection instance or active state of the first item on a get operation. | |
*/ | |
// active: function(state) {}, -- Generated by code below | |
/** | |
* Sets/gets the selected state on the items in the current collection. | |
* | |
* @method selected | |
* @return {tinymce.ui.Collection} Current collection instance or selected state of the first item on a get operation. | |
*/ | |
// selected: function(state) {}, -- Generated by code below | |
/** | |
* Sets/gets the selected state on the items in the current collection. | |
* | |
* @method visible | |
* @return {tinymce.ui.Collection} Current collection instance or visible state of the first item on a get operation. | |
*/ | |
// visible: function(state) {}, -- Generated by code below | |
}; | |
// Extend tinymce.ui.Collection prototype with some generated control specific methods | |
Tools.each('fire on off show hide append prepend before after reflow'.split(' '), function (name) { | |
proto[name] = function () { | |
var args = Tools.toArray(arguments); | |
this.each(function (ctrl) { | |
if (name in ctrl) { | |
ctrl[name].apply(ctrl, args); | |
} | |
}); | |
return this; | |
}; | |
}); | |
// Extend tinymce.ui.Collection prototype with some property methods | |
Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function (name) { | |
proto[name] = function (value) { | |
return this.prop(name, value); | |
}; | |
}); | |
// Create class based on the new prototype | |
Collection = Class.extend(proto); | |
// Stick Collection into Selector to prevent circual references | |
Selector.Collection = Collection; | |
return Collection; | |
} | |
); | |
/** | |
* Binding.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class gets dynamically extended to provide a binding between two models. This makes it possible to | |
* sync the state of two properties in two models by a layer of abstraction. | |
* | |
* @private | |
* @class tinymce.data.Binding | |
*/ | |
define( | |
'tinymce.ui.data.Binding', | |
[ | |
], | |
function () { | |
/** | |
* Constructs a new bidning. | |
* | |
* @constructor | |
* @method Binding | |
* @param {Object} settings Settings to the binding. | |
*/ | |
function Binding(settings) { | |
this.create = settings.create; | |
} | |
/** | |
* Creates a binding for a property on a model. | |
* | |
* @method create | |
* @param {tinymce.data.ObservableObject} model Model to create binding to. | |
* @param {String} name Name of property to bind. | |
* @return {tinymce.data.Binding} Binding instance. | |
*/ | |
Binding.create = function (model, name) { | |
return new Binding({ | |
create: function (otherModel, otherName) { | |
var bindings; | |
function fromSelfToOther(e) { | |
otherModel.set(otherName, e.value); | |
} | |
function fromOtherToSelf(e) { | |
model.set(name, e.value); | |
} | |
otherModel.on('change:' + otherName, fromOtherToSelf); | |
model.on('change:' + name, fromSelfToOther); | |
// Keep track of the bindings | |
bindings = otherModel._bindings; | |
if (!bindings) { | |
bindings = otherModel._bindings = []; | |
otherModel.on('destroy', function () { | |
var i = bindings.length; | |
while (i--) { | |
bindings[i](); | |
} | |
}); | |
} | |
bindings.push(function () { | |
model.off('change:' + name, fromSelfToOther); | |
}); | |
return model.get(name); | |
} | |
}); | |
}; | |
return Binding; | |
} | |
); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.util.Observable', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.util.Observable'); | |
} | |
); | |
/** | |
* ObservableObject.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class is a object that is observable when properties changes a change event gets emitted. | |
* | |
* @private | |
* @class tinymce.data.ObservableObject | |
*/ | |
define( | |
'tinymce.ui.data.ObservableObject', | |
[ | |
'tinymce.ui.data.Binding', | |
'tinymce.core.util.Class', | |
'tinymce.core.util.Observable', | |
'tinymce.core.util.Tools' | |
], function (Binding, Class, Observable, Tools) { | |
function isNode(node) { | |
return node.nodeType > 0; | |
} | |
// Todo: Maybe this should be shallow compare since it might be huge object references | |
function isEqual(a, b) { | |
var k, checked; | |
// Strict equals | |
if (a === b) { | |
return true; | |
} | |
// Compare null | |
if (a === null || b === null) { | |
return a === b; | |
} | |
// Compare number, boolean, string, undefined | |
if (typeof a !== "object" || typeof b !== "object") { | |
return a === b; | |
} | |
// Compare arrays | |
if (Tools.isArray(b)) { | |
if (a.length !== b.length) { | |
return false; | |
} | |
k = a.length; | |
while (k--) { | |
if (!isEqual(a[k], b[k])) { | |
return false; | |
} | |
} | |
} | |
// Shallow compare nodes | |
if (isNode(a) || isNode(b)) { | |
return a === b; | |
} | |
// Compare objects | |
checked = {}; | |
for (k in b) { | |
if (!isEqual(a[k], b[k])) { | |
return false; | |
} | |
checked[k] = true; | |
} | |
for (k in a) { | |
if (!checked[k] && !isEqual(a[k], b[k])) { | |
return false; | |
} | |
} | |
return true; | |
} | |
return Class.extend({ | |
Mixins: [Observable], | |
/** | |
* Constructs a new observable object instance. | |
* | |
* @constructor | |
* @param {Object} data Initial data for the object. | |
*/ | |
init: function (data) { | |
var name, value; | |
data = data || {}; | |
for (name in data) { | |
value = data[name]; | |
if (value instanceof Binding) { | |
data[name] = value.create(this, name); | |
} | |
} | |
this.data = data; | |
}, | |
/** | |
* Sets a property on the value this will call | |
* observers if the value is a change from the current value. | |
* | |
* @method set | |
* @param {String/object} name Name of the property to set or a object of items to set. | |
* @param {Object} value Value to set for the property. | |
* @return {tinymce.data.ObservableObject} Observable object instance. | |
*/ | |
set: function (name, value) { | |
var key, args, oldValue = this.data[name]; | |
if (value instanceof Binding) { | |
value = value.create(this, name); | |
} | |
if (typeof name === "object") { | |
for (key in name) { | |
this.set(key, name[key]); | |
} | |
return this; | |
} | |
if (!isEqual(oldValue, value)) { | |
this.data[name] = value; | |
args = { | |
target: this, | |
name: name, | |
value: value, | |
oldValue: oldValue | |
}; | |
this.fire('change:' + name, args); | |
this.fire('change', args); | |
} | |
return this; | |
}, | |
/** | |
* Gets a property by name. | |
* | |
* @method get | |
* @param {String} name Name of the property to get. | |
* @return {Object} Object value of propery. | |
*/ | |
get: function (name) { | |
return this.data[name]; | |
}, | |
/** | |
* Returns true/false if the specified property exists. | |
* | |
* @method has | |
* @param {String} name Name of the property to check for. | |
* @return {Boolean} true/false if the item exists. | |
*/ | |
has: function (name) { | |
return name in this.data; | |
}, | |
/** | |
* Returns a dynamic property binding for the specified property name. This makes | |
* it possible to sync the state of two properties in two ObservableObject instances. | |
* | |
* @method bind | |
* @param {String} name Name of the property to sync with the property it's inserted to. | |
* @return {tinymce.data.Binding} Data binding instance. | |
*/ | |
bind: function (name) { | |
return Binding.create(this, name); | |
}, | |
/** | |
* Destroys the observable object and fires the "destroy" | |
* event and clean up any internal resources. | |
* | |
* @method destroy | |
*/ | |
destroy: function () { | |
this.fire('destroy'); | |
} | |
}); | |
} | |
); | |
/** | |
* ReflowQueue.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class will automatically reflow controls on the next animation frame within a few milliseconds on older browsers. | |
* If the user manually reflows then the automatic reflow will be cancelled. This class is used internally when various control states | |
* changes that triggers a reflow. | |
* | |
* @class tinymce.ui.ReflowQueue | |
* @static | |
*/ | |
define( | |
'tinymce.ui.ReflowQueue', | |
[ | |
'global!document', | |
'tinymce.core.util.Delay' | |
], | |
function (document, Delay) { | |
var dirtyCtrls = {}, animationFrameRequested; | |
return { | |
/** | |
* Adds a control to the next automatic reflow call. This is the control that had a state | |
* change for example if the control was hidden/shown. | |
* | |
* @method add | |
* @param {tinymce.ui.Control} ctrl Control to add to queue. | |
*/ | |
add: function (ctrl) { | |
var parent = ctrl.parent(); | |
if (parent) { | |
if (!parent._layout || parent._layout.isNative()) { | |
return; | |
} | |
if (!dirtyCtrls[parent._id]) { | |
dirtyCtrls[parent._id] = parent; | |
} | |
if (!animationFrameRequested) { | |
animationFrameRequested = true; | |
Delay.requestAnimationFrame(function () { | |
var id, ctrl; | |
animationFrameRequested = false; | |
for (id in dirtyCtrls) { | |
ctrl = dirtyCtrls[id]; | |
if (ctrl.state.get('rendered')) { | |
ctrl.reflow(); | |
} | |
} | |
dirtyCtrls = {}; | |
}, document.body); | |
} | |
} | |
}, | |
/** | |
* Removes the specified control from the automatic reflow. This will happen when for example the user | |
* manually triggers a reflow. | |
* | |
* @method remove | |
* @param {tinymce.ui.Control} ctrl Control to remove from queue. | |
*/ | |
remove: function (ctrl) { | |
if (dirtyCtrls[ctrl._id]) { | |
delete dirtyCtrls[ctrl._id]; | |
} | |
} | |
}; | |
} | |
); | |
/** | |
* Control.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/*eslint consistent-this:0 */ | |
/** | |
* This is the base class for all controls and containers. All UI control instances inherit | |
* from this one as it has the base logic needed by all of them. | |
* | |
* @class tinymce.ui.Control | |
*/ | |
define( | |
'tinymce.ui.Control', | |
[ | |
'global!document', | |
'tinymce.core.dom.DomQuery', | |
'tinymce.core.util.Class', | |
'tinymce.core.util.EventDispatcher', | |
'tinymce.core.util.Tools', | |
'tinymce.ui.BoxUtils', | |
'tinymce.ui.ClassList', | |
'tinymce.ui.Collection', | |
'tinymce.ui.data.ObservableObject', | |
'tinymce.ui.DomUtils', | |
'tinymce.ui.ReflowQueue' | |
], | |
function (document, DomQuery, Class, EventDispatcher, Tools, BoxUtils, ClassList, Collection, ObservableObject, DomUtils, ReflowQueue) { | |
"use strict"; | |
var hasMouseWheelEventSupport = "onmousewheel" in document; | |
var hasWheelEventSupport = false; | |
var classPrefix = "mce-"; | |
var Control, idCounter = 0; | |
var proto = { | |
Statics: { | |
classPrefix: classPrefix | |
}, | |
isRtl: function () { | |
return Control.rtl; | |
}, | |
/** | |
* Class/id prefix to use for all controls. | |
* | |
* @final | |
* @field {String} classPrefix | |
*/ | |
classPrefix: classPrefix, | |
/** | |
* Constructs a new control instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {String} style Style CSS properties to add. | |
* @setting {String} border Border box values example: 1 1 1 1 | |
* @setting {String} padding Padding box values example: 1 1 1 1 | |
* @setting {String} margin Margin box values example: 1 1 1 1 | |
* @setting {Number} minWidth Minimal width for the control. | |
* @setting {Number} minHeight Minimal height for the control. | |
* @setting {String} classes Space separated list of classes to add. | |
* @setting {String} role WAI-ARIA role to use for control. | |
* @setting {Boolean} hidden Is the control hidden by default. | |
* @setting {Boolean} disabled Is the control disabled by default. | |
* @setting {String} name Name of the control instance. | |
*/ | |
init: function (settings) { | |
var self = this, classes, defaultClasses; | |
function applyClasses(classes) { | |
var i; | |
classes = classes.split(' '); | |
for (i = 0; i < classes.length; i++) { | |
self.classes.add(classes[i]); | |
} | |
} | |
self.settings = settings = Tools.extend({}, self.Defaults, settings); | |
// Initial states | |
self._id = settings.id || ('mceu_' + (idCounter++)); | |
self._aria = { role: settings.role }; | |
self._elmCache = {}; | |
self.$ = DomQuery; | |
self.state = new ObservableObject({ | |
visible: true, | |
active: false, | |
disabled: false, | |
value: '' | |
}); | |
self.data = new ObservableObject(settings.data); | |
self.classes = new ClassList(function () { | |
if (self.state.get('rendered')) { | |
self.getEl().className = this.toString(); | |
} | |
}); | |
self.classes.prefix = self.classPrefix; | |
// Setup classes | |
classes = settings.classes; | |
if (classes) { | |
if (self.Defaults) { | |
defaultClasses = self.Defaults.classes; | |
if (defaultClasses && classes != defaultClasses) { | |
applyClasses(defaultClasses); | |
} | |
} | |
applyClasses(classes); | |
} | |
Tools.each('title text name visible disabled active value'.split(' '), function (name) { | |
if (name in settings) { | |
self[name](settings[name]); | |
} | |
}); | |
self.on('click', function () { | |
if (self.disabled()) { | |
return false; | |
} | |
}); | |
/** | |
* Name/value object with settings for the current control. | |
* | |
* @field {Object} settings | |
*/ | |
self.settings = settings; | |
self.borderBox = BoxUtils.parseBox(settings.border); | |
self.paddingBox = BoxUtils.parseBox(settings.padding); | |
self.marginBox = BoxUtils.parseBox(settings.margin); | |
if (settings.hidden) { | |
self.hide(); | |
} | |
}, | |
// Will generate getter/setter methods for these properties | |
Properties: 'parent,name', | |
/** | |
* Returns the root element to render controls into. | |
* | |
* @method getContainerElm | |
* @return {Element} HTML DOM element to render into. | |
*/ | |
getContainerElm: function () { | |
return DomUtils.getContainer(); | |
}, | |
/** | |
* Returns a control instance for the current DOM element. | |
* | |
* @method getParentCtrl | |
* @param {Element} elm HTML dom element to get parent control from. | |
* @return {tinymce.ui.Control} Control instance or undefined. | |
*/ | |
getParentCtrl: function (elm) { | |
var ctrl, lookup = this.getRoot().controlIdLookup; | |
while (elm && lookup) { | |
ctrl = lookup[elm.id]; | |
if (ctrl) { | |
break; | |
} | |
elm = elm.parentNode; | |
} | |
return ctrl; | |
}, | |
/** | |
* Initializes the current controls layout rect. | |
* This will be executed by the layout managers to determine the | |
* default minWidth/minHeight etc. | |
* | |
* @method initLayoutRect | |
* @return {Object} Layout rect instance. | |
*/ | |
initLayoutRect: function () { | |
var self = this, settings = self.settings, borderBox, layoutRect; | |
var elm = self.getEl(), width, height, minWidth, minHeight, autoResize; | |
var startMinWidth, startMinHeight, initialSize; | |
// Measure the current element | |
borderBox = self.borderBox = self.borderBox || BoxUtils.measureBox(elm, 'border'); | |
self.paddingBox = self.paddingBox || BoxUtils.measureBox(elm, 'padding'); | |
self.marginBox = self.marginBox || BoxUtils.measureBox(elm, 'margin'); | |
initialSize = DomUtils.getSize(elm); | |
// Setup minWidth/minHeight and width/height | |
startMinWidth = settings.minWidth; | |
startMinHeight = settings.minHeight; | |
minWidth = startMinWidth || initialSize.width; | |
minHeight = startMinHeight || initialSize.height; | |
width = settings.width; | |
height = settings.height; | |
autoResize = settings.autoResize; | |
autoResize = typeof autoResize != "undefined" ? autoResize : !width && !height; | |
width = width || minWidth; | |
height = height || minHeight; | |
var deltaW = borderBox.left + borderBox.right; | |
var deltaH = borderBox.top + borderBox.bottom; | |
var maxW = settings.maxWidth || 0xFFFF; | |
var maxH = settings.maxHeight || 0xFFFF; | |
// Setup initial layout rect | |
self._layoutRect = layoutRect = { | |
x: settings.x || 0, | |
y: settings.y || 0, | |
w: width, | |
h: height, | |
deltaW: deltaW, | |
deltaH: deltaH, | |
contentW: width - deltaW, | |
contentH: height - deltaH, | |
innerW: width - deltaW, | |
innerH: height - deltaH, | |
startMinWidth: startMinWidth || 0, | |
startMinHeight: startMinHeight || 0, | |
minW: Math.min(minWidth, maxW), | |
minH: Math.min(minHeight, maxH), | |
maxW: maxW, | |
maxH: maxH, | |
autoResize: autoResize, | |
scrollW: 0 | |
}; | |
self._lastLayoutRect = {}; | |
return layoutRect; | |
}, | |
/** | |
* Getter/setter for the current layout rect. | |
* | |
* @method layoutRect | |
* @param {Object} [newRect] Optional new layout rect. | |
* @return {tinymce.ui.Control/Object} Current control or rect object. | |
*/ | |
layoutRect: function (newRect) { | |
var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls; | |
// Initialize default layout rect | |
if (!curRect) { | |
curRect = self.initLayoutRect(); | |
} | |
// Set new rect values | |
if (newRect) { | |
// Calc deltas between inner and outer sizes | |
deltaWidth = curRect.deltaW; | |
deltaHeight = curRect.deltaH; | |
// Set x position | |
if (newRect.x !== undef) { | |
curRect.x = newRect.x; | |
} | |
// Set y position | |
if (newRect.y !== undef) { | |
curRect.y = newRect.y; | |
} | |
// Set minW | |
if (newRect.minW !== undef) { | |
curRect.minW = newRect.minW; | |
} | |
// Set minH | |
if (newRect.minH !== undef) { | |
curRect.minH = newRect.minH; | |
} | |
// Set new width and calculate inner width | |
size = newRect.w; | |
if (size !== undef) { | |
size = size < curRect.minW ? curRect.minW : size; | |
size = size > curRect.maxW ? curRect.maxW : size; | |
curRect.w = size; | |
curRect.innerW = size - deltaWidth; | |
} | |
// Set new height and calculate inner height | |
size = newRect.h; | |
if (size !== undef) { | |
size = size < curRect.minH ? curRect.minH : size; | |
size = size > curRect.maxH ? curRect.maxH : size; | |
curRect.h = size; | |
curRect.innerH = size - deltaHeight; | |
} | |
// Set new inner width and calculate width | |
size = newRect.innerW; | |
if (size !== undef) { | |
size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size; | |
size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size; | |
curRect.innerW = size; | |
curRect.w = size + deltaWidth; | |
} | |
// Set new height and calculate inner height | |
size = newRect.innerH; | |
if (size !== undef) { | |
size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size; | |
size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size; | |
curRect.innerH = size; | |
curRect.h = size + deltaHeight; | |
} | |
// Set new contentW | |
if (newRect.contentW !== undef) { | |
curRect.contentW = newRect.contentW; | |
} | |
// Set new contentH | |
if (newRect.contentH !== undef) { | |
curRect.contentH = newRect.contentH; | |
} | |
// Compare last layout rect with the current one to see if we need to repaint or not | |
lastLayoutRect = self._lastLayoutRect; | |
if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y || | |
lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) { | |
repaintControls = Control.repaintControls; | |
if (repaintControls) { | |
if (repaintControls.map && !repaintControls.map[self._id]) { | |
repaintControls.push(self); | |
repaintControls.map[self._id] = true; | |
} | |
} | |
lastLayoutRect.x = curRect.x; | |
lastLayoutRect.y = curRect.y; | |
lastLayoutRect.w = curRect.w; | |
lastLayoutRect.h = curRect.h; | |
} | |
return self; | |
} | |
return curRect; | |
}, | |
/** | |
* Repaints the control after a layout operation. | |
* | |
* @method repaint | |
*/ | |
repaint: function () { | |
var self = this, style, bodyStyle, bodyElm, rect, borderBox; | |
var borderW, borderH, lastRepaintRect, round, value; | |
// Use Math.round on all values on IE < 9 | |
round = !document.createRange ? Math.round : function (value) { | |
return value; | |
}; | |
style = self.getEl().style; | |
rect = self._layoutRect; | |
lastRepaintRect = self._lastRepaintRect || {}; | |
borderBox = self.borderBox; | |
borderW = borderBox.left + borderBox.right; | |
borderH = borderBox.top + borderBox.bottom; | |
if (rect.x !== lastRepaintRect.x) { | |
style.left = round(rect.x) + 'px'; | |
lastRepaintRect.x = rect.x; | |
} | |
if (rect.y !== lastRepaintRect.y) { | |
style.top = round(rect.y) + 'px'; | |
lastRepaintRect.y = rect.y; | |
} | |
if (rect.w !== lastRepaintRect.w) { | |
value = round(rect.w - borderW); | |
style.width = (value >= 0 ? value : 0) + 'px'; | |
lastRepaintRect.w = rect.w; | |
} | |
if (rect.h !== lastRepaintRect.h) { | |
value = round(rect.h - borderH); | |
style.height = (value >= 0 ? value : 0) + 'px'; | |
lastRepaintRect.h = rect.h; | |
} | |
// Update body if needed | |
if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) { | |
value = round(rect.innerW); | |
bodyElm = self.getEl('body'); | |
if (bodyElm) { | |
bodyStyle = bodyElm.style; | |
bodyStyle.width = (value >= 0 ? value : 0) + 'px'; | |
} | |
lastRepaintRect.innerW = rect.innerW; | |
} | |
if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) { | |
value = round(rect.innerH); | |
bodyElm = bodyElm || self.getEl('body'); | |
if (bodyElm) { | |
bodyStyle = bodyStyle || bodyElm.style; | |
bodyStyle.height = (value >= 0 ? value : 0) + 'px'; | |
} | |
lastRepaintRect.innerH = rect.innerH; | |
} | |
self._lastRepaintRect = lastRepaintRect; | |
self.fire('repaint', {}, false); | |
}, | |
/** | |
* Updates the controls layout rect by re-measuing it. | |
*/ | |
updateLayoutRect: function () { | |
var self = this; | |
self.parent()._lastRect = null; | |
DomUtils.css(self.getEl(), { width: '', height: '' }); | |
self._layoutRect = self._lastRepaintRect = self._lastLayoutRect = null; | |
self.initLayoutRect(); | |
}, | |
/** | |
* Binds a callback to the specified event. This event can both be | |
* native browser events like "click" or custom ones like PostRender. | |
* | |
* The callback function will be passed a DOM event like object that enables yout do stop propagation. | |
* | |
* @method on | |
* @param {String} name Name of the event to bind. For example "click". | |
* @param {String/function} callback Callback function to execute ones the event occurs. | |
* @return {tinymce.ui.Control} Current control object. | |
*/ | |
on: function (name, callback) { | |
var self = this; | |
function resolveCallbackName(name) { | |
var callback, scope; | |
if (typeof name != 'string') { | |
return name; | |
} | |
return function (e) { | |
if (!callback) { | |
self.parentsAndSelf().each(function (ctrl) { | |
var callbacks = ctrl.settings.callbacks; | |
if (callbacks && (callback = callbacks[name])) { | |
scope = ctrl; | |
return false; | |
} | |
}); | |
} | |
if (!callback) { | |
e.action = name; | |
this.fire('execute', e); | |
return; | |
} | |
return callback.call(scope, e); | |
}; | |
} | |
getEventDispatcher(self).on(name, resolveCallbackName(callback)); | |
return self; | |
}, | |
/** | |
* Unbinds the specified event and optionally a specific callback. If you omit the name | |
* parameter all event handlers will be removed. If you omit the callback all event handles | |
* by the specified name will be removed. | |
* | |
* @method off | |
* @param {String} [name] Name for the event to unbind. | |
* @param {function} [callback] Callback function to unbind. | |
* @return {tinymce.ui.Control} Current control object. | |
*/ | |
off: function (name, callback) { | |
getEventDispatcher(this).off(name, callback); | |
return this; | |
}, | |
/** | |
* Fires the specified event by name and arguments on the control. This will execute all | |
* bound event handlers. | |
* | |
* @method fire | |
* @param {String} name Name of the event to fire. | |
* @param {Object} [args] Arguments to pass to the event. | |
* @param {Boolean} [bubble] Value to control bubbling. Defaults to true. | |
* @return {Object} Current arguments object. | |
*/ | |
fire: function (name, args, bubble) { | |
var self = this; | |
args = args || {}; | |
if (!args.control) { | |
args.control = self; | |
} | |
args = getEventDispatcher(self).fire(name, args); | |
// Bubble event up to parents | |
if (bubble !== false && self.parent) { | |
var parent = self.parent(); | |
while (parent && !args.isPropagationStopped()) { | |
parent.fire(name, args, false); | |
parent = parent.parent(); | |
} | |
} | |
return args; | |
}, | |
/** | |
* Returns true/false if the specified event has any listeners. | |
* | |
* @method hasEventListeners | |
* @param {String} name Name of the event to check for. | |
* @return {Boolean} True/false state if the event has listeners. | |
*/ | |
hasEventListeners: function (name) { | |
return getEventDispatcher(this).has(name); | |
}, | |
/** | |
* Returns a control collection with all parent controls. | |
* | |
* @method parents | |
* @param {String} selector Optional selector expression to find parents. | |
* @return {tinymce.ui.Collection} Collection with all parent controls. | |
*/ | |
parents: function (selector) { | |
var self = this, ctrl, parents = new Collection(); | |
// Add each parent to collection | |
for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) { | |
parents.add(ctrl); | |
} | |
// Filter away everything that doesn't match the selector | |
if (selector) { | |
parents = parents.filter(selector); | |
} | |
return parents; | |
}, | |
/** | |
* Returns the current control and it's parents. | |
* | |
* @method parentsAndSelf | |
* @param {String} selector Optional selector expression to find parents. | |
* @return {tinymce.ui.Collection} Collection with all parent controls. | |
*/ | |
parentsAndSelf: function (selector) { | |
return new Collection(this).add(this.parents(selector)); | |
}, | |
/** | |
* Returns the control next to the current control. | |
* | |
* @method next | |
* @return {tinymce.ui.Control} Next control instance. | |
*/ | |
next: function () { | |
var parentControls = this.parent().items(); | |
return parentControls[parentControls.indexOf(this) + 1]; | |
}, | |
/** | |
* Returns the control previous to the current control. | |
* | |
* @method prev | |
* @return {tinymce.ui.Control} Previous control instance. | |
*/ | |
prev: function () { | |
var parentControls = this.parent().items(); | |
return parentControls[parentControls.indexOf(this) - 1]; | |
}, | |
/** | |
* Sets the inner HTML of the control element. | |
* | |
* @method innerHtml | |
* @param {String} html Html string to set as inner html. | |
* @return {tinymce.ui.Control} Current control object. | |
*/ | |
innerHtml: function (html) { | |
this.$el.html(html); | |
return this; | |
}, | |
/** | |
* Returns the control DOM element or sub element. | |
* | |
* @method getEl | |
* @param {String} [suffix] Suffix to get element by. | |
* @return {Element} HTML DOM element for the current control or it's children. | |
*/ | |
getEl: function (suffix) { | |
var id = suffix ? this._id + '-' + suffix : this._id; | |
if (!this._elmCache[id]) { | |
this._elmCache[id] = DomQuery('#' + id)[0]; | |
} | |
return this._elmCache[id]; | |
}, | |
/** | |
* Sets the visible state to true. | |
* | |
* @method show | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
show: function () { | |
return this.visible(true); | |
}, | |
/** | |
* Sets the visible state to false. | |
* | |
* @method hide | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
hide: function () { | |
return this.visible(false); | |
}, | |
/** | |
* Focuses the current control. | |
* | |
* @method focus | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
focus: function () { | |
try { | |
this.getEl().focus(); | |
} catch (ex) { | |
// Ignore IE error | |
} | |
return this; | |
}, | |
/** | |
* Blurs the current control. | |
* | |
* @method blur | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
blur: function () { | |
this.getEl().blur(); | |
return this; | |
}, | |
/** | |
* Sets the specified aria property. | |
* | |
* @method aria | |
* @param {String} name Name of the aria property to set. | |
* @param {String} value Value of the aria property. | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
aria: function (name, value) { | |
var self = this, elm = self.getEl(self.ariaTarget); | |
if (typeof value === "undefined") { | |
return self._aria[name]; | |
} | |
self._aria[name] = value; | |
if (self.state.get('rendered')) { | |
elm.setAttribute(name == 'role' ? name : 'aria-' + name, value); | |
} | |
return self; | |
}, | |
/** | |
* Encodes the specified string with HTML entities. It will also | |
* translate the string to different languages. | |
* | |
* @method encode | |
* @param {String/Object/Array} text Text to entity encode. | |
* @param {Boolean} [translate=true] False if the contents shouldn't be translated. | |
* @return {String} Encoded and possible traslated string. | |
*/ | |
encode: function (text, translate) { | |
if (translate !== false) { | |
text = this.translate(text); | |
} | |
return (text || '').replace(/[&<>"]/g, function (match) { | |
return '&#' + match.charCodeAt(0) + ';'; | |
}); | |
}, | |
/** | |
* Returns the translated string. | |
* | |
* @method translate | |
* @param {String} text Text to translate. | |
* @return {String} Translated string or the same as the input. | |
*/ | |
translate: function (text) { | |
return Control.translate ? Control.translate(text) : text; | |
}, | |
/** | |
* Adds items before the current control. | |
* | |
* @method before | |
* @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control. | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
before: function (items) { | |
var self = this, parent = self.parent(); | |
if (parent) { | |
parent.insert(items, parent.items().indexOf(self), true); | |
} | |
return self; | |
}, | |
/** | |
* Adds items after the current control. | |
* | |
* @method after | |
* @param {Array/tinymce.ui.Collection} items Array of items to append after this control. | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
after: function (items) { | |
var self = this, parent = self.parent(); | |
if (parent) { | |
parent.insert(items, parent.items().indexOf(self)); | |
} | |
return self; | |
}, | |
/** | |
* Removes the current control from DOM and from UI collections. | |
* | |
* @method remove | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
remove: function () { | |
var self = this, elm = self.getEl(), parent = self.parent(), newItems, i; | |
if (self.items) { | |
var controls = self.items().toArray(); | |
i = controls.length; | |
while (i--) { | |
controls[i].remove(); | |
} | |
} | |
if (parent && parent.items) { | |
newItems = []; | |
parent.items().each(function (item) { | |
if (item !== self) { | |
newItems.push(item); | |
} | |
}); | |
parent.items().set(newItems); | |
parent._lastRect = null; | |
} | |
if (self._eventsRoot && self._eventsRoot == self) { | |
DomQuery(elm).off(); | |
} | |
var lookup = self.getRoot().controlIdLookup; | |
if (lookup) { | |
delete lookup[self._id]; | |
} | |
if (elm && elm.parentNode) { | |
elm.parentNode.removeChild(elm); | |
} | |
self.state.set('rendered', false); | |
self.state.destroy(); | |
self.fire('remove'); | |
return self; | |
}, | |
/** | |
* Renders the control before the specified element. | |
* | |
* @method renderBefore | |
* @param {Element} elm Element to render before. | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
renderBefore: function (elm) { | |
DomQuery(elm).before(this.renderHtml()); | |
this.postRender(); | |
return this; | |
}, | |
/** | |
* Renders the control to the specified element. | |
* | |
* @method renderBefore | |
* @param {Element} elm Element to render to. | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
renderTo: function (elm) { | |
DomQuery(elm || this.getContainerElm()).append(this.renderHtml()); | |
this.postRender(); | |
return this; | |
}, | |
preRender: function () { | |
}, | |
render: function () { | |
}, | |
renderHtml: function () { | |
return '<div id="' + this._id + '" class="' + this.classes + '"></div>'; | |
}, | |
/** | |
* Post render method. Called after the control has been rendered to the target. | |
* | |
* @method postRender | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
postRender: function () { | |
var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot; | |
self.$el = DomQuery(self.getEl()); | |
self.state.set('rendered', true); | |
// Bind on<event> settings | |
for (name in settings) { | |
if (name.indexOf("on") === 0) { | |
self.on(name.substr(2), settings[name]); | |
} | |
} | |
if (self._eventsRoot) { | |
for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) { | |
parentEventsRoot = parent._eventsRoot; | |
} | |
if (parentEventsRoot) { | |
for (name in parentEventsRoot._nativeEvents) { | |
self._nativeEvents[name] = true; | |
} | |
} | |
} | |
bindPendingEvents(self); | |
if (settings.style) { | |
elm = self.getEl(); | |
if (elm) { | |
elm.setAttribute('style', settings.style); | |
elm.style.cssText = settings.style; | |
} | |
} | |
if (self.settings.border) { | |
box = self.borderBox; | |
self.$el.css({ | |
'border-top-width': box.top, | |
'border-right-width': box.right, | |
'border-bottom-width': box.bottom, | |
'border-left-width': box.left | |
}); | |
} | |
// Add instance to lookup | |
var root = self.getRoot(); | |
if (!root.controlIdLookup) { | |
root.controlIdLookup = {}; | |
} | |
root.controlIdLookup[self._id] = self; | |
for (var key in self._aria) { | |
self.aria(key, self._aria[key]); | |
} | |
if (self.state.get('visible') === false) { | |
self.getEl().style.display = 'none'; | |
} | |
self.bindStates(); | |
self.state.on('change:visible', function (e) { | |
var state = e.value, parentCtrl; | |
if (self.state.get('rendered')) { | |
self.getEl().style.display = state === false ? 'none' : ''; | |
// Need to force a reflow here on IE 8 | |
self.getEl().getBoundingClientRect(); | |
} | |
// Parent container needs to reflow | |
parentCtrl = self.parent(); | |
if (parentCtrl) { | |
parentCtrl._lastRect = null; | |
} | |
self.fire(state ? 'show' : 'hide'); | |
ReflowQueue.add(self); | |
}); | |
self.fire('postrender', {}, false); | |
}, | |
bindStates: function () { | |
}, | |
/** | |
* Scrolls the current control into view. | |
* | |
* @method scrollIntoView | |
* @param {String} align Alignment in view top|center|bottom. | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
scrollIntoView: function (align) { | |
function getOffset(elm, rootElm) { | |
var x, y, parent = elm; | |
x = y = 0; | |
while (parent && parent != rootElm && parent.nodeType) { | |
x += parent.offsetLeft || 0; | |
y += parent.offsetTop || 0; | |
parent = parent.offsetParent; | |
} | |
return { x: x, y: y }; | |
} | |
var elm = this.getEl(), parentElm = elm.parentNode; | |
var x, y, width, height, parentWidth, parentHeight; | |
var pos = getOffset(elm, parentElm); | |
x = pos.x; | |
y = pos.y; | |
width = elm.offsetWidth; | |
height = elm.offsetHeight; | |
parentWidth = parentElm.clientWidth; | |
parentHeight = parentElm.clientHeight; | |
if (align == "end") { | |
x -= parentWidth - width; | |
y -= parentHeight - height; | |
} else if (align == "center") { | |
x -= (parentWidth / 2) - (width / 2); | |
y -= (parentHeight / 2) - (height / 2); | |
} | |
parentElm.scrollLeft = x; | |
parentElm.scrollTop = y; | |
return this; | |
}, | |
getRoot: function () { | |
var ctrl = this, rootControl, parents = []; | |
while (ctrl) { | |
if (ctrl.rootControl) { | |
rootControl = ctrl.rootControl; | |
break; | |
} | |
parents.push(ctrl); | |
rootControl = ctrl; | |
ctrl = ctrl.parent(); | |
} | |
if (!rootControl) { | |
rootControl = this; | |
} | |
var i = parents.length; | |
while (i--) { | |
parents[i].rootControl = rootControl; | |
} | |
return rootControl; | |
}, | |
/** | |
* Reflows the current control and it's parents. | |
* This should be used after you for example append children to the current control so | |
* that the layout managers know that they need to reposition everything. | |
* | |
* @example | |
* container.append({type: 'button', text: 'My button'}).reflow(); | |
* | |
* @method reflow | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
reflow: function () { | |
ReflowQueue.remove(this); | |
var parent = this.parent(); | |
if (parent && parent._layout && !parent._layout.isNative()) { | |
parent.reflow(); | |
} | |
return this; | |
} | |
/** | |
* Sets/gets the parent container for the control. | |
* | |
* @method parent | |
* @param {tinymce.ui.Container} parent Optional parent to set. | |
* @return {tinymce.ui.Control} Parent control or the current control on a set action. | |
*/ | |
// parent: function(parent) {} -- Generated | |
/** | |
* Sets/gets the text for the control. | |
* | |
* @method text | |
* @param {String} value Value to set to control. | |
* @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. | |
*/ | |
// text: function(value) {} -- Generated | |
/** | |
* Sets/gets the disabled state on the control. | |
* | |
* @method disabled | |
* @param {Boolean} state Value to set to control. | |
* @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get. | |
*/ | |
// disabled: function(state) {} -- Generated | |
/** | |
* Sets/gets the active for the control. | |
* | |
* @method active | |
* @param {Boolean} state Value to set to control. | |
* @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get. | |
*/ | |
// active: function(state) {} -- Generated | |
/** | |
* Sets/gets the name for the control. | |
* | |
* @method name | |
* @param {String} value Value to set to control. | |
* @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. | |
*/ | |
// name: function(value) {} -- Generated | |
/** | |
* Sets/gets the title for the control. | |
* | |
* @method title | |
* @param {String} value Value to set to control. | |
* @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. | |
*/ | |
// title: function(value) {} -- Generated | |
/** | |
* Sets/gets the visible for the control. | |
* | |
* @method visible | |
* @param {Boolean} state Value to set to control. | |
* @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get. | |
*/ | |
// visible: function(value) {} -- Generated | |
}; | |
/** | |
* Setup state properties. | |
*/ | |
Tools.each('text title visible disabled active value'.split(' '), function (name) { | |
proto[name] = function (value) { | |
if (arguments.length === 0) { | |
return this.state.get(name); | |
} | |
if (typeof value != "undefined") { | |
this.state.set(name, value); | |
} | |
return this; | |
}; | |
}); | |
Control = Class.extend(proto); | |
function getEventDispatcher(obj) { | |
if (!obj._eventDispatcher) { | |
obj._eventDispatcher = new EventDispatcher({ | |
scope: obj, | |
toggleEvent: function (name, state) { | |
if (state && EventDispatcher.isNative(name)) { | |
if (!obj._nativeEvents) { | |
obj._nativeEvents = {}; | |
} | |
obj._nativeEvents[name] = true; | |
if (obj.state.get('rendered')) { | |
bindPendingEvents(obj); | |
} | |
} | |
} | |
}); | |
} | |
return obj._eventDispatcher; | |
} | |
function bindPendingEvents(eventCtrl) { | |
var i, l, parents, eventRootCtrl, nativeEvents, name; | |
function delegate(e) { | |
var control = eventCtrl.getParentCtrl(e.target); | |
if (control) { | |
control.fire(e.type, e); | |
} | |
} | |
function mouseLeaveHandler() { | |
var ctrl = eventRootCtrl._lastHoverCtrl; | |
if (ctrl) { | |
ctrl.fire("mouseleave", { target: ctrl.getEl() }); | |
ctrl.parents().each(function (ctrl) { | |
ctrl.fire("mouseleave", { target: ctrl.getEl() }); | |
}); | |
eventRootCtrl._lastHoverCtrl = null; | |
} | |
} | |
function mouseEnterHandler(e) { | |
var ctrl = eventCtrl.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents; | |
// Over on a new control | |
if (ctrl !== lastCtrl) { | |
eventRootCtrl._lastHoverCtrl = ctrl; | |
parents = ctrl.parents().toArray().reverse(); | |
parents.push(ctrl); | |
if (lastCtrl) { | |
lastParents = lastCtrl.parents().toArray().reverse(); | |
lastParents.push(lastCtrl); | |
for (idx = 0; idx < lastParents.length; idx++) { | |
if (parents[idx] !== lastParents[idx]) { | |
break; | |
} | |
} | |
for (i = lastParents.length - 1; i >= idx; i--) { | |
lastCtrl = lastParents[i]; | |
lastCtrl.fire("mouseleave", { | |
target: lastCtrl.getEl() | |
}); | |
} | |
} | |
for (i = idx; i < parents.length; i++) { | |
ctrl = parents[i]; | |
ctrl.fire("mouseenter", { | |
target: ctrl.getEl() | |
}); | |
} | |
} | |
} | |
function fixWheelEvent(e) { | |
e.preventDefault(); | |
if (e.type == "mousewheel") { | |
e.deltaY = -1 / 40 * e.wheelDelta; | |
if (e.wheelDeltaX) { | |
e.deltaX = -1 / 40 * e.wheelDeltaX; | |
} | |
} else { | |
e.deltaX = 0; | |
e.deltaY = e.detail; | |
} | |
e = eventCtrl.fire("wheel", e); | |
} | |
nativeEvents = eventCtrl._nativeEvents; | |
if (nativeEvents) { | |
// Find event root element if it exists | |
parents = eventCtrl.parents().toArray(); | |
parents.unshift(eventCtrl); | |
for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) { | |
eventRootCtrl = parents[i]._eventsRoot; | |
} | |
// Event root wasn't found the use the root control | |
if (!eventRootCtrl) { | |
eventRootCtrl = parents[parents.length - 1] || eventCtrl; | |
} | |
// Set the eventsRoot property on children that didn't have it | |
eventCtrl._eventsRoot = eventRootCtrl; | |
for (l = i, i = 0; i < l; i++) { | |
parents[i]._eventsRoot = eventRootCtrl; | |
} | |
var eventRootDelegates = eventRootCtrl._delegates; | |
if (!eventRootDelegates) { | |
eventRootDelegates = eventRootCtrl._delegates = {}; | |
} | |
// Bind native event delegates | |
for (name in nativeEvents) { | |
if (!nativeEvents) { | |
return false; | |
} | |
if (name === "wheel" && !hasWheelEventSupport) { | |
if (hasMouseWheelEventSupport) { | |
DomQuery(eventCtrl.getEl()).on("mousewheel", fixWheelEvent); | |
} else { | |
DomQuery(eventCtrl.getEl()).on("DOMMouseScroll", fixWheelEvent); | |
} | |
continue; | |
} | |
// Special treatment for mousenter/mouseleave since these doesn't bubble | |
if (name === "mouseenter" || name === "mouseleave") { | |
// Fake mousenter/mouseleave | |
if (!eventRootCtrl._hasMouseEnter) { | |
DomQuery(eventRootCtrl.getEl()).on("mouseleave", mouseLeaveHandler).on("mouseover", mouseEnterHandler); | |
eventRootCtrl._hasMouseEnter = 1; | |
} | |
} else if (!eventRootDelegates[name]) { | |
DomQuery(eventRootCtrl.getEl()).on(name, delegate); | |
eventRootDelegates[name] = true; | |
} | |
// Remove the event once it's bound | |
nativeEvents[name] = false; | |
} | |
} | |
} | |
return Control; | |
} | |
); | |
/** | |
* KeyboardNavigation.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class handles keyboard navigation of controls and elements. | |
* | |
* @class tinymce.ui.KeyboardNavigation | |
*/ | |
define( | |
'tinymce.ui.KeyboardNavigation', | |
[ | |
'global!document' | |
], | |
function (document) { | |
"use strict"; | |
var hasTabstopData = function (elm) { | |
return elm.getAttribute('data-mce-tabstop') ? true : false; | |
}; | |
/** | |
* This class handles all keyboard navigation for WAI-ARIA support. Each root container | |
* gets an instance of this class. | |
* | |
* @constructor | |
*/ | |
return function (settings) { | |
var root = settings.root, focusedElement, focusedControl; | |
function isElement(node) { | |
return node && node.nodeType === 1; | |
} | |
try { | |
focusedElement = document.activeElement; | |
} catch (ex) { | |
// IE sometimes fails to return a proper element | |
focusedElement = document.body; | |
} | |
focusedControl = root.getParentCtrl(focusedElement); | |
/** | |
* Returns the currently focused elements wai aria role of the currently | |
* focused element or specified element. | |
* | |
* @private | |
* @param {Element} elm Optional element to get role from. | |
* @return {String} Role of specified element. | |
*/ | |
function getRole(elm) { | |
elm = elm || focusedElement; | |
if (isElement(elm)) { | |
return elm.getAttribute('role'); | |
} | |
return null; | |
} | |
/** | |
* Returns the wai role of the parent element of the currently | |
* focused element or specified element. | |
* | |
* @private | |
* @param {Element} elm Optional element to get parent role from. | |
* @return {String} Role of the first parent that has a role. | |
*/ | |
function getParentRole(elm) { | |
var role, parent = elm || focusedElement; | |
while ((parent = parent.parentNode)) { | |
if ((role = getRole(parent))) { | |
return role; | |
} | |
} | |
} | |
/** | |
* Returns a wai aria property by name for example aria-selected. | |
* | |
* @private | |
* @param {String} name Name of the aria property to get for example "disabled". | |
* @return {String} Aria property value. | |
*/ | |
function getAriaProp(name) { | |
var elm = focusedElement; | |
if (isElement(elm)) { | |
return elm.getAttribute('aria-' + name); | |
} | |
} | |
/** | |
* Is the element a text input element or not. | |
* | |
* @private | |
* @param {Element} elm Element to check if it's an text input element or not. | |
* @return {Boolean} True/false if the element is a text element or not. | |
*/ | |
function isTextInputElement(elm) { | |
var tagName = elm.tagName.toUpperCase(); | |
// Notice: since type can be "email" etc we don't check the type | |
// So all input elements gets treated as text input elements | |
return tagName == "INPUT" || tagName == "TEXTAREA" || tagName == "SELECT"; | |
} | |
/** | |
* Returns true/false if the specified element can be focused or not. | |
* | |
* @private | |
* @param {Element} elm DOM element to check if it can be focused or not. | |
* @return {Boolean} True/false if the element can have focus. | |
*/ | |
function canFocus(elm) { | |
if (isTextInputElement(elm) && !elm.hidden) { | |
return true; | |
} | |
if (hasTabstopData(elm)) { | |
return true; | |
} | |
if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell|slider)$/.test(getRole(elm))) { | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Returns an array of focusable visible elements within the specified container element. | |
* | |
* @private | |
* @param {Element} elm DOM element to find focusable elements within. | |
* @return {Array} Array of focusable elements. | |
*/ | |
function getFocusElements(elm) { | |
var elements = []; | |
function collect(elm) { | |
if (elm.nodeType != 1 || elm.style.display == 'none' || elm.disabled) { | |
return; | |
} | |
if (canFocus(elm)) { | |
elements.push(elm); | |
} | |
for (var i = 0; i < elm.childNodes.length; i++) { | |
collect(elm.childNodes[i]); | |
} | |
} | |
collect(elm || root.getEl()); | |
return elements; | |
} | |
/** | |
* Returns the navigation root control for the specified control. The navigation root | |
* is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group. | |
* It will look for parents of the specified target control or the currently focused control if this option is omitted. | |
* | |
* @private | |
* @param {tinymce.ui.Control} targetControl Optional target control to find root of. | |
* @return {tinymce.ui.Control} Navigation root control. | |
*/ | |
function getNavigationRoot(targetControl) { | |
var navigationRoot, controls; | |
targetControl = targetControl || focusedControl; | |
controls = targetControl.parents().toArray(); | |
controls.unshift(targetControl); | |
for (var i = 0; i < controls.length; i++) { | |
navigationRoot = controls[i]; | |
if (navigationRoot.settings.ariaRoot) { | |
break; | |
} | |
} | |
return navigationRoot; | |
} | |
/** | |
* Focuses the first item in the specified targetControl element or the last aria index if the | |
* navigation root has the ariaRemember option enabled. | |
* | |
* @private | |
* @param {tinymce.ui.Control} targetControl Target control to focus the first item in. | |
*/ | |
function focusFirst(targetControl) { | |
var navigationRoot = getNavigationRoot(targetControl); | |
var focusElements = getFocusElements(navigationRoot.getEl()); | |
if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) { | |
moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements); | |
} else { | |
moveFocusToIndex(0, focusElements); | |
} | |
} | |
/** | |
* Moves the focus to the specified index within the elements list. | |
* This will scope the index to the size of the element list if it changed. | |
* | |
* @private | |
* @param {Number} idx Specified index to move to. | |
* @param {Array} elements Array with dom elements to move focus within. | |
* @return {Number} Input index or a changed index if it was out of range. | |
*/ | |
function moveFocusToIndex(idx, elements) { | |
if (idx < 0) { | |
idx = elements.length - 1; | |
} else if (idx >= elements.length) { | |
idx = 0; | |
} | |
if (elements[idx]) { | |
elements[idx].focus(); | |
} | |
return idx; | |
} | |
/** | |
* Moves the focus forwards or backwards. | |
* | |
* @private | |
* @param {Number} dir Direction to move in positive means forward, negative means backwards. | |
* @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements. | |
*/ | |
function moveFocus(dir, elements) { | |
var idx = -1, navigationRoot = getNavigationRoot(); | |
elements = elements || getFocusElements(navigationRoot.getEl()); | |
for (var i = 0; i < elements.length; i++) { | |
if (elements[i] === focusedElement) { | |
idx = i; | |
} | |
} | |
idx += dir; | |
navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements); | |
} | |
/** | |
* Moves the focus to the left this is called by the left key. | |
* | |
* @private | |
*/ | |
function left() { | |
var parentRole = getParentRole(); | |
if (parentRole == "tablist") { | |
moveFocus(-1, getFocusElements(focusedElement.parentNode)); | |
} else if (focusedControl.parent().submenu) { | |
cancel(); | |
} else { | |
moveFocus(-1); | |
} | |
} | |
/** | |
* Moves the focus to the right this is called by the right key. | |
* | |
* @private | |
*/ | |
function right() { | |
var role = getRole(), parentRole = getParentRole(); | |
if (parentRole == "tablist") { | |
moveFocus(1, getFocusElements(focusedElement.parentNode)); | |
} else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) { | |
enter(); | |
} else { | |
moveFocus(1); | |
} | |
} | |
/** | |
* Moves the focus to the up this is called by the up key. | |
* | |
* @private | |
*/ | |
function up() { | |
moveFocus(-1); | |
} | |
/** | |
* Moves the focus to the up this is called by the down key. | |
* | |
* @private | |
*/ | |
function down() { | |
var role = getRole(), parentRole = getParentRole(); | |
if (role == "menuitem" && parentRole == "menubar") { | |
enter(); | |
} else if (role == "button" && getAriaProp('haspopup')) { | |
enter({ key: 'down' }); | |
} else { | |
moveFocus(1); | |
} | |
} | |
/** | |
* Moves the focus to the next item or previous item depending on shift key. | |
* | |
* @private | |
* @param {DOMEvent} e DOM event object. | |
*/ | |
function tab(e) { | |
var parentRole = getParentRole(); | |
if (parentRole == "tablist") { | |
var elm = getFocusElements(focusedControl.getEl('body'))[0]; | |
if (elm) { | |
elm.focus(); | |
} | |
} else { | |
moveFocus(e.shiftKey ? -1 : 1); | |
} | |
} | |
/** | |
* Calls the cancel event on the currently focused control. This is normally done using the Esc key. | |
* | |
* @private | |
*/ | |
function cancel() { | |
focusedControl.fire('cancel'); | |
} | |
/** | |
* Calls the click event on the currently focused control. This is normally done using the Enter/Space keys. | |
* | |
* @private | |
* @param {Object} aria Optional aria data to pass along with the enter event. | |
*/ | |
function enter(aria) { | |
aria = aria || {}; | |
focusedControl.fire('click', { target: focusedElement, aria: aria }); | |
} | |
root.on('keydown', function (e) { | |
function handleNonTabOrEscEvent(e, handler) { | |
// Ignore non tab keys for text elements | |
if (isTextInputElement(focusedElement) || hasTabstopData(focusedElement)) { | |
return; | |
} | |
if (getRole(focusedElement) === 'slider') { | |
return; | |
} | |
if (handler(e) !== false) { | |
e.preventDefault(); | |
} | |
} | |
if (e.isDefaultPrevented()) { | |
return; | |
} | |
switch (e.keyCode) { | |
case 37: // DOM_VK_LEFT | |
handleNonTabOrEscEvent(e, left); | |
break; | |
case 39: // DOM_VK_RIGHT | |
handleNonTabOrEscEvent(e, right); | |
break; | |
case 38: // DOM_VK_UP | |
handleNonTabOrEscEvent(e, up); | |
break; | |
case 40: // DOM_VK_DOWN | |
handleNonTabOrEscEvent(e, down); | |
break; | |
case 27: // DOM_VK_ESCAPE | |
cancel(); | |
break; | |
case 14: // DOM_VK_ENTER | |
case 13: // DOM_VK_RETURN | |
case 32: // DOM_VK_SPACE | |
handleNonTabOrEscEvent(e, enter); | |
break; | |
case 9: // DOM_VK_TAB | |
if (tab(e) !== false) { | |
e.preventDefault(); | |
} | |
break; | |
} | |
}); | |
root.on('focusin', function (e) { | |
focusedElement = e.target; | |
focusedControl = e.control; | |
}); | |
return { | |
focusFirst: focusFirst | |
}; | |
}; | |
} | |
); | |
/** | |
* Container.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Container control. This is extended by all controls that can have | |
* children such as panels etc. You can also use this class directly as an | |
* generic container instance. The container doesn't have any specific role or style. | |
* | |
* @-x-less Container.less | |
* @class tinymce.ui.Container | |
* @extends tinymce.ui.Control | |
*/ | |
define( | |
'tinymce.ui.Container', | |
[ | |
"tinymce.ui.Control", | |
"tinymce.ui.Collection", | |
"tinymce.ui.Selector", | |
"tinymce.core.ui.Factory", | |
"tinymce.ui.KeyboardNavigation", | |
"tinymce.core.util.Tools", | |
"tinymce.core.dom.DomQuery", | |
"tinymce.ui.ClassList", | |
"tinymce.ui.ReflowQueue" | |
], | |
function (Control, Collection, Selector, Factory, KeyboardNavigation, Tools, $, ClassList, ReflowQueue) { | |
"use strict"; | |
var selectorCache = {}; | |
return Control.extend({ | |
/** | |
* Constructs a new control instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {Array} items Items to add to container in JSON format or control instances. | |
* @setting {String} layout Layout manager by name to use. | |
* @setting {Object} defaults Default settings to apply to all items. | |
*/ | |
init: function (settings) { | |
var self = this; | |
self._super(settings); | |
settings = self.settings; | |
if (settings.fixed) { | |
self.state.set('fixed', true); | |
} | |
self._items = new Collection(); | |
if (self.isRtl()) { | |
self.classes.add('rtl'); | |
} | |
self.bodyClasses = new ClassList(function () { | |
if (self.state.get('rendered')) { | |
self.getEl('body').className = this.toString(); | |
} | |
}); | |
self.bodyClasses.prefix = self.classPrefix; | |
self.classes.add('container'); | |
self.bodyClasses.add('container-body'); | |
if (settings.containerCls) { | |
self.classes.add(settings.containerCls); | |
} | |
self._layout = Factory.create((settings.layout || '') + 'layout'); | |
if (self.settings.items) { | |
self.add(self.settings.items); | |
} else { | |
self.add(self.render()); | |
} | |
// TODO: Fix this! | |
self._hasBody = true; | |
}, | |
/** | |
* Returns a collection of child items that the container currently have. | |
* | |
* @method items | |
* @return {tinymce.ui.Collection} Control collection direct child controls. | |
*/ | |
items: function () { | |
return this._items; | |
}, | |
/** | |
* Find child controls by selector. | |
* | |
* @method find | |
* @param {String} selector Selector CSS pattern to find children by. | |
* @return {tinymce.ui.Collection} Control collection with child controls. | |
*/ | |
find: function (selector) { | |
selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector); | |
return selector.find(this); | |
}, | |
/** | |
* Adds one or many items to the current container. This will create instances of | |
* the object representations if needed. | |
* | |
* @method add | |
* @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container. | |
* @return {tinymce.ui.Collection} Current collection control. | |
*/ | |
add: function (items) { | |
var self = this; | |
self.items().add(self.create(items)).parent(self); | |
return self; | |
}, | |
/** | |
* Focuses the current container instance. This will look | |
* for the first control in the container and focus that. | |
* | |
* @method focus | |
* @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not. | |
* @return {tinymce.ui.Collection} Current instance. | |
*/ | |
focus: function (keyboard) { | |
var self = this, focusCtrl, keyboardNav, items; | |
if (keyboard) { | |
keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav; | |
if (keyboardNav) { | |
keyboardNav.focusFirst(self); | |
return; | |
} | |
} | |
items = self.find('*'); | |
// TODO: Figure out a better way to auto focus alert dialog buttons | |
if (self.statusbar) { | |
items.add(self.statusbar.items()); | |
} | |
items.each(function (ctrl) { | |
if (ctrl.settings.autofocus) { | |
focusCtrl = null; | |
return false; | |
} | |
if (ctrl.canFocus) { | |
focusCtrl = focusCtrl || ctrl; | |
} | |
}); | |
if (focusCtrl) { | |
focusCtrl.focus(); | |
} | |
return self; | |
}, | |
/** | |
* Replaces the specified child control with a new control. | |
* | |
* @method replace | |
* @param {tinymce.ui.Control} oldItem Old item to be replaced. | |
* @param {tinymce.ui.Control} newItem New item to be inserted. | |
*/ | |
replace: function (oldItem, newItem) { | |
var ctrlElm, items = this.items(), i = items.length; | |
// Replace the item in collection | |
while (i--) { | |
if (items[i] === oldItem) { | |
items[i] = newItem; | |
break; | |
} | |
} | |
if (i >= 0) { | |
// Remove new item from DOM | |
ctrlElm = newItem.getEl(); | |
if (ctrlElm) { | |
ctrlElm.parentNode.removeChild(ctrlElm); | |
} | |
// Remove old item from DOM | |
ctrlElm = oldItem.getEl(); | |
if (ctrlElm) { | |
ctrlElm.parentNode.removeChild(ctrlElm); | |
} | |
} | |
// Adopt the item | |
newItem.parent(this); | |
}, | |
/** | |
* Creates the specified items. If any of the items is plain JSON style objects | |
* it will convert these into real tinymce.ui.Control instances. | |
* | |
* @method create | |
* @param {Array} items Array of items to convert into control instances. | |
* @return {Array} Array with control instances. | |
*/ | |
create: function (items) { | |
var self = this, settings, ctrlItems = []; | |
// Non array structure, then force it into an array | |
if (!Tools.isArray(items)) { | |
items = [items]; | |
} | |
// Add default type to each child control | |
Tools.each(items, function (item) { | |
if (item) { | |
// Construct item if needed | |
if (!(item instanceof Control)) { | |
// Name only then convert it to an object | |
if (typeof item == "string") { | |
item = { type: item }; | |
} | |
// Create control instance based on input settings and default settings | |
settings = Tools.extend({}, self.settings.defaults, item); | |
item.type = settings.type = settings.type || item.type || self.settings.defaultType || | |
(settings.defaults ? settings.defaults.type : null); | |
item = Factory.create(settings); | |
} | |
ctrlItems.push(item); | |
} | |
}); | |
return ctrlItems; | |
}, | |
/** | |
* Renders new control instances. | |
* | |
* @private | |
*/ | |
renderNew: function () { | |
var self = this; | |
// Render any new items | |
self.items().each(function (ctrl, index) { | |
var containerElm; | |
ctrl.parent(self); | |
if (!ctrl.state.get('rendered')) { | |
containerElm = self.getEl('body'); | |
// Insert or append the item | |
if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) { | |
$(containerElm.childNodes[index]).before(ctrl.renderHtml()); | |
} else { | |
$(containerElm).append(ctrl.renderHtml()); | |
} | |
ctrl.postRender(); | |
ReflowQueue.add(ctrl); | |
} | |
}); | |
self._layout.applyClasses(self.items().filter(':visible')); | |
self._lastRect = null; | |
return self; | |
}, | |
/** | |
* Appends new instances to the current container. | |
* | |
* @method append | |
* @param {Array/tinymce.ui.Collection} items Array if controls to append. | |
* @return {tinymce.ui.Container} Current container instance. | |
*/ | |
append: function (items) { | |
return this.add(items).renderNew(); | |
}, | |
/** | |
* Prepends new instances to the current container. | |
* | |
* @method prepend | |
* @param {Array/tinymce.ui.Collection} items Array if controls to prepend. | |
* @return {tinymce.ui.Container} Current container instance. | |
*/ | |
prepend: function (items) { | |
var self = this; | |
self.items().set(self.create(items).concat(self.items().toArray())); | |
return self.renderNew(); | |
}, | |
/** | |
* Inserts an control at a specific index. | |
* | |
* @method insert | |
* @param {Array/tinymce.ui.Collection} items Array if controls to insert. | |
* @param {Number} index Index to insert controls at. | |
* @param {Boolean} [before=false] Inserts controls before the index. | |
*/ | |
insert: function (items, index, before) { | |
var self = this, curItems, beforeItems, afterItems; | |
items = self.create(items); | |
curItems = self.items(); | |
if (!before && index < curItems.length - 1) { | |
index += 1; | |
} | |
if (index >= 0 && index < curItems.length) { | |
beforeItems = curItems.slice(0, index).toArray(); | |
afterItems = curItems.slice(index).toArray(); | |
curItems.set(beforeItems.concat(items, afterItems)); | |
} | |
return self.renderNew(); | |
}, | |
/** | |
* Populates the form fields from the specified JSON data object. | |
* | |
* Control items in the form that matches the data will have it's value set. | |
* | |
* @method fromJSON | |
* @param {Object} data JSON data object to set control values by. | |
* @return {tinymce.ui.Container} Current form instance. | |
*/ | |
fromJSON: function (data) { | |
var self = this; | |
for (var name in data) { | |
self.find('#' + name).value(data[name]); | |
} | |
return self; | |
}, | |
/** | |
* Serializes the form into a JSON object by getting all items | |
* that has a name and a value. | |
* | |
* @method toJSON | |
* @return {Object} JSON object with form data. | |
*/ | |
toJSON: function () { | |
var self = this, data = {}; | |
self.find('*').each(function (ctrl) { | |
var name = ctrl.name(), value = ctrl.value(); | |
if (name && typeof value != "undefined") { | |
data[name] = value; | |
} | |
}); | |
return data; | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, layout = self._layout, role = this.settings.role; | |
self.preRender(); | |
layout.preRender(self); | |
return ( | |
'<div id="' + self._id + '" class="' + self.classes + '"' + (role ? ' role="' + this.settings.role + '"' : '') + '>' + | |
'<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' + | |
(self.settings.html || '') + layout.renderHtml(self) + | |
'</div>' + | |
'</div>' | |
); | |
}, | |
/** | |
* Post render method. Called after the control has been rendered to the target. | |
* | |
* @method postRender | |
* @return {tinymce.ui.Container} Current combobox instance. | |
*/ | |
postRender: function () { | |
var self = this, box; | |
self.items().exec('postRender'); | |
self._super(); | |
self._layout.postRender(self); | |
self.state.set('rendered', true); | |
if (self.settings.style) { | |
self.$el.css(self.settings.style); | |
} | |
if (self.settings.border) { | |
box = self.borderBox; | |
self.$el.css({ | |
'border-top-width': box.top, | |
'border-right-width': box.right, | |
'border-bottom-width': box.bottom, | |
'border-left-width': box.left | |
}); | |
} | |
if (!self.parent()) { | |
self.keyboardNav = new KeyboardNavigation({ | |
root: self | |
}); | |
} | |
return self; | |
}, | |
/** | |
* Initializes the current controls layout rect. | |
* This will be executed by the layout managers to determine the | |
* default minWidth/minHeight etc. | |
* | |
* @method initLayoutRect | |
* @return {Object} Layout rect instance. | |
*/ | |
initLayoutRect: function () { | |
var self = this, layoutRect = self._super(); | |
// Recalc container size by asking layout manager | |
self._layout.recalc(self); | |
return layoutRect; | |
}, | |
/** | |
* Recalculates the positions of the controls in the current container. | |
* This is invoked by the reflow method and shouldn't be called directly. | |
* | |
* @method recalc | |
*/ | |
recalc: function () { | |
var self = this, rect = self._layoutRect, lastRect = self._lastRect; | |
if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) { | |
self._layout.recalc(self); | |
rect = self.layoutRect(); | |
self._lastRect = { x: rect.x, y: rect.y, w: rect.w, h: rect.h }; | |
return true; | |
} | |
}, | |
/** | |
* Reflows the current container and it's children and possible parents. | |
* This should be used after you for example append children to the current control so | |
* that the layout managers know that they need to reposition everything. | |
* | |
* @example | |
* container.append({type: 'button', text: 'My button'}).reflow(); | |
* | |
* @method reflow | |
* @return {tinymce.ui.Container} Current container instance. | |
*/ | |
reflow: function () { | |
var i; | |
ReflowQueue.remove(this); | |
if (this.visible()) { | |
Control.repaintControls = []; | |
Control.repaintControls.map = {}; | |
this.recalc(); | |
i = Control.repaintControls.length; | |
while (i--) { | |
Control.repaintControls[i].repaint(); | |
} | |
// TODO: Fix me! | |
if (this.settings.layout !== "flow" && this.settings.layout !== "stack") { | |
this.repaint(); | |
} | |
Control.repaintControls = []; | |
} | |
return this; | |
} | |
}); | |
} | |
); | |
/** | |
* DragHelper.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Drag/drop helper class. | |
* | |
* @example | |
* var dragHelper = new tinymce.ui.DragHelper('mydiv', { | |
* start: function(document, window, evt) { | |
* }, | |
* | |
* drag: function(evt) { | |
* }, | |
* | |
* end: function(evt) { | |
* } | |
* }); | |
* | |
* @class tinymce.ui.DragHelper | |
*/ | |
define( | |
'tinymce.ui.DragHelper', | |
[ | |
'global!document', | |
'global!window', | |
'tinymce.core.dom.DomQuery' | |
], | |
function (document, window, DomQuery) { | |
"use strict"; | |
function getDocumentSize(doc) { | |
var documentElement, body, scrollWidth, clientWidth; | |
var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max; | |
documentElement = doc.documentElement; | |
body = doc.body; | |
scrollWidth = max(documentElement.scrollWidth, body.scrollWidth); | |
clientWidth = max(documentElement.clientWidth, body.clientWidth); | |
offsetWidth = max(documentElement.offsetWidth, body.offsetWidth); | |
scrollHeight = max(documentElement.scrollHeight, body.scrollHeight); | |
clientHeight = max(documentElement.clientHeight, body.clientHeight); | |
offsetHeight = max(documentElement.offsetHeight, body.offsetHeight); | |
return { | |
width: scrollWidth < offsetWidth ? clientWidth : scrollWidth, | |
height: scrollHeight < offsetHeight ? clientHeight : scrollHeight | |
}; | |
} | |
function updateWithTouchData(e) { | |
var keys, i; | |
if (e.changedTouches) { | |
keys = "screenX screenY pageX pageY clientX clientY".split(' '); | |
for (i = 0; i < keys.length; i++) { | |
e[keys[i]] = e.changedTouches[0][keys[i]]; | |
} | |
} | |
} | |
return function (id, settings) { | |
var $eventOverlay, doc = settings.document || document, downButton, start, stop, drag, startX, startY; | |
var handleElm; | |
settings = settings || {}; | |
handleElm = doc.getElementById(settings.handle || id); | |
start = function (e) { | |
var docSize = getDocumentSize(doc), cursor; | |
updateWithTouchData(e); | |
e.preventDefault(); | |
downButton = e.button; | |
startX = e.screenX; | |
startY = e.screenY; | |
// Grab cursor from handle so we can place it on overlay | |
if (window.getComputedStyle) { | |
cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor"); | |
} else { | |
cursor = handleElm.runtimeStyle.cursor; | |
} | |
$eventOverlay = DomQuery('<div></div>').css({ | |
position: "absolute", | |
top: 0, left: 0, | |
width: docSize.width, | |
height: docSize.height, | |
zIndex: 0x7FFFFFFF, | |
opacity: 0.0001, | |
cursor: cursor | |
}).appendTo(doc.body); | |
DomQuery(doc).on('mousemove touchmove', drag).on('mouseup touchend', stop); | |
settings.start(e); | |
}; | |
drag = function (e) { | |
updateWithTouchData(e); | |
if (e.button !== downButton) { | |
return stop(e); | |
} | |
e.deltaX = e.screenX - startX; | |
e.deltaY = e.screenY - startY; | |
e.preventDefault(); | |
settings.drag(e); | |
}; | |
stop = function (e) { | |
updateWithTouchData(e); | |
DomQuery(doc).off('mousemove touchmove', drag).off('mouseup touchend', stop); | |
$eventOverlay.remove(); | |
if (settings.stop) { | |
settings.stop(e); | |
} | |
}; | |
/** | |
* Destroys the drag/drop helper instance. | |
* | |
* @method destroy | |
*/ | |
this.destroy = function () { | |
DomQuery(handleElm).off(); | |
}; | |
DomQuery(handleElm).on('mousedown touchstart', start); | |
}; | |
} | |
); | |
/** | |
* Scrollable.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This mixin makes controls scrollable using custom scrollbars. | |
* | |
* @-x-less Scrollable.less | |
* @mixin tinymce.ui.Scrollable | |
*/ | |
define( | |
'tinymce.ui.Scrollable', | |
[ | |
"tinymce.core.dom.DomQuery", | |
"tinymce.ui.DragHelper" | |
], | |
function ($, DragHelper) { | |
"use strict"; | |
return { | |
init: function () { | |
var self = this; | |
self.on('repaint', self.renderScroll); | |
}, | |
renderScroll: function () { | |
var self = this, margin = 2; | |
function repaintScroll() { | |
var hasScrollH, hasScrollV, bodyElm; | |
function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) { | |
var containerElm, scrollBarElm, scrollThumbElm; | |
var containerSize, scrollSize, ratio, rect; | |
var posNameLower, sizeNameLower; | |
scrollBarElm = self.getEl('scroll' + axisName); | |
if (scrollBarElm) { | |
posNameLower = posName.toLowerCase(); | |
sizeNameLower = sizeName.toLowerCase(); | |
$(self.getEl('absend')).css(posNameLower, self.layoutRect()[contentSizeName] - 1); | |
if (!hasScroll) { | |
$(scrollBarElm).css('display', 'none'); | |
return; | |
} | |
$(scrollBarElm).css('display', 'block'); | |
containerElm = self.getEl('body'); | |
scrollThumbElm = self.getEl('scroll' + axisName + "t"); | |
containerSize = containerElm["client" + sizeName] - (margin * 2); | |
containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0; | |
scrollSize = containerElm["scroll" + sizeName]; | |
ratio = containerSize / scrollSize; | |
rect = {}; | |
rect[posNameLower] = containerElm["offset" + posName] + margin; | |
rect[sizeNameLower] = containerSize; | |
$(scrollBarElm).css(rect); | |
rect = {}; | |
rect[posNameLower] = containerElm["scroll" + posName] * ratio; | |
rect[sizeNameLower] = containerSize * ratio; | |
$(scrollThumbElm).css(rect); | |
} | |
} | |
bodyElm = self.getEl('body'); | |
hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth; | |
hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight; | |
repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height"); | |
repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width"); | |
} | |
function addScroll() { | |
function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) { | |
var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix; | |
$(self.getEl()).append( | |
'<div id="' + axisId + '" class="' + prefix + 'scrollbar ' + prefix + 'scrollbar-' + axisName + '">' + | |
'<div id="' + axisId + 't" class="' + prefix + 'scrollbar-thumb"></div>' + | |
'</div>' | |
); | |
self.draghelper = new DragHelper(axisId + 't', { | |
start: function () { | |
scrollStart = self.getEl('body')["scroll" + posName]; | |
$('#' + axisId).addClass(prefix + 'active'); | |
}, | |
drag: function (e) { | |
var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect(); | |
hasScrollH = layoutRect.contentW > layoutRect.innerW; | |
hasScrollV = layoutRect.contentH > layoutRect.innerH; | |
containerSize = self.getEl('body')["client" + sizeName] - (margin * 2); | |
containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0; | |
ratio = containerSize / self.getEl('body')["scroll" + sizeName]; | |
self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio); | |
}, | |
stop: function () { | |
$('#' + axisId).removeClass(prefix + 'active'); | |
} | |
}); | |
} | |
self.classes.add('scroll'); | |
addScrollAxis("v", "Top", "Height", "Y", "Width"); | |
addScrollAxis("h", "Left", "Width", "X", "Height"); | |
} | |
if (self.settings.autoScroll) { | |
if (!self._hasScroll) { | |
self._hasScroll = true; | |
addScroll(); | |
self.on('wheel', function (e) { | |
var bodyEl = self.getEl('body'); | |
bodyEl.scrollLeft += (e.deltaX || 0) * 10; | |
bodyEl.scrollTop += e.deltaY * 10; | |
repaintScroll(); | |
}); | |
$(self.getEl('body')).on("scroll", repaintScroll); | |
} | |
repaintScroll(); | |
} | |
} | |
}; | |
} | |
); | |
/** | |
* Panel.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new panel. | |
* | |
* @-x-less Panel.less | |
* @class tinymce.ui.Panel | |
* @extends tinymce.ui.Container | |
* @mixes tinymce.ui.Scrollable | |
*/ | |
define( | |
'tinymce.ui.Panel', | |
[ | |
"tinymce.ui.Container", | |
"tinymce.ui.Scrollable" | |
], | |
function (Container, Scrollable) { | |
"use strict"; | |
return Container.extend({ | |
Defaults: { | |
layout: 'fit', | |
containerCls: 'panel' | |
}, | |
Mixins: [Scrollable], | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, layout = self._layout, innerHtml = self.settings.html; | |
self.preRender(); | |
layout.preRender(self); | |
if (typeof innerHtml == "undefined") { | |
innerHtml = ( | |
'<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' + | |
layout.renderHtml(self) + | |
'</div>' | |
); | |
} else { | |
if (typeof innerHtml == 'function') { | |
innerHtml = innerHtml.call(self); | |
} | |
self._hasBody = false; | |
} | |
return ( | |
'<div id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1" role="group">' + | |
(self._preBodyHtml || '') + | |
innerHtml + | |
'</div>' | |
); | |
} | |
}); | |
} | |
); | |
/** | |
* Resizable.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Resizable mixin. Enables controls to be resized. | |
* | |
* @mixin tinymce.ui.Resizable | |
*/ | |
define( | |
'tinymce.ui.Resizable', | |
[ | |
"tinymce.ui.DomUtils" | |
], | |
function (DomUtils) { | |
"use strict"; | |
return { | |
/** | |
* Resizes the control to contents. | |
* | |
* @method resizeToContent | |
*/ | |
resizeToContent: function () { | |
this._layoutRect.autoResize = true; | |
this._lastRect = null; | |
this.reflow(); | |
}, | |
/** | |
* Resizes the control to a specific width/height. | |
* | |
* @method resizeTo | |
* @param {Number} w Control width. | |
* @param {Number} h Control height. | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
resizeTo: function (w, h) { | |
// TODO: Fix hack | |
if (w <= 1 || h <= 1) { | |
var rect = DomUtils.getWindowSize(); | |
w = w <= 1 ? w * rect.w : w; | |
h = h <= 1 ? h * rect.h : h; | |
} | |
this._layoutRect.autoResize = false; | |
return this.layoutRect({ minW: w, minH: h, w: w, h: h }).reflow(); | |
}, | |
/** | |
* Resizes the control to a specific relative width/height. | |
* | |
* @method resizeBy | |
* @param {Number} dw Relative control width. | |
* @param {Number} dh Relative control height. | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
resizeBy: function (dw, dh) { | |
var self = this, rect = self.layoutRect(); | |
return self.resizeTo(rect.w + dw, rect.h + dh); | |
} | |
}; | |
} | |
); | |
/** | |
* FloatPanel.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class creates a floating panel. | |
* | |
* @-x-less FloatPanel.less | |
* @class tinymce.ui.FloatPanel | |
* @extends tinymce.ui.Panel | |
* @mixes tinymce.ui.Movable | |
* @mixes tinymce.ui.Resizable | |
*/ | |
define( | |
'tinymce.ui.FloatPanel', | |
[ | |
'global!document', | |
'global!window', | |
'tinymce.core.dom.DomQuery', | |
'tinymce.core.util.Delay', | |
'tinymce.ui.DomUtils', | |
'tinymce.ui.Movable', | |
'tinymce.ui.Panel', | |
'tinymce.ui.Resizable' | |
], | |
function (document, window, DomQuery, Delay, DomUtils, Movable, Panel, Resizable) { | |
"use strict"; | |
var documentClickHandler, documentScrollHandler, windowResizeHandler, visiblePanels = []; | |
var zOrder = [], hasModal; | |
function isChildOf(ctrl, parent) { | |
while (ctrl) { | |
if (ctrl == parent) { | |
return true; | |
} | |
ctrl = ctrl.parent(); | |
} | |
} | |
function skipOrHidePanels(e) { | |
// Hide any float panel when a click/focus out is out side that float panel and the | |
// float panels direct parent for example a click on a menu button | |
var i = visiblePanels.length; | |
while (i--) { | |
var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target); | |
if (panel.settings.autohide) { | |
if (clickCtrl) { | |
if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) { | |
continue; | |
} | |
} | |
e = panel.fire('autohide', { target: e.target }); | |
if (!e.isDefaultPrevented()) { | |
panel.hide(); | |
} | |
} | |
} | |
} | |
function bindDocumentClickHandler() { | |
if (!documentClickHandler) { | |
documentClickHandler = function (e) { | |
// Gecko fires click event and in the wrong order on Mac so lets normalize | |
if (e.button == 2) { | |
return; | |
} | |
skipOrHidePanels(e); | |
}; | |
DomQuery(document).on('click touchstart', documentClickHandler); | |
} | |
} | |
function bindDocumentScrollHandler() { | |
if (!documentScrollHandler) { | |
documentScrollHandler = function () { | |
var i; | |
i = visiblePanels.length; | |
while (i--) { | |
repositionPanel(visiblePanels[i]); | |
} | |
}; | |
DomQuery(window).on('scroll', documentScrollHandler); | |
} | |
} | |
function bindWindowResizeHandler() { | |
if (!windowResizeHandler) { | |
var docElm = document.documentElement, clientWidth = docElm.clientWidth, clientHeight = docElm.clientHeight; | |
windowResizeHandler = function () { | |
// Workaround for #7065 IE 7 fires resize events event though the window wasn't resized | |
if (!document.all || clientWidth != docElm.clientWidth || clientHeight != docElm.clientHeight) { | |
clientWidth = docElm.clientWidth; | |
clientHeight = docElm.clientHeight; | |
FloatPanel.hideAll(); | |
} | |
}; | |
DomQuery(window).on('resize', windowResizeHandler); | |
} | |
} | |
/** | |
* Repositions the panel to the top of page if the panel is outside of the visual viewport. It will | |
* also reposition all child panels of the current panel. | |
*/ | |
function repositionPanel(panel) { | |
var scrollY = DomUtils.getViewPort().y; | |
function toggleFixedChildPanels(fixed, deltaY) { | |
var parent; | |
for (var i = 0; i < visiblePanels.length; i++) { | |
if (visiblePanels[i] != panel) { | |
parent = visiblePanels[i].parent(); | |
while (parent && (parent = parent.parent())) { | |
if (parent == panel) { | |
visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint(); | |
} | |
} | |
} | |
} | |
} | |
if (panel.settings.autofix) { | |
if (!panel.state.get('fixed')) { | |
panel._autoFixY = panel.layoutRect().y; | |
if (panel._autoFixY < scrollY) { | |
panel.fixed(true).layoutRect({ y: 0 }).repaint(); | |
toggleFixedChildPanels(true, scrollY - panel._autoFixY); | |
} | |
} else { | |
if (panel._autoFixY > scrollY) { | |
panel.fixed(false).layoutRect({ y: panel._autoFixY }).repaint(); | |
toggleFixedChildPanels(false, panel._autoFixY - scrollY); | |
} | |
} | |
} | |
} | |
function addRemove(add, ctrl) { | |
var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal; | |
if (add) { | |
zOrder.push(ctrl); | |
} else { | |
i = zOrder.length; | |
while (i--) { | |
if (zOrder[i] === ctrl) { | |
zOrder.splice(i, 1); | |
} | |
} | |
} | |
if (zOrder.length) { | |
for (i = 0; i < zOrder.length; i++) { | |
if (zOrder[i].modal) { | |
zIndex++; | |
topModal = zOrder[i]; | |
} | |
zOrder[i].getEl().style.zIndex = zIndex; | |
zOrder[i].zIndex = zIndex; | |
zIndex++; | |
} | |
} | |
var modalBlockEl = DomQuery('#' + ctrl.classPrefix + 'modal-block', ctrl.getContainerElm())[0]; | |
if (topModal) { | |
DomQuery(modalBlockEl).css('z-index', topModal.zIndex - 1); | |
} else if (modalBlockEl) { | |
modalBlockEl.parentNode.removeChild(modalBlockEl); | |
hasModal = false; | |
} | |
FloatPanel.currentZIndex = zIndex; | |
} | |
var FloatPanel = Panel.extend({ | |
Mixins: [Movable, Resizable], | |
/** | |
* Constructs a new control instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {Boolean} autohide Automatically hide the panel. | |
*/ | |
init: function (settings) { | |
var self = this; | |
self._super(settings); | |
self._eventsRoot = self; | |
self.classes.add('floatpanel'); | |
// Hide floatpanes on click out side the root button | |
if (settings.autohide) { | |
bindDocumentClickHandler(); | |
bindWindowResizeHandler(); | |
visiblePanels.push(self); | |
} | |
if (settings.autofix) { | |
bindDocumentScrollHandler(); | |
self.on('move', function () { | |
repositionPanel(this); | |
}); | |
} | |
self.on('postrender show', function (e) { | |
if (e.control == self) { | |
var $modalBlockEl, prefix = self.classPrefix; | |
if (self.modal && !hasModal) { | |
$modalBlockEl = DomQuery('#' + prefix + 'modal-block', self.getContainerElm()); | |
if (!$modalBlockEl[0]) { | |
$modalBlockEl = DomQuery( | |
'<div id="' + prefix + 'modal-block" class="' + prefix + 'reset ' + prefix + 'fade"></div>' | |
).appendTo(self.getContainerElm()); | |
} | |
Delay.setTimeout(function () { | |
$modalBlockEl.addClass(prefix + 'in'); | |
DomQuery(self.getEl()).addClass(prefix + 'in'); | |
}); | |
hasModal = true; | |
} | |
addRemove(true, self); | |
} | |
}); | |
self.on('show', function () { | |
self.parents().each(function (ctrl) { | |
if (ctrl.state.get('fixed')) { | |
self.fixed(true); | |
return false; | |
} | |
}); | |
}); | |
if (settings.popover) { | |
self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>'; | |
self.classes.add('popover').add('bottom').add(self.isRtl() ? 'end' : 'start'); | |
} | |
self.aria('label', settings.ariaLabel); | |
self.aria('labelledby', self._id); | |
self.aria('describedby', self.describedBy || self._id + '-none'); | |
}, | |
fixed: function (state) { | |
var self = this; | |
if (self.state.get('fixed') != state) { | |
if (self.state.get('rendered')) { | |
var viewport = DomUtils.getViewPort(); | |
if (state) { | |
self.layoutRect().y -= viewport.y; | |
} else { | |
self.layoutRect().y += viewport.y; | |
} | |
} | |
self.classes.toggle('fixed', state); | |
self.state.set('fixed', state); | |
} | |
return self; | |
}, | |
/** | |
* Shows the current float panel. | |
* | |
* @method show | |
* @return {tinymce.ui.FloatPanel} Current floatpanel instance. | |
*/ | |
show: function () { | |
var self = this, i, state = self._super(); | |
i = visiblePanels.length; | |
while (i--) { | |
if (visiblePanels[i] === self) { | |
break; | |
} | |
} | |
if (i === -1) { | |
visiblePanels.push(self); | |
} | |
return state; | |
}, | |
/** | |
* Hides the current float panel. | |
* | |
* @method hide | |
* @return {tinymce.ui.FloatPanel} Current floatpanel instance. | |
*/ | |
hide: function () { | |
removeVisiblePanel(this); | |
addRemove(false, this); | |
return this._super(); | |
}, | |
/** | |
* Hide all visible float panels with he autohide setting enabled. This is for | |
* manually hiding floating menus or panels. | |
* | |
* @method hideAll | |
*/ | |
hideAll: function () { | |
FloatPanel.hideAll(); | |
}, | |
/** | |
* Closes the float panel. This will remove the float panel from page and fire the close event. | |
* | |
* @method close | |
*/ | |
close: function () { | |
var self = this; | |
if (!self.fire('close').isDefaultPrevented()) { | |
self.remove(); | |
addRemove(false, self); | |
} | |
return self; | |
}, | |
/** | |
* Removes the float panel from page. | |
* | |
* @method remove | |
*/ | |
remove: function () { | |
removeVisiblePanel(this); | |
this._super(); | |
}, | |
postRender: function () { | |
var self = this; | |
if (self.settings.bodyRole) { | |
this.getEl('body').setAttribute('role', self.settings.bodyRole); | |
} | |
return self._super(); | |
} | |
}); | |
/** | |
* Hide all visible float panels with he autohide setting enabled. This is for | |
* manually hiding floating menus or panels. | |
* | |
* @static | |
* @method hideAll | |
*/ | |
FloatPanel.hideAll = function () { | |
var i = visiblePanels.length; | |
while (i--) { | |
var panel = visiblePanels[i]; | |
if (panel && panel.settings.autohide) { | |
panel.hide(); | |
visiblePanels.splice(i, 1); | |
} | |
} | |
}; | |
function removeVisiblePanel(panel) { | |
var i; | |
i = visiblePanels.length; | |
while (i--) { | |
if (visiblePanels[i] === panel) { | |
visiblePanels.splice(i, 1); | |
} | |
} | |
i = zOrder.length; | |
while (i--) { | |
if (zOrder[i] === panel) { | |
zOrder.splice(i, 1); | |
} | |
} | |
} | |
return FloatPanel; | |
} | |
); | |
/** | |
* Inline.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.modes.Inline', | |
[ | |
'global!document', | |
'tinymce.core.Env', | |
'tinymce.core.dom.DOMUtils', | |
'tinymce.core.ui.Factory', | |
'tinymce.themes.modern.api.Events', | |
'tinymce.themes.modern.api.Settings', | |
'tinymce.themes.modern.ui.A11y', | |
'tinymce.themes.modern.ui.ContextToolbars', | |
'tinymce.themes.modern.ui.Menubar', | |
'tinymce.themes.modern.ui.SkinLoaded', | |
'tinymce.themes.modern.ui.Toolbar', | |
'tinymce.ui.FloatPanel' | |
], | |
function (document, Env, DOMUtils, Factory, Events, Settings, A11y, ContextToolbars, Menubar, SkinLoaded, Toolbar, FloatPanel) { | |
var isFixed = function (inlineToolbarContainer) { | |
return !!(inlineToolbarContainer && !Env.container); | |
}; | |
var render = function (editor, theme, args) { | |
var panel, inlineToolbarContainer; | |
var DOM = DOMUtils.DOM; | |
var fixedToolbarContainer = Settings.getFixedToolbarContainer(editor); | |
if (fixedToolbarContainer) { | |
inlineToolbarContainer = DOM.select(fixedToolbarContainer)[0]; | |
} | |
var reposition = function () { | |
if (panel && panel.moveRel && panel.visible() && !panel._fixed) { | |
// TODO: This is kind of ugly and doesn't handle multiple scrollable elements | |
var scrollContainer = editor.selection.getScrollContainer(), body = editor.getBody(); | |
var deltaX = 0, deltaY = 0; | |
if (scrollContainer) { | |
var bodyPos = DOM.getPos(body), scrollContainerPos = DOM.getPos(scrollContainer); | |
deltaX = Math.max(0, scrollContainerPos.x - bodyPos.x); | |
deltaY = Math.max(0, scrollContainerPos.y - bodyPos.y); | |
} | |
panel.fixed(false).moveRel(body, editor.rtl ? ['tr-br', 'br-tr'] : ['tl-bl', 'bl-tl', 'tr-br']).moveBy(deltaX, deltaY); | |
} | |
}; | |
var show = function () { | |
if (panel) { | |
panel.show(); | |
reposition(); | |
DOM.addClass(editor.getBody(), 'mce-edit-focus'); | |
} | |
}; | |
var hide = function () { | |
if (panel) { | |
// We require two events as the inline float panel based toolbar does not have autohide=true | |
panel.hide(); | |
// All other autohidden float panels will be closed below. | |
FloatPanel.hideAll(); | |
DOM.removeClass(editor.getBody(), 'mce-edit-focus'); | |
} | |
}; | |
var render = function () { | |
if (panel) { | |
if (!panel.visible()) { | |
show(); | |
} | |
return; | |
} | |
// Render a plain panel inside the inlineToolbarContainer if it's defined | |
panel = theme.panel = Factory.create({ | |
type: inlineToolbarContainer ? 'panel' : 'floatpanel', | |
role: 'application', | |
classes: 'tinymce tinymce-inline', | |
layout: 'flex', | |
direction: 'column', | |
align: 'stretch', | |
autohide: false, | |
autofix: isFixed(inlineToolbarContainer), | |
fixed: isFixed(inlineToolbarContainer), | |
border: 1, | |
items: [ | |
Settings.hasMenubar(editor) === false ? null : { type: 'menubar', border: '0 0 1 0', items: Menubar.createMenuButtons(editor) }, | |
Toolbar.createToolbars(editor, Settings.getToolbarSize(editor)) | |
] | |
}); | |
// Add statusbar | |
/*if (settings.statusbar !== false) { | |
panel.add({type: 'panel', classes: 'statusbar', layout: 'flow', border: '1 0 0 0', items: [ | |
{type: 'elementpath'} | |
]}); | |
}*/ | |
Events.fireBeforeRenderUI(editor); | |
if (inlineToolbarContainer) { | |
panel.renderTo(inlineToolbarContainer).reflow(); | |
} else { | |
panel.renderTo().reflow(); | |
} | |
A11y.addKeys(editor, panel); | |
show(); | |
ContextToolbars.addContextualToolbars(editor); | |
editor.on('nodeChange', reposition); | |
editor.on('activate', show); | |
editor.on('deactivate', hide); | |
editor.nodeChanged(); | |
}; | |
editor.settings.content_editable = true; | |
editor.on('focus', function () { | |
// Render only when the CSS file has been loaded | |
if (Settings.isSkinDisabled(editor) === false && args.skinUiCss) { | |
DOM.styleSheetLoader.load(args.skinUiCss, render, render); | |
} else { | |
render(); | |
} | |
}); | |
editor.on('blur hide', hide); | |
// Remove the panel when the editor is removed | |
editor.on('remove', function () { | |
if (panel) { | |
panel.remove(); | |
panel = null; | |
} | |
}); | |
// Preload skin css | |
if (Settings.isSkinDisabled(editor) === false && args.skinUiCss) { | |
DOM.styleSheetLoader.load(args.skinUiCss, SkinLoaded.fireSkinLoaded(editor)); | |
} else { | |
SkinLoaded.fireSkinLoaded(editor)(); | |
} | |
return {}; | |
}; | |
return { | |
render: render | |
}; | |
} | |
); | |
/** | |
* Throbber.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class enables you to display a Throbber for any element. | |
* | |
* @-x-less Throbber.less | |
* @class tinymce.ui.Throbber | |
*/ | |
define( | |
'tinymce.ui.Throbber', | |
[ | |
"tinymce.core.dom.DomQuery", | |
"tinymce.ui.Control", | |
"tinymce.core.util.Delay" | |
], | |
function ($, Control, Delay) { | |
"use strict"; | |
/** | |
* Constructs a new throbber. | |
* | |
* @constructor | |
* @param {Element} elm DOM Html element to display throbber in. | |
* @param {Boolean} inline Optional true/false state if the throbber should be appended to end of element for infinite scroll. | |
*/ | |
return function (elm, inline) { | |
var self = this, state, classPrefix = Control.classPrefix, timer; | |
/** | |
* Shows the throbber. | |
* | |
* @method show | |
* @param {Number} [time] Time to wait before showing. | |
* @param {function} [callback] Optional callback to execute when the throbber is shown. | |
* @return {tinymce.ui.Throbber} Current throbber instance. | |
*/ | |
self.show = function (time, callback) { | |
function render() { | |
if (state) { | |
$(elm).append( | |
'<div class="' + classPrefix + 'throbber' + (inline ? ' ' + classPrefix + 'throbber-inline' : '') + '"></div>' | |
); | |
if (callback) { | |
callback(); | |
} | |
} | |
} | |
self.hide(); | |
state = true; | |
if (time) { | |
timer = Delay.setTimeout(render, time); | |
} else { | |
render(); | |
} | |
return self; | |
}; | |
/** | |
* Hides the throbber. | |
* | |
* @method hide | |
* @return {tinymce.ui.Throbber} Current throbber instance. | |
*/ | |
self.hide = function () { | |
var child = elm.lastChild; | |
Delay.clearTimeout(timer); | |
if (child && child.className.indexOf('throbber') != -1) { | |
child.parentNode.removeChild(child); | |
} | |
state = false; | |
return self; | |
}; | |
}; | |
} | |
); | |
/** | |
* ProgressState.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.ui.ProgressState', | |
[ | |
'tinymce.ui.Throbber' | |
], | |
function (Throbber) { | |
var setup = function (editor, theme) { | |
var throbber; | |
editor.on('ProgressState', function (e) { | |
throbber = throbber || new Throbber(theme.panel.getEl('body')); | |
if (e.state) { | |
throbber.show(e.time); | |
} else { | |
throbber.hide(); | |
} | |
}); | |
}; | |
return { | |
setup: setup | |
}; | |
} | |
); | |
/** | |
* Render.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.ui.Render', | |
[ | |
'tinymce.themes.modern.api.Settings', | |
'tinymce.themes.modern.modes.Iframe', | |
'tinymce.themes.modern.modes.Inline', | |
'tinymce.themes.modern.ui.ProgressState' | |
], | |
function (Settings, Iframe, Inline, ProgressState) { | |
var renderUI = function (editor, theme, args) { | |
var skinUrl = Settings.getSkinUrl(editor); | |
if (skinUrl) { | |
args.skinUiCss = skinUrl + '/skin.min.css'; | |
editor.contentCSS.push(skinUrl + '/content' + (editor.inline ? '.inline' : '') + '.min.css'); | |
} | |
ProgressState.setup(editor, theme); | |
return Settings.isInline(editor) ? Inline.render(editor, theme, args) : Iframe.render(editor, theme, args); | |
}; | |
return { | |
renderUI: renderUI | |
}; | |
} | |
); | |
defineGlobal("global!setTimeout", setTimeout); | |
/** | |
* Tooltip.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a tooltip instance. | |
* | |
* @-x-less ToolTip.less | |
* @class tinymce.ui.ToolTip | |
* @extends tinymce.ui.Control | |
* @mixes tinymce.ui.Movable | |
*/ | |
define( | |
'tinymce.ui.Tooltip', | |
[ | |
"tinymce.ui.Control", | |
"tinymce.ui.Movable" | |
], | |
function (Control, Movable) { | |
return Control.extend({ | |
Mixins: [Movable], | |
Defaults: { | |
classes: 'widget tooltip tooltip-n' | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, prefix = self.classPrefix; | |
return ( | |
'<div id="' + self._id + '" class="' + self.classes + '" role="presentation">' + | |
'<div class="' + prefix + 'tooltip-arrow"></div>' + | |
'<div class="' + prefix + 'tooltip-inner">' + self.encode(self.state.get('text')) + '</div>' + | |
'</div>' | |
); | |
}, | |
bindStates: function () { | |
var self = this; | |
self.state.on('change:text', function (e) { | |
self.getEl().lastChild.innerHTML = self.encode(e.value); | |
}); | |
return self._super(); | |
}, | |
/** | |
* Repaints the control after a layout operation. | |
* | |
* @method repaint | |
*/ | |
repaint: function () { | |
var self = this, style, rect; | |
style = self.getEl().style; | |
rect = self._layoutRect; | |
style.left = rect.x + 'px'; | |
style.top = rect.y + 'px'; | |
style.zIndex = 0xFFFF + 0xFFFF; | |
} | |
}); | |
} | |
); | |
/** | |
* Widget.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Widget base class a widget is a control that has a tooltip and some basic states. | |
* | |
* @class tinymce.ui.Widget | |
* @extends tinymce.ui.Control | |
*/ | |
define( | |
'tinymce.ui.Widget', | |
[ | |
"tinymce.ui.Control", | |
"tinymce.ui.Tooltip" | |
], | |
function (Control, Tooltip) { | |
"use strict"; | |
var tooltip; | |
var Widget = Control.extend({ | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {String} tooltip Tooltip text to display when hovering. | |
* @setting {Boolean} autofocus True if the control should be focused when rendered. | |
* @setting {String} text Text to display inside widget. | |
*/ | |
init: function (settings) { | |
var self = this; | |
self._super(settings); | |
settings = self.settings; | |
self.canFocus = true; | |
if (settings.tooltip && Widget.tooltips !== false) { | |
self.on('mouseenter', function (e) { | |
var tooltip = self.tooltip().moveTo(-0xFFFF); | |
if (e.control == self) { | |
var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']); | |
tooltip.classes.toggle('tooltip-n', rel == 'bc-tc'); | |
tooltip.classes.toggle('tooltip-nw', rel == 'bc-tl'); | |
tooltip.classes.toggle('tooltip-ne', rel == 'bc-tr'); | |
tooltip.moveRel(self.getEl(), rel); | |
} else { | |
tooltip.hide(); | |
} | |
}); | |
self.on('mouseleave mousedown click', function () { | |
self.tooltip().hide(); | |
}); | |
} | |
self.aria('label', settings.ariaLabel || settings.tooltip); | |
}, | |
/** | |
* Returns the current tooltip instance. | |
* | |
* @method tooltip | |
* @return {tinymce.ui.Tooltip} Tooltip instance. | |
*/ | |
tooltip: function () { | |
if (!tooltip) { | |
tooltip = new Tooltip({ type: 'tooltip' }); | |
tooltip.renderTo(); | |
} | |
return tooltip; | |
}, | |
/** | |
* Called after the control has been rendered. | |
* | |
* @method postRender | |
*/ | |
postRender: function () { | |
var self = this, settings = self.settings; | |
self._super(); | |
if (!self.parent() && (settings.width || settings.height)) { | |
self.initLayoutRect(); | |
self.repaint(); | |
} | |
if (settings.autofocus) { | |
self.focus(); | |
} | |
}, | |
bindStates: function () { | |
var self = this; | |
function disable(state) { | |
self.aria('disabled', state); | |
self.classes.toggle('disabled', state); | |
} | |
function active(state) { | |
self.aria('pressed', state); | |
self.classes.toggle('active', state); | |
} | |
self.state.on('change:disabled', function (e) { | |
disable(e.value); | |
}); | |
self.state.on('change:active', function (e) { | |
active(e.value); | |
}); | |
if (self.state.get('disabled')) { | |
disable(true); | |
} | |
if (self.state.get('active')) { | |
active(true); | |
} | |
return self._super(); | |
}, | |
/** | |
* Removes the current control from DOM and from UI collections. | |
* | |
* @method remove | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
remove: function () { | |
this._super(); | |
if (tooltip) { | |
tooltip.remove(); | |
tooltip = null; | |
} | |
} | |
}); | |
return Widget; | |
} | |
); | |
/** | |
* Progress.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Progress control. | |
* | |
* @-x-less Progress.less | |
* @class tinymce.ui.Progress | |
* @extends tinymce.ui.Control | |
*/ | |
define( | |
'tinymce.ui.Progress', | |
[ | |
"tinymce.ui.Widget" | |
], | |
function (Widget) { | |
"use strict"; | |
return Widget.extend({ | |
Defaults: { | |
value: 0 | |
}, | |
init: function (settings) { | |
var self = this; | |
self._super(settings); | |
self.classes.add('progress'); | |
if (!self.settings.filter) { | |
self.settings.filter = function (value) { | |
return Math.round(value); | |
}; | |
} | |
}, | |
renderHtml: function () { | |
var self = this, id = self._id, prefix = this.classPrefix; | |
return ( | |
'<div id="' + id + '" class="' + self.classes + '">' + | |
'<div class="' + prefix + 'bar-container">' + | |
'<div class="' + prefix + 'bar"></div>' + | |
'</div>' + | |
'<div class="' + prefix + 'text">0%</div>' + | |
'</div>' | |
); | |
}, | |
postRender: function () { | |
var self = this; | |
self._super(); | |
self.value(self.settings.value); | |
return self; | |
}, | |
bindStates: function () { | |
var self = this; | |
function setValue(value) { | |
value = self.settings.filter(value); | |
self.getEl().lastChild.innerHTML = value + '%'; | |
self.getEl().firstChild.firstChild.style.width = value + '%'; | |
} | |
self.state.on('change:value', function (e) { | |
setValue(e.value); | |
}); | |
setValue(self.state.get('value')); | |
return self._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* Notification.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a notification instance. | |
* | |
* @-x-less Notification.less | |
* @class tinymce.ui.Notification | |
* @extends tinymce.ui.Container | |
* @mixes tinymce.ui.Movable | |
*/ | |
define( | |
'tinymce.ui.Notification', | |
[ | |
"tinymce.ui.Control", | |
"tinymce.ui.Movable", | |
"tinymce.ui.Progress", | |
"tinymce.core.util.Delay" | |
], | |
function (Control, Movable, Progress, Delay) { | |
var updateLiveRegion = function (ctx, text) { | |
ctx.getEl().lastChild.textContent = text + (ctx.progressBar ? ' ' + ctx.progressBar.value() + '%' : ''); | |
}; | |
return Control.extend({ | |
Mixins: [Movable], | |
Defaults: { | |
classes: 'widget notification' | |
}, | |
init: function (settings) { | |
var self = this; | |
self._super(settings); | |
self.maxWidth = settings.maxWidth; | |
if (settings.text) { | |
self.text(settings.text); | |
} | |
if (settings.icon) { | |
self.icon = settings.icon; | |
} | |
if (settings.color) { | |
self.color = settings.color; | |
} | |
if (settings.type) { | |
self.classes.add('notification-' + settings.type); | |
} | |
if (settings.timeout && (settings.timeout < 0 || settings.timeout > 0) && !settings.closeButton) { | |
self.closeButton = false; | |
} else { | |
self.classes.add('has-close'); | |
self.closeButton = true; | |
} | |
if (settings.progressBar) { | |
self.progressBar = new Progress(); | |
} | |
self.on('click', function (e) { | |
if (e.target.className.indexOf(self.classPrefix + 'close') != -1) { | |
self.close(); | |
} | |
}); | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, prefix = self.classPrefix, icon = '', closeButton = '', progressBar = '', notificationStyle = ''; | |
if (self.icon) { | |
icon = '<i class="' + prefix + 'ico' + ' ' + prefix + 'i-' + self.icon + '"></i>'; | |
} | |
notificationStyle = ' style="max-width: ' + self.maxWidth + 'px;' + (self.color ? 'background-color: ' + self.color + ';"' : '"'); | |
if (self.closeButton) { | |
closeButton = '<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>'; | |
} | |
if (self.progressBar) { | |
progressBar = self.progressBar.renderHtml(); | |
} | |
return ( | |
'<div id="' + self._id + '" class="' + self.classes + '"' + notificationStyle + ' role="presentation">' + | |
icon + | |
'<div class="' + prefix + 'notification-inner">' + self.state.get('text') + '</div>' + | |
progressBar + | |
closeButton + | |
'<div style="clip: rect(1px, 1px, 1px, 1px);height: 1px;overflow: hidden;position: absolute;width: 1px;"' + | |
' aria-live="assertive" aria-relevant="additions" aria-atomic="true"></div>' + | |
'</div>' | |
); | |
}, | |
postRender: function () { | |
var self = this; | |
Delay.setTimeout(function () { | |
self.$el.addClass(self.classPrefix + 'in'); | |
updateLiveRegion(self, self.state.get('text')); | |
}, 100); | |
return self._super(); | |
}, | |
bindStates: function () { | |
var self = this; | |
self.state.on('change:text', function (e) { | |
self.getEl().firstChild.innerHTML = e.value; | |
updateLiveRegion(self, e.value); | |
}); | |
if (self.progressBar) { | |
self.progressBar.bindStates(); | |
self.progressBar.state.on('change:value', function (e) { | |
updateLiveRegion(self, self.state.get('text')); | |
}); | |
} | |
return self._super(); | |
}, | |
close: function () { | |
var self = this; | |
if (!self.fire('close').isDefaultPrevented()) { | |
self.remove(); | |
} | |
return self; | |
}, | |
/** | |
* Repaints the control after a layout operation. | |
* | |
* @method repaint | |
*/ | |
repaint: function () { | |
var self = this, style, rect; | |
style = self.getEl().style; | |
rect = self._layoutRect; | |
style.left = rect.x + 'px'; | |
style.top = rect.y + 'px'; | |
// Hardcoded arbitrary z-value because we want the | |
// notifications under the other windows | |
style.zIndex = 0xFFFF - 1; | |
} | |
}); | |
} | |
); | |
/** | |
* NotificationManagerImpl.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.NotificationManagerImpl', | |
[ | |
'ephox.katamari.api.Arr', | |
'global!setTimeout', | |
'tinymce.core.util.Tools', | |
'tinymce.ui.DomUtils', | |
'tinymce.ui.Notification' | |
], | |
function (Arr, setTimeout, Tools, DomUtils, Notification) { | |
return function (editor) { | |
var getEditorContainer = function (editor) { | |
return editor.inline ? editor.getElement() : editor.getContentAreaContainer(); | |
}; | |
var getContainerWidth = function () { | |
var container = getEditorContainer(editor); | |
return DomUtils.getSize(container).width; | |
}; | |
// Since the viewport will change based on the present notifications, we need to move them all to the | |
// top left of the viewport to give an accurate size measurement so we can position them later. | |
var prePositionNotifications = function (notifications) { | |
Arr.each(notifications, function (notification) { | |
notification.moveTo(0, 0); | |
}); | |
}; | |
var positionNotifications = function (notifications) { | |
if (notifications.length > 0) { | |
var firstItem = notifications.slice(0, 1)[0]; | |
var container = getEditorContainer(editor); | |
firstItem.moveRel(container, 'tc-tc'); | |
Arr.each(notifications, function (notification, index) { | |
if (index > 0) { | |
notification.moveRel(notifications[index - 1].getEl(), 'bc-tc'); | |
} | |
}); | |
} | |
}; | |
var reposition = function (notifications) { | |
prePositionNotifications(notifications); | |
positionNotifications(notifications); | |
}; | |
var open = function (args, closeCallback) { | |
var extendedArgs = Tools.extend(args, { maxWidth: getContainerWidth() }); | |
var notif = new Notification(extendedArgs); | |
notif.args = extendedArgs; | |
//If we have a timeout value | |
if (extendedArgs.timeout > 0) { | |
notif.timer = setTimeout(function () { | |
notif.close(); | |
closeCallback(); | |
}, extendedArgs.timeout); | |
} | |
notif.on('close', function () { | |
closeCallback(); | |
}); | |
notif.renderTo(); | |
return notif; | |
}; | |
var close = function (notification) { | |
notification.close(); | |
}; | |
var getArgs = function (notification) { | |
return notification.args; | |
}; | |
return { | |
open: open, | |
close: close, | |
reposition: reposition, | |
getArgs: getArgs | |
}; | |
}; | |
} | |
); | |
/** | |
* Window.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new window. | |
* | |
* @-x-less Window.less | |
* @class tinymce.ui.Window | |
* @extends tinymce.ui.FloatPanel | |
*/ | |
define( | |
'tinymce.ui.Window', | |
[ | |
'global!document', | |
'global!setTimeout', | |
'global!window', | |
'tinymce.core.dom.DomQuery', | |
'tinymce.core.Env', | |
'tinymce.core.util.Delay', | |
'tinymce.ui.BoxUtils', | |
'tinymce.ui.DomUtils', | |
'tinymce.ui.DragHelper', | |
'tinymce.ui.FloatPanel', | |
'tinymce.ui.Panel' | |
], | |
function (document, setTimeout, window, DomQuery, Env, Delay, BoxUtils, DomUtils, DragHelper, FloatPanel, Panel) { | |
"use strict"; | |
var windows = [], oldMetaValue = ''; | |
function toggleFullScreenState(state) { | |
var noScaleMetaValue = 'width=device-width,initial-scale=1.0,user-scalable=0,minimum-scale=1.0,maximum-scale=1.0', | |
viewport = DomQuery("meta[name=viewport]")[0], | |
contentValue; | |
if (Env.overrideViewPort === false) { | |
return; | |
} | |
if (!viewport) { | |
viewport = document.createElement('meta'); | |
viewport.setAttribute('name', 'viewport'); | |
document.getElementsByTagName('head')[0].appendChild(viewport); | |
} | |
contentValue = viewport.getAttribute('content'); | |
if (contentValue && typeof oldMetaValue != 'undefined') { | |
oldMetaValue = contentValue; | |
} | |
viewport.setAttribute('content', state ? noScaleMetaValue : oldMetaValue); | |
} | |
function toggleBodyFullScreenClasses(classPrefix, state) { | |
if (checkFullscreenWindows() && state === false) { | |
DomQuery([document.documentElement, document.body]).removeClass(classPrefix + 'fullscreen'); | |
} | |
} | |
function checkFullscreenWindows() { | |
for (var i = 0; i < windows.length; i++) { | |
if (windows[i]._fullscreen) { | |
return true; | |
} | |
} | |
return false; | |
} | |
function handleWindowResize() { | |
if (!Env.desktop) { | |
var lastSize = { | |
w: window.innerWidth, | |
h: window.innerHeight | |
}; | |
Delay.setInterval(function () { | |
var w = window.innerWidth, | |
h = window.innerHeight; | |
if (lastSize.w != w || lastSize.h != h) { | |
lastSize = { | |
w: w, | |
h: h | |
}; | |
DomQuery(window).trigger('resize'); | |
} | |
}, 100); | |
} | |
function reposition() { | |
var i, rect = DomUtils.getWindowSize(), layoutRect; | |
for (i = 0; i < windows.length; i++) { | |
layoutRect = windows[i].layoutRect(); | |
windows[i].moveTo( | |
windows[i].settings.x || Math.max(0, rect.w / 2 - layoutRect.w / 2), | |
windows[i].settings.y || Math.max(0, rect.h / 2 - layoutRect.h / 2) | |
); | |
} | |
} | |
DomQuery(window).on('resize', reposition); | |
} | |
var Window = FloatPanel.extend({ | |
modal: true, | |
Defaults: { | |
border: 1, | |
layout: 'flex', | |
containerCls: 'panel', | |
role: 'dialog', | |
callbacks: { | |
submit: function () { | |
this.fire('submit', { data: this.toJSON() }); | |
}, | |
close: function () { | |
this.close(); | |
} | |
} | |
}, | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
*/ | |
init: function (settings) { | |
var self = this; | |
self._super(settings); | |
if (self.isRtl()) { | |
self.classes.add('rtl'); | |
} | |
self.classes.add('window'); | |
self.bodyClasses.add('window-body'); | |
self.state.set('fixed', true); | |
// Create statusbar | |
if (settings.buttons) { | |
self.statusbar = new Panel({ | |
layout: 'flex', | |
border: '1 0 0 0', | |
spacing: 3, | |
padding: 10, | |
align: 'center', | |
pack: self.isRtl() ? 'start' : 'end', | |
defaults: { | |
type: 'button' | |
}, | |
items: settings.buttons | |
}); | |
self.statusbar.classes.add('foot'); | |
self.statusbar.parent(self); | |
} | |
self.on('click', function (e) { | |
var closeClass = self.classPrefix + 'close'; | |
if (DomUtils.hasClass(e.target, closeClass) || DomUtils.hasClass(e.target.parentNode, closeClass)) { | |
self.close(); | |
} | |
}); | |
self.on('cancel', function () { | |
self.close(); | |
}); | |
self.aria('describedby', self.describedBy || self._id + '-none'); | |
self.aria('label', settings.title); | |
self._fullscreen = false; | |
}, | |
/** | |
* Recalculates the positions of the controls in the current container. | |
* This is invoked by the reflow method and shouldn't be called directly. | |
* | |
* @method recalc | |
*/ | |
recalc: function () { | |
var self = this, statusbar = self.statusbar, layoutRect, width, x, needsRecalc; | |
if (self._fullscreen) { | |
self.layoutRect(DomUtils.getWindowSize()); | |
self.layoutRect().contentH = self.layoutRect().innerH; | |
} | |
self._super(); | |
layoutRect = self.layoutRect(); | |
// Resize window based on title width | |
if (self.settings.title && !self._fullscreen) { | |
width = layoutRect.headerW; | |
if (width > layoutRect.w) { | |
x = layoutRect.x - Math.max(0, width / 2); | |
self.layoutRect({ w: width, x: x }); | |
needsRecalc = true; | |
} | |
} | |
// Resize window based on statusbar width | |
if (statusbar) { | |
statusbar.layoutRect({ w: self.layoutRect().innerW }).recalc(); | |
width = statusbar.layoutRect().minW + layoutRect.deltaW; | |
if (width > layoutRect.w) { | |
x = layoutRect.x - Math.max(0, width - layoutRect.w); | |
self.layoutRect({ w: width, x: x }); | |
needsRecalc = true; | |
} | |
} | |
// Recalc body and disable auto resize | |
if (needsRecalc) { | |
self.recalc(); | |
} | |
}, | |
/** | |
* Initializes the current controls layout rect. | |
* This will be executed by the layout managers to determine the | |
* default minWidth/minHeight etc. | |
* | |
* @method initLayoutRect | |
* @return {Object} Layout rect instance. | |
*/ | |
initLayoutRect: function () { | |
var self = this, layoutRect = self._super(), deltaH = 0, headEl; | |
// Reserve vertical space for title | |
if (self.settings.title && !self._fullscreen) { | |
headEl = self.getEl('head'); | |
var size = DomUtils.getSize(headEl); | |
layoutRect.headerW = size.width; | |
layoutRect.headerH = size.height; | |
deltaH += layoutRect.headerH; | |
} | |
// Reserve vertical space for statusbar | |
if (self.statusbar) { | |
deltaH += self.statusbar.layoutRect().h; | |
} | |
layoutRect.deltaH += deltaH; | |
layoutRect.minH += deltaH; | |
//layoutRect.innerH -= deltaH; | |
layoutRect.h += deltaH; | |
var rect = DomUtils.getWindowSize(); | |
layoutRect.x = self.settings.x || Math.max(0, rect.w / 2 - layoutRect.w / 2); | |
layoutRect.y = self.settings.y || Math.max(0, rect.h / 2 - layoutRect.h / 2); | |
return layoutRect; | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix; | |
var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html; | |
self.preRender(); | |
layout.preRender(self); | |
if (settings.title) { | |
headerHtml = ( | |
'<div id="' + id + '-head" class="' + prefix + 'window-head">' + | |
'<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' + | |
'<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' + | |
'<button type="button" class="' + prefix + 'close" aria-hidden="true">' + | |
'<i class="mce-ico mce-i-remove"></i>' + | |
'</button>' + | |
'</div>' | |
); | |
} | |
if (settings.url) { | |
html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>'; | |
} | |
if (typeof html == "undefined") { | |
html = layout.renderHtml(self); | |
} | |
if (self.statusbar) { | |
footerHtml = self.statusbar.renderHtml(); | |
} | |
return ( | |
'<div id="' + id + '" class="' + self.classes + '" hidefocus="1">' + | |
'<div class="' + self.classPrefix + 'reset" role="application">' + | |
headerHtml + | |
'<div id="' + id + '-body" class="' + self.bodyClasses + '">' + | |
html + | |
'</div>' + | |
footerHtml + | |
'</div>' + | |
'</div>' | |
); | |
}, | |
/** | |
* Switches the window fullscreen mode. | |
* | |
* @method fullscreen | |
* @param {Boolean} state True/false state. | |
* @return {tinymce.ui.Window} Current window instance. | |
*/ | |
fullscreen: function (state) { | |
var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect; | |
if (state != self._fullscreen) { | |
DomQuery(window).on('resize', function () { | |
var time; | |
if (self._fullscreen) { | |
// Time the layout time if it's to slow use a timeout to not hog the CPU | |
if (!slowRendering) { | |
time = new Date().getTime(); | |
var rect = DomUtils.getWindowSize(); | |
self.moveTo(0, 0).resizeTo(rect.w, rect.h); | |
if ((new Date().getTime()) - time > 50) { | |
slowRendering = true; | |
} | |
} else { | |
if (!self._timer) { | |
self._timer = Delay.setTimeout(function () { | |
var rect = DomUtils.getWindowSize(); | |
self.moveTo(0, 0).resizeTo(rect.w, rect.h); | |
self._timer = 0; | |
}, 50); | |
} | |
} | |
} | |
}); | |
layoutRect = self.layoutRect(); | |
self._fullscreen = state; | |
if (!state) { | |
self.borderBox = BoxUtils.parseBox(self.settings.border); | |
self.getEl('head').style.display = ''; | |
layoutRect.deltaH += layoutRect.headerH; | |
DomQuery([documentElement, document.body]).removeClass(prefix + 'fullscreen'); | |
self.classes.remove('fullscreen'); | |
self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h); | |
} else { | |
self._initial = { x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h }; | |
self.borderBox = BoxUtils.parseBox('0'); | |
self.getEl('head').style.display = 'none'; | |
layoutRect.deltaH -= layoutRect.headerH + 2; | |
DomQuery([documentElement, document.body]).addClass(prefix + 'fullscreen'); | |
self.classes.add('fullscreen'); | |
var rect = DomUtils.getWindowSize(); | |
self.moveTo(0, 0).resizeTo(rect.w, rect.h); | |
} | |
} | |
return self.reflow(); | |
}, | |
/** | |
* Called after the control has been rendered. | |
* | |
* @method postRender | |
*/ | |
postRender: function () { | |
var self = this, startPos; | |
setTimeout(function () { | |
self.classes.add('in'); | |
self.fire('open'); | |
}, 0); | |
self._super(); | |
if (self.statusbar) { | |
self.statusbar.postRender(); | |
} | |
self.focus(); | |
this.dragHelper = new DragHelper(self._id + '-dragh', { | |
start: function () { | |
startPos = { | |
x: self.layoutRect().x, | |
y: self.layoutRect().y | |
}; | |
}, | |
drag: function (e) { | |
self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY); | |
} | |
}); | |
self.on('submit', function (e) { | |
if (!e.isDefaultPrevented()) { | |
self.close(); | |
} | |
}); | |
windows.push(self); | |
toggleFullScreenState(true); | |
}, | |
/** | |
* Fires a submit event with the serialized form. | |
* | |
* @method submit | |
* @return {Object} Event arguments object. | |
*/ | |
submit: function () { | |
return this.fire('submit', { data: this.toJSON() }); | |
}, | |
/** | |
* Removes the current control from DOM and from UI collections. | |
* | |
* @method remove | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
remove: function () { | |
var self = this, i; | |
self.dragHelper.destroy(); | |
self._super(); | |
if (self.statusbar) { | |
this.statusbar.remove(); | |
} | |
toggleBodyFullScreenClasses(self.classPrefix, false); | |
i = windows.length; | |
while (i--) { | |
if (windows[i] === self) { | |
windows.splice(i, 1); | |
} | |
} | |
toggleFullScreenState(windows.length > 0); | |
}, | |
/** | |
* Returns the contentWindow object of the iframe if it exists. | |
* | |
* @method getContentWindow | |
* @return {Window} window object or null. | |
*/ | |
getContentWindow: function () { | |
var ifr = this.getEl().getElementsByTagName('iframe')[0]; | |
return ifr ? ifr.contentWindow : null; | |
} | |
}); | |
handleWindowResize(); | |
return Window; | |
} | |
); | |
/** | |
* MessageBox.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class is used to create MessageBoxes like alerts/confirms etc. | |
* | |
* @class tinymce.ui.MessageBox | |
* @extends tinymce.ui.FloatPanel | |
*/ | |
define( | |
'tinymce.ui.MessageBox', | |
[ | |
'global!document', | |
'tinymce.ui.Window' | |
], | |
function (document, Window) { | |
"use strict"; | |
var MessageBox = Window.extend({ | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
*/ | |
init: function (settings) { | |
settings = { | |
border: 1, | |
padding: 20, | |
layout: 'flex', | |
pack: "center", | |
align: "center", | |
containerCls: 'panel', | |
autoScroll: true, | |
buttons: { type: "button", text: "Ok", action: "ok" }, | |
items: { | |
type: "label", | |
multiline: true, | |
maxWidth: 500, | |
maxHeight: 200 | |
} | |
}; | |
this._super(settings); | |
}, | |
Statics: { | |
/** | |
* Ok buttons constant. | |
* | |
* @static | |
* @final | |
* @field {Number} OK | |
*/ | |
OK: 1, | |
/** | |
* Ok/cancel buttons constant. | |
* | |
* @static | |
* @final | |
* @field {Number} OK_CANCEL | |
*/ | |
OK_CANCEL: 2, | |
/** | |
* yes/no buttons constant. | |
* | |
* @static | |
* @final | |
* @field {Number} YES_NO | |
*/ | |
YES_NO: 3, | |
/** | |
* yes/no/cancel buttons constant. | |
* | |
* @static | |
* @final | |
* @field {Number} YES_NO_CANCEL | |
*/ | |
YES_NO_CANCEL: 4, | |
/** | |
* Constructs a new message box and renders it to the body element. | |
* | |
* @static | |
* @method msgBox | |
* @param {Object} settings Name/value object with settings. | |
*/ | |
msgBox: function (settings) { | |
var buttons, callback = settings.callback || function () { }; | |
function createButton(text, status, primary) { | |
return { | |
type: "button", | |
text: text, | |
subtype: primary ? 'primary' : '', | |
onClick: function (e) { | |
e.control.parents()[1].close(); | |
callback(status); | |
} | |
}; | |
} | |
switch (settings.buttons) { | |
case MessageBox.OK_CANCEL: | |
buttons = [ | |
createButton('Ok', true, true), | |
createButton('Cancel', false) | |
]; | |
break; | |
case MessageBox.YES_NO: | |
case MessageBox.YES_NO_CANCEL: | |
buttons = [ | |
createButton('Yes', 1, true), | |
createButton('No', 0) | |
]; | |
if (settings.buttons == MessageBox.YES_NO_CANCEL) { | |
buttons.push(createButton('Cancel', -1)); | |
} | |
break; | |
default: | |
buttons = [ | |
createButton('Ok', true, true) | |
]; | |
break; | |
} | |
return new Window({ | |
padding: 20, | |
x: settings.x, | |
y: settings.y, | |
minWidth: 300, | |
minHeight: 100, | |
layout: "flex", | |
pack: "center", | |
align: "center", | |
buttons: buttons, | |
title: settings.title, | |
role: 'alertdialog', | |
items: { | |
type: "label", | |
multiline: true, | |
maxWidth: 500, | |
maxHeight: 200, | |
text: settings.text | |
}, | |
onPostRender: function () { | |
this.aria('describedby', this.items()[0]._id); | |
}, | |
onClose: settings.onClose, | |
onCancel: function () { | |
callback(false); | |
} | |
}).renderTo(document.body).reflow(); | |
}, | |
/** | |
* Creates a new alert dialog. | |
* | |
* @method alert | |
* @param {Object} settings Settings for the alert dialog. | |
* @param {function} [callback] Callback to execute when the user makes a choice. | |
*/ | |
alert: function (settings, callback) { | |
if (typeof settings == "string") { | |
settings = { text: settings }; | |
} | |
settings.callback = callback; | |
return MessageBox.msgBox(settings); | |
}, | |
/** | |
* Creates a new confirm dialog. | |
* | |
* @method confirm | |
* @param {Object} settings Settings for the confirm dialog. | |
* @param {function} [callback] Callback to execute when the user makes a choice. | |
*/ | |
confirm: function (settings, callback) { | |
if (typeof settings == "string") { | |
settings = { text: settings }; | |
} | |
settings.callback = callback; | |
settings.buttons = MessageBox.OK_CANCEL; | |
return MessageBox.msgBox(settings); | |
} | |
} | |
}); | |
return MessageBox; | |
} | |
); | |
/** | |
* WindowManagerImpl.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.WindowManagerImpl', | |
[ | |
"tinymce.ui.Window", | |
"tinymce.ui.MessageBox" | |
], | |
function (Window, MessageBox) { | |
return function (editor) { | |
var open = function (args, params, closeCallback) { | |
var win; | |
args.title = args.title || ' '; | |
// Handle URL | |
args.url = args.url || args.file; // Legacy | |
if (args.url) { | |
args.width = parseInt(args.width || 320, 10); | |
args.height = parseInt(args.height || 240, 10); | |
} | |
// Handle body | |
if (args.body) { | |
args.items = { | |
defaults: args.defaults, | |
type: args.bodyType || 'form', | |
items: args.body, | |
data: args.data, | |
callbacks: args.commands | |
}; | |
} | |
if (!args.url && !args.buttons) { | |
args.buttons = [ | |
{ | |
text: 'Ok', subtype: 'primary', onclick: function () { | |
win.find('form')[0].submit(); | |
} | |
}, | |
{ | |
text: 'Cancel', onclick: function () { | |
win.close(); | |
} | |
} | |
]; | |
} | |
win = new Window(args); | |
win.on('close', function () { | |
closeCallback(win); | |
}); | |
// Handle data | |
if (args.data) { | |
win.on('postRender', function () { | |
this.find('*').each(function (ctrl) { | |
var name = ctrl.name(); | |
if (name in args.data) { | |
ctrl.value(args.data[name]); | |
} | |
}); | |
}); | |
} | |
// store args and parameters | |
win.features = args || {}; | |
win.params = params || {}; | |
win = win.renderTo().reflow(); | |
return win; | |
}; | |
var alert = function (message, choiceCallback, closeCallback) { | |
var win; | |
win = MessageBox.alert(message, function () { | |
choiceCallback(); | |
}); | |
win.on('close', function () { | |
closeCallback(win); | |
}); | |
return win; | |
}; | |
var confirm = function (message, choiceCallback, closeCallback) { | |
var win; | |
win = MessageBox.confirm(message, function (state) { | |
choiceCallback(state); | |
}); | |
win.on('close', function () { | |
closeCallback(win); | |
}); | |
return win; | |
}; | |
var close = function (window) { | |
window.close(); | |
}; | |
var getParams = function (window) { | |
return window.params; | |
}; | |
var setParams = function (window, params) { | |
window.params = params; | |
}; | |
return { | |
open: open, | |
alert: alert, | |
confirm: confirm, | |
close: close, | |
getParams: getParams, | |
setParams: setParams | |
}; | |
}; | |
} | |
); | |
/** | |
* ThemeApi.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.api.ThemeApi', | |
[ | |
'tinymce.themes.modern.ui.Render', | |
'tinymce.themes.modern.ui.Resize', | |
'tinymce.ui.NotificationManagerImpl', | |
'tinymce.ui.WindowManagerImpl' | |
], | |
function (Render, Resize, NotificationManagerImpl, WindowManagerImpl) { | |
var get = function (editor) { | |
var renderUI = function (args) { | |
return Render.renderUI(editor, this, args); | |
}; | |
var resizeTo = function (w, h) { | |
return Resize.resizeTo(editor, w, h); | |
}; | |
var resizeBy = function (dw, dh) { | |
return Resize.resizeBy(editor, dw, dh); | |
}; | |
var getNotificationManagerImpl = function () { | |
return NotificationManagerImpl(editor); | |
}; | |
var getWindowManagerImpl = function () { | |
return WindowManagerImpl(editor); | |
}; | |
return { | |
renderUI: renderUI, | |
resizeTo: resizeTo, | |
resizeBy: resizeBy, | |
getNotificationManagerImpl: getNotificationManagerImpl, | |
getWindowManagerImpl: getWindowManagerImpl | |
}; | |
}; | |
return { | |
get: get | |
}; | |
} | |
); | |
/** | |
* Layout.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Base layout manager class. | |
* | |
* @class tinymce.ui.Layout | |
*/ | |
define( | |
'tinymce.ui.Layout', | |
[ | |
"tinymce.core.util.Class", | |
"tinymce.core.util.Tools" | |
], | |
function (Class, Tools) { | |
"use strict"; | |
return Class.extend({ | |
Defaults: { | |
firstControlClass: 'first', | |
lastControlClass: 'last' | |
}, | |
/** | |
* Constructs a layout instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
*/ | |
init: function (settings) { | |
this.settings = Tools.extend({}, this.Defaults, settings); | |
}, | |
/** | |
* This method gets invoked before the layout renders the controls. | |
* | |
* @method preRender | |
* @param {tinymce.ui.Container} container Container instance to preRender. | |
*/ | |
preRender: function (container) { | |
container.bodyClasses.add(this.settings.containerClass); | |
}, | |
/** | |
* Applies layout classes to the container. | |
* | |
* @private | |
*/ | |
applyClasses: function (items) { | |
var self = this, settings = self.settings, firstClass, lastClass, firstItem, lastItem; | |
firstClass = settings.firstControlClass; | |
lastClass = settings.lastControlClass; | |
items.each(function (item) { | |
item.classes.remove(firstClass).remove(lastClass).add(settings.controlClass); | |
if (item.visible()) { | |
if (!firstItem) { | |
firstItem = item; | |
} | |
lastItem = item; | |
} | |
}); | |
if (firstItem) { | |
firstItem.classes.add(firstClass); | |
} | |
if (lastItem) { | |
lastItem.classes.add(lastClass); | |
} | |
}, | |
/** | |
* Renders the specified container and any layout specific HTML. | |
* | |
* @method renderHtml | |
* @param {tinymce.ui.Container} container Container to render HTML for. | |
*/ | |
renderHtml: function (container) { | |
var self = this, html = ''; | |
self.applyClasses(container.items()); | |
container.items().each(function (item) { | |
html += item.renderHtml(); | |
}); | |
return html; | |
}, | |
/** | |
* Recalculates the positions of the controls in the specified container. | |
* | |
* @method recalc | |
* @param {tinymce.ui.Container} container Container instance to recalc. | |
*/ | |
recalc: function () { | |
}, | |
/** | |
* This method gets invoked after the layout renders the controls. | |
* | |
* @method postRender | |
* @param {tinymce.ui.Container} container Container instance to postRender. | |
*/ | |
postRender: function () { | |
}, | |
isNative: function () { | |
return false; | |
} | |
}); | |
} | |
); | |
/** | |
* AbsoluteLayout.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* LayoutManager for absolute positioning. This layout manager is more of | |
* a base class for other layouts but can be created and used directly. | |
* | |
* @-x-less AbsoluteLayout.less | |
* @class tinymce.ui.AbsoluteLayout | |
* @extends tinymce.ui.Layout | |
*/ | |
define( | |
'tinymce.ui.AbsoluteLayout', | |
[ | |
"tinymce.ui.Layout" | |
], | |
function (Layout) { | |
"use strict"; | |
return Layout.extend({ | |
Defaults: { | |
containerClass: 'abs-layout', | |
controlClass: 'abs-layout-item' | |
}, | |
/** | |
* Recalculates the positions of the controls in the specified container. | |
* | |
* @method recalc | |
* @param {tinymce.ui.Container} container Container instance to recalc. | |
*/ | |
recalc: function (container) { | |
container.items().filter(':visible').each(function (ctrl) { | |
var settings = ctrl.settings; | |
ctrl.layoutRect({ | |
x: settings.x, | |
y: settings.y, | |
w: settings.w, | |
h: settings.h | |
}); | |
if (ctrl.recalc) { | |
ctrl.recalc(); | |
} | |
}); | |
}, | |
/** | |
* Renders the specified container and any layout specific HTML. | |
* | |
* @method renderHtml | |
* @param {tinymce.ui.Container} container Container to render HTML for. | |
*/ | |
renderHtml: function (container) { | |
return '<div id="' + container._id + '-absend" class="' + container.classPrefix + 'abs-end"></div>' + this._super(container); | |
} | |
}); | |
} | |
); | |
/** | |
* Button.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class is used to create buttons. You can create them directly or through the Factory. | |
* | |
* @example | |
* // Create and render a button to the body element | |
* tinymce.ui.Factory.create({ | |
* type: 'button', | |
* text: 'My button' | |
* }).renderTo(document.body); | |
* | |
* @-x-less Button.less | |
* @class tinymce.ui.Button | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.Button', | |
[ | |
'global!document', | |
'global!window', | |
'tinymce.ui.Widget' | |
], | |
function (document, window, Widget) { | |
"use strict"; | |
return Widget.extend({ | |
Defaults: { | |
classes: "widget btn", | |
role: "button" | |
}, | |
/** | |
* Constructs a new button instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {String} size Size of the button small|medium|large. | |
* @setting {String} image Image to use for icon. | |
* @setting {String} icon Icon to use for button. | |
*/ | |
init: function (settings) { | |
var self = this, size; | |
self._super(settings); | |
settings = self.settings; | |
size = self.settings.size; | |
self.on('click mousedown', function (e) { | |
e.preventDefault(); | |
}); | |
self.on('touchstart', function (e) { | |
self.fire('click', e); | |
e.preventDefault(); | |
}); | |
if (settings.subtype) { | |
self.classes.add(settings.subtype); | |
} | |
if (size) { | |
self.classes.add('btn-' + size); | |
} | |
if (settings.icon) { | |
self.icon(settings.icon); | |
} | |
}, | |
/** | |
* Sets/gets the current button icon. | |
* | |
* @method icon | |
* @param {String} [icon] New icon identifier. | |
* @return {String|tinymce.ui.MenuButton} Current icon or current MenuButton instance. | |
*/ | |
icon: function (icon) { | |
if (!arguments.length) { | |
return this.state.get('icon'); | |
} | |
this.state.set('icon', icon); | |
return this; | |
}, | |
/** | |
* Repaints the button for example after it's been resizes by a layout engine. | |
* | |
* @method repaint | |
*/ | |
repaint: function () { | |
var btnElm = this.getEl().firstChild, | |
btnStyle; | |
if (btnElm) { | |
btnStyle = btnElm.style; | |
btnStyle.width = btnStyle.height = "100%"; | |
} | |
this._super(); | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, id = self._id, prefix = self.classPrefix; | |
var icon = self.state.get('icon'), image, text = self.state.get('text'), textHtml = ''; | |
var ariaPressed, settings = self.settings; | |
image = settings.image; | |
if (image) { | |
icon = 'none'; | |
// Support for [high dpi, low dpi] image sources | |
if (typeof image != "string") { | |
image = window.getSelection ? image[0] : image[1]; | |
} | |
image = ' style="background-image: url(\'' + image + '\')"'; | |
} else { | |
image = ''; | |
} | |
if (text) { | |
self.classes.add('btn-has-text'); | |
textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>'; | |
} | |
icon = icon ? prefix + 'ico ' + prefix + 'i-' + icon : ''; | |
ariaPressed = typeof settings.active === 'boolean' ? ' aria-pressed="' + settings.active + '"' : ''; | |
return ( | |
'<div id="' + id + '" class="' + self.classes + '" tabindex="-1"' + ariaPressed + '>' + | |
'<button id="' + id + '-button" role="presentation" type="button" tabindex="-1">' + | |
(icon ? '<i class="' + icon + '"' + image + '></i>' : '') + | |
textHtml + | |
'</button>' + | |
'</div>' | |
); | |
}, | |
bindStates: function () { | |
var self = this, $ = self.$, textCls = self.classPrefix + 'txt'; | |
function setButtonText(text) { | |
var $span = $('span.' + textCls, self.getEl()); | |
if (text) { | |
if (!$span[0]) { | |
$('button:first', self.getEl()).append('<span class="' + textCls + '"></span>'); | |
$span = $('span.' + textCls, self.getEl()); | |
} | |
$span.html(self.encode(text)); | |
} else { | |
$span.remove(); | |
} | |
self.classes.toggle('btn-has-text', !!text); | |
} | |
self.state.on('change:text', function (e) { | |
setButtonText(e.value); | |
}); | |
self.state.on('change:icon', function (e) { | |
var icon = e.value, prefix = self.classPrefix; | |
self.settings.icon = icon; | |
icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; | |
var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0]; | |
if (icon) { | |
if (!iconElm || iconElm != btnElm.firstChild) { | |
iconElm = document.createElement('i'); | |
btnElm.insertBefore(iconElm, btnElm.firstChild); | |
} | |
iconElm.className = icon; | |
} else if (iconElm) { | |
btnElm.removeChild(iconElm); | |
} | |
setButtonText(self.state.get('text')); | |
}); | |
return self._super(); | |
} | |
}); | |
} | |
); | |
defineGlobal("global!RegExp", RegExp); | |
/** | |
* BrowseButton.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new browse button. | |
* | |
* @-x-less BrowseButton.less | |
* @class tinymce.ui.BrowseButton | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.BrowseButton', | |
[ | |
'tinymce.ui.Button', | |
'tinymce.core.util.Tools', | |
'tinymce.ui.DomUtils', | |
'tinymce.core.dom.DomQuery', | |
'global!RegExp' | |
], | |
function (Button, Tools, DomUtils, $, RegExp) { | |
return Button.extend({ | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {Boolean} multiple True if the dropzone is a multiple control. | |
* @setting {Number} maxLength Max length for the dropzone. | |
* @setting {Number} size Size of the dropzone in characters. | |
*/ | |
init: function (settings) { | |
var self = this; | |
settings = Tools.extend({ | |
text: "Browse...", | |
multiple: false, | |
accept: null // by default accept any files | |
}, settings); | |
self._super(settings); | |
self.classes.add('browsebutton'); | |
if (settings.multiple) { | |
self.classes.add('multiple'); | |
} | |
}, | |
/** | |
* Called after the control has been rendered. | |
* | |
* @method postRender | |
*/ | |
postRender: function () { | |
var self = this; | |
var input = DomUtils.create('input', { | |
type: 'file', | |
id: self._id + '-browse', | |
accept: self.settings.accept | |
}); | |
self._super(); | |
$(input).on('change', function (e) { | |
var files = e.target.files; | |
self.value = function () { | |
if (!files.length) { | |
return null; | |
} else if (self.settings.multiple) { | |
return files; | |
} else { | |
return files[0]; | |
} | |
}; | |
e.preventDefault(); | |
if (files.length) { | |
self.fire('change', e); | |
} | |
}); | |
// ui.Button prevents default on click, so we shouldn't let the click to propagate up to it | |
$(input).on('click', function (e) { | |
e.stopPropagation(); | |
}); | |
$(self.getEl('button')).on('click', function (e) { | |
e.stopPropagation(); | |
input.click(); | |
}); | |
// in newer browsers input doesn't have to be attached to dom to trigger browser dialog | |
// however older IE11 (< 11.1358.14393.0) still requires this | |
self.getEl().appendChild(input); | |
}, | |
remove: function () { | |
$(this.getEl('button')).off(); | |
$(this.getEl('input')).off(); | |
this._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* ButtonGroup.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This control enables you to put multiple buttons into a group. This is | |
* useful when you want to combine similar toolbar buttons into a group. | |
* | |
* @example | |
* // Create and render a buttongroup with two buttons to the body element | |
* tinymce.ui.Factory.create({ | |
* type: 'buttongroup', | |
* items: [ | |
* {text: 'Button A'}, | |
* {text: 'Button B'} | |
* ] | |
* }).renderTo(document.body); | |
* | |
* @-x-less ButtonGroup.less | |
* @class tinymce.ui.ButtonGroup | |
* @extends tinymce.ui.Container | |
*/ | |
define( | |
'tinymce.ui.ButtonGroup', | |
[ | |
"tinymce.ui.Container" | |
], | |
function (Container) { | |
"use strict"; | |
return Container.extend({ | |
Defaults: { | |
defaultType: 'button', | |
role: 'group' | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, layout = self._layout; | |
self.classes.add('btn-group'); | |
self.preRender(); | |
layout.preRender(self); | |
return ( | |
'<div id="' + self._id + '" class="' + self.classes + '">' + | |
'<div id="' + self._id + '-body">' + | |
(self.settings.html || '') + layout.renderHtml(self) + | |
'</div>' + | |
'</div>' | |
); | |
} | |
}); | |
} | |
); | |
/** | |
* Checkbox.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This control creates a custom checkbox. | |
* | |
* @example | |
* // Create and render a checkbox to the body element | |
* tinymce.core.ui.Factory.create({ | |
* type: 'checkbox', | |
* checked: true, | |
* text: 'My checkbox' | |
* }).renderTo(document.body); | |
* | |
* @-x-less Checkbox.less | |
* @class tinymce.ui.Checkbox | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.Checkbox', | |
[ | |
'global!document', | |
'tinymce.ui.Widget' | |
], | |
function (document, Widget) { | |
"use strict"; | |
return Widget.extend({ | |
Defaults: { | |
classes: "checkbox", | |
role: "checkbox", | |
checked: false | |
}, | |
/** | |
* Constructs a new Checkbox instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {Boolean} checked True if the checkbox should be checked by default. | |
*/ | |
init: function (settings) { | |
var self = this; | |
self._super(settings); | |
self.on('click mousedown', function (e) { | |
e.preventDefault(); | |
}); | |
self.on('click', function (e) { | |
e.preventDefault(); | |
if (!self.disabled()) { | |
self.checked(!self.checked()); | |
} | |
}); | |
self.checked(self.settings.checked); | |
}, | |
/** | |
* Getter/setter function for the checked state. | |
* | |
* @method checked | |
* @param {Boolean} [state] State to be set. | |
* @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation. | |
*/ | |
checked: function (state) { | |
if (!arguments.length) { | |
return this.state.get('checked'); | |
} | |
this.state.set('checked', state); | |
return this; | |
}, | |
/** | |
* Getter/setter function for the value state. | |
* | |
* @method value | |
* @param {Boolean} [state] State to be set. | |
* @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation. | |
*/ | |
value: function (state) { | |
if (!arguments.length) { | |
return this.checked(); | |
} | |
return this.checked(state); | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, id = self._id, prefix = self.classPrefix; | |
return ( | |
'<div id="' + id + '" class="' + self.classes + '" unselectable="on" aria-labelledby="' + id + '-al" tabindex="-1">' + | |
'<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' + | |
'<span id="' + id + '-al" class="' + prefix + 'label">' + self.encode(self.state.get('text')) + '</span>' + | |
'</div>' | |
); | |
}, | |
bindStates: function () { | |
var self = this; | |
function checked(state) { | |
self.classes.toggle("checked", state); | |
self.aria('checked', state); | |
} | |
self.state.on('change:text', function (e) { | |
self.getEl('al').firstChild.data = self.translate(e.value); | |
}); | |
self.state.on('change:checked change:value', function (e) { | |
self.fire('change'); | |
checked(e.value); | |
}); | |
self.state.on('change:icon', function (e) { | |
var icon = e.value, prefix = self.classPrefix; | |
if (typeof icon == 'undefined') { | |
return self.settings.icon; | |
} | |
self.settings.icon = icon; | |
icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; | |
var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0]; | |
if (icon) { | |
if (!iconElm || iconElm != btnElm.firstChild) { | |
iconElm = document.createElement('i'); | |
btnElm.insertBefore(iconElm, btnElm.firstChild); | |
} | |
iconElm.className = icon; | |
} else if (iconElm) { | |
btnElm.removeChild(iconElm); | |
} | |
}); | |
if (self.state.get('checked')) { | |
checked(true); | |
} | |
return self._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.util.VK', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.util.VK'); | |
} | |
); | |
/** | |
* ComboBox.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class creates a combobox control. Select box that you select a value from or | |
* type a value into. | |
* | |
* @-x-less ComboBox.less | |
* @class tinymce.ui.ComboBox | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.ComboBox', | |
[ | |
'global!document', | |
'tinymce.core.dom.DomQuery', | |
'tinymce.core.ui.Factory', | |
'tinymce.core.util.Tools', | |
'tinymce.core.util.VK', | |
'tinymce.ui.DomUtils', | |
'tinymce.ui.Widget' | |
], | |
function (document, DomQuery, Factory, Tools, VK, DomUtils, Widget) { | |
"use strict"; | |
return Widget.extend({ | |
/** | |
* Constructs a new control instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {String} placeholder Placeholder text to display. | |
*/ | |
init: function (settings) { | |
var self = this; | |
self._super(settings); | |
settings = self.settings; | |
self.classes.add('combobox'); | |
self.subinput = true; | |
self.ariaTarget = 'inp'; // TODO: Figure out a better way | |
settings.menu = settings.menu || settings.values; | |
if (settings.menu) { | |
settings.icon = 'caret'; | |
} | |
self.on('click', function (e) { | |
var elm = e.target, root = self.getEl(); | |
if (!DomQuery.contains(root, elm) && elm != root) { | |
return; | |
} | |
while (elm && elm != root) { | |
if (elm.id && elm.id.indexOf('-open') != -1) { | |
self.fire('action'); | |
if (settings.menu) { | |
self.showMenu(); | |
if (e.aria) { | |
self.menu.items()[0].focus(); | |
} | |
} | |
} | |
elm = elm.parentNode; | |
} | |
}); | |
// TODO: Rework this | |
self.on('keydown', function (e) { | |
var rootControl; | |
if (e.keyCode == 13 && e.target.nodeName === 'INPUT') { | |
e.preventDefault(); | |
// Find root control that we can do toJSON on | |
self.parents().reverse().each(function (ctrl) { | |
if (ctrl.toJSON) { | |
rootControl = ctrl; | |
return false; | |
} | |
}); | |
// Fire event on current text box with the serialized data of the whole form | |
self.fire('submit', { data: rootControl.toJSON() }); | |
} | |
}); | |
self.on('keyup', function (e) { | |
if (e.target.nodeName == "INPUT") { | |
var oldValue = self.state.get('value'); | |
var newValue = e.target.value; | |
if (newValue !== oldValue) { | |
self.state.set('value', newValue); | |
self.fire('autocomplete', e); | |
} | |
} | |
}); | |
self.on('mouseover', function (e) { | |
var tooltip = self.tooltip().moveTo(-0xFFFF); | |
if (self.statusLevel() && e.target.className.indexOf(self.classPrefix + 'status') !== -1) { | |
var statusMessage = self.statusMessage() || 'Ok'; | |
var rel = tooltip.text(statusMessage).show().testMoveRel(e.target, ['bc-tc', 'bc-tl', 'bc-tr']); | |
tooltip.classes.toggle('tooltip-n', rel == 'bc-tc'); | |
tooltip.classes.toggle('tooltip-nw', rel == 'bc-tl'); | |
tooltip.classes.toggle('tooltip-ne', rel == 'bc-tr'); | |
tooltip.moveRel(e.target, rel); | |
} | |
}); | |
}, | |
statusLevel: function (value) { | |
if (arguments.length > 0) { | |
this.state.set('statusLevel', value); | |
} | |
return this.state.get('statusLevel'); | |
}, | |
statusMessage: function (value) { | |
if (arguments.length > 0) { | |
this.state.set('statusMessage', value); | |
} | |
return this.state.get('statusMessage'); | |
}, | |
showMenu: function () { | |
var self = this, settings = self.settings, menu; | |
if (!self.menu) { | |
menu = settings.menu || []; | |
// Is menu array then auto constuct menu control | |
if (menu.length) { | |
menu = { | |
type: 'menu', | |
items: menu | |
}; | |
} else { | |
menu.type = menu.type || 'menu'; | |
} | |
self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm()); | |
self.fire('createmenu'); | |
self.menu.reflow(); | |
self.menu.on('cancel', function (e) { | |
if (e.control === self.menu) { | |
self.focus(); | |
} | |
}); | |
self.menu.on('show hide', function (e) { | |
e.control.items().each(function (ctrl) { | |
ctrl.active(ctrl.value() == self.value()); | |
}); | |
}).fire('show'); | |
self.menu.on('select', function (e) { | |
self.value(e.control.value()); | |
}); | |
self.on('focusin', function (e) { | |
if (e.target.tagName.toUpperCase() == 'INPUT') { | |
self.menu.hide(); | |
} | |
}); | |
self.aria('expanded', true); | |
} | |
self.menu.show(); | |
self.menu.layoutRect({ w: self.layoutRect().w }); | |
self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']); | |
}, | |
/** | |
* Focuses the input area of the control. | |
* | |
* @method focus | |
*/ | |
focus: function () { | |
this.getEl('inp').focus(); | |
}, | |
/** | |
* Repaints the control after a layout operation. | |
* | |
* @method repaint | |
*/ | |
repaint: function () { | |
var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect(); | |
var width, lineHeight, innerPadding = 0, inputElm = elm.firstChild; | |
if (self.statusLevel() && self.statusLevel() !== 'none') { | |
innerPadding = ( | |
parseInt(DomUtils.getRuntimeStyle(inputElm, 'padding-right'), 10) - | |
parseInt(DomUtils.getRuntimeStyle(inputElm, 'padding-left'), 10) | |
); | |
} | |
if (openElm) { | |
width = rect.w - DomUtils.getSize(openElm).width - 10; | |
} else { | |
width = rect.w - 10; | |
} | |
// Detect old IE 7+8 add lineHeight to align caret vertically in the middle | |
var doc = document; | |
if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) { | |
lineHeight = (self.layoutRect().h - 2) + 'px'; | |
} | |
DomQuery(inputElm).css({ | |
width: width - innerPadding, | |
lineHeight: lineHeight | |
}); | |
self._super(); | |
return self; | |
}, | |
/** | |
* Post render method. Called after the control has been rendered to the target. | |
* | |
* @method postRender | |
* @return {tinymce.ui.ComboBox} Current combobox instance. | |
*/ | |
postRender: function () { | |
var self = this; | |
DomQuery(this.getEl('inp')).on('change', function (e) { | |
self.state.set('value', e.target.value); | |
self.fire('change', e); | |
}); | |
return self._super(); | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix; | |
var value = self.state.get('value') || ''; | |
var icon, text, openBtnHtml = '', extraAttrs = '', statusHtml = ''; | |
if ("spellcheck" in settings) { | |
extraAttrs += ' spellcheck="' + settings.spellcheck + '"'; | |
} | |
if (settings.maxLength) { | |
extraAttrs += ' maxlength="' + settings.maxLength + '"'; | |
} | |
if (settings.size) { | |
extraAttrs += ' size="' + settings.size + '"'; | |
} | |
if (settings.subtype) { | |
extraAttrs += ' type="' + settings.subtype + '"'; | |
} | |
statusHtml = '<i id="' + id + '-status" class="mce-status mce-ico" style="display: none"></i>'; | |
if (self.disabled()) { | |
extraAttrs += ' disabled="disabled"'; | |
} | |
icon = settings.icon; | |
if (icon && icon != 'caret') { | |
icon = prefix + 'ico ' + prefix + 'i-' + settings.icon; | |
} | |
text = self.state.get('text'); | |
if (icon || text) { | |
openBtnHtml = ( | |
'<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' + | |
'<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' + | |
(icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') + | |
(text ? (icon ? ' ' : '') + text : '') + | |
'</button>' + | |
'</div>' | |
); | |
self.classes.add('has-open'); | |
} | |
return ( | |
'<div id="' + id + '" class="' + self.classes + '">' + | |
'<input id="' + id + '-inp" class="' + prefix + 'textbox" value="' + | |
self.encode(value, false) + '" hidefocus="1"' + extraAttrs + ' placeholder="' + | |
self.encode(settings.placeholder) + '" />' + | |
statusHtml + | |
openBtnHtml + | |
'</div>' | |
); | |
}, | |
value: function (value) { | |
if (arguments.length) { | |
this.state.set('value', value); | |
return this; | |
} | |
// Make sure the real state is in sync | |
if (this.state.get('rendered')) { | |
this.state.set('value', this.getEl('inp').value); | |
} | |
return this.state.get('value'); | |
}, | |
showAutoComplete: function (items, term) { | |
var self = this; | |
if (items.length === 0) { | |
self.hideMenu(); | |
return; | |
} | |
var insert = function (value, title) { | |
return function () { | |
self.fire('selectitem', { | |
title: title, | |
value: value | |
}); | |
}; | |
}; | |
if (self.menu) { | |
self.menu.items().remove(); | |
} else { | |
self.menu = Factory.create({ | |
type: 'menu', | |
classes: 'combobox-menu', | |
layout: 'flow' | |
}).parent(self).renderTo(); | |
} | |
Tools.each(items, function (item) { | |
self.menu.add({ | |
text: item.title, | |
url: item.previewUrl, | |
match: term, | |
classes: 'menu-item-ellipsis', | |
onclick: insert(item.value, item.title) | |
}); | |
}); | |
self.menu.renderNew(); | |
self.hideMenu(); | |
self.menu.on('cancel', function (e) { | |
if (e.control.parent() === self.menu) { | |
e.stopPropagation(); | |
self.focus(); | |
self.hideMenu(); | |
} | |
}); | |
self.menu.on('select', function () { | |
self.focus(); | |
}); | |
var maxW = self.layoutRect().w; | |
self.menu.layoutRect({ w: maxW, minW: 0, maxW: maxW }); | |
self.menu.reflow(); | |
self.menu.show(); | |
self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']); | |
}, | |
hideMenu: function () { | |
if (this.menu) { | |
this.menu.hide(); | |
} | |
}, | |
bindStates: function () { | |
var self = this; | |
self.state.on('change:value', function (e) { | |
if (self.getEl('inp').value != e.value) { | |
self.getEl('inp').value = e.value; | |
} | |
}); | |
self.state.on('change:disabled', function (e) { | |
self.getEl('inp').disabled = e.value; | |
}); | |
self.state.on('change:statusLevel', function (e) { | |
var statusIconElm = self.getEl('status'); | |
var prefix = self.classPrefix, value = e.value; | |
DomUtils.css(statusIconElm, 'display', value === 'none' ? 'none' : ''); | |
DomUtils.toggleClass(statusIconElm, prefix + 'i-checkmark', value === 'ok'); | |
DomUtils.toggleClass(statusIconElm, prefix + 'i-warning', value === 'warn'); | |
DomUtils.toggleClass(statusIconElm, prefix + 'i-error', value === 'error'); | |
self.classes.toggle('has-status', value !== 'none'); | |
self.repaint(); | |
}); | |
DomUtils.on(self.getEl('status'), 'mouseleave', function () { | |
self.tooltip().hide(); | |
}); | |
self.on('cancel', function (e) { | |
if (self.menu && self.menu.visible()) { | |
e.stopPropagation(); | |
self.hideMenu(); | |
} | |
}); | |
var focusIdx = function (idx, menu) { | |
if (menu && menu.items().length > 0) { | |
menu.items().eq(idx)[0].focus(); | |
} | |
}; | |
self.on('keydown', function (e) { | |
var keyCode = e.keyCode; | |
if (e.target.nodeName === 'INPUT') { | |
if (keyCode === VK.DOWN) { | |
e.preventDefault(); | |
self.fire('autocomplete'); | |
focusIdx(0, self.menu); | |
} else if (keyCode === VK.UP) { | |
e.preventDefault(); | |
focusIdx(-1, self.menu); | |
} | |
} | |
}); | |
return self._super(); | |
}, | |
remove: function () { | |
DomQuery(this.getEl('inp')).off(); | |
if (this.menu) { | |
this.menu.remove(); | |
} | |
this._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* ColorBox.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This widget lets you enter colors and browse for colors by pressing the color button. It also displays | |
* a preview of the current color. | |
* | |
* @-x-less ColorBox.less | |
* @class tinymce.ui.ColorBox | |
* @extends tinymce.ui.ComboBox | |
*/ | |
define( | |
'tinymce.ui.ColorBox', | |
[ | |
"tinymce.ui.ComboBox" | |
], | |
function (ComboBox) { | |
"use strict"; | |
return ComboBox.extend({ | |
/** | |
* Constructs a new control instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
*/ | |
init: function (settings) { | |
var self = this; | |
settings.spellcheck = false; | |
if (settings.onaction) { | |
settings.icon = 'none'; | |
} | |
self._super(settings); | |
self.classes.add('colorbox'); | |
self.on('change keyup postrender', function () { | |
self.repaintColor(self.value()); | |
}); | |
}, | |
repaintColor: function (value) { | |
var openElm = this.getEl('open'); | |
var elm = openElm ? openElm.getElementsByTagName('i')[0] : null; | |
if (elm) { | |
try { | |
elm.style.background = value; | |
} catch (ex) { | |
// Ignore | |
} | |
} | |
}, | |
bindStates: function () { | |
var self = this; | |
self.state.on('change:value', function (e) { | |
if (self.state.get('rendered')) { | |
self.repaintColor(e.value); | |
} | |
}); | |
return self._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* PanelButton.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new panel button. | |
* | |
* @class tinymce.ui.PanelButton | |
* @extends tinymce.ui.Button | |
*/ | |
define( | |
'tinymce.ui.PanelButton', | |
[ | |
"tinymce.ui.Button", | |
"tinymce.ui.FloatPanel" | |
], | |
function (Button, FloatPanel) { | |
"use strict"; | |
return Button.extend({ | |
/** | |
* Shows the panel for the button. | |
* | |
* @method showPanel | |
*/ | |
showPanel: function () { | |
var self = this, settings = self.settings; | |
self.classes.add('opened'); | |
if (!self.panel) { | |
var panelSettings = settings.panel; | |
// Wrap panel in grid layout if type if specified | |
// This makes it possible to add forms or other containers directly in the panel option | |
if (panelSettings.type) { | |
panelSettings = { | |
layout: 'grid', | |
items: panelSettings | |
}; | |
} | |
panelSettings.role = panelSettings.role || 'dialog'; | |
panelSettings.popover = true; | |
panelSettings.autohide = true; | |
panelSettings.ariaRoot = true; | |
self.panel = new FloatPanel(panelSettings).on('hide', function () { | |
self.classes.remove('opened'); | |
}).on('cancel', function (e) { | |
e.stopPropagation(); | |
self.focus(); | |
self.hidePanel(); | |
}).parent(self).renderTo(self.getContainerElm()); | |
self.panel.fire('show'); | |
self.panel.reflow(); | |
} else { | |
self.panel.show(); | |
} | |
var rel = self.panel.testMoveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tc', 'bc-tl', 'bc-tr'] : ['bc-tc', 'bc-tr', 'bc-tl'])); | |
self.panel.classes.toggle('start', rel === 'bc-tl'); | |
self.panel.classes.toggle('end', rel === 'bc-tr'); | |
self.panel.moveRel(self.getEl(), rel); | |
}, | |
/** | |
* Hides the panel for the button. | |
* | |
* @method hidePanel | |
*/ | |
hidePanel: function () { | |
var self = this; | |
if (self.panel) { | |
self.panel.hide(); | |
} | |
}, | |
/** | |
* Called after the control has been rendered. | |
* | |
* @method postRender | |
*/ | |
postRender: function () { | |
var self = this; | |
self.aria('haspopup', true); | |
self.on('click', function (e) { | |
if (e.control === self) { | |
if (self.panel && self.panel.visible()) { | |
self.hidePanel(); | |
} else { | |
self.showPanel(); | |
self.panel.focus(!!e.aria); | |
} | |
} | |
}); | |
return self._super(); | |
}, | |
remove: function () { | |
if (this.panel) { | |
this.panel.remove(); | |
this.panel = null; | |
} | |
return this._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* ColorButton.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class creates a color button control. This is a split button in which the main | |
* button has a visual representation of the currently selected color. When clicked | |
* the caret button displays a color picker, allowing the user to select a new color. | |
* | |
* @-x-less ColorButton.less | |
* @class tinymce.ui.ColorButton | |
* @extends tinymce.ui.PanelButton | |
*/ | |
define( | |
'tinymce.ui.ColorButton', | |
[ | |
"tinymce.ui.PanelButton", | |
"tinymce.core.dom.DOMUtils" | |
], | |
function (PanelButton, DomUtils) { | |
"use strict"; | |
var DOM = DomUtils.DOM; | |
return PanelButton.extend({ | |
/** | |
* Constructs a new ColorButton instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
*/ | |
init: function (settings) { | |
this._super(settings); | |
this.classes.add('splitbtn'); | |
this.classes.add('colorbutton'); | |
}, | |
/** | |
* Getter/setter for the current color. | |
* | |
* @method color | |
* @param {String} [color] Color to set. | |
* @return {String|tinymce.ui.ColorButton} Current color or current instance. | |
*/ | |
color: function (color) { | |
if (color) { | |
this._color = color; | |
this.getEl('preview').style.backgroundColor = color; | |
return this; | |
} | |
return this._color; | |
}, | |
/** | |
* Resets the current color. | |
* | |
* @method resetColor | |
* @return {tinymce.ui.ColorButton} Current instance. | |
*/ | |
resetColor: function () { | |
this._color = null; | |
this.getEl('preview').style.backgroundColor = null; | |
return this; | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, id = self._id, prefix = self.classPrefix, text = self.state.get('text'); | |
var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; | |
var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '', | |
textHtml = ''; | |
if (text) { | |
self.classes.add('btn-has-text'); | |
textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>'; | |
} | |
return ( | |
'<div id="' + id + '" class="' + self.classes + '" role="button" tabindex="-1" aria-haspopup="true">' + | |
'<button role="presentation" hidefocus="1" type="button" tabindex="-1">' + | |
(icon ? '<i class="' + icon + '"' + image + '></i>' : '') + | |
'<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' + | |
textHtml + | |
'</button>' + | |
'<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' + | |
' <i class="' + prefix + 'caret"></i>' + | |
'</button>' + | |
'</div>' | |
); | |
}, | |
/** | |
* Called after the control has been rendered. | |
* | |
* @method postRender | |
*/ | |
postRender: function () { | |
var self = this, onClickHandler = self.settings.onclick; | |
self.on('click', function (e) { | |
if (e.aria && e.aria.key === 'down') { | |
return; | |
} | |
if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) { | |
e.stopImmediatePropagation(); | |
onClickHandler.call(self, e); | |
} | |
}); | |
delete self.settings.onclick; | |
return self._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* ResolveGlobal.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.core.util.Color', | |
[ | |
'global!tinymce.util.Tools.resolve' | |
], | |
function (resolve) { | |
return resolve('tinymce.util.Color'); | |
} | |
); | |
/** | |
* ColorPicker.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Color picker widget lets you select colors. | |
* | |
* @-x-less ColorPicker.less | |
* @class tinymce.ui.ColorPicker | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.ColorPicker', | |
[ | |
"tinymce.ui.Widget", | |
"tinymce.ui.DragHelper", | |
"tinymce.ui.DomUtils", | |
"tinymce.core.util.Color" | |
], | |
function (Widget, DragHelper, DomUtils, Color) { | |
"use strict"; | |
return Widget.extend({ | |
Defaults: { | |
classes: "widget colorpicker" | |
}, | |
/** | |
* Constructs a new colorpicker instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {String} color Initial color value. | |
*/ | |
init: function (settings) { | |
this._super(settings); | |
}, | |
postRender: function () { | |
var self = this, color = self.color(), hsv, hueRootElm, huePointElm, svRootElm, svPointElm; | |
hueRootElm = self.getEl('h'); | |
huePointElm = self.getEl('hp'); | |
svRootElm = self.getEl('sv'); | |
svPointElm = self.getEl('svp'); | |
function getPos(elm, event) { | |
var pos = DomUtils.getPos(elm), x, y; | |
x = event.pageX - pos.x; | |
y = event.pageY - pos.y; | |
x = Math.max(0, Math.min(x / elm.clientWidth, 1)); | |
y = Math.max(0, Math.min(y / elm.clientHeight, 1)); | |
return { | |
x: x, | |
y: y | |
}; | |
} | |
function updateColor(hsv, hueUpdate) { | |
var hue = (360 - hsv.h) / 360; | |
DomUtils.css(huePointElm, { | |
top: (hue * 100) + '%' | |
}); | |
if (!hueUpdate) { | |
DomUtils.css(svPointElm, { | |
left: hsv.s + '%', | |
top: (100 - hsv.v) + '%' | |
}); | |
} | |
svRootElm.style.background = new Color({ s: 100, v: 100, h: hsv.h }).toHex(); | |
self.color().parse({ s: hsv.s, v: hsv.v, h: hsv.h }); | |
} | |
function updateSaturationAndValue(e) { | |
var pos; | |
pos = getPos(svRootElm, e); | |
hsv.s = pos.x * 100; | |
hsv.v = (1 - pos.y) * 100; | |
updateColor(hsv); | |
self.fire('change'); | |
} | |
function updateHue(e) { | |
var pos; | |
pos = getPos(hueRootElm, e); | |
hsv = color.toHsv(); | |
hsv.h = (1 - pos.y) * 360; | |
updateColor(hsv, true); | |
self.fire('change'); | |
} | |
self._repaint = function () { | |
hsv = color.toHsv(); | |
updateColor(hsv); | |
}; | |
self._super(); | |
self._svdraghelper = new DragHelper(self._id + '-sv', { | |
start: updateSaturationAndValue, | |
drag: updateSaturationAndValue | |
}); | |
self._hdraghelper = new DragHelper(self._id + '-h', { | |
start: updateHue, | |
drag: updateHue | |
}); | |
self._repaint(); | |
}, | |
rgb: function () { | |
return this.color().toRgb(); | |
}, | |
value: function (value) { | |
var self = this; | |
if (arguments.length) { | |
self.color().parse(value); | |
if (self._rendered) { | |
self._repaint(); | |
} | |
} else { | |
return self.color().toHex(); | |
} | |
}, | |
color: function () { | |
if (!this._color) { | |
this._color = new Color(); | |
} | |
return this._color; | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, id = self._id, prefix = self.classPrefix, hueHtml; | |
var stops = '#ff0000,#ff0080,#ff00ff,#8000ff,#0000ff,#0080ff,#00ffff,#00ff80,#00ff00,#80ff00,#ffff00,#ff8000,#ff0000'; | |
function getOldIeFallbackHtml() { | |
var i, l, html = '', gradientPrefix, stopsList; | |
gradientPrefix = 'filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='; | |
stopsList = stops.split(','); | |
for (i = 0, l = stopsList.length - 1; i < l; i++) { | |
html += ( | |
'<div class="' + prefix + 'colorpicker-h-chunk" style="' + | |
'height:' + (100 / l) + '%;' + | |
gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ');' + | |
'-ms-' + gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ')' + | |
'"></div>' | |
); | |
} | |
return html; | |
} | |
var gradientCssText = ( | |
'background: -ms-linear-gradient(top,' + stops + ');' + | |
'background: linear-gradient(to bottom,' + stops + ');' | |
); | |
hueHtml = ( | |
'<div id="' + id + '-h" class="' + prefix + 'colorpicker-h" style="' + gradientCssText + '">' + | |
getOldIeFallbackHtml() + | |
'<div id="' + id + '-hp" class="' + prefix + 'colorpicker-h-marker"></div>' + | |
'</div>' | |
); | |
return ( | |
'<div id="' + id + '" class="' + self.classes + '">' + | |
'<div id="' + id + '-sv" class="' + prefix + 'colorpicker-sv">' + | |
'<div class="' + prefix + 'colorpicker-overlay1">' + | |
'<div class="' + prefix + 'colorpicker-overlay2">' + | |
'<div id="' + id + '-svp" class="' + prefix + 'colorpicker-selector1">' + | |
'<div class="' + prefix + 'colorpicker-selector2"></div>' + | |
'</div>' + | |
'</div>' + | |
'</div>' + | |
'</div>' + | |
hueHtml + | |
'</div>' | |
); | |
} | |
}); | |
} | |
); | |
/** | |
* DropZone.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new dropzone. | |
* | |
* @-x-less DropZone.less | |
* @class tinymce.ui.DropZone | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.DropZone', | |
[ | |
'tinymce.ui.Widget', | |
'tinymce.core.util.Tools', | |
'tinymce.ui.DomUtils', | |
'global!RegExp' | |
], | |
function (Widget, Tools, DomUtils, RegExp) { | |
return Widget.extend({ | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {Boolean} multiple True if the dropzone is a multiple control. | |
* @setting {Number} maxLength Max length for the dropzone. | |
* @setting {Number} size Size of the dropzone in characters. | |
*/ | |
init: function (settings) { | |
var self = this; | |
settings = Tools.extend({ | |
height: 100, | |
text: "Drop an image here", | |
multiple: false, | |
accept: null // by default accept any files | |
}, settings); | |
self._super(settings); | |
self.classes.add('dropzone'); | |
if (settings.multiple) { | |
self.classes.add('multiple'); | |
} | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, attrs, elm; | |
var cfg = self.settings; | |
attrs = { | |
id: self._id, | |
hidefocus: '1' | |
}; | |
elm = DomUtils.create('div', attrs, '<span>' + this.translate(cfg.text) + '</span>'); | |
if (cfg.height) { | |
DomUtils.css(elm, 'height', cfg.height + 'px'); | |
} | |
if (cfg.width) { | |
DomUtils.css(elm, 'width', cfg.width + 'px'); | |
} | |
elm.className = self.classes; | |
return elm.outerHTML; | |
}, | |
/** | |
* Called after the control has been rendered. | |
* | |
* @method postRender | |
*/ | |
postRender: function () { | |
var self = this; | |
var toggleDragClass = function (e) { | |
e.preventDefault(); | |
self.classes.toggle('dragenter'); | |
self.getEl().className = self.classes; | |
}; | |
var filter = function (files) { | |
var accept = self.settings.accept; | |
if (typeof accept !== 'string') { | |
return files; | |
} | |
var re = new RegExp('(' + accept.split(/\s*,\s*/).join('|') + ')$', 'i'); | |
return Tools.grep(files, function (file) { | |
return re.test(file.name); | |
}); | |
}; | |
self._super(); | |
self.$el.on('dragover', function (e) { | |
e.preventDefault(); | |
}); | |
self.$el.on('dragenter', toggleDragClass); | |
self.$el.on('dragleave', toggleDragClass); | |
self.$el.on('drop', function (e) { | |
e.preventDefault(); | |
if (self.state.get('disabled')) { | |
return; | |
} | |
var files = filter(e.dataTransfer.files); | |
self.value = function () { | |
if (!files.length) { | |
return null; | |
} else if (self.settings.multiple) { | |
return files; | |
} else { | |
return files[0]; | |
} | |
}; | |
if (files.length) { | |
self.fire('change', e); | |
} | |
}); | |
}, | |
remove: function () { | |
this.$el.off(); | |
this._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* Path.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new path control. | |
* | |
* @-x-less Path.less | |
* @class tinymce.ui.Path | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.Path', | |
[ | |
"tinymce.ui.Widget" | |
], | |
function (Widget) { | |
"use strict"; | |
return Widget.extend({ | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {String} delimiter Delimiter to display between row in path. | |
*/ | |
init: function (settings) { | |
var self = this; | |
if (!settings.delimiter) { | |
settings.delimiter = '\u00BB'; | |
} | |
self._super(settings); | |
self.classes.add('path'); | |
self.canFocus = true; | |
self.on('click', function (e) { | |
var index, target = e.target; | |
if ((index = target.getAttribute('data-index'))) { | |
self.fire('select', { value: self.row()[index], index: index }); | |
} | |
}); | |
self.row(self.settings.row); | |
}, | |
/** | |
* Focuses the current control. | |
* | |
* @method focus | |
* @return {tinymce.ui.Control} Current control instance. | |
*/ | |
focus: function () { | |
var self = this; | |
self.getEl().firstChild.focus(); | |
return self; | |
}, | |
/** | |
* Sets/gets the data to be used for the path. | |
* | |
* @method row | |
* @param {Array} row Array with row name is rendered to path. | |
*/ | |
row: function (row) { | |
if (!arguments.length) { | |
return this.state.get('row'); | |
} | |
this.state.set('row', row); | |
return this; | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this; | |
return ( | |
'<div id="' + self._id + '" class="' + self.classes + '">' + | |
self._getDataPathHtml(self.state.get('row')) + | |
'</div>' | |
); | |
}, | |
bindStates: function () { | |
var self = this; | |
self.state.on('change:row', function (e) { | |
self.innerHtml(self._getDataPathHtml(e.value)); | |
}); | |
return self._super(); | |
}, | |
_getDataPathHtml: function (data) { | |
var self = this, parts = data || [], i, l, html = '', prefix = self.classPrefix; | |
for (i = 0, l = parts.length; i < l; i++) { | |
html += ( | |
(i > 0 ? '<div class="' + prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') + | |
'<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' + | |
i + '" tabindex="-1" id="' + self._id + '-' + i + '" aria-level="' + (i + 1) + '">' + parts[i].name + '</div>' | |
); | |
} | |
if (!html) { | |
html = '<div class="' + prefix + 'path-item">\u00a0</div>'; | |
} | |
return html; | |
} | |
}); | |
} | |
); | |
/** | |
* ElementPath.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This control creates an path for the current selections parent elements in TinyMCE. | |
* | |
* @class tinymce.ui.ElementPath | |
* @extends tinymce.ui.Path | |
*/ | |
define( | |
'tinymce.ui.ElementPath', | |
[ | |
"tinymce.ui.Path" | |
], | |
function (Path) { | |
return Path.extend({ | |
/** | |
* Post render method. Called after the control has been rendered to the target. | |
* | |
* @method postRender | |
* @return {tinymce.ui.ElementPath} Current combobox instance. | |
*/ | |
postRender: function () { | |
var self = this, editor = self.settings.editor; | |
function isHidden(elm) { | |
if (elm.nodeType === 1) { | |
if (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus')) { | |
return true; | |
} | |
if (elm.getAttribute('data-mce-type') === 'bookmark') { | |
return true; | |
} | |
} | |
return false; | |
} | |
if (editor.settings.elementpath !== false) { | |
self.on('select', function (e) { | |
editor.focus(); | |
editor.selection.select(this.row()[e.index].element); | |
editor.nodeChanged(); | |
}); | |
editor.on('nodeChange', function (e) { | |
var outParents = [], parents = e.parents, i = parents.length; | |
while (i--) { | |
if (parents[i].nodeType == 1 && !isHidden(parents[i])) { | |
var args = editor.fire('ResolveName', { | |
name: parents[i].nodeName.toLowerCase(), | |
target: parents[i] | |
}); | |
if (!args.isDefaultPrevented()) { | |
outParents.push({ name: args.name, element: parents[i] }); | |
} | |
if (args.isPropagationStopped()) { | |
break; | |
} | |
} | |
} | |
self.row(outParents); | |
}); | |
} | |
return self._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* FormItem.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class is a container created by the form element with | |
* a label and control item. | |
* | |
* @class tinymce.ui.FormItem | |
* @extends tinymce.ui.Container | |
* @setting {String} label Label to display for the form item. | |
*/ | |
define( | |
'tinymce.ui.FormItem', | |
[ | |
"tinymce.ui.Container" | |
], | |
function (Container) { | |
"use strict"; | |
return Container.extend({ | |
Defaults: { | |
layout: 'flex', | |
align: 'center', | |
defaults: { | |
flex: 1 | |
} | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, layout = self._layout, prefix = self.classPrefix; | |
self.classes.add('formitem'); | |
layout.preRender(self); | |
return ( | |
'<div id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1">' + | |
(self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' + | |
self.settings.title + '</div>') : '') + | |
'<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' + | |
(self.settings.html || '') + layout.renderHtml(self) + | |
'</div>' + | |
'</div>' | |
); | |
} | |
}); | |
} | |
); | |
/** | |
* Form.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class creates a form container. A form container has the ability | |
* to automatically wrap items in tinymce.ui.FormItem instances. | |
* | |
* Each FormItem instance is a container for the label and the item. | |
* | |
* @example | |
* tinymce.core.ui.Factory.create({ | |
* type: 'form', | |
* items: [ | |
* {type: 'textbox', label: 'My text box'} | |
* ] | |
* }).renderTo(document.body); | |
* | |
* @class tinymce.ui.Form | |
* @extends tinymce.ui.Container | |
*/ | |
define( | |
'tinymce.ui.Form', | |
[ | |
"tinymce.ui.Container", | |
"tinymce.ui.FormItem", | |
"tinymce.core.util.Tools" | |
], | |
function (Container, FormItem, Tools) { | |
"use strict"; | |
return Container.extend({ | |
Defaults: { | |
containerCls: 'form', | |
layout: 'flex', | |
direction: 'column', | |
align: 'stretch', | |
flex: 1, | |
padding: 15, | |
labelGap: 30, | |
spacing: 10, | |
callbacks: { | |
submit: function () { | |
this.submit(); | |
} | |
} | |
}, | |
/** | |
* This method gets invoked before the control is rendered. | |
* | |
* @method preRender | |
*/ | |
preRender: function () { | |
var self = this, items = self.items(); | |
if (!self.settings.formItemDefaults) { | |
self.settings.formItemDefaults = { | |
layout: 'flex', | |
autoResize: "overflow", | |
defaults: { flex: 1 } | |
}; | |
} | |
// Wrap any labeled items in FormItems | |
items.each(function (ctrl) { | |
var formItem, label = ctrl.settings.label; | |
if (label) { | |
formItem = new FormItem(Tools.extend({ | |
items: { | |
type: 'label', | |
id: ctrl._id + '-l', | |
text: label, | |
flex: 0, | |
forId: ctrl._id, | |
disabled: ctrl.disabled() | |
} | |
}, self.settings.formItemDefaults)); | |
formItem.type = 'formitem'; | |
ctrl.aria('labelledby', ctrl._id + '-l'); | |
if (typeof ctrl.settings.flex == "undefined") { | |
ctrl.settings.flex = 1; | |
} | |
self.replace(ctrl, formItem); | |
formItem.add(ctrl); | |
} | |
}); | |
}, | |
/** | |
* Fires a submit event with the serialized form. | |
* | |
* @method submit | |
* @return {Object} Event arguments object. | |
*/ | |
submit: function () { | |
return this.fire('submit', { data: this.toJSON() }); | |
}, | |
/** | |
* Post render method. Called after the control has been rendered to the target. | |
* | |
* @method postRender | |
* @return {tinymce.ui.ComboBox} Current combobox instance. | |
*/ | |
postRender: function () { | |
var self = this; | |
self._super(); | |
self.fromJSON(self.settings.data); | |
}, | |
bindStates: function () { | |
var self = this; | |
self._super(); | |
function recalcLabels() { | |
var maxLabelWidth = 0, labels = [], i, labelGap, items; | |
if (self.settings.labelGapCalc === false) { | |
return; | |
} | |
if (self.settings.labelGapCalc == "children") { | |
items = self.find('formitem'); | |
} else { | |
items = self.items(); | |
} | |
items.filter('formitem').each(function (item) { | |
var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth; | |
maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth; | |
labels.push(labelCtrl); | |
}); | |
labelGap = self.settings.labelGap || 0; | |
i = labels.length; | |
while (i--) { | |
labels[i].settings.minWidth = maxLabelWidth + labelGap; | |
} | |
} | |
self.on('show', recalcLabels); | |
recalcLabels(); | |
} | |
}); | |
} | |
); | |
/** | |
* FieldSet.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class creates fieldset containers. | |
* | |
* @-x-less FieldSet.less | |
* @class tinymce.ui.FieldSet | |
* @extends tinymce.ui.Form | |
*/ | |
define( | |
'tinymce.ui.FieldSet', | |
[ | |
"tinymce.ui.Form" | |
], | |
function (Form) { | |
"use strict"; | |
return Form.extend({ | |
Defaults: { | |
containerCls: 'fieldset', | |
layout: 'flex', | |
direction: 'column', | |
align: 'stretch', | |
flex: 1, | |
padding: "25 15 5 15", | |
labelGap: 30, | |
spacing: 10, | |
border: 1 | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, layout = self._layout, prefix = self.classPrefix; | |
self.preRender(); | |
layout.preRender(self); | |
return ( | |
'<fieldset id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1">' + | |
(self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' + | |
self.settings.title + '</legend>') : '') + | |
'<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' + | |
(self.settings.html || '') + layout.renderHtml(self) + | |
'</div>' + | |
'</fieldset>' | |
); | |
} | |
}); | |
} | |
); | |
defineGlobal("global!Date", Date); | |
defineGlobal("global!Math", Math); | |
define( | |
'ephox.katamari.api.Id', | |
[ | |
'global!Date', | |
'global!Math', | |
'global!String' | |
], | |
function (Date, Math, String) { | |
/** | |
* Generate a unique identifier. | |
* | |
* The unique portion of the identifier only contains an underscore | |
* and digits, so that it may safely be used within HTML attributes. | |
* | |
* The chance of generating a non-unique identifier has been minimized | |
* by combining the current time, a random number and a one-up counter. | |
* | |
* generate :: String -> String | |
*/ | |
var unique = 0; | |
var generate = function (prefix) { | |
var date = new Date(); | |
var time = date.getTime(); | |
var random = Math.floor(Math.random() * 1000000000); | |
unique++; | |
return prefix + '_' + random + unique + String(time); | |
}; | |
return { | |
generate: generate | |
}; | |
} | |
); | |
define("global!console", [], function () { if (typeof console === "undefined") console = { log: function () {} }; return console; }); | |
define( | |
'ephox.sugar.api.node.Element', | |
[ | |
'ephox.katamari.api.Fun', | |
'ephox.katamari.api.Option', | |
'global!Error', | |
'global!console', | |
'global!document' | |
], | |
function (Fun, Option, Error, console, document) { | |
var fromHtml = function (html, scope) { | |
var doc = scope || document; | |
var div = doc.createElement('div'); | |
div.innerHTML = html; | |
if (!div.hasChildNodes() || div.childNodes.length > 1) { | |
console.error('HTML does not have a single root node', html); | |
throw 'HTML must have a single root node'; | |
} | |
return fromDom(div.childNodes[0]); | |
}; | |
var fromTag = function (tag, scope) { | |
var doc = scope || document; | |
var node = doc.createElement(tag); | |
return fromDom(node); | |
}; | |
var fromText = function (text, scope) { | |
var doc = scope || document; | |
var node = doc.createTextNode(text); | |
return fromDom(node); | |
}; | |
var fromDom = function (node) { | |
if (node === null || node === undefined) throw new Error('Node cannot be null or undefined'); | |
return { | |
dom: Fun.constant(node) | |
}; | |
}; | |
var fromPoint = function (doc, x, y) { | |
return Option.from(doc.dom().elementFromPoint(x, y)).map(fromDom); | |
}; | |
return { | |
fromHtml: fromHtml, | |
fromTag: fromTag, | |
fromText: fromText, | |
fromDom: fromDom, | |
fromPoint: fromPoint | |
}; | |
} | |
); | |
define( | |
'ephox.katamari.api.Thunk', | |
[ | |
], | |
function () { | |
var cached = function (f) { | |
var called = false; | |
var r; | |
return function() { | |
if (!called) { | |
called = true; | |
r = f.apply(null, arguments); | |
} | |
return r; | |
}; | |
}; | |
return { | |
cached: cached | |
}; | |
} | |
); | |
define( | |
'ephox.sugar.api.node.NodeTypes', | |
[ | |
], | |
function () { | |
return { | |
ATTRIBUTE: 2, | |
CDATA_SECTION: 4, | |
COMMENT: 8, | |
DOCUMENT: 9, | |
DOCUMENT_TYPE: 10, | |
DOCUMENT_FRAGMENT: 11, | |
ELEMENT: 1, | |
TEXT: 3, | |
PROCESSING_INSTRUCTION: 7, | |
ENTITY_REFERENCE: 5, | |
ENTITY: 6, | |
NOTATION: 12 | |
}; | |
} | |
); | |
define( | |
'ephox.sugar.api.node.Node', | |
[ | |
'ephox.sugar.api.node.NodeTypes' | |
], | |
function (NodeTypes) { | |
var name = function (element) { | |
var r = element.dom().nodeName; | |
return r.toLowerCase(); | |
}; | |
var type = function (element) { | |
return element.dom().nodeType; | |
}; | |
var value = function (element) { | |
return element.dom().nodeValue; | |
}; | |
var isType = function (t) { | |
return function (element) { | |
return type(element) === t; | |
}; | |
}; | |
var isComment = function (element) { | |
return type(element) === NodeTypes.COMMENT || name(element) === '#comment'; | |
}; | |
var isElement = isType(NodeTypes.ELEMENT); | |
var isText = isType(NodeTypes.TEXT); | |
var isDocument = isType(NodeTypes.DOCUMENT); | |
return { | |
name: name, | |
type: type, | |
value: value, | |
isElement: isElement, | |
isText: isText, | |
isDocument: isDocument, | |
isComment: isComment | |
}; | |
} | |
); | |
define( | |
'ephox.sugar.api.node.Body', | |
[ | |
'ephox.katamari.api.Thunk', | |
'ephox.sugar.api.node.Element', | |
'ephox.sugar.api.node.Node', | |
'global!document' | |
], | |
function (Thunk, Element, Node, document) { | |
// Node.contains() is very, very, very good performance | |
// http://jsperf.com/closest-vs-contains/5 | |
var inBody = function (element) { | |
// Technically this is only required on IE, where contains() returns false for text nodes. | |
// But it's cheap enough to run everywhere and Sugar doesn't have platform detection (yet). | |
var dom = Node.isText(element) ? element.dom().parentNode : element.dom(); | |
// use ownerDocument.body to ensure this works inside iframes. | |
// Normally contains is bad because an element "contains" itself, but here we want that. | |
return dom !== undefined && dom !== null && dom.ownerDocument.body.contains(dom); | |
}; | |
var body = Thunk.cached(function() { | |
return getBody(Element.fromDom(document)); | |
}); | |
var getBody = function (doc) { | |
var body = doc.dom().body; | |
if (body === null || body === undefined) throw 'Body is not available yet'; | |
return Element.fromDom(body); | |
}; | |
return { | |
body: body, | |
getBody: getBody, | |
inBody: inBody | |
}; | |
} | |
); | |
define( | |
'ephox.katamari.api.Type', | |
[ | |
'global!Array', | |
'global!String' | |
], | |
function (Array, String) { | |
var typeOf = function(x) { | |
if (x === null) return 'null'; | |
var t = typeof x; | |
if (t === 'object' && Array.prototype.isPrototypeOf(x)) return 'array'; | |
if (t === 'object' && String.prototype.isPrototypeOf(x)) return 'string'; | |
return t; | |
}; | |
var isType = function (type) { | |
return function (value) { | |
return typeOf(value) === type; | |
}; | |
}; | |
return { | |
isString: isType('string'), | |
isObject: isType('object'), | |
isArray: isType('array'), | |
isNull: isType('null'), | |
isBoolean: isType('boolean'), | |
isUndefined: isType('undefined'), | |
isFunction: isType('function'), | |
isNumber: isType('number') | |
}; | |
} | |
); | |
define( | |
'ephox.katamari.data.Immutable', | |
[ | |
'ephox.katamari.api.Arr', | |
'ephox.katamari.api.Fun', | |
'global!Array', | |
'global!Error' | |
], | |
function (Arr, Fun, Array, Error) { | |
return function () { | |
var fields = arguments; | |
return function(/* values */) { | |
// Don't use array slice(arguments), makes the whole function unoptimisable on Chrome | |
var values = new Array(arguments.length); | |
for (var i = 0; i < values.length; i++) values[i] = arguments[i]; | |
if (fields.length !== values.length) | |
throw new Error('Wrong number of arguments to struct. Expected "[' + fields.length + ']", got ' + values.length + ' arguments'); | |
var struct = {}; | |
Arr.each(fields, function (name, i) { | |
struct[name] = Fun.constant(values[i]); | |
}); | |
return struct; | |
}; | |
}; | |
} | |
); | |
define( | |
'ephox.katamari.api.Obj', | |
[ | |
'ephox.katamari.api.Option', | |
'global!Object' | |
], | |
function (Option, Object) { | |
// There are many variations of Object iteration that are faster than the 'for-in' style: | |
// http://jsperf.com/object-keys-iteration/107 | |
// | |
// Use the native keys if it is available (IE9+), otherwise fall back to manually filtering | |
var keys = (function () { | |
var fastKeys = Object.keys; | |
// This technically means that 'each' and 'find' on IE8 iterate through the object twice. | |
// This code doesn't run on IE8 much, so it's an acceptable tradeoff. | |
// If it becomes a problem we can always duplicate the feature detection inside each and find as well. | |
var slowKeys = function (o) { | |
var r = []; | |
for (var i in o) { | |
if (o.hasOwnProperty(i)) { | |
r.push(i); | |
} | |
} | |
return r; | |
}; | |
return fastKeys === undefined ? slowKeys : fastKeys; | |
})(); | |
var each = function (obj, f) { | |
var props = keys(obj); | |
for (var k = 0, len = props.length; k < len; k++) { | |
var i = props[k]; | |
var x = obj[i]; | |
f(x, i, obj); | |
} | |
}; | |
/** objectMap :: (JsObj(k, v), (v, k, JsObj(k, v) -> x)) -> JsObj(k, x) */ | |
var objectMap = function (obj, f) { | |
return tupleMap(obj, function (x, i, obj) { | |
return { | |
k: i, | |
v: f(x, i, obj) | |
}; | |
}); | |
}; | |
/** tupleMap :: (JsObj(k, v), (v, k, JsObj(k, v) -> { k: x, v: y })) -> JsObj(x, y) */ | |
var tupleMap = function (obj, f) { | |
var r = {}; | |
each(obj, function (x, i) { | |
var tuple = f(x, i, obj); | |
r[tuple.k] = tuple.v; | |
}); | |
return r; | |
}; | |
/** bifilter :: (JsObj(k, v), (v, k -> Bool)) -> { t: JsObj(k, v), f: JsObj(k, v) } */ | |
var bifilter = function (obj, pred) { | |
var t = {}; | |
var f = {}; | |
each(obj, function(x, i) { | |
var branch = pred(x, i) ? t : f; | |
branch[i] = x; | |
}); | |
return { | |
t: t, | |
f: f | |
}; | |
}; | |
/** mapToArray :: (JsObj(k, v), (v, k -> a)) -> [a] */ | |
var mapToArray = function (obj, f) { | |
var r = []; | |
each(obj, function(value, name) { | |
r.push(f(value, name)); | |
}); | |
return r; | |
}; | |
/** find :: (JsObj(k, v), (v, k, JsObj(k, v) -> Bool)) -> Option v */ | |
var find = function (obj, pred) { | |
var props = keys(obj); | |
for (var k = 0, len = props.length; k < len; k++) { | |
var i = props[k]; | |
var x = obj[i]; | |
if (pred(x, i, obj)) { | |
return Option.some(x); | |
} | |
} | |
return Option.none(); | |
}; | |
/** values :: JsObj(k, v) -> [v] */ | |
var values = function (obj) { | |
return mapToArray(obj, function (v) { | |
return v; | |
}); | |
}; | |
var size = function (obj) { | |
return values(obj).length; | |
}; | |
return { | |
bifilter: bifilter, | |
each: each, | |
map: objectMap, | |
mapToArray: mapToArray, | |
tupleMap: tupleMap, | |
find: find, | |
keys: keys, | |
values: values, | |
size: size | |
}; | |
} | |
); | |
define( | |
'ephox.katamari.util.BagUtils', | |
[ | |
'ephox.katamari.api.Arr', | |
'ephox.katamari.api.Type', | |
'global!Error' | |
], | |
function (Arr, Type, Error) { | |
var sort = function (arr) { | |
return arr.slice(0).sort(); | |
}; | |
var reqMessage = function (required, keys) { | |
throw new Error('All required keys (' + sort(required).join(', ') + ') were not specified. Specified keys were: ' + sort(keys).join(', ') + '.'); | |
}; | |
var unsuppMessage = function (unsupported) { | |
throw new Error('Unsupported keys for object: ' + sort(unsupported).join(', ')); | |
}; | |
var validateStrArr = function (label, array) { | |
if (!Type.isArray(array)) throw new Error('The ' + label + ' fields must be an array. Was: ' + array + '.'); | |
Arr.each(array, function (a) { | |
if (!Type.isString(a)) throw new Error('The value ' + a + ' in the ' + label + ' fields was not a string.'); | |
}); | |
}; | |
var invalidTypeMessage = function (incorrect, type) { | |
throw new Error('All values need to be of type: ' + type + '. Keys (' + sort(incorrect).join(', ') + ') were not.'); | |
}; | |
var checkDupes = function (everything) { | |
var sorted = sort(everything); | |
var dupe = Arr.find(sorted, function (s, i) { | |
return i < sorted.length -1 && s === sorted[i + 1]; | |
}); | |
dupe.each(function (d) { | |
throw new Error('The field: ' + d + ' occurs more than once in the combined fields: [' + sorted.join(', ') + '].'); | |
}); | |
}; | |
return { | |
sort: sort, | |
reqMessage: reqMessage, | |
unsuppMessage: unsuppMessage, | |
validateStrArr: validateStrArr, | |
invalidTypeMessage: invalidTypeMessage, | |
checkDupes: checkDupes | |
}; | |
} | |
); | |
define( | |
'ephox.katamari.data.MixedBag', | |
[ | |
'ephox.katamari.api.Arr', | |
'ephox.katamari.api.Fun', | |
'ephox.katamari.api.Obj', | |
'ephox.katamari.api.Option', | |
'ephox.katamari.util.BagUtils', | |
'global!Error', | |
'global!Object' | |
], | |
function (Arr, Fun, Obj, Option, BagUtils, Error, Object) { | |
return function (required, optional) { | |
var everything = required.concat(optional); | |
if (everything.length === 0) throw new Error('You must specify at least one required or optional field.'); | |
BagUtils.validateStrArr('required', required); | |
BagUtils.validateStrArr('optional', optional); | |
BagUtils.checkDupes(everything); | |
return function (obj) { | |
var keys = Obj.keys(obj); | |
// Ensure all required keys are present. | |
var allReqd = Arr.forall(required, function (req) { | |
return Arr.contains(keys, req); | |
}); | |
if (! allReqd) BagUtils.reqMessage(required, keys); | |
var unsupported = Arr.filter(keys, function (key) { | |
return !Arr.contains(everything, key); | |
}); | |
if (unsupported.length > 0) BagUtils.unsuppMessage(unsupported); | |
var r = {}; | |
Arr.each(required, function (req) { | |
r[req] = Fun.constant(obj[req]); | |
}); | |
Arr.each(optional, function (opt) { | |
r[opt] = Fun.constant(Object.prototype.hasOwnProperty.call(obj, opt) ? Option.some(obj[opt]): Option.none()); | |
}); | |
return r; | |
}; | |
}; | |
} | |
); | |
define( | |
'ephox.katamari.api.Struct', | |
[ | |
'ephox.katamari.data.Immutable', | |
'ephox.katamari.data.MixedBag' | |
], | |
function (Immutable, MixedBag) { | |
return { | |
immutable: Immutable, | |
immutableBag: MixedBag | |
}; | |
} | |
); | |
define( | |
'ephox.sugar.alien.Recurse', | |
[ | |
], | |
function () { | |
/** | |
* Applies f repeatedly until it completes (by returning Option.none()). | |
* | |
* Normally would just use recursion, but JavaScript lacks tail call optimisation. | |
* | |
* This is what recursion looks like when manually unravelled :) | |
*/ | |
var toArray = function (target, f) { | |
var r = []; | |
var recurse = function (e) { | |
r.push(e); | |
return f(e); | |
}; | |
var cur = f(target); | |
do { | |
cur = cur.bind(recurse); | |
} while (cur.isSome()); | |
return r; | |
}; | |
return { | |
toArray: toArray | |
}; | |
} | |
); | |
define( | |
'ephox.katamari.api.Global', | |
[ | |
], | |
function () { | |
// Use window object as the global if it's available since CSP will block script evals | |
var global = typeof window !== 'undefined' ? window : Function('return this;')(); | |
return global; | |
} | |
); | |
define( | |
'ephox.katamari.api.Resolve', | |
[ | |
'ephox.katamari.api.Global' | |
], | |
function (Global) { | |
/** path :: ([String], JsObj?) -> JsObj */ | |
var path = function (parts, scope) { | |
var o = scope !== undefined ? scope : Global; | |
for (var i = 0; i < parts.length && o !== undefined && o !== null; ++i) | |
o = o[parts[i]]; | |
return o; | |
}; | |
/** resolve :: (String, JsObj?) -> JsObj */ | |
var resolve = function (p, scope) { | |
var parts = p.split('.'); | |
return path(parts, scope); | |
}; | |
/** step :: (JsObj, String) -> JsObj */ | |
var step = function (o, part) { | |
if (o[part] === undefined || o[part] === null) | |
o[part] = {}; | |
return o[part]; | |
}; | |
/** forge :: ([String], JsObj?) -> JsObj */ | |
var forge = function (parts, target) { | |
var o = target !== undefined ? target : Global; | |
for (var i = 0; i < parts.length; ++i) | |
o = step(o, parts[i]); | |
return o; | |
}; | |
/** namespace :: (String, JsObj?) -> JsObj */ | |
var namespace = function (name, target) { | |
var parts = name.split('.'); | |
return forge(parts, target); | |
}; | |
return { | |
path: path, | |
resolve: resolve, | |
forge: forge, | |
namespace: namespace | |
}; | |
} | |
); | |
define( | |
'ephox.sand.util.Global', | |
[ | |
'ephox.katamari.api.Resolve' | |
], | |
function (Resolve) { | |
var unsafe = function (name, scope) { | |
return Resolve.resolve(name, scope); | |
}; | |
var getOrDie = function (name, scope) { | |
var actual = unsafe(name, scope); | |
if (actual === undefined) throw name + ' not available on this browser'; | |
return actual; | |
}; | |
return { | |
getOrDie: getOrDie | |
}; | |
} | |
); | |
define( | |
'ephox.sand.api.Node', | |
[ | |
'ephox.sand.util.Global' | |
], | |
function (Global) { | |
/* | |
* MDN says (yes) for IE, but it's undefined on IE8 | |
*/ | |
var node = function () { | |
var f = Global.getOrDie('Node'); | |
return f; | |
}; | |
/* | |
* Most of numerosity doesn't alter the methods on the object. | |
* We're making an exception for Node, because bitwise and is so easy to get wrong. | |
* | |
* Might be nice to ADT this at some point instead of having individual methods. | |
*/ | |
var compareDocumentPosition = function (a, b, match) { | |
// Returns: 0 if e1 and e2 are the same node, or a bitmask comparing the positions | |
// of nodes e1 and e2 in their documents. See the URL below for bitmask interpretation | |
// https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition | |
return (a.compareDocumentPosition(b) & match) !== 0; | |
}; | |
var documentPositionPreceding = function (a, b) { | |
return compareDocumentPosition(a, b, node().DOCUMENT_POSITION_PRECEDING); | |
}; | |
var documentPositionContainedBy = function (a, b) { | |
return compareDocumentPosition(a, b, node().DOCUMENT_POSITION_CONTAINED_BY); | |
}; | |
return { | |
documentPositionPreceding: documentPositionPreceding, | |
documentPositionContainedBy: documentPositionContainedBy | |
}; | |
} | |
); | |
defineGlobal("global!Number", Number); | |
define( | |
'ephox.sand.detect.Version', | |
[ | |
'ephox.katamari.api.Arr', | |
'global!Number', | |
'global!String' | |
], | |
function (Arr, Number, String) { | |
var firstMatch = function (regexes, s) { | |
for (var i = 0; i < regexes.length; i++) { | |
var x = regexes[i]; | |
if (x.test(s)) return x; | |
} | |
return undefined; | |
}; | |
var find = function (regexes, agent) { | |
var r = firstMatch(regexes, agent); | |
if (!r) return { major : 0, minor : 0 }; | |
var group = function(i) { | |
return Number(agent.replace(r, '$' + i)); | |
}; | |
return nu(group(1), group(2)); | |
}; | |
var detect = function (versionRegexes, agent) { | |
var cleanedAgent = String(agent).toLowerCase(); | |
if (versionRegexes.length === 0) return unknown(); | |
return find(versionRegexes, cleanedAgent); | |
}; | |
var unknown = function () { | |
return nu(0, 0); | |
}; | |
var nu = function (major, minor) { | |
return { major: major, minor: minor }; | |
}; | |
return { | |
nu: nu, | |
detect: detect, | |
unknown: unknown | |
}; | |
} | |
); | |
define( | |
'ephox.sand.core.Browser', | |
[ | |
'ephox.katamari.api.Fun', | |
'ephox.sand.detect.Version' | |
], | |
function (Fun, Version) { | |
var edge = 'Edge'; | |
var chrome = 'Chrome'; | |
var ie = 'IE'; | |
var opera = 'Opera'; | |
var firefox = 'Firefox'; | |
var safari = 'Safari'; | |
var isBrowser = function (name, current) { | |
return function () { | |
return current === name; | |
}; | |
}; | |
var unknown = function () { | |
return nu({ | |
current: undefined, | |
version: Version.unknown() | |
}); | |
}; | |
var nu = function (info) { | |
var current = info.current; | |
var version = info.version; | |
return { | |
current: current, | |
version: version, | |
// INVESTIGATE: Rename to Edge ? | |
isEdge: isBrowser(edge, current), | |
isChrome: isBrowser(chrome, current), | |
// NOTE: isIe just looks too weird | |
isIE: isBrowser(ie, current), | |
isOpera: isBrowser(opera, current), | |
isFirefox: isBrowser(firefox, current), | |
isSafari: isBrowser(safari, current) | |
}; | |
}; | |
return { | |
unknown: unknown, | |
nu: nu, | |
edge: Fun.constant(edge), | |
chrome: Fun.constant(chrome), | |
ie: Fun.constant(ie), | |
opera: Fun.constant(opera), | |
firefox: Fun.constant(firefox), | |
safari: Fun.constant(safari) | |
}; | |
} | |
); | |
define( | |
'ephox.sand.core.OperatingSystem', | |
[ | |
'ephox.katamari.api.Fun', | |
'ephox.sand.detect.Version' | |
], | |
function (Fun, Version) { | |
var windows = 'Windows'; | |
var ios = 'iOS'; | |
var android = 'Android'; | |
var linux = 'Linux'; | |
var osx = 'OSX'; | |
var solaris = 'Solaris'; | |
var freebsd = 'FreeBSD'; | |
// Though there is a bit of dupe with this and Browser, trying to | |
// reuse code makes it much harder to follow and change. | |
var isOS = function (name, current) { | |
return function () { | |
return current === name; | |
}; | |
}; | |
var unknown = function () { | |
return nu({ | |
current: undefined, | |
version: Version.unknown() | |
}); | |
}; | |
var nu = function (info) { | |
var current = info.current; | |
var version = info.version; | |
return { | |
current: current, | |
version: version, | |
isWindows: isOS(windows, current), | |
// TODO: Fix capitalisation | |
isiOS: isOS(ios, current), | |
isAndroid: isOS(android, current), | |
isOSX: isOS(osx, current), | |
isLinux: isOS(linux, current), | |
isSolaris: isOS(solaris, current), | |
isFreeBSD: isOS(freebsd, current) | |
}; | |
}; | |
return { | |
unknown: unknown, | |
nu: nu, | |
windows: Fun.constant(windows), | |
ios: Fun.constant(ios), | |
android: Fun.constant(android), | |
linux: Fun.constant(linux), | |
osx: Fun.constant(osx), | |
solaris: Fun.constant(solaris), | |
freebsd: Fun.constant(freebsd) | |
}; | |
} | |
); | |
define( | |
'ephox.sand.detect.DeviceType', | |
[ | |
'ephox.katamari.api.Fun' | |
], | |
function (Fun) { | |
return function (os, browser, userAgent) { | |
var isiPad = os.isiOS() && /ipad/i.test(userAgent) === true; | |
var isiPhone = os.isiOS() && !isiPad; | |
var isAndroid3 = os.isAndroid() && os.version.major === 3; | |
var isAndroid4 = os.isAndroid() && os.version.major === 4; | |
var isTablet = isiPad || isAndroid3 || ( isAndroid4 && /mobile/i.test(userAgent) === true ); | |
var isTouch = os.isiOS() || os.isAndroid(); | |
var isPhone = isTouch && !isTablet; | |
var iOSwebview = browser.isSafari() && os.isiOS() && /safari/i.test(userAgent) === false; | |
return { | |
isiPad : Fun.constant(isiPad), | |
isiPhone: Fun.constant(isiPhone), | |
isTablet: Fun.constant(isTablet), | |
isPhone: Fun.constant(isPhone), | |
isTouch: Fun.constant(isTouch), | |
isAndroid: os.isAndroid, | |
isiOS: os.isiOS, | |
isWebView: Fun.constant(iOSwebview) | |
}; | |
}; | |
} | |
); | |
define( | |
'ephox.sand.detect.UaString', | |
[ | |
'ephox.katamari.api.Arr', | |
'ephox.sand.detect.Version', | |
'global!String' | |
], | |
function (Arr, Version, String) { | |
var detect = function (candidates, userAgent) { | |
var agent = String(userAgent).toLowerCase(); | |
return Arr.find(candidates, function (candidate) { | |
return candidate.search(agent); | |
}); | |
}; | |
// They (browser and os) are the same at the moment, but they might | |
// not stay that way. | |
var detectBrowser = function (browsers, userAgent) { | |
return detect(browsers, userAgent).map(function (browser) { | |
var version = Version.detect(browser.versionRegexes, userAgent); | |
return { | |
current: browser.name, | |
version: version | |
}; | |
}); | |
}; | |
var detectOs = function (oses, userAgent) { | |
return detect(oses, userAgent).map(function (os) { | |
var version = Version.detect(os.versionRegexes, userAgent); | |
return { | |
current: os.name, | |
version: version | |
}; | |
}); | |
}; | |
return { | |
detectBrowser: detectBrowser, | |
detectOs: detectOs | |
}; | |
} | |
); | |
define( | |
'ephox.katamari.str.StrAppend', | |
[ | |
], | |
function () { | |
var addToStart = function (str, prefix) { | |
return prefix + str; | |
}; | |
var addToEnd = function (str, suffix) { | |
return str + suffix; | |
}; | |
var removeFromStart = function (str, numChars) { | |
return str.substring(numChars); | |
}; | |
var removeFromEnd = function (str, numChars) { | |
return str.substring(0, str.length - numChars); | |
}; | |
return { | |
addToStart: addToStart, | |
addToEnd: addToEnd, | |
removeFromStart: removeFromStart, | |
removeFromEnd: removeFromEnd | |
}; | |
} | |
); | |
define( | |
'ephox.katamari.str.StringParts', | |
[ | |
'ephox.katamari.api.Option', | |
'global!Error' | |
], | |
function (Option, Error) { | |
/** Return the first 'count' letters from 'str'. | |
- * e.g. first("abcde", 2) === "ab" | |
- */ | |
var first = function(str, count) { | |
return str.substr(0, count); | |
}; | |
/** Return the last 'count' letters from 'str'. | |
* e.g. last("abcde", 2) === "de" | |
*/ | |
var last = function(str, count) { | |
return str.substr(str.length - count, str.length); | |
}; | |
var head = function(str) { | |
return str === '' ? Option.none() : Option.some(str.substr(0, 1)); | |
}; | |
var tail = function(str) { | |
return str === '' ? Option.none() : Option.some(str.substring(1)); | |
}; | |
return { | |
first: first, | |
last: last, | |
head: head, | |
tail: tail | |
}; | |
} | |
); | |
define( | |
'ephox.katamari.api.Strings', | |
[ | |
'ephox.katamari.str.StrAppend', | |
'ephox.katamari.str.StringParts', | |
'global!Error' | |
], | |
function (StrAppend, StringParts, Error) { | |
var checkRange = function(str, substr, start) { | |
if (substr === '') return true; | |
if (str.length < substr.length) return false; | |
var x = str.substr(start, start + substr.length); | |
return x === substr; | |
}; | |
/** Given a string and object, perform template-replacements on the string, as specified by the object. | |
* Any template fields of the form ${name} are replaced by the string or number specified as obj["name"] | |
* Based on Douglas Crockford's 'supplant' method for template-replace of strings. Uses different template format. | |
*/ | |
var supplant = function(str, obj) { | |
var isStringOrNumber = function(a) { | |
var t = typeof a; | |
return t === 'string' || t === 'number'; | |
}; | |
return str.replace(/\${([^{}]*)}/g, | |
function (a, b) { | |
var value = obj[b]; | |
return isStringOrNumber(value) ? value : a; | |
} | |
); | |
}; | |
var removeLeading = function (str, prefix) { | |
return startsWith(str, prefix) ? StrAppend.removeFromStart(str, prefix.length) : str; | |
}; | |
var removeTrailing = function (str, prefix) { | |
return endsWith(str, prefix) ? StrAppend.removeFromEnd(str, prefix.length) : str; | |
}; | |
var ensureLeading = function (str, prefix) { | |
return startsWith(str, prefix) ? str : StrAppend.addToStart(str, prefix); | |
}; | |
var ensureTrailing = function (str, prefix) { | |
return endsWith(str, prefix) ? str : StrAppend.addToEnd(str, prefix); | |
}; | |
var contains = function(str, substr) { | |
return str.indexOf(substr) !== -1; | |
}; | |
var capitalize = function(str) { | |
return StringParts.head(str).bind(function (head) { | |
return StringParts.tail(str).map(function (tail) { | |
return head.toUpperCase() + tail; | |
}); | |
}).getOr(str); | |
}; | |
/** Does 'str' start with 'prefix'? | |
* Note: all strings start with the empty string. | |
* More formally, for all strings x, startsWith(x, ""). | |
* This is so that for all strings x and y, startsWith(y + x, y) | |
*/ | |
var startsWith = function(str, prefix) { | |
return checkRange(str, prefix, 0); | |
}; | |
/** Does 'str' end with 'suffix'? | |
* Note: all strings end with the empty string. | |
* More formally, for all strings x, endsWith(x, ""). | |
* This is so that for all strings x and y, endsWith(x + y, y) | |
*/ | |
var endsWith = function(str, suffix) { | |
return checkRange(str, suffix, str.length - suffix.length); | |
}; | |
/** removes all leading and trailing spaces */ | |
var trim = function(str) { | |
return str.replace(/^\s+|\s+$/g, ''); | |
}; | |
var lTrim = function(str) { | |
return str.replace(/^\s+/g, ''); | |
}; | |
var rTrim = function(str) { | |
return str.replace(/\s+$/g, ''); | |
}; | |
return { | |
supplant: supplant, | |
startsWith: startsWith, | |
removeLeading: removeLeading, | |
removeTrailing: removeTrailing, | |
ensureLeading: ensureLeading, | |
ensureTrailing: ensureTrailing, | |
endsWith: endsWith, | |
contains: contains, | |
trim: trim, | |
lTrim: lTrim, | |
rTrim: rTrim, | |
capitalize: capitalize | |
}; | |
} | |
); | |
define( | |
'ephox.sand.info.PlatformInfo', | |
[ | |
'ephox.katamari.api.Fun', | |
'ephox.katamari.api.Strings' | |
], | |
function (Fun, Strings) { | |
var normalVersionRegex = /.*?version\/\ ?([0-9]+)\.([0-9]+).*/; | |
var checkContains = function (target) { | |
return function (uastring) { | |
return Strings.contains(uastring, target); | |
}; | |
}; | |
var browsers = [ | |
{ | |
name : 'Edge', | |
versionRegexes: [/.*?edge\/ ?([0-9]+)\.([0-9]+)$/], | |
search: function (uastring) { | |
var monstrosity = Strings.contains(uastring, 'edge/') && Strings.contains(uastring, 'chrome') && Strings.contains(uastring, 'safari') && Strings.contains(uastring, 'applewebkit'); | |
return monstrosity; | |
} | |
}, | |
{ | |
name : 'Chrome', | |
versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/, normalVersionRegex], | |
search : function (uastring) { | |
return Strings.contains(uastring, 'chrome') && !Strings.contains(uastring, 'chromeframe'); | |
} | |
}, | |
{ | |
name : 'IE', | |
versionRegexes: [/.*?msie\ ?([0-9]+)\.([0-9]+).*/, /.*?rv:([0-9]+)\.([0-9]+).*/], | |
search: function (uastring) { | |
return Strings.contains(uastring, 'msie') || Strings.contains(uastring, 'trident'); | |
} | |
}, | |
// INVESTIGATE: Is this still the Opera user agent? | |
{ | |
name : 'Opera', | |
versionRegexes: [normalVersionRegex, /.*?opera\/([0-9]+)\.([0-9]+).*/], | |
search : checkContains('opera') | |
}, | |
{ | |
name : 'Firefox', | |
versionRegexes: [/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/], | |
search : checkContains('firefox') | |
}, | |
{ | |
name : 'Safari', | |
versionRegexes: [normalVersionRegex, /.*?cpu os ([0-9]+)_([0-9]+).*/], | |
search : function (uastring) { | |
return (Strings.contains(uastring, 'safari') || Strings.contains(uastring, 'mobile/')) && Strings.contains(uastring, 'applewebkit'); | |
} | |
} | |
]; | |
var oses = [ | |
{ | |
name : 'Windows', | |
search : checkContains('win'), | |
versionRegexes: [/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/] | |
}, | |
{ | |
name : 'iOS', | |
search : function (uastring) { | |
return Strings.contains(uastring, 'iphone') || Strings.contains(uastring, 'ipad'); | |
}, | |
versionRegexes: [/.*?version\/\ ?([0-9]+)\.([0-9]+).*/, /.*cpu os ([0-9]+)_([0-9]+).*/, /.*cpu iphone os ([0-9]+)_([0-9]+).*/] | |
}, | |
{ | |
name : 'Android', | |
search : checkContains('android'), | |
versionRegexes: [/.*?android\ ?([0-9]+)\.([0-9]+).*/] | |
}, | |
{ | |
name : 'OSX', | |
search : checkContains('os x'), | |
versionRegexes: [/.*?os\ x\ ?([0-9]+)_([0-9]+).*/] | |
}, | |
{ | |
name : 'Linux', | |
search : checkContains('linux'), | |
versionRegexes: [ ] | |
}, | |
{ name : 'Solaris', | |
search : checkContains('sunos'), | |
versionRegexes: [ ] | |
}, | |
{ | |
name : 'FreeBSD', | |
search : checkContains('freebsd'), | |
versionRegexes: [ ] | |
} | |
]; | |
return { | |
browsers: Fun.constant(browsers), | |
oses: Fun.constant(oses) | |
}; | |
} | |
); | |
define( | |
'ephox.sand.core.PlatformDetection', | |
[ | |
'ephox.sand.core.Browser', | |
'ephox.sand.core.OperatingSystem', | |
'ephox.sand.detect.DeviceType', | |
'ephox.sand.detect.UaString', | |
'ephox.sand.info.PlatformInfo' | |
], | |
function (Browser, OperatingSystem, DeviceType, UaString, PlatformInfo) { | |
var detect = function (userAgent) { | |
var browsers = PlatformInfo.browsers(); | |
var oses = PlatformInfo.oses(); | |
var browser = UaString.detectBrowser(browsers, userAgent).fold( | |
Browser.unknown, | |
Browser.nu | |
); | |
var os = UaString.detectOs(oses, userAgent).fold( | |
OperatingSystem.unknown, | |
OperatingSystem.nu | |
); | |
var deviceType = DeviceType(os, browser, userAgent); | |
return { | |
browser: browser, | |
os: os, | |
deviceType: deviceType | |
}; | |
}; | |
return { | |
detect: detect | |
}; | |
} | |
); | |
defineGlobal("global!navigator", navigator); | |
define( | |
'ephox.sand.api.PlatformDetection', | |
[ | |
'ephox.katamari.api.Thunk', | |
'ephox.sand.core.PlatformDetection', | |
'global!navigator' | |
], | |
function (Thunk, PlatformDetection, navigator) { | |
var detect = Thunk.cached(function () { | |
var userAgent = navigator.userAgent; | |
return PlatformDetection.detect(userAgent); | |
}); | |
return { | |
detect: detect | |
}; | |
} | |
); | |
define( | |
'ephox.sugar.api.search.Selectors', | |
[ | |
'ephox.katamari.api.Arr', | |
'ephox.katamari.api.Option', | |
'ephox.sugar.api.node.Element', | |
'ephox.sugar.api.node.NodeTypes', | |
'global!Error', | |
'global!document' | |
], | |
function (Arr, Option, Element, NodeTypes, Error, document) { | |
var ELEMENT = NodeTypes.ELEMENT; | |
var DOCUMENT = NodeTypes.DOCUMENT; | |
var is = function (element, selector) { | |
var elem = element.dom(); | |
if (elem.nodeType !== ELEMENT) return false; // documents have querySelector but not matches | |
// As of Chrome 34 / Safari 7.1 / FireFox 34, everyone except IE has the unprefixed function. | |
// Still check for the others, but do it last. | |
else if (elem.matches !== undefined) return elem.matches(selector); | |
else if (elem.msMatchesSelector !== undefined) return elem.msMatchesSelector(selector); | |
else if (elem.webkitMatchesSelector !== undefined) return elem.webkitMatchesSelector(selector); | |
else if (elem.mozMatchesSelector !== undefined) return elem.mozMatchesSelector(selector); | |
else throw new Error('Browser lacks native selectors'); // unfortunately we can't throw this on startup :( | |
}; | |
var bypassSelector = function (dom) { | |
// Only elements and documents support querySelector | |
return dom.nodeType !== ELEMENT && dom.nodeType !== DOCUMENT || | |
// IE fix for complex queries on empty nodes: http://jsfiddle.net/spyder/fv9ptr5L/ | |
dom.childElementCount === 0; | |
}; | |
var all = function (selector, scope) { | |
var base = scope === undefined ? document : scope.dom(); | |
return bypassSelector(base) ? [] : Arr.map(base.querySelectorAll(selector), Element.fromDom); | |
}; | |
var one = function (selector, scope) { | |
var base = scope === undefined ? document : scope.dom(); | |
return bypassSelector(base) ? Option.none() : Option.from(base.querySelector(selector)).map(Element.fromDom); | |
}; | |
return { | |
all: all, | |
is: is, | |
one: one | |
}; | |
} | |
); | |
define( | |
'ephox.sugar.api.dom.Compare', | |
[ | |
'ephox.katamari.api.Arr', | |
'ephox.katamari.api.Fun', | |
'ephox.sand.api.Node', | |
'ephox.sand.api.PlatformDetection', | |
'ephox.sugar.api.search.Selectors' | |
], | |
function (Arr, Fun, Node, PlatformDetection, Selectors) { | |
var eq = function (e1, e2) { | |
return e1.dom() === e2.dom(); | |
}; | |
var isEqualNode = function (e1, e2) { | |
return e1.dom().isEqualNode(e2.dom()); | |
}; | |
var member = function (element, elements) { | |
return Arr.exists(elements, Fun.curry(eq, element)); | |
}; | |
// DOM contains() method returns true if e1===e2, we define our contains() to return false (a node does not contain itself). | |
var regularContains = function (e1, e2) { | |
var d1 = e1.dom(), d2 = e2.dom(); | |
return d1 === d2 ? false : d1.contains(d2); | |
}; | |
var ieContains = function (e1, e2) { | |
// IE only implements the contains() method for Element nodes. | |
// It fails for Text nodes, so implement it using compareDocumentPosition() | |
// https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect | |
// Note that compareDocumentPosition returns CONTAINED_BY if 'e2 *is_contained_by* e1': | |
// Also, compareDocumentPosition defines a node containing itself as false. | |
return Node.documentPositionContainedBy(e1.dom(), e2.dom()); | |
}; | |
var browser = PlatformDetection.detect().browser; | |
// Returns: true if node e1 contains e2, otherwise false. | |
// (returns false if e1===e2: A node does not contain itself). | |
var contains = browser.isIE() ? ieContains : regularContains; | |
return { | |
eq: eq, | |
isEqualNode: isEqualNode, | |
member: member, | |
contains: contains, | |
// Only used by DomUniverse. Remove (or should Selectors.is move here?) | |
is: Selectors.is | |
}; | |
} | |
); | |
define( | |
'ephox.sugar.api.search.Traverse', | |
[ | |
'ephox.katamari.api.Type', | |
'ephox.katamari.api.Arr', | |
'ephox.katamari.api.Fun', | |
'ephox.katamari.api.Option', | |
'ephox.katamari.api.Struct', | |
'ephox.sugar.alien.Recurse', | |
'ephox.sugar.api.dom.Compare', | |
'ephox.sugar.api.node.Element' | |
], | |
function (Type, Arr, Fun, Option, Struct, Recurse, Compare, Element) { | |
// The document associated with the current element | |
var owner = function (element) { | |
return Element.fromDom(element.dom().ownerDocument); | |
}; | |
var documentElement = function (element) { | |
// TODO: Avoid unnecessary wrap/unwrap here | |
var doc = owner(element); | |
return Element.fromDom(doc.dom().documentElement); | |
}; | |
// The window element associated with the element | |
var defaultView = function (element) { | |
var el = element.dom(); | |
var defaultView = el.ownerDocument.defaultView; | |
return Element.fromDom(defaultView); | |
}; | |
var parent = function (element) { | |
var dom = element.dom(); | |
return Option.from(dom.parentNode).map(Element.fromDom); | |
}; | |
var findIndex = function (element) { | |
return parent(element).bind(function (p) { | |
// TODO: Refactor out children so we can avoid the constant unwrapping | |
var kin = children(p); | |
return Arr.findIndex(kin, function (elem) { | |
return Compare.eq(element, elem); | |
}); | |
}); | |
}; | |
var parents = function (element, isRoot) { | |
var stop = Type.isFunction(isRoot) ? isRoot : Fun.constant(false); | |
// This is used a *lot* so it needs to be performant, not recursive | |
var dom = element.dom(); | |
var ret = []; | |
while (dom.parentNode !== null && dom.parentNode !== undefined) { | |
var rawParent = dom.parentNode; | |
var parent = Element.fromDom(rawParent); | |
ret.push(parent); | |
if (stop(parent) === true) break; | |
else dom = rawParent; | |
} | |
return ret; | |
}; | |
var siblings = function (element) { | |
// TODO: Refactor out children so we can just not add self instead of filtering afterwards | |
var filterSelf = function (elements) { | |
return Arr.filter(elements, function (x) { | |
return !Compare.eq(element, x); | |
}); | |
}; | |
return parent(element).map(children).map(filterSelf).getOr([]); | |
}; | |
var offsetParent = function (element) { | |
var dom = element.dom(); | |
return Option.from(dom.offsetParent).map(Element.fromDom); | |
}; | |
var prevSibling = function (element) { | |
var dom = element.dom(); | |
return Option.from(dom.previousSibling).map(Element.fromDom); | |
}; | |
var nextSibling = function (element) { | |
var dom = element.dom(); | |
return Option.from(dom.nextSibling).map(Element.fromDom); | |
}; | |
var prevSiblings = function (element) { | |
// This one needs to be reversed, so they're still in DOM order | |
return Arr.reverse(Recurse.toArray(element, prevSibling)); | |
}; | |
var nextSiblings = function (element) { | |
return Recurse.toArray(element, nextSibling); | |
}; | |
var children = function (element) { | |
var dom = element.dom(); | |
return Arr.map(dom.childNodes, Element.fromDom); | |
}; | |
var child = function (element, index) { | |
var children = element.dom().childNodes; | |
return Option.from(children[index]).map(Element.fromDom); | |
}; | |
var firstChild = function (element) { | |
return child(element, 0); | |
}; | |
var lastChild = function (element) { | |
return child(element, element.dom().childNodes.length - 1); | |
}; | |
var childNodesCount = function (element) { | |
return element.dom().childNodes.length; | |
}; | |
var hasChildNodes = function (element) { | |
return element.dom().hasChildNodes(); | |
}; | |
var spot = Struct.immutable('element', 'offset'); | |
var leaf = function (element, offset) { | |
var cs = children(element); | |
return cs.length > 0 && offset < cs.length ? spot(cs[offset], 0) : spot(element, offset); | |
}; | |
return { | |
owner: owner, | |
defaultView: defaultView, | |
documentElement: documentElement, | |
parent: parent, | |
findIndex: findIndex, | |
parents: parents, | |
siblings: siblings, | |
prevSibling: prevSibling, | |
offsetParent: offsetParent, | |
prevSiblings: prevSiblings, | |
nextSibling: nextSibling, | |
nextSiblings: nextSiblings, | |
children: children, | |
child: child, | |
firstChild: firstChild, | |
lastChild: lastChild, | |
childNodesCount: childNodesCount, | |
hasChildNodes: hasChildNodes, | |
leaf: leaf | |
}; | |
} | |
); | |
define( | |
'ephox.sugar.api.search.PredicateFilter', | |
[ | |
'ephox.katamari.api.Arr', | |
'ephox.sugar.api.node.Body', | |
'ephox.sugar.api.search.Traverse' | |
], | |
function (Arr, Body, Traverse) { | |
// maybe TraverseWith, similar to traverse but with a predicate? | |
var all = function (predicate) { | |
return descendants(Body.body(), predicate); | |
}; | |
var ancestors = function (scope, predicate, isRoot) { | |
return Arr.filter(Traverse.parents(scope, isRoot), predicate); | |
}; | |
var siblings = function (scope, predicate) { | |
return Arr.filter(Traverse.siblings(scope), predicate); | |
}; | |
var children = function (scope, predicate) { | |
return Arr.filter(Traverse.children(scope), predicate); | |
}; | |
var descendants = function (scope, predicate) { | |
var result = []; | |
// Recurse.toArray() might help here | |
Arr.each(Traverse.children(scope), function (x) { | |
if (predicate(x)) { | |
result = result.concat([ x ]); | |
} | |
result = result.concat(descendants(x, predicate)); | |
}); | |
return result; | |
}; | |
return { | |
all: all, | |
ancestors: ancestors, | |
siblings: siblings, | |
children: children, | |
descendants: descendants | |
}; | |
} | |
); | |
define( | |
'ephox.sugar.api.search.SelectorFilter', | |
[ | |
'ephox.sugar.api.search.PredicateFilter', | |
'ephox.sugar.api.search.Selectors' | |
], | |
function (PredicateFilter, Selectors) { | |
var all = function (selector) { | |
return Selectors.all(selector); | |
}; | |
// For all of the following: | |
// | |
// jQuery does siblings of firstChild. IE9+ supports scope.dom().children (similar to Traverse.children but elements only). | |
// Traverse should also do this (but probably not by default). | |
// | |
var ancestors = function (scope, selector, isRoot) { | |
// It may surprise you to learn this is exactly what JQuery does | |
// TODO: Avoid all this wrapping and unwrapping | |
return PredicateFilter.ancestors(scope, function (e) { | |
return Selectors.is(e, selector); | |
}, isRoot); | |
}; | |
var siblings = function (scope, selector) { | |
// It may surprise you to learn this is exactly what JQuery does | |
// TODO: Avoid all the wrapping and unwrapping | |
return PredicateFilter.siblings(scope, function (e) { | |
return Selectors.is(e, selector); | |
}); | |
}; | |
var children = function (scope, selector) { | |
// It may surprise you to learn this is exactly what JQuery does | |
// TODO: Avoid all the wrapping and unwrapping | |
return PredicateFilter.children(scope, function (e) { | |
return Selectors.is(e, selector); | |
}); | |
}; | |
var descendants = function (scope, selector) { | |
return Selectors.all(selector, scope); | |
}; | |
return { | |
all: all, | |
ancestors: ancestors, | |
siblings: siblings, | |
children: children, | |
descendants: descendants | |
}; | |
} | |
); | |
/** | |
* LinkTargets.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This module is enables you to get anything that you can link to in a element. | |
* | |
* @private | |
* @class tinymce.ui.LinkTargets | |
*/ | |
define( | |
'tinymce.ui.content.LinkTargets', | |
[ | |
'ephox.katamari.api.Arr', | |
'ephox.katamari.api.Fun', | |
'ephox.katamari.api.Id', | |
'ephox.sugar.api.node.Element', | |
'ephox.sugar.api.search.SelectorFilter', | |
'tinymce.core.dom.DOMUtils', | |
'tinymce.core.util.Tools' | |
], | |
function (Arr, Fun, Id, Element, SelectorFilter, DOMUtils, Tools) { | |
var trim = Tools.trim; | |
var hasContentEditableState = function (value) { | |
return function (node) { | |
if (node && node.nodeType === 1) { | |
if (node.contentEditable === value) { | |
return true; | |
} | |
if (node.getAttribute('data-mce-contenteditable') === value) { | |
return true; | |
} | |
} | |
return false; | |
}; | |
}; | |
var isContentEditableTrue = hasContentEditableState('true'); | |
var isContentEditableFalse = hasContentEditableState('false'); | |
var create = function (type, title, url, level, attach) { | |
return { | |
type: type, | |
title: title, | |
url: url, | |
level: level, | |
attach: attach | |
}; | |
}; | |
var isChildOfContentEditableTrue = function (node) { | |
while ((node = node.parentNode)) { | |
var value = node.contentEditable; | |
if (value && value !== 'inherit') { | |
return isContentEditableTrue(node); | |
} | |
} | |
return false; | |
}; | |
var select = function (selector, root) { | |
return Arr.map(SelectorFilter.descendants(Element.fromDom(root), selector), function (element) { | |
return element.dom(); | |
}); | |
}; | |
var getElementText = function (elm) { | |
return elm.innerText || elm.textContent; | |
}; | |
var getOrGenerateId = function (elm) { | |
return elm.id ? elm.id : Id.generate('h'); | |
}; | |
var isAnchor = function (elm) { | |
return elm && elm.nodeName === 'A' && (elm.id || elm.name); | |
}; | |
var isValidAnchor = function (elm) { | |
return isAnchor(elm) && isEditable(elm); | |
}; | |
var isHeader = function (elm) { | |
return elm && /^(H[1-6])$/.test(elm.nodeName); | |
}; | |
var isEditable = function (elm) { | |
return isChildOfContentEditableTrue(elm) && !isContentEditableFalse(elm); | |
}; | |
var isValidHeader = function (elm) { | |
return isHeader(elm) && isEditable(elm); | |
}; | |
var getLevel = function (elm) { | |
return isHeader(elm) ? parseInt(elm.nodeName.substr(1), 10) : 0; | |
}; | |
var headerTarget = function (elm) { | |
var headerId = getOrGenerateId(elm); | |
var attach = function () { | |
elm.id = headerId; | |
}; | |
return create('header', getElementText(elm), '#' + headerId, getLevel(elm), attach); | |
}; | |
var anchorTarget = function (elm) { | |
var anchorId = elm.id || elm.name; | |
var anchorText = getElementText(elm); | |
return create('anchor', anchorText ? anchorText : '#' + anchorId, '#' + anchorId, 0, Fun.noop); | |
}; | |
var getHeaderTargets = function (elms) { | |
return Arr.map(Arr.filter(elms, isValidHeader), headerTarget); | |
}; | |
var getAnchorTargets = function (elms) { | |
return Arr.map(Arr.filter(elms, isValidAnchor), anchorTarget); | |
}; | |
var getTargetElements = function (elm) { | |
var elms = select('h1,h2,h3,h4,h5,h6,a:not([href])', elm); | |
return elms; | |
}; | |
var hasTitle = function (target) { | |
return trim(target.title).length > 0; | |
}; | |
var find = function (elm) { | |
var elms = getTargetElements(elm); | |
return Arr.filter(getHeaderTargets(elms).concat(getAnchorTargets(elms)), hasTitle); | |
}; | |
return { | |
find: find | |
}; | |
} | |
); | |
/** | |
* FilePicker.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class creates a file picker control. | |
* | |
* @class tinymce.ui.FilePicker | |
* @extends tinymce.ui.ComboBox | |
*/ | |
define( | |
'tinymce.ui.FilePicker', | |
[ | |
'ephox.katamari.api.Arr', | |
'ephox.katamari.api.Fun', | |
'global!window', | |
'tinymce.ui.content.LinkTargets', | |
'tinymce.core.EditorManager', | |
'tinymce.ui.ComboBox', | |
'tinymce.core.util.Tools' | |
], | |
function (Arr, Fun, window, LinkTargets, EditorManager, ComboBox, Tools) { | |
"use strict"; | |
var getActiveEditor = function () { | |
return window.tinymce ? window.tinymce.activeEditor : EditorManager.activeEditor; | |
}; | |
var history = {}; | |
var HISTORY_LENGTH = 5; | |
var clearHistory = function () { | |
history = {}; | |
}; | |
var toMenuItem = function (target) { | |
return { | |
title: target.title, | |
value: { | |
title: { raw: target.title }, | |
url: target.url, | |
attach: target.attach | |
} | |
}; | |
}; | |
var toMenuItems = function (targets) { | |
return Tools.map(targets, toMenuItem); | |
}; | |
var staticMenuItem = function (title, url) { | |
return { | |
title: title, | |
value: { | |
title: title, | |
url: url, | |
attach: Fun.noop | |
} | |
}; | |
}; | |
var isUniqueUrl = function (url, targets) { | |
var foundTarget = Arr.exists(targets, function (target) { | |
return target.url === url; | |
}); | |
return !foundTarget; | |
}; | |
var getSetting = function (editorSettings, name, defaultValue) { | |
var value = name in editorSettings ? editorSettings[name] : defaultValue; | |
return value === false ? null : value; | |
}; | |
var createMenuItems = function (term, targets, fileType, editorSettings) { | |
var separator = { title: '-' }; | |
var fromHistoryMenuItems = function (history) { | |
var historyItems = history.hasOwnProperty(fileType) ? history[fileType] : [ ]; | |
var uniqueHistory = Arr.filter(historyItems, function (url) { | |
return isUniqueUrl(url, targets); | |
}); | |
return Tools.map(uniqueHistory, function (url) { | |
return { | |
title: url, | |
value: { | |
title: url, | |
url: url, | |
attach: Fun.noop | |
} | |
}; | |
}); | |
}; | |
var fromMenuItems = function (type) { | |
var filteredTargets = Arr.filter(targets, function (target) { | |
return target.type === type; | |
}); | |
return toMenuItems(filteredTargets); | |
}; | |
var anchorMenuItems = function () { | |
var anchorMenuItems = fromMenuItems('anchor'); | |
var topAnchor = getSetting(editorSettings, 'anchor_top', '#top'); | |
var bottomAchor = getSetting(editorSettings, 'anchor_bottom', '#bottom'); | |
if (topAnchor !== null) { | |
anchorMenuItems.unshift(staticMenuItem('<top>', topAnchor)); | |
} | |
if (bottomAchor !== null) { | |
anchorMenuItems.push(staticMenuItem('<bottom>', bottomAchor)); | |
} | |
return anchorMenuItems; | |
}; | |
var join = function (items) { | |
return Arr.foldl(items, function (a, b) { | |
var bothEmpty = a.length === 0 || b.length === 0; | |
return bothEmpty ? a.concat(b) : a.concat(separator, b); | |
}, []); | |
}; | |
if (editorSettings.typeahead_urls === false) { | |
return []; | |
} | |
return fileType === 'file' ? join([ | |
filterByQuery(term, fromHistoryMenuItems(history)), | |
filterByQuery(term, fromMenuItems('header')), | |
filterByQuery(term, anchorMenuItems()) | |
]) : filterByQuery(term, fromHistoryMenuItems(history)); | |
}; | |
var addToHistory = function (url, fileType) { | |
var items = history[fileType]; | |
if (!/^https?/.test(url)) { | |
return; | |
} | |
if (items) { | |
if (Arr.indexOf(items, url) === -1) { | |
history[fileType] = items.slice(0, HISTORY_LENGTH).concat(url); | |
} | |
} else { | |
history[fileType] = [url]; | |
} | |
}; | |
var filterByQuery = function (term, menuItems) { | |
var lowerCaseTerm = term.toLowerCase(); | |
var result = Tools.grep(menuItems, function (item) { | |
return item.title.toLowerCase().indexOf(lowerCaseTerm) !== -1; | |
}); | |
return result.length === 1 && result[0].title === term ? [] : result; | |
}; | |
var getTitle = function (linkDetails) { | |
var title = linkDetails.title; | |
return title.raw ? title.raw : title; | |
}; | |
var setupAutoCompleteHandler = function (ctrl, editorSettings, bodyElm, fileType) { | |
var autocomplete = function (term) { | |
var linkTargets = LinkTargets.find(bodyElm); | |
var menuItems = createMenuItems(term, linkTargets, fileType, editorSettings); | |
ctrl.showAutoComplete(menuItems, term); | |
}; | |
ctrl.on('autocomplete', function () { | |
autocomplete(ctrl.value()); | |
}); | |
ctrl.on('selectitem', function (e) { | |
var linkDetails = e.value; | |
ctrl.value(linkDetails.url); | |
var title = getTitle(linkDetails); | |
if (fileType === 'image') { | |
ctrl.fire('change', { meta: { alt: title, attach: linkDetails.attach } }); | |
} else { | |
ctrl.fire('change', { meta: { text: title, attach: linkDetails.attach } }); | |
} | |
ctrl.focus(); | |
}); | |
ctrl.on('click', function (e) { | |
if (ctrl.value().length === 0 && e.target.nodeName === 'INPUT') { | |
autocomplete(''); | |
} | |
}); | |
ctrl.on('PostRender', function () { | |
ctrl.getRoot().on('submit', function (e) { | |
if (!e.isDefaultPrevented()) { | |
addToHistory(ctrl.value(), fileType); | |
} | |
}); | |
}); | |
}; | |
var statusToUiState = function (result) { | |
var status = result.status, message = result.message; | |
if (status === 'valid') { | |
return { status: 'ok', message: message }; | |
} else if (status === 'unknown') { | |
return { status: 'warn', message: message }; | |
} else if (status === 'invalid') { | |
return { status: 'warn', message: message }; | |
} else { | |
return { status: 'none', message: '' }; | |
} | |
}; | |
var setupLinkValidatorHandler = function (ctrl, editorSettings, fileType) { | |
var validatorHandler = editorSettings.filepicker_validator_handler; | |
if (validatorHandler) { | |
var validateUrl = function (url) { | |
if (url.length === 0) { | |
ctrl.statusLevel('none'); | |
return; | |
} | |
validatorHandler({ | |
url: url, | |
type: fileType | |
}, function (result) { | |
var uiState = statusToUiState(result); | |
ctrl.statusMessage(uiState.message); | |
ctrl.statusLevel(uiState.status); | |
}); | |
}; | |
ctrl.state.on('change:value', function (e) { | |
validateUrl(e.value); | |
}); | |
} | |
}; | |
return ComboBox.extend({ | |
Statics: { | |
clearHistory: clearHistory | |
}, | |
/** | |
* Constructs a new control instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
*/ | |
init: function (settings) { | |
var self = this, editor = getActiveEditor(), editorSettings = editor.settings; | |
var actionCallback, fileBrowserCallback, fileBrowserCallbackTypes; | |
var fileType = settings.filetype; | |
settings.spellcheck = false; | |
fileBrowserCallbackTypes = editorSettings.file_picker_types || editorSettings.file_browser_callback_types; | |
if (fileBrowserCallbackTypes) { | |
fileBrowserCallbackTypes = Tools.makeMap(fileBrowserCallbackTypes, /[, ]/); | |
} | |
if (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[fileType]) { | |
fileBrowserCallback = editorSettings.file_picker_callback; | |
if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[fileType])) { | |
actionCallback = function () { | |
var meta = self.fire('beforecall').meta; | |
meta = Tools.extend({ filetype: fileType }, meta); | |
// file_picker_callback(callback, currentValue, metaData) | |
fileBrowserCallback.call( | |
editor, | |
function (value, meta) { | |
self.value(value).fire('change', { meta: meta }); | |
}, | |
self.value(), | |
meta | |
); | |
}; | |
} else { | |
// Legacy callback: file_picker_callback(id, currentValue, filetype, window) | |
fileBrowserCallback = editorSettings.file_browser_callback; | |
if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[fileType])) { | |
actionCallback = function () { | |
fileBrowserCallback( | |
self.getEl('inp').id, | |
self.value(), | |
fileType, | |
window | |
); | |
}; | |
} | |
} | |
} | |
if (actionCallback) { | |
settings.icon = 'browse'; | |
settings.onaction = actionCallback; | |
} | |
self._super(settings); | |
setupAutoCompleteHandler(self, editorSettings, editor.getBody(), fileType); | |
setupLinkValidatorHandler(self, editorSettings, fileType); | |
} | |
}); | |
} | |
); | |
/** | |
* FitLayout.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This layout manager will resize the control to be the size of it's parent container. | |
* In other words width: 100% and height: 100%. | |
* | |
* @-x-less FitLayout.less | |
* @class tinymce.ui.FitLayout | |
* @extends tinymce.ui.AbsoluteLayout | |
*/ | |
define( | |
'tinymce.ui.FitLayout', | |
[ | |
"tinymce.ui.AbsoluteLayout" | |
], | |
function (AbsoluteLayout) { | |
"use strict"; | |
return AbsoluteLayout.extend({ | |
/** | |
* Recalculates the positions of the controls in the specified container. | |
* | |
* @method recalc | |
* @param {tinymce.ui.Container} container Container instance to recalc. | |
*/ | |
recalc: function (container) { | |
var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox; | |
container.items().filter(':visible').each(function (ctrl) { | |
ctrl.layoutRect({ | |
x: paddingBox.left, | |
y: paddingBox.top, | |
w: contLayoutRect.innerW - paddingBox.right - paddingBox.left, | |
h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom | |
}); | |
if (ctrl.recalc) { | |
ctrl.recalc(); | |
} | |
}); | |
} | |
}); | |
} | |
); | |
/** | |
* FlexLayout.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This layout manager works similar to the CSS flex box. | |
* | |
* @setting {String} direction row|row-reverse|column|column-reverse | |
* @setting {Number} flex A positive-number to flex by. | |
* @setting {String} align start|end|center|stretch | |
* @setting {String} pack start|end|justify | |
* | |
* @class tinymce.ui.FlexLayout | |
* @extends tinymce.ui.AbsoluteLayout | |
*/ | |
define( | |
'tinymce.ui.FlexLayout', | |
[ | |
"tinymce.ui.AbsoluteLayout" | |
], | |
function (AbsoluteLayout) { | |
"use strict"; | |
return AbsoluteLayout.extend({ | |
/** | |
* Recalculates the positions of the controls in the specified container. | |
* | |
* @method recalc | |
* @param {tinymce.ui.Container} container Container instance to recalc. | |
*/ | |
recalc: function (container) { | |
// A ton of variables, needs to be in the same scope for performance | |
var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction; | |
var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos; | |
var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName; | |
var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName; | |
var alignDeltaSizeName, alignContentSizeName; | |
var max = Math.max, min = Math.min; | |
// Get container items, properties and settings | |
items = container.items().filter(':visible'); | |
contLayoutRect = container.layoutRect(); | |
contPaddingBox = container.paddingBox; | |
contSettings = container.settings; | |
direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction; | |
align = contSettings.align; | |
pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack; | |
spacing = contSettings.spacing || 0; | |
if (direction == "row-reversed" || direction == "column-reverse") { | |
items = items.set(items.toArray().reverse()); | |
direction = direction.split('-')[0]; | |
} | |
// Setup axis variable name for row/column direction since the calculations is the same | |
if (direction == "column") { | |
posName = "y"; | |
sizeName = "h"; | |
minSizeName = "minH"; | |
maxSizeName = "maxH"; | |
innerSizeName = "innerH"; | |
beforeName = 'top'; | |
deltaSizeName = "deltaH"; | |
contentSizeName = "contentH"; | |
alignBeforeName = "left"; | |
alignSizeName = "w"; | |
alignAxisName = "x"; | |
alignInnerSizeName = "innerW"; | |
alignMinSizeName = "minW"; | |
alignAfterName = "right"; | |
alignDeltaSizeName = "deltaW"; | |
alignContentSizeName = "contentW"; | |
} else { | |
posName = "x"; | |
sizeName = "w"; | |
minSizeName = "minW"; | |
maxSizeName = "maxW"; | |
innerSizeName = "innerW"; | |
beforeName = 'left'; | |
deltaSizeName = "deltaW"; | |
contentSizeName = "contentW"; | |
alignBeforeName = "top"; | |
alignSizeName = "h"; | |
alignAxisName = "y"; | |
alignInnerSizeName = "innerH"; | |
alignMinSizeName = "minH"; | |
alignAfterName = "bottom"; | |
alignDeltaSizeName = "deltaH"; | |
alignContentSizeName = "contentH"; | |
} | |
// Figure out total flex, availableSpace and collect any max size elements | |
availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName]; | |
maxAlignEndPos = totalFlex = 0; | |
for (i = 0, l = items.length; i < l; i++) { | |
ctrl = items[i]; | |
ctrlLayoutRect = ctrl.layoutRect(); | |
ctrlSettings = ctrl.settings; | |
flex = ctrlSettings.flex; | |
availableSpace -= (i < l - 1 ? spacing : 0); | |
if (flex > 0) { | |
totalFlex += flex; | |
// Flexed item has a max size then we need to check if we will hit that size | |
if (ctrlLayoutRect[maxSizeName]) { | |
maxSizeItems.push(ctrl); | |
} | |
ctrlLayoutRect.flex = flex; | |
} | |
availableSpace -= ctrlLayoutRect[minSizeName]; | |
// Calculate the align end position to be used to check for overflow/underflow | |
size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName]; | |
if (size > maxAlignEndPos) { | |
maxAlignEndPos = size; | |
} | |
} | |
// Calculate minW/minH | |
rect = {}; | |
if (availableSpace < 0) { | |
rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName]; | |
} else { | |
rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName]; | |
} | |
rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName]; | |
rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace; | |
rect[alignContentSizeName] = maxAlignEndPos; | |
rect.minW = min(rect.minW, contLayoutRect.maxW); | |
rect.minH = min(rect.minH, contLayoutRect.maxH); | |
rect.minW = max(rect.minW, contLayoutRect.startMinWidth); | |
rect.minH = max(rect.minH, contLayoutRect.startMinHeight); | |
// Resize container container if minSize was changed | |
if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) { | |
rect.w = rect.minW; | |
rect.h = rect.minH; | |
container.layoutRect(rect); | |
this.recalc(container); | |
// Forced recalc for example if items are hidden/shown | |
if (container._lastRect === null) { | |
var parentCtrl = container.parent(); | |
if (parentCtrl) { | |
parentCtrl._lastRect = null; | |
parentCtrl.recalc(); | |
} | |
} | |
return; | |
} | |
// Handle max size elements, check if they will become to wide with current options | |
ratio = availableSpace / totalFlex; | |
for (i = 0, l = maxSizeItems.length; i < l; i++) { | |
ctrl = maxSizeItems[i]; | |
ctrlLayoutRect = ctrl.layoutRect(); | |
maxSize = ctrlLayoutRect[maxSizeName]; | |
size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio; | |
if (size > maxSize) { | |
availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]); | |
totalFlex -= ctrlLayoutRect.flex; | |
ctrlLayoutRect.flex = 0; | |
ctrlLayoutRect.maxFlexSize = maxSize; | |
} else { | |
ctrlLayoutRect.maxFlexSize = 0; | |
} | |
} | |
// Setup new ratio, target layout rect, start position | |
ratio = availableSpace / totalFlex; | |
pos = contPaddingBox[beforeName]; | |
rect = {}; | |
// Handle pack setting moves the start position to end, center | |
if (totalFlex === 0) { | |
if (pack == "end") { | |
pos = availableSpace + contPaddingBox[beforeName]; | |
} else if (pack == "center") { | |
pos = Math.round( | |
(contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2) | |
) + contPaddingBox[beforeName]; | |
if (pos < 0) { | |
pos = contPaddingBox[beforeName]; | |
} | |
} else if (pack == "justify") { | |
pos = contPaddingBox[beforeName]; | |
spacing = Math.floor(availableSpace / (items.length - 1)); | |
} | |
} | |
// Default aligning (start) the other ones needs to be calculated while doing the layout | |
rect[alignAxisName] = contPaddingBox[alignBeforeName]; | |
// Start laying out controls | |
for (i = 0, l = items.length; i < l; i++) { | |
ctrl = items[i]; | |
ctrlLayoutRect = ctrl.layoutRect(); | |
size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName]; | |
// Align the control on the other axis | |
if (align === "center") { | |
rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2)); | |
} else if (align === "stretch") { | |
rect[alignSizeName] = max( | |
ctrlLayoutRect[alignMinSizeName] || 0, | |
contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName] | |
); | |
rect[alignAxisName] = contPaddingBox[alignBeforeName]; | |
} else if (align === "end") { | |
rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top; | |
} | |
// Calculate new size based on flex | |
if (ctrlLayoutRect.flex > 0) { | |
size += ctrlLayoutRect.flex * ratio; | |
} | |
rect[sizeName] = size; | |
rect[posName] = pos; | |
ctrl.layoutRect(rect); | |
// Recalculate containers | |
if (ctrl.recalc) { | |
ctrl.recalc(); | |
} | |
// Move x/y position | |
pos += size + spacing; | |
} | |
} | |
}); | |
} | |
); | |
/** | |
* FlowLayout.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This layout manager will place the controls by using the browsers native layout. | |
* | |
* @-x-less FlowLayout.less | |
* @class tinymce.ui.FlowLayout | |
* @extends tinymce.ui.Layout | |
*/ | |
define( | |
'tinymce.ui.FlowLayout', | |
[ | |
"tinymce.ui.Layout" | |
], | |
function (Layout) { | |
return Layout.extend({ | |
Defaults: { | |
containerClass: 'flow-layout', | |
controlClass: 'flow-layout-item', | |
endClass: 'break' | |
}, | |
/** | |
* Recalculates the positions of the controls in the specified container. | |
* | |
* @method recalc | |
* @param {tinymce.ui.Container} container Container instance to recalc. | |
*/ | |
recalc: function (container) { | |
container.items().filter(':visible').each(function (ctrl) { | |
if (ctrl.recalc) { | |
ctrl.recalc(); | |
} | |
}); | |
}, | |
isNative: function () { | |
return true; | |
} | |
}); | |
} | |
); | |
define( | |
'ephox.sugar.impl.ClosestOrAncestor', | |
[ | |
'ephox.katamari.api.Type', | |
'ephox.katamari.api.Option' | |
], | |
function (Type, Option) { | |
return function (is, ancestor, scope, a, isRoot) { | |
return is(scope, a) ? | |
Option.some(scope) : | |
Type.isFunction(isRoot) && isRoot(scope) ? | |
Option.none() : | |
ancestor(scope, a, isRoot); | |
}; | |
} | |
); | |
define( | |
'ephox.sugar.api.search.PredicateFind', | |
[ | |
'ephox.katamari.api.Type', | |
'ephox.katamari.api.Arr', | |
'ephox.katamari.api.Fun', | |
'ephox.katamari.api.Option', | |
'ephox.sugar.api.node.Body', | |
'ephox.sugar.api.dom.Compare', | |
'ephox.sugar.api.node.Element', | |
'ephox.sugar.impl.ClosestOrAncestor' | |
], | |
function (Type, Arr, Fun, Option, Body, Compare, Element, ClosestOrAncestor) { | |
var first = function (predicate) { | |
return descendant(Body.body(), predicate); | |
}; | |
var ancestor = function (scope, predicate, isRoot) { | |
var element = scope.dom(); | |
var stop = Type.isFunction(isRoot) ? isRoot : Fun.constant(false); | |
while (element.parentNode) { | |
element = element.parentNode; | |
var el = Element.fromDom(element); | |
if (predicate(el)) return Option.some(el); | |
else if (stop(el)) break; | |
} | |
return Option.none(); | |
}; | |
var closest = function (scope, predicate, isRoot) { | |
// This is required to avoid ClosestOrAncestor passing the predicate to itself | |
var is = function (scope) { | |
return predicate(scope); | |
}; | |
return ClosestOrAncestor(is, ancestor, scope, predicate, isRoot); | |
}; | |
var sibling = function (scope, predicate) { | |
var element = scope.dom(); | |
if (!element.parentNode) return Option.none(); | |
return child(Element.fromDom(element.parentNode), function (x) { | |
return !Compare.eq(scope, x) && predicate(x); | |
}); | |
}; | |
var child = function (scope, predicate) { | |
var result = Arr.find(scope.dom().childNodes, | |
Fun.compose(predicate, Element.fromDom)); | |
return result.map(Element.fromDom); | |
}; | |
var descendant = function (scope, predicate) { | |
var descend = function (element) { | |
for (var i = 0; i < element.childNodes.length; i++) { | |
if (predicate(Element.fromDom(element.childNodes[i]))) | |
return Option.some(Element.fromDom(element.childNodes[i])); | |
var res = descend(element.childNodes[i]); | |
if (res.isSome()) | |
return res; | |
} | |
return Option.none(); | |
}; | |
return descend(scope.dom()); | |
}; | |
return { | |
first: first, | |
ancestor: ancestor, | |
closest: closest, | |
sibling: sibling, | |
child: child, | |
descendant: descendant | |
}; | |
} | |
); | |
define( | |
'ephox.sugar.api.search.SelectorFind', | |
[ | |
'ephox.sugar.api.search.PredicateFind', | |
'ephox.sugar.api.search.Selectors', | |
'ephox.sugar.impl.ClosestOrAncestor' | |
], | |
function (PredicateFind, Selectors, ClosestOrAncestor) { | |
// TODO: An internal SelectorFilter module that doesn't Element.fromDom() everything | |
var first = function (selector) { | |
return Selectors.one(selector); | |
}; | |
var ancestor = function (scope, selector, isRoot) { | |
return PredicateFind.ancestor(scope, function (e) { | |
return Selectors.is(e, selector); | |
}, isRoot); | |
}; | |
var sibling = function (scope, selector) { | |
return PredicateFind.sibling(scope, function (e) { | |
return Selectors.is(e, selector); | |
}); | |
}; | |
var child = function (scope, selector) { | |
return PredicateFind.child(scope, function (e) { | |
return Selectors.is(e, selector); | |
}); | |
}; | |
var descendant = function (scope, selector) { | |
return Selectors.one(selector, scope); | |
}; | |
// Returns Some(closest ancestor element (sugared)) matching 'selector' up to isRoot, or None() otherwise | |
var closest = function (scope, selector, isRoot) { | |
return ClosestOrAncestor(Selectors.is, ancestor, scope, selector, isRoot); | |
}; | |
return { | |
first: first, | |
ancestor: ancestor, | |
sibling: sibling, | |
child: child, | |
descendant: descendant, | |
closest: closest | |
}; | |
} | |
); | |
/** | |
* FormatUtils.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.editorui.FormatUtils', | |
[ | |
], | |
function () { | |
var toggleFormat = function (editor, fmt) { | |
return function () { | |
editor.execCommand('mceToggleFormat', false, fmt); | |
}; | |
}; | |
var postRenderFormat = function (editor, name) { | |
return function () { | |
var self = this; | |
// TODO: Fix this | |
if (editor.formatter) { | |
editor.formatter.formatChanged(name, function (state) { | |
self.active(state); | |
}); | |
} else { | |
editor.on('init', function () { | |
editor.formatter.formatChanged(name, function (state) { | |
self.active(state); | |
}); | |
}); | |
} | |
}; | |
}; | |
return { | |
toggleFormat: toggleFormat, | |
postRenderFormat: postRenderFormat | |
}; | |
} | |
); | |
/** | |
* Align.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.editorui.Align', | |
[ | |
'tinymce.core.util.Tools', | |
'tinymce.ui.editorui.FormatUtils' | |
], | |
function (Tools, FormatUtils) { | |
var register = function (editor) { | |
editor.addMenuItem('align', { | |
text: 'Align', | |
menu: [ | |
{ text: 'Left', icon: 'alignleft', onclick: FormatUtils.toggleFormat(editor, 'alignleft') }, | |
{ text: 'Center', icon: 'aligncenter', onclick: FormatUtils.toggleFormat(editor, 'aligncenter') }, | |
{ text: 'Right', icon: 'alignright', onclick: FormatUtils.toggleFormat(editor, 'alignright') }, | |
{ text: 'Justify', icon: 'alignjustify', onclick: FormatUtils.toggleFormat(editor, 'alignjustify') } | |
] | |
}); | |
Tools.each({ | |
alignleft: ['Align left', 'JustifyLeft'], | |
aligncenter: ['Align center', 'JustifyCenter'], | |
alignright: ['Align right', 'JustifyRight'], | |
alignjustify: ['Justify', 'JustifyFull'], | |
alignnone: ['No alignment', 'JustifyNone'] | |
}, function (item, name) { | |
editor.addButton(name, { | |
active: false, | |
tooltip: item[0], | |
cmd: item[1], | |
onPostRender: FormatUtils.postRenderFormat(editor, name) | |
}); | |
}); | |
}; | |
return { | |
register: register | |
}; | |
} | |
); | |
/** | |
* FontInfo.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Internal class for computing font size for elements. | |
* | |
* @private | |
* @class tinymce.fmt.FontInfo | |
*/ | |
define( | |
'tinymce.ui.fmt.FontInfo', | |
[ | |
'ephox.katamari.api.Fun', | |
'ephox.katamari.api.Option', | |
'ephox.sugar.api.node.Element', | |
'ephox.sugar.api.node.Node', | |
'tinymce.core.dom.DOMUtils' | |
], | |
function (Fun, Option, Element, Node, DOMUtils) { | |
var getSpecifiedFontProp = function (propName, rootElm, elm) { | |
while (elm !== rootElm) { | |
if (elm.style[propName]) { | |
var foundStyle = elm.style[propName]; | |
return foundStyle !== '' ? Option.some(foundStyle) : Option.none(); | |
} | |
elm = elm.parentNode; | |
} | |
return Option.none(); | |
}; | |
var round = function (number, precision) { | |
var factor = Math.pow(10, precision); | |
return Math.round(number * factor) / factor; | |
}; | |
var toPt = function (fontSize, precision) { | |
if (/[0-9.]+px$/.test(fontSize)) { | |
// Round to the nearest 0.5 | |
return round(parseInt(fontSize, 10) * 72 / 96, precision || 0) + 'pt'; | |
} | |
return fontSize; | |
}; | |
var normalizeFontFamily = function (fontFamily) { | |
// 'Font name', Font -> Font name,Font | |
return fontFamily.replace(/[\'\"]/g, '').replace(/,\s+/g, ','); | |
}; | |
var getComputedFontProp = function (propName, elm) { | |
return Option.from(DOMUtils.DOM.getStyle(elm, propName, true)); | |
}; | |
var getFontProp = function (propName) { | |
return function (rootElm, elm) { | |
return Option.from(elm) | |
.map(Element.fromDom) | |
.filter(Node.isElement) | |
.bind(function (element) { | |
return getSpecifiedFontProp(propName, rootElm, element.dom()) | |
.or(getComputedFontProp(propName, element.dom())); | |
}) | |
.getOr(''); | |
}; | |
}; | |
return { | |
getFontSize: getFontProp('fontSize'), | |
getFontFamily: Fun.compose(normalizeFontFamily, getFontProp('fontFamily')), | |
toPt: toPt | |
}; | |
} | |
); | |
/** | |
* FontSelect.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.editorui.FontSelect', | |
[ | |
'tinymce.core.util.Tools', | |
'tinymce.ui.fmt.FontInfo' | |
], | |
function (Tools, FontInfo) { | |
var getFirstFont = function (fontFamily) { | |
return fontFamily ? fontFamily.split(',')[0] : ''; | |
}; | |
var findMatchingValue = function (items, fontFamily) { | |
var value; | |
Tools.each(items, function (item) { | |
if (item.value.toLowerCase() === fontFamily.toLowerCase()) { | |
value = item.value; | |
} | |
}); | |
Tools.each(items, function (item) { | |
if (!value && getFirstFont(item.value).toLowerCase() === getFirstFont(fontFamily).toLowerCase()) { | |
value = item.value; | |
} | |
}); | |
return value; | |
}; | |
var createFontNameListBoxChangeHandler = function (editor, items) { | |
return function () { | |
var self = this; | |
editor.on('init nodeChange', function (e) { | |
var fontFamily = FontInfo.getFontFamily(editor.getBody(), e.element); | |
var match = findMatchingValue(items, fontFamily); | |
self.value(match ? match : null); | |
if (!match && fontFamily) { | |
self.text(getFirstFont(fontFamily)); | |
} | |
}); | |
}; | |
}; | |
var createFormats = function (formats) { | |
formats = formats.replace(/;$/, '').split(';'); | |
var i = formats.length; | |
while (i--) { | |
formats[i] = formats[i].split('='); | |
} | |
return formats; | |
}; | |
var getFontItems = function (editor) { | |
var defaultFontsFormats = ( | |
'Andale Mono=andale mono,monospace;' + | |
'Arial=arial,helvetica,sans-serif;' + | |
'Arial Black=arial black,sans-serif;' + | |
'Book Antiqua=book antiqua,palatino,serif;' + | |
'Comic Sans MS=comic sans ms,sans-serif;' + | |
'Courier New=courier new,courier,monospace;' + | |
'Georgia=georgia,palatino,serif;' + | |
'Helvetica=helvetica,arial,sans-serif;' + | |
'Impact=impact,sans-serif;' + | |
'Symbol=symbol;' + | |
'Tahoma=tahoma,arial,helvetica,sans-serif;' + | |
'Terminal=terminal,monaco,monospace;' + | |
'Times New Roman=times new roman,times,serif;' + | |
'Trebuchet MS=trebuchet ms,geneva,sans-serif;' + | |
'Verdana=verdana,geneva,sans-serif;' + | |
'Webdings=webdings;' + | |
'Wingdings=wingdings,zapf dingbats' | |
); | |
var fonts = createFormats(editor.settings.font_formats || defaultFontsFormats); | |
return Tools.map(fonts, function (font) { | |
return { | |
text: { raw: font[0] }, | |
value: font[1], | |
textStyle: font[1].indexOf('dings') === -1 ? 'font-family:' + font[1] : '' | |
}; | |
}); | |
}; | |
var registerButtons = function (editor) { | |
editor.addButton('fontselect', function () { | |
var items = getFontItems(editor); | |
return { | |
type: 'listbox', | |
text: 'Font Family', | |
tooltip: 'Font Family', | |
values: items, | |
fixedWidth: true, | |
onPostRender: createFontNameListBoxChangeHandler(editor, items), | |
onselect: function (e) { | |
if (e.control.settings.value) { | |
editor.execCommand('FontName', false, e.control.settings.value); | |
} | |
} | |
}; | |
}); | |
}; | |
var register = function (editor) { | |
registerButtons(editor); | |
}; | |
return { | |
register: register | |
}; | |
} | |
); | |
/** | |
* FontSizeSelect.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.editorui.FontSizeSelect', | |
[ | |
'tinymce.core.util.Tools', | |
'tinymce.ui.fmt.FontInfo' | |
], | |
function (Tools, FontInfo) { | |
var findMatchingValue = function (items, pt, px) { | |
var value; | |
Tools.each(items, function (item) { | |
if (item.value === px) { | |
value = px; | |
} else if (item.value === pt) { | |
value = pt; | |
} | |
}); | |
return value; | |
}; | |
var createFontSizeListBoxChangeHandler = function (editor, items) { | |
return function () { | |
var self = this; | |
editor.on('init nodeChange', function (e) { | |
var px, pt, precision, match; | |
px = FontInfo.getFontSize(editor.getBody(), e.element); | |
if (px) { | |
// checking for three digits after decimal point, should be precise enough | |
for (precision = 3; !match && precision >= 0; precision--) { | |
pt = FontInfo.toPt(px, precision); | |
match = findMatchingValue(items, pt, px); | |
} | |
} | |
self.value(match ? match : null); | |
if (!match) { | |
self.text(pt); | |
} | |
}); | |
}; | |
}; | |
var getFontSizeItems = function (editor) { | |
var defaultFontsizeFormats = '8pt 10pt 12pt 14pt 18pt 24pt 36pt'; | |
var fontsizeFormats = editor.settings.fontsize_formats || defaultFontsizeFormats; | |
return Tools.map(fontsizeFormats.split(' '), function (item) { | |
var text = item, value = item; | |
// Allow text=value font sizes. | |
var values = item.split('='); | |
if (values.length > 1) { | |
text = values[0]; | |
value = values[1]; | |
} | |
return { text: text, value: value }; | |
}); | |
}; | |
var registerButtons = function (editor) { | |
editor.addButton('fontsizeselect', function () { | |
var items = getFontSizeItems(editor); | |
return { | |
type: 'listbox', | |
text: 'Font Sizes', | |
tooltip: 'Font Sizes', | |
values: items, | |
fixedWidth: true, | |
onPostRender: createFontSizeListBoxChangeHandler(editor, items), | |
onclick: function (e) { | |
if (e.control.settings.value) { | |
editor.execCommand('FontSize', false, e.control.settings.value); | |
} | |
} | |
}; | |
}); | |
}; | |
var register = function (editor) { | |
registerButtons(editor); | |
}; | |
return { | |
register: register | |
}; | |
} | |
); | |
/** | |
* FormatSelect.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.editorui.FormatSelect', | |
[ | |
'tinymce.core.util.Tools', | |
'tinymce.ui.editorui.FormatUtils' | |
], | |
function (Tools, FormatUtils) { | |
var defaultBlocks = ( | |
'Paragraph=p;' + | |
'Heading 1=h1;' + | |
'Heading 2=h2;' + | |
'Heading 3=h3;' + | |
'Heading 4=h4;' + | |
'Heading 5=h5;' + | |
'Heading 6=h6;' + | |
'Preformatted=pre' | |
); | |
var createFormats = function (formats) { | |
formats = formats.replace(/;$/, '').split(';'); | |
var i = formats.length; | |
while (i--) { | |
formats[i] = formats[i].split('='); | |
} | |
return formats; | |
}; | |
var createListBoxChangeHandler = function (editor, items, formatName) { | |
return function () { | |
var self = this; | |
editor.on('nodeChange', function (e) { | |
var formatter = editor.formatter; | |
var value = null; | |
Tools.each(e.parents, function (node) { | |
Tools.each(items, function (item) { | |
if (formatName) { | |
if (formatter.matchNode(node, formatName, { value: item.value })) { | |
value = item.value; | |
} | |
} else { | |
if (formatter.matchNode(node, item.value)) { | |
value = item.value; | |
} | |
} | |
if (value) { | |
return false; | |
} | |
}); | |
if (value) { | |
return false; | |
} | |
}); | |
self.value(value); | |
}); | |
}; | |
}; | |
var lazyFormatSelectBoxItems = function (editor, blocks) { | |
return function () { | |
var items = []; | |
Tools.each(blocks, function (block) { | |
items.push({ | |
text: block[0], | |
value: block[1], | |
textStyle: function () { | |
return editor.formatter.getCssText(block[1]); | |
} | |
}); | |
}); | |
return { | |
type: 'listbox', | |
text: blocks[0][0], | |
values: items, | |
fixedWidth: true, | |
onselect: function (e) { | |
if (e.control) { | |
var fmt = e.control.value(); | |
FormatUtils.toggleFormat(editor, fmt)(); | |
} | |
}, | |
onPostRender: createListBoxChangeHandler(editor, items) | |
}; | |
}; | |
}; | |
var buildMenuItems = function (editor, blocks) { | |
return Tools.map(blocks, function (block) { | |
return { | |
text: block[0], | |
onclick: FormatUtils.toggleFormat(editor, block[1]), | |
textStyle: function () { | |
return editor.formatter.getCssText(block[1]); | |
} | |
}; | |
}); | |
}; | |
var register = function (editor) { | |
var blocks = createFormats(editor.settings.block_formats || defaultBlocks); | |
editor.addMenuItem('blockformats', { | |
text: 'Blocks', | |
menu: buildMenuItems(editor, blocks) | |
}); | |
editor.addButton('formatselect', lazyFormatSelectBoxItems(editor, blocks)); | |
}; | |
return { | |
register: register | |
}; | |
} | |
); | |
/** | |
* Formats.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.editorui.Formats', | |
[ | |
'tinymce.core.util.Tools', | |
'tinymce.ui.editorui.FormatUtils' | |
], | |
function (Tools, FormatUtils) { | |
var hideMenuObjects = function (editor, menu) { | |
var count = menu.length; | |
Tools.each(menu, function (item) { | |
if (item.menu) { | |
item.hidden = hideMenuObjects(editor, item.menu) === 0; | |
} | |
var formatName = item.format; | |
if (formatName) { | |
item.hidden = !editor.formatter.canApply(formatName); | |
} | |
if (item.hidden) { | |
count--; | |
} | |
}); | |
return count; | |
}; | |
var hideFormatMenuItems = function (editor, menu) { | |
var count = menu.items().length; | |
menu.items().each(function (item) { | |
if (item.menu) { | |
item.visible(hideFormatMenuItems(editor, item.menu) > 0); | |
} | |
if (!item.menu && item.settings.menu) { | |
item.visible(hideMenuObjects(editor, item.settings.menu) > 0); | |
} | |
var formatName = item.settings.format; | |
if (formatName) { | |
item.visible(editor.formatter.canApply(formatName)); | |
} | |
if (!item.visible()) { | |
count--; | |
} | |
}); | |
return count; | |
}; | |
var createFormatMenu = function (editor) { | |
var count = 0, newFormats = []; | |
var defaultStyleFormats = [ | |
{ | |
title: 'Headings', items: [ | |
{ title: 'Heading 1', format: 'h1' }, | |
{ title: 'Heading 2', format: 'h2' }, | |
{ title: 'Heading 3', format: 'h3' }, | |
{ title: 'Heading 4', format: 'h4' }, | |
{ title: 'Heading 5', format: 'h5' }, | |
{ title: 'Heading 6', format: 'h6' } | |
] | |
}, | |
{ | |
title: 'Inline', items: [ | |
{ title: 'Bold', icon: 'bold', format: 'bold' }, | |
{ title: 'Italic', icon: 'italic', format: 'italic' }, | |
{ title: 'Underline', icon: 'underline', format: 'underline' }, | |
{ title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough' }, | |
{ title: 'Superscript', icon: 'superscript', format: 'superscript' }, | |
{ title: 'Subscript', icon: 'subscript', format: 'subscript' }, | |
{ title: 'Code', icon: 'code', format: 'code' } | |
] | |
}, | |
{ | |
title: 'Blocks', items: [ | |
{ title: 'Paragraph', format: 'p' }, | |
{ title: 'Blockquote', format: 'blockquote' }, | |
{ title: 'Div', format: 'div' }, | |
{ title: 'Pre', format: 'pre' } | |
] | |
}, | |
{ | |
title: 'Alignment', items: [ | |
{ title: 'Left', icon: 'alignleft', format: 'alignleft' }, | |
{ title: 'Center', icon: 'aligncenter', format: 'aligncenter' }, | |
{ title: 'Right', icon: 'alignright', format: 'alignright' }, | |
{ title: 'Justify', icon: 'alignjustify', format: 'alignjustify' } | |
] | |
} | |
]; | |
var createMenu = function (formats) { | |
var menu = []; | |
if (!formats) { | |
return; | |
} | |
Tools.each(formats, function (format) { | |
var menuItem = { | |
text: format.title, | |
icon: format.icon | |
}; | |
if (format.items) { | |
menuItem.menu = createMenu(format.items); | |
} else { | |
var formatName = format.format || "custom" + count++; | |
if (!format.format) { | |
format.name = formatName; | |
newFormats.push(format); | |
} | |
menuItem.format = formatName; | |
menuItem.cmd = format.cmd; | |
} | |
menu.push(menuItem); | |
}); | |
return menu; | |
}; | |
var createStylesMenu = function () { | |
var menu; | |
if (editor.settings.style_formats_merge) { | |
if (editor.settings.style_formats) { | |
menu = createMenu(defaultStyleFormats.concat(editor.settings.style_formats)); | |
} else { | |
menu = createMenu(defaultStyleFormats); | |
} | |
} else { | |
menu = createMenu(editor.settings.style_formats || defaultStyleFormats); | |
} | |
return menu; | |
}; | |
editor.on('init', function () { | |
Tools.each(newFormats, function (format) { | |
editor.formatter.register(format.name, format); | |
}); | |
}); | |
return { | |
type: 'menu', | |
items: createStylesMenu(), | |
onPostRender: function (e) { | |
editor.fire('renderFormatsMenu', { control: e.control }); | |
}, | |
itemDefaults: { | |
preview: true, | |
textStyle: function () { | |
if (this.settings.format) { | |
return editor.formatter.getCssText(this.settings.format); | |
} | |
}, | |
onPostRender: function () { | |
var self = this; | |
self.parent().on('show', function () { | |
var formatName, command; | |
formatName = self.settings.format; | |
if (formatName) { | |
self.disabled(!editor.formatter.canApply(formatName)); | |
self.active(editor.formatter.match(formatName)); | |
} | |
command = self.settings.cmd; | |
if (command) { | |
self.active(editor.queryCommandState(command)); | |
} | |
}); | |
}, | |
onclick: function () { | |
if (this.settings.format) { | |
FormatUtils.toggleFormat(editor, this.settings.format)(); | |
} | |
if (this.settings.cmd) { | |
editor.execCommand(this.settings.cmd); | |
} | |
} | |
} | |
}; | |
}; | |
var registerMenuItems = function (editor, formatMenu) { | |
editor.addMenuItem('formats', { | |
text: 'Formats', | |
menu: formatMenu | |
}); | |
}; | |
var registerButtons = function (editor, formatMenu) { | |
editor.addButton('styleselect', { | |
type: 'menubutton', | |
text: 'Formats', | |
menu: formatMenu, | |
onShowMenu: function () { | |
if (editor.settings.style_formats_autohide) { | |
hideFormatMenuItems(editor, this.menu); | |
} | |
} | |
}); | |
}; | |
var register = function (editor) { | |
var formatMenu = createFormatMenu(editor); | |
registerMenuItems(editor, formatMenu); | |
registerButtons(editor, formatMenu); | |
}; | |
return { | |
register: register | |
}; | |
} | |
); | |
/** | |
* InsertButton.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.editorui.InsertButton', | |
[ | |
'ephox.katamari.api.Arr', | |
'tinymce.core.util.Tools' | |
], | |
function (Arr, Tools) { | |
var createCustomMenuItems = function (editor, names) { | |
var items, nameList; | |
if (typeof names === 'string') { | |
nameList = names.split(' '); | |
} else if (Tools.isArray(names)) { | |
return Arr.flatten(Tools.map(names, function (names) { | |
return createCustomMenuItems(editor, names); | |
})); | |
} | |
items = Tools.grep(nameList, function (name) { | |
return name === '|' || name in editor.menuItems; | |
}); | |
return Tools.map(items, function (name) { | |
return name === '|' ? { text: '-' } : editor.menuItems[name]; | |
}); | |
}; | |
var isSeparator = function (menuItem) { | |
return menuItem && menuItem.text === '-'; | |
}; | |
var trimMenuItems = function (menuItems) { | |
var menuItems2 = Arr.filter(menuItems, function (menuItem, i, menuItems) { | |
return !isSeparator(menuItem) || !isSeparator(menuItems[i - 1]); | |
}); | |
return Arr.filter(menuItems2, function (menuItem, i, menuItems) { | |
return !isSeparator(menuItem) || i > 0 && i < menuItems.length - 1; | |
}); | |
}; | |
var createContextMenuItems = function (editor, context) { | |
var outputMenuItems = [{ text: '-' }]; | |
var menuItems = Tools.grep(editor.menuItems, function (menuItem) { | |
return menuItem.context === context; | |
}); | |
Tools.each(menuItems, function (menuItem) { | |
if (menuItem.separator === 'before') { | |
outputMenuItems.push({ text: '|' }); | |
} | |
if (menuItem.prependToContext) { | |
outputMenuItems.unshift(menuItem); | |
} else { | |
outputMenuItems.push(menuItem); | |
} | |
if (menuItem.separator === 'after') { | |
outputMenuItems.push({ text: '|' }); | |
} | |
}); | |
return outputMenuItems; | |
}; | |
var createInsertMenu = function (editor) { | |
var insertButtonItems = editor.settings.insert_button_items; | |
if (insertButtonItems) { | |
return trimMenuItems(createCustomMenuItems(editor, insertButtonItems)); | |
} else { | |
return trimMenuItems(createContextMenuItems(editor, 'insert')); | |
} | |
}; | |
var registerButtons = function (editor) { | |
editor.addButton('insert', { | |
type: 'menubutton', | |
icon: 'insert', | |
menu: [], | |
oncreatemenu: function () { | |
this.menu.add(createInsertMenu(editor)); | |
this.menu.renderNew(); | |
} | |
}); | |
}; | |
var register = function (editor) { | |
registerButtons(editor); | |
}; | |
return { | |
register: register | |
}; | |
} | |
); | |
/** | |
* SimpleControls.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.editorui.SimpleControls', | |
[ | |
'tinymce.core.util.Tools', | |
'tinymce.ui.editorui.FormatUtils' | |
], | |
function (Tools, FormatUtils) { | |
var registerFormatButtons = function (editor) { | |
Tools.each({ | |
bold: 'Bold', | |
italic: 'Italic', | |
underline: 'Underline', | |
strikethrough: 'Strikethrough', | |
subscript: 'Subscript', | |
superscript: 'Superscript' | |
}, function (text, name) { | |
editor.addButton(name, { | |
active: false, | |
tooltip: text, | |
onPostRender: FormatUtils.postRenderFormat(editor, name), | |
onclick: FormatUtils.toggleFormat(editor, name) | |
}); | |
}); | |
}; | |
var registerCommandButtons = function (editor) { | |
Tools.each({ | |
outdent: ['Decrease indent', 'Outdent'], | |
indent: ['Increase indent', 'Indent'], | |
cut: ['Cut', 'Cut'], | |
copy: ['Copy', 'Copy'], | |
paste: ['Paste', 'Paste'], | |
help: ['Help', 'mceHelp'], | |
selectall: ['Select all', 'SelectAll'], | |
visualaid: ['Visual aids', 'mceToggleVisualAid'], | |
newdocument: ['New document', 'mceNewDocument'], | |
removeformat: ['Clear formatting', 'RemoveFormat'], | |
remove: ['Remove', 'Delete'] | |
}, function (item, name) { | |
editor.addButton(name, { | |
tooltip: item[0], | |
cmd: item[1] | |
}); | |
}); | |
}; | |
var registerCommandToggleButtons = function (editor) { | |
Tools.each({ | |
blockquote: ['Blockquote', 'mceBlockQuote'], | |
subscript: ['Subscript', 'Subscript'], | |
superscript: ['Superscript', 'Superscript'] | |
}, function (item, name) { | |
editor.addButton(name, { | |
active: false, | |
tooltip: item[0], | |
cmd: item[1], | |
onPostRender: FormatUtils.postRenderFormat(editor, name) | |
}); | |
}); | |
}; | |
var registerButtons = function (editor) { | |
registerFormatButtons(editor); | |
registerCommandButtons(editor); | |
registerCommandToggleButtons(editor); | |
}; | |
var registerMenuItems = function (editor) { | |
Tools.each({ | |
bold: ['Bold', 'Bold', 'Meta+B'], | |
italic: ['Italic', 'Italic', 'Meta+I'], | |
underline: ['Underline', 'Underline', 'Meta+U'], | |
strikethrough: ['Strikethrough', 'Strikethrough'], | |
subscript: ['Subscript', 'Subscript'], | |
superscript: ['Superscript', 'Superscript'], | |
removeformat: ['Clear formatting', 'RemoveFormat'], | |
newdocument: ['New document', 'mceNewDocument'], | |
cut: ['Cut', 'Cut', 'Meta+X'], | |
copy: ['Copy', 'Copy', 'Meta+C'], | |
paste: ['Paste', 'Paste', 'Meta+V'], | |
selectall: ['Select all', 'SelectAll', 'Meta+A'] | |
}, function (item, name) { | |
editor.addMenuItem(name, { | |
text: item[0], | |
icon: name, | |
shortcut: item[2], | |
cmd: item[1] | |
}); | |
}); | |
editor.addMenuItem('codeformat', { | |
text: 'Code', | |
icon: 'code', | |
onclick: FormatUtils.toggleFormat(editor, 'code') | |
}); | |
}; | |
var register = function (editor) { | |
registerButtons(editor); | |
registerMenuItems(editor); | |
}; | |
return { | |
register: register | |
}; | |
} | |
); | |
/** | |
* UndoRedo.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.editorui.UndoRedo', | |
[ | |
], | |
function () { | |
var toggleUndoRedoState = function (editor, type) { | |
return function () { | |
var self = this; | |
var checkState = function () { | |
var typeFn = type === 'redo' ? 'hasRedo' : 'hasUndo'; | |
return editor.undoManager ? editor.undoManager[typeFn]() : false; | |
}; | |
self.disabled(!checkState()); | |
editor.on('Undo Redo AddUndo TypingUndo ClearUndos SwitchMode', function () { | |
self.disabled(editor.readonly || !checkState()); | |
}); | |
}; | |
}; | |
var registerMenuItems = function (editor) { | |
editor.addMenuItem('undo', { | |
text: 'Undo', | |
icon: 'undo', | |
shortcut: 'Meta+Z', | |
onPostRender: toggleUndoRedoState(editor, 'undo'), | |
cmd: 'undo' | |
}); | |
editor.addMenuItem('redo', { | |
text: 'Redo', | |
icon: 'redo', | |
shortcut: 'Meta+Y', | |
onPostRender: toggleUndoRedoState(editor, 'redo'), | |
cmd: 'redo' | |
}); | |
}; | |
var registerButtons = function (editor) { | |
editor.addButton('undo', { | |
tooltip: 'Undo', | |
onPostRender: toggleUndoRedoState(editor, 'undo'), | |
cmd: 'undo' | |
}); | |
editor.addButton('redo', { | |
tooltip: 'Redo', | |
onPostRender: toggleUndoRedoState(editor, 'redo'), | |
cmd: 'redo' | |
}); | |
}; | |
var register = function (editor) { | |
registerMenuItems(editor); | |
registerButtons(editor); | |
}; | |
return { | |
register: register | |
}; | |
} | |
); | |
/** | |
* VisualAid.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.editorui.VisualAid', | |
[ | |
], | |
function () { | |
var toggleVisualAidState = function (editor) { | |
return function () { | |
var self = this; | |
editor.on('VisualAid', function (e) { | |
self.active(e.hasVisual); | |
}); | |
self.active(editor.hasVisual); | |
}; | |
}; | |
var registerMenuItems = function (editor) { | |
editor.addMenuItem('visualaid', { | |
text: 'Visual aids', | |
selectable: true, | |
onPostRender: toggleVisualAidState(editor), | |
cmd: 'mceToggleVisualAid' | |
}); | |
}; | |
var register = function (editor) { | |
registerMenuItems(editor); | |
}; | |
return { | |
register: register | |
}; | |
} | |
); | |
/** | |
* FormatControls.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.FormatControls', | |
[ | |
'ephox.katamari.api.Fun', | |
'ephox.sugar.api.node.Element', | |
'ephox.sugar.api.search.SelectorFind', | |
'global!document', | |
'tinymce.core.EditorManager', | |
'tinymce.core.Env', | |
'tinymce.ui.Control', | |
'tinymce.ui.FloatPanel', | |
'tinymce.ui.Widget', | |
'tinymce.ui.editorui.Align', | |
'tinymce.ui.editorui.FontSelect', | |
'tinymce.ui.editorui.FontSizeSelect', | |
'tinymce.ui.editorui.FormatSelect', | |
'tinymce.ui.editorui.Formats', | |
'tinymce.ui.editorui.InsertButton', | |
'tinymce.ui.editorui.SimpleControls', | |
'tinymce.ui.editorui.UndoRedo', | |
'tinymce.ui.editorui.VisualAid' | |
], | |
function ( | |
Fun, Element, SelectorFind, document, EditorManager, Env, Control, FloatPanel, Widget, Align, FontSelect, FontSizeSelect, FormatSelect, Formats, InsertButton, | |
SimpleControls, UndoRedo, VisualAid | |
) { | |
var setupEnvironment = function () { | |
Widget.tooltips = !Env.iOS; | |
Control.translate = function (text) { | |
return EditorManager.translate(text); | |
}; | |
}; | |
var setupUiContainer = function (editor) { | |
if (editor.settings.ui_container) { | |
Env.container = SelectorFind.descendant(Element.fromDom(document.body), editor.settings.ui_container).fold(Fun.constant(null), function (elm) { | |
return elm.dom(); | |
}); | |
} | |
}; | |
var setupRtlMode = function (editor) { | |
if (editor.rtl) { | |
Control.rtl = true; | |
} | |
}; | |
var setupHideFloatPanels = function (editor) { | |
editor.on('mousedown', function () { | |
FloatPanel.hideAll(); | |
}); | |
}; | |
var setup = function (editor) { | |
setupRtlMode(editor); | |
setupHideFloatPanels(editor); | |
setupUiContainer(editor); | |
setupEnvironment(editor); | |
FormatSelect.register(editor); | |
Align.register(editor); | |
SimpleControls.register(editor); | |
UndoRedo.register(editor); | |
FontSizeSelect.register(editor); | |
FontSelect.register(editor); | |
Formats.register(editor); | |
VisualAid.register(editor); | |
InsertButton.register(editor); | |
}; | |
return { | |
setup: setup | |
}; | |
} | |
); | |
/** | |
* GridLayout.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This layout manager places controls in a grid. | |
* | |
* @setting {Number} spacing Spacing between controls. | |
* @setting {Number} spacingH Horizontal spacing between controls. | |
* @setting {Number} spacingV Vertical spacing between controls. | |
* @setting {Number} columns Number of columns to use. | |
* @setting {String/Array} alignH start|end|center|stretch or array of values for each column. | |
* @setting {String/Array} alignV start|end|center|stretch or array of values for each column. | |
* @setting {String} pack start|end | |
* | |
* @class tinymce.ui.GridLayout | |
* @extends tinymce.ui.AbsoluteLayout | |
*/ | |
define( | |
'tinymce.ui.GridLayout', | |
[ | |
"tinymce.ui.AbsoluteLayout" | |
], | |
function (AbsoluteLayout) { | |
"use strict"; | |
return AbsoluteLayout.extend({ | |
/** | |
* Recalculates the positions of the controls in the specified container. | |
* | |
* @method recalc | |
* @param {tinymce.ui.Container} container Container instance to recalc. | |
*/ | |
recalc: function (container) { | |
var settings, rows, cols, items, contLayoutRect, width, height, rect, | |
ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY, | |
colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, availableWidth, availableHeight, reverseRows, idx; | |
// Get layout settings | |
settings = container.settings; | |
items = container.items().filter(':visible'); | |
contLayoutRect = container.layoutRect(); | |
cols = settings.columns || Math.ceil(Math.sqrt(items.length)); | |
rows = Math.ceil(items.length / cols); | |
spacingH = settings.spacingH || settings.spacing || 0; | |
spacingV = settings.spacingV || settings.spacing || 0; | |
alignH = settings.alignH || settings.align; | |
alignV = settings.alignV || settings.align; | |
contPaddingBox = container.paddingBox; | |
reverseRows = 'reverseRows' in settings ? settings.reverseRows : container.isRtl(); | |
if (alignH && typeof alignH == "string") { | |
alignH = [alignH]; | |
} | |
if (alignV && typeof alignV == "string") { | |
alignV = [alignV]; | |
} | |
// Zero padd columnWidths | |
for (x = 0; x < cols; x++) { | |
colWidths.push(0); | |
} | |
// Zero padd rowHeights | |
for (y = 0; y < rows; y++) { | |
rowHeights.push(0); | |
} | |
// Calculate columnWidths and rowHeights | |
for (y = 0; y < rows; y++) { | |
for (x = 0; x < cols; x++) { | |
ctrl = items[y * cols + x]; | |
// Out of bounds | |
if (!ctrl) { | |
break; | |
} | |
ctrlLayoutRect = ctrl.layoutRect(); | |
ctrlMinWidth = ctrlLayoutRect.minW; | |
ctrlMinHeight = ctrlLayoutRect.minH; | |
colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x]; | |
rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y]; | |
} | |
} | |
// Calculate maxX | |
availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right; | |
for (maxX = 0, x = 0; x < cols; x++) { | |
maxX += colWidths[x] + (x > 0 ? spacingH : 0); | |
availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x]; | |
} | |
// Calculate maxY | |
availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom; | |
for (maxY = 0, y = 0; y < rows; y++) { | |
maxY += rowHeights[y] + (y > 0 ? spacingV : 0); | |
availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y]; | |
} | |
maxX += contPaddingBox.left + contPaddingBox.right; | |
maxY += contPaddingBox.top + contPaddingBox.bottom; | |
// Calculate minW/minH | |
rect = {}; | |
rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW); | |
rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH); | |
rect.contentW = rect.minW - contLayoutRect.deltaW; | |
rect.contentH = rect.minH - contLayoutRect.deltaH; | |
rect.minW = Math.min(rect.minW, contLayoutRect.maxW); | |
rect.minH = Math.min(rect.minH, contLayoutRect.maxH); | |
rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth); | |
rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight); | |
// Resize container container if minSize was changed | |
if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) { | |
rect.w = rect.minW; | |
rect.h = rect.minH; | |
container.layoutRect(rect); | |
this.recalc(container); | |
// Forced recalc for example if items are hidden/shown | |
if (container._lastRect === null) { | |
var parentCtrl = container.parent(); | |
if (parentCtrl) { | |
parentCtrl._lastRect = null; | |
parentCtrl.recalc(); | |
} | |
} | |
return; | |
} | |
// Update contentW/contentH so absEnd moves correctly | |
if (contLayoutRect.autoResize) { | |
rect = container.layoutRect(rect); | |
rect.contentW = rect.minW - contLayoutRect.deltaW; | |
rect.contentH = rect.minH - contLayoutRect.deltaH; | |
} | |
var flexV; | |
if (settings.packV == 'start') { | |
flexV = 0; | |
} else { | |
flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0; | |
} | |
// Calculate totalFlex | |
var totalFlex = 0; | |
var flexWidths = settings.flexWidths; | |
if (flexWidths) { | |
for (x = 0; x < flexWidths.length; x++) { | |
totalFlex += flexWidths[x]; | |
} | |
} else { | |
totalFlex = cols; | |
} | |
// Calculate new column widths based on flex values | |
var ratio = availableWidth / totalFlex; | |
for (x = 0; x < cols; x++) { | |
colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio; | |
} | |
// Move/resize controls | |
posY = contPaddingBox.top; | |
for (y = 0; y < rows; y++) { | |
posX = contPaddingBox.left; | |
height = rowHeights[y] + flexV; | |
for (x = 0; x < cols; x++) { | |
if (reverseRows) { | |
idx = y * cols + cols - 1 - x; | |
} else { | |
idx = y * cols + x; | |
} | |
ctrl = items[idx]; | |
// No more controls to render then break | |
if (!ctrl) { | |
break; | |
} | |
// Get control settings and calculate x, y | |
ctrlSettings = ctrl.settings; | |
ctrlLayoutRect = ctrl.layoutRect(); | |
width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth); | |
ctrlLayoutRect.x = posX; | |
ctrlLayoutRect.y = posY; | |
// Align control horizontal | |
align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null); | |
if (align == "center") { | |
ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2); | |
} else if (align == "right") { | |
ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w; | |
} else if (align == "stretch") { | |
ctrlLayoutRect.w = width; | |
} | |
// Align control vertical | |
align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null); | |
if (align == "center") { | |
ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2); | |
} else if (align == "bottom") { | |
ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h; | |
} else if (align == "stretch") { | |
ctrlLayoutRect.h = height; | |
} | |
ctrl.layoutRect(ctrlLayoutRect); | |
posX += width + spacingH; | |
if (ctrl.recalc) { | |
ctrl.recalc(); | |
} | |
} | |
posY += height + spacingV; | |
} | |
} | |
}); | |
} | |
); | |
/** | |
* Iframe.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/*jshint scripturl:true */ | |
/** | |
* This class creates an iframe. | |
* | |
* @setting {String} url Url to open in the iframe. | |
* | |
* @-x-less Iframe.less | |
* @class tinymce.ui.Iframe | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.Iframe', | |
[ | |
"tinymce.ui.Widget", | |
"tinymce.core.util.Delay" | |
], | |
function (Widget, Delay) { | |
"use strict"; | |
return Widget.extend({ | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this; | |
self.classes.add('iframe'); | |
self.canFocus = false; | |
/*eslint no-script-url:0 */ | |
return ( | |
'<iframe id="' + self._id + '" class="' + self.classes + '" tabindex="-1" src="' + | |
(self.settings.url || "javascript:''") + '" frameborder="0"></iframe>' | |
); | |
}, | |
/** | |
* Setter for the iframe source. | |
* | |
* @method src | |
* @param {String} src Source URL for iframe. | |
*/ | |
src: function (src) { | |
this.getEl().src = src; | |
}, | |
/** | |
* Inner HTML for the iframe. | |
* | |
* @method html | |
* @param {String} html HTML string to set as HTML inside the iframe. | |
* @param {function} callback Optional callback to execute when the iframe body is filled with contents. | |
* @return {tinymce.ui.Iframe} Current iframe control. | |
*/ | |
html: function (html, callback) { | |
var self = this, body = this.getEl().contentWindow.document.body; | |
// Wait for iframe to initialize IE 10 takes time | |
if (!body) { | |
Delay.setTimeout(function () { | |
self.html(html); | |
}); | |
} else { | |
body.innerHTML = html; | |
if (callback) { | |
callback(); | |
} | |
} | |
return this; | |
} | |
}); | |
} | |
); | |
/** | |
* InfoBox.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* .... | |
* | |
* @-x-less InfoBox.less | |
* @class tinymce.ui.InfoBox | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.InfoBox', | |
[ | |
"tinymce.ui.Widget" | |
], | |
function (Widget) { | |
"use strict"; | |
return Widget.extend({ | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {Boolean} multiline Multiline label. | |
*/ | |
init: function (settings) { | |
var self = this; | |
self._super(settings); | |
self.classes.add('widget').add('infobox'); | |
self.canFocus = false; | |
}, | |
severity: function (level) { | |
this.classes.remove('error'); | |
this.classes.remove('warning'); | |
this.classes.remove('success'); | |
this.classes.add(level); | |
}, | |
help: function (state) { | |
this.state.set('help', state); | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, prefix = self.classPrefix; | |
return ( | |
'<div id="' + self._id + '" class="' + self.classes + '">' + | |
'<div id="' + self._id + '-body">' + | |
self.encode(self.state.get('text')) + | |
'<button role="button" tabindex="-1">' + | |
'<i class="' + prefix + 'ico ' + prefix + 'i-help"></i>' + | |
'</button>' + | |
'</div>' + | |
'</div>' | |
); | |
}, | |
bindStates: function () { | |
var self = this; | |
self.state.on('change:text', function (e) { | |
self.getEl('body').firstChild.data = self.encode(e.value); | |
if (self.state.get('rendered')) { | |
self.updateLayoutRect(); | |
} | |
}); | |
self.state.on('change:help', function (e) { | |
self.classes.toggle('has-help', e.value); | |
if (self.state.get('rendered')) { | |
self.updateLayoutRect(); | |
} | |
}); | |
return self._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* Label.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This class creates a label element. A label is a simple text control | |
* that can be bound to other controls. | |
* | |
* @-x-less Label.less | |
* @class tinymce.ui.Label | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.Label', | |
[ | |
"tinymce.ui.Widget", | |
"tinymce.ui.DomUtils" | |
], | |
function (Widget, DomUtils) { | |
"use strict"; | |
return Widget.extend({ | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {Boolean} multiline Multiline label. | |
*/ | |
init: function (settings) { | |
var self = this; | |
self._super(settings); | |
self.classes.add('widget').add('label'); | |
self.canFocus = false; | |
if (settings.multiline) { | |
self.classes.add('autoscroll'); | |
} | |
if (settings.strong) { | |
self.classes.add('strong'); | |
} | |
}, | |
/** | |
* Initializes the current controls layout rect. | |
* This will be executed by the layout managers to determine the | |
* default minWidth/minHeight etc. | |
* | |
* @method initLayoutRect | |
* @return {Object} Layout rect instance. | |
*/ | |
initLayoutRect: function () { | |
var self = this, layoutRect = self._super(); | |
if (self.settings.multiline) { | |
var size = DomUtils.getSize(self.getEl()); | |
// Check if the text fits within maxW if not then try word wrapping it | |
if (size.width > layoutRect.maxW) { | |
layoutRect.minW = layoutRect.maxW; | |
self.classes.add('multiline'); | |
} | |
self.getEl().style.width = layoutRect.minW + 'px'; | |
layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, DomUtils.getSize(self.getEl()).height); | |
} | |
return layoutRect; | |
}, | |
/** | |
* Repaints the control after a layout operation. | |
* | |
* @method repaint | |
*/ | |
repaint: function () { | |
var self = this; | |
if (!self.settings.multiline) { | |
self.getEl().style.lineHeight = self.layoutRect().h + 'px'; | |
} | |
return self._super(); | |
}, | |
severity: function (level) { | |
this.classes.remove('error'); | |
this.classes.remove('warning'); | |
this.classes.remove('success'); | |
this.classes.add(level); | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, targetCtrl, forName, forId = self.settings.forId; | |
var text = self.settings.html ? self.settings.html : self.encode(self.state.get('text')); | |
if (!forId && (forName = self.settings.forName)) { | |
targetCtrl = self.getRoot().find('#' + forName)[0]; | |
if (targetCtrl) { | |
forId = targetCtrl._id; | |
} | |
} | |
if (forId) { | |
return ( | |
'<label id="' + self._id + '" class="' + self.classes + '"' + (forId ? ' for="' + forId + '"' : '') + '>' + | |
text + | |
'</label>' | |
); | |
} | |
return ( | |
'<span id="' + self._id + '" class="' + self.classes + '">' + | |
text + | |
'</span>' | |
); | |
}, | |
bindStates: function () { | |
var self = this; | |
self.state.on('change:text', function (e) { | |
self.innerHtml(self.encode(e.value)); | |
if (self.state.get('rendered')) { | |
self.updateLayoutRect(); | |
} | |
}); | |
return self._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* Toolbar.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new toolbar. | |
* | |
* @class tinymce.ui.Toolbar | |
* @extends tinymce.ui.Container | |
*/ | |
define( | |
'tinymce.ui.Toolbar', | |
[ | |
"tinymce.ui.Container" | |
], | |
function (Container) { | |
"use strict"; | |
return Container.extend({ | |
Defaults: { | |
role: 'toolbar', | |
layout: 'flow' | |
}, | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
*/ | |
init: function (settings) { | |
var self = this; | |
self._super(settings); | |
self.classes.add('toolbar'); | |
}, | |
/** | |
* Called after the control has been rendered. | |
* | |
* @method postRender | |
*/ | |
postRender: function () { | |
var self = this; | |
self.items().each(function (ctrl) { | |
ctrl.classes.add('toolbar-item'); | |
}); | |
return self._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* MenuBar.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new menubar. | |
* | |
* @-x-less MenuBar.less | |
* @class tinymce.ui.MenuBar | |
* @extends tinymce.ui.Container | |
*/ | |
define( | |
'tinymce.ui.MenuBar', | |
[ | |
"tinymce.ui.Toolbar" | |
], | |
function (Toolbar) { | |
"use strict"; | |
return Toolbar.extend({ | |
Defaults: { | |
role: 'menubar', | |
containerCls: 'menubar', | |
ariaRoot: true, | |
defaults: { | |
type: 'menubutton' | |
} | |
} | |
}); | |
} | |
); | |
/** | |
* MenuButton.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new menu button. | |
* | |
* @-x-less MenuButton.less | |
* @class tinymce.ui.MenuButton | |
* @extends tinymce.ui.Button | |
*/ | |
define( | |
'tinymce.ui.MenuButton', | |
[ | |
'global!window', | |
'tinymce.core.ui.Factory', | |
'tinymce.ui.Button', | |
'tinymce.ui.MenuBar' | |
], | |
function (window, Factory, Button, MenuBar) { | |
"use strict"; | |
// TODO: Maybe add as some global function | |
function isChildOf(node, parent) { | |
while (node) { | |
if (parent === node) { | |
return true; | |
} | |
node = node.parentNode; | |
} | |
return false; | |
} | |
var MenuButton = Button.extend({ | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
*/ | |
init: function (settings) { | |
var self = this; | |
self._renderOpen = true; | |
self._super(settings); | |
settings = self.settings; | |
self.classes.add('menubtn'); | |
if (settings.fixedWidth) { | |
self.classes.add('fixed-width'); | |
} | |
self.aria('haspopup', true); | |
self.state.set('menu', settings.menu || self.render()); | |
}, | |
/** | |
* Shows the menu for the button. | |
* | |
* @method showMenu | |
*/ | |
showMenu: function (toggle) { | |
var self = this, menu; | |
if (self.menu && self.menu.visible() && toggle !== false) { | |
return self.hideMenu(); | |
} | |
if (!self.menu) { | |
menu = self.state.get('menu') || []; | |
self.classes.add('opened'); | |
// Is menu array then auto constuct menu control | |
if (menu.length) { | |
menu = { | |
type: 'menu', | |
animate: true, | |
items: menu | |
}; | |
} else { | |
menu.type = menu.type || 'menu'; | |
menu.animate = true; | |
} | |
if (!menu.renderTo) { | |
self.menu = Factory.create(menu).parent(self).renderTo(); | |
} else { | |
self.menu = menu.parent(self).show().renderTo(); | |
} | |
self.fire('createmenu'); | |
self.menu.reflow(); | |
self.menu.on('cancel', function (e) { | |
if (e.control.parent() === self.menu) { | |
e.stopPropagation(); | |
self.focus(); | |
self.hideMenu(); | |
} | |
}); | |
// Move focus to button when a menu item is selected/clicked | |
self.menu.on('select', function () { | |
self.focus(); | |
}); | |
self.menu.on('show hide', function (e) { | |
if (e.control === self.menu) { | |
self.activeMenu(e.type == 'show'); | |
self.classes.toggle('opened', e.type == 'show'); | |
} | |
self.aria('expanded', e.type == 'show'); | |
}).fire('show'); | |
} | |
self.menu.show(); | |
self.menu.layoutRect({ w: self.layoutRect().w }); | |
self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']); | |
self.fire('showmenu'); | |
}, | |
/** | |
* Hides the menu for the button. | |
* | |
* @method hideMenu | |
*/ | |
hideMenu: function () { | |
var self = this; | |
if (self.menu) { | |
self.menu.items().each(function (item) { | |
if (item.hideMenu) { | |
item.hideMenu(); | |
} | |
}); | |
self.menu.hide(); | |
} | |
}, | |
/** | |
* Sets the active menu state. | |
* | |
* @private | |
*/ | |
activeMenu: function (state) { | |
this.classes.toggle('active', state); | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, id = self._id, prefix = self.classPrefix; | |
var icon = self.settings.icon, image, text = self.state.get('text'), | |
textHtml = ''; | |
image = self.settings.image; | |
if (image) { | |
icon = 'none'; | |
// Support for [high dpi, low dpi] image sources | |
if (typeof image != "string") { | |
image = window.getSelection ? image[0] : image[1]; | |
} | |
image = ' style="background-image: url(\'' + image + '\')"'; | |
} else { | |
image = ''; | |
} | |
if (text) { | |
self.classes.add('btn-has-text'); | |
textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>'; | |
} | |
icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : ''; | |
self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button'); | |
return ( | |
'<div id="' + id + '" class="' + self.classes + '" tabindex="-1" aria-labelledby="' + id + '">' + | |
'<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' + | |
(icon ? '<i class="' + icon + '"' + image + '></i>' : '') + | |
textHtml + | |
' <i class="' + prefix + 'caret"></i>' + | |
'</button>' + | |
'</div>' | |
); | |
}, | |
/** | |
* Gets invoked after the control has been rendered. | |
* | |
* @method postRender | |
*/ | |
postRender: function () { | |
var self = this; | |
self.on('click', function (e) { | |
if (e.control === self && isChildOf(e.target, self.getEl())) { | |
self.focus(); | |
self.showMenu(!e.aria); | |
if (e.aria) { | |
self.menu.items().filter(':visible')[0].focus(); | |
} | |
} | |
}); | |
self.on('mouseenter', function (e) { | |
var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu; | |
if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) { | |
parent.items().filter('MenuButton').each(function (ctrl) { | |
if (ctrl.hideMenu && ctrl != overCtrl) { | |
if (ctrl.menu && ctrl.menu.visible()) { | |
hasVisibleSiblingMenu = true; | |
} | |
ctrl.hideMenu(); | |
} | |
}); | |
if (hasVisibleSiblingMenu) { | |
overCtrl.focus(); // Fix for: #5887 | |
overCtrl.showMenu(); | |
} | |
} | |
}); | |
return self._super(); | |
}, | |
bindStates: function () { | |
var self = this; | |
self.state.on('change:menu', function () { | |
if (self.menu) { | |
self.menu.remove(); | |
} | |
self.menu = null; | |
}); | |
return self._super(); | |
}, | |
/** | |
* Removes the control and it's menus. | |
* | |
* @method remove | |
*/ | |
remove: function () { | |
this._super(); | |
if (this.menu) { | |
this.menu.remove(); | |
} | |
} | |
}); | |
return MenuButton; | |
} | |
); | |
/** | |
* MenuItem.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new menu item. | |
* | |
* @-x-less MenuItem.less | |
* @class tinymce.ui.MenuItem | |
* @extends tinymce.ui.Control | |
*/ | |
define( | |
'tinymce.ui.MenuItem', | |
[ | |
"tinymce.ui.Widget", | |
"tinymce.core.ui.Factory", | |
"tinymce.core.Env", | |
"tinymce.core.util.Delay" | |
], | |
function (Widget, Factory, Env, Delay) { | |
"use strict"; | |
var toggleTextStyle = function (ctrl, state) { | |
var textStyle = ctrl._textStyle; | |
if (textStyle) { | |
var textElm = ctrl.getEl('text'); | |
textElm.setAttribute('style', textStyle); | |
if (state) { | |
textElm.style.color = ''; | |
textElm.style.backgroundColor = ''; | |
} | |
} | |
}; | |
return Widget.extend({ | |
Defaults: { | |
border: 0, | |
role: 'menuitem' | |
}, | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {Boolean} selectable Selectable menu. | |
* @setting {Array} menu Submenu array with items. | |
* @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X | |
*/ | |
init: function (settings) { | |
var self = this, text; | |
self._super(settings); | |
settings = self.settings; | |
self.classes.add('menu-item'); | |
if (settings.menu) { | |
self.classes.add('menu-item-expand'); | |
} | |
if (settings.preview) { | |
self.classes.add('menu-item-preview'); | |
} | |
text = self.state.get('text'); | |
if (text === '-' || text === '|') { | |
self.classes.add('menu-item-sep'); | |
self.aria('role', 'separator'); | |
self.state.set('text', '-'); | |
} | |
if (settings.selectable) { | |
self.aria('role', 'menuitemcheckbox'); | |
self.classes.add('menu-item-checkbox'); | |
settings.icon = 'selected'; | |
} | |
if (!settings.preview && !settings.selectable) { | |
self.classes.add('menu-item-normal'); | |
} | |
self.on('mousedown', function (e) { | |
e.preventDefault(); | |
}); | |
if (settings.menu && !settings.ariaHideMenu) { | |
self.aria('haspopup', true); | |
} | |
}, | |
/** | |
* Returns true/false if the menuitem has sub menu. | |
* | |
* @method hasMenus | |
* @return {Boolean} True/false state if it has submenu. | |
*/ | |
hasMenus: function () { | |
return !!this.settings.menu; | |
}, | |
/** | |
* Shows the menu for the menu item. | |
* | |
* @method showMenu | |
*/ | |
showMenu: function () { | |
var self = this, settings = self.settings, menu, parent = self.parent(); | |
parent.items().each(function (ctrl) { | |
if (ctrl !== self) { | |
ctrl.hideMenu(); | |
} | |
}); | |
if (settings.menu) { | |
menu = self.menu; | |
if (!menu) { | |
menu = settings.menu; | |
// Is menu array then auto constuct menu control | |
if (menu.length) { | |
menu = { | |
type: 'menu', | |
animate: true, | |
items: menu | |
}; | |
} else { | |
menu.type = menu.type || 'menu'; | |
menu.animate = true; | |
} | |
if (parent.settings.itemDefaults) { | |
menu.itemDefaults = parent.settings.itemDefaults; | |
} | |
menu = self.menu = Factory.create(menu).parent(self).renderTo(); | |
menu.reflow(); | |
menu.on('cancel', function (e) { | |
e.stopPropagation(); | |
self.focus(); | |
menu.hide(); | |
}); | |
menu.on('show hide', function (e) { | |
if (e.control.items) { | |
e.control.items().each(function (ctrl) { | |
ctrl.active(ctrl.settings.selected); | |
}); | |
} | |
}).fire('show'); | |
menu.on('hide', function (e) { | |
if (e.control === menu) { | |
self.classes.remove('selected'); | |
} | |
}); | |
menu.submenu = true; | |
} else { | |
menu.show(); | |
} | |
menu._parentMenu = parent; | |
menu.classes.add('menu-sub'); | |
var rel = menu.testMoveRel( | |
self.getEl(), | |
self.isRtl() ? ['tl-tr', 'bl-br', 'tr-tl', 'br-bl'] : ['tr-tl', 'br-bl', 'tl-tr', 'bl-br'] | |
); | |
menu.moveRel(self.getEl(), rel); | |
menu.rel = rel; | |
rel = 'menu-sub-' + rel; | |
menu.classes.remove(menu._lastRel).add(rel); | |
menu._lastRel = rel; | |
self.classes.add('selected'); | |
self.aria('expanded', true); | |
} | |
}, | |
/** | |
* Hides the menu for the menu item. | |
* | |
* @method hideMenu | |
*/ | |
hideMenu: function () { | |
var self = this; | |
if (self.menu) { | |
self.menu.items().each(function (item) { | |
if (item.hideMenu) { | |
item.hideMenu(); | |
} | |
}); | |
self.menu.hide(); | |
self.aria('expanded', false); | |
} | |
return self; | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.state.get('text'); | |
var icon = self.settings.icon, image = '', shortcut = settings.shortcut; | |
var url = self.encode(settings.url), iconHtml = ''; | |
// Converts shortcut format to Mac/PC variants | |
function convertShortcut(shortcut) { | |
var i, value, replace = {}; | |
if (Env.mac) { | |
replace = { | |
alt: '⌥', | |
ctrl: '⌘', | |
shift: '⇧', | |
meta: '⌘' | |
}; | |
} else { | |
replace = { | |
meta: 'Ctrl' | |
}; | |
} | |
shortcut = shortcut.split('+'); | |
for (i = 0; i < shortcut.length; i++) { | |
value = replace[shortcut[i].toLowerCase()]; | |
if (value) { | |
shortcut[i] = value; | |
} | |
} | |
return shortcut.join('+'); | |
} | |
function escapeRegExp(str) { | |
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | |
} | |
function markMatches(text) { | |
var match = settings.match || ''; | |
return match ? text.replace(new RegExp(escapeRegExp(match), 'gi'), function (match) { | |
return '!mce~match[' + match + ']mce~match!'; | |
}) : text; | |
} | |
function boldMatches(text) { | |
return text. | |
replace(new RegExp(escapeRegExp('!mce~match['), 'g'), '<b>'). | |
replace(new RegExp(escapeRegExp(']mce~match!'), 'g'), '</b>'); | |
} | |
if (icon) { | |
self.parent().classes.add('menu-has-icons'); | |
} | |
if (settings.image) { | |
image = ' style="background-image: url(\'' + settings.image + '\')"'; | |
} | |
if (shortcut) { | |
shortcut = convertShortcut(shortcut); | |
} | |
icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none'); | |
iconHtml = (text !== '-' ? '<i class="' + icon + '"' + image + '></i>\u00a0' : ''); | |
text = boldMatches(self.encode(markMatches(text))); | |
url = boldMatches(self.encode(markMatches(url))); | |
return ( | |
'<div id="' + id + '" class="' + self.classes + '" tabindex="-1">' + | |
iconHtml + | |
(text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') + | |
(shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') + | |
(settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') + | |
(url ? '<div class="' + prefix + 'menu-item-link">' + url + '</div>' : '') + | |
'</div>' | |
); | |
}, | |
/** | |
* Gets invoked after the control has been rendered. | |
* | |
* @method postRender | |
*/ | |
postRender: function () { | |
var self = this, settings = self.settings; | |
var textStyle = settings.textStyle; | |
if (typeof textStyle === "function") { | |
textStyle = textStyle.call(this); | |
} | |
if (textStyle) { | |
var textElm = self.getEl('text'); | |
if (textElm) { | |
textElm.setAttribute('style', textStyle); | |
self._textStyle = textStyle; | |
} | |
} | |
self.on('mouseenter click', function (e) { | |
if (e.control === self) { | |
if (!settings.menu && e.type === 'click') { | |
self.fire('select'); | |
// Edge will crash if you stress it see #2660 | |
Delay.requestAnimationFrame(function () { | |
self.parent().hideAll(); | |
}); | |
} else { | |
self.showMenu(); | |
if (e.aria) { | |
self.menu.focus(true); | |
} | |
} | |
} | |
}); | |
self._super(); | |
return self; | |
}, | |
hover: function () { | |
var self = this; | |
self.parent().items().each(function (ctrl) { | |
ctrl.classes.remove('selected'); | |
}); | |
self.classes.toggle('selected', true); | |
return self; | |
}, | |
active: function (state) { | |
toggleTextStyle(this, state); | |
if (typeof state != "undefined") { | |
this.aria('checked', state); | |
} | |
return this._super(state); | |
}, | |
/** | |
* Removes the control and it's menus. | |
* | |
* @method remove | |
*/ | |
remove: function () { | |
this._super(); | |
if (this.menu) { | |
this.menu.remove(); | |
} | |
} | |
}); | |
} | |
); | |
/** | |
* Menu.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new menu. | |
* | |
* @-x-less Menu.less | |
* @class tinymce.ui.Menu | |
* @extends tinymce.ui.FloatPanel | |
*/ | |
define( | |
'tinymce.ui.Menu', | |
[ | |
'tinymce.core.Env', | |
'tinymce.core.util.Delay', | |
'tinymce.core.util.Tools', | |
'tinymce.ui.FloatPanel', | |
'tinymce.ui.MenuItem', | |
'tinymce.ui.Throbber' | |
], | |
function (Env, Delay, Tools, FloatPanel, MenuItem, Throbber) { | |
"use strict"; | |
return FloatPanel.extend({ | |
Defaults: { | |
defaultType: 'menuitem', | |
border: 1, | |
layout: 'stack', | |
role: 'application', | |
bodyRole: 'menu', | |
ariaRoot: true | |
}, | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
*/ | |
init: function (settings) { | |
var self = this; | |
settings.autohide = true; | |
settings.constrainToViewport = true; | |
if (typeof settings.items === 'function') { | |
settings.itemsFactory = settings.items; | |
settings.items = []; | |
} | |
if (settings.itemDefaults) { | |
var items = settings.items, i = items.length; | |
while (i--) { | |
items[i] = Tools.extend({}, settings.itemDefaults, items[i]); | |
} | |
} | |
self._super(settings); | |
self.classes.add('menu'); | |
if (settings.animate && Env.ie !== 11) { | |
// IE 11 can't handle transforms it looks horrible and blurry so lets disable that | |
self.classes.add('animate'); | |
} | |
}, | |
/** | |
* Repaints the control after a layout operation. | |
* | |
* @method repaint | |
*/ | |
repaint: function () { | |
this.classes.toggle('menu-align', true); | |
this._super(); | |
this.getEl().style.height = ''; | |
this.getEl('body').style.height = ''; | |
return this; | |
}, | |
/** | |
* Hides/closes the menu. | |
* | |
* @method cancel | |
*/ | |
cancel: function () { | |
var self = this; | |
self.hideAll(); | |
self.fire('select'); | |
}, | |
/** | |
* Loads new items from the factory items function. | |
* | |
* @method load | |
*/ | |
load: function () { | |
var self = this, time, factory; | |
function hideThrobber() { | |
if (self.throbber) { | |
self.throbber.hide(); | |
self.throbber = null; | |
} | |
} | |
factory = self.settings.itemsFactory; | |
if (!factory) { | |
return; | |
} | |
if (!self.throbber) { | |
self.throbber = new Throbber(self.getEl('body'), true); | |
if (self.items().length === 0) { | |
self.throbber.show(); | |
self.fire('loading'); | |
} else { | |
self.throbber.show(100, function () { | |
self.items().remove(); | |
self.fire('loading'); | |
}); | |
} | |
self.on('hide close', hideThrobber); | |
} | |
self.requestTime = time = new Date().getTime(); | |
self.settings.itemsFactory(function (items) { | |
if (items.length === 0) { | |
self.hide(); | |
return; | |
} | |
if (self.requestTime !== time) { | |
return; | |
} | |
self.getEl().style.width = ''; | |
self.getEl('body').style.width = ''; | |
hideThrobber(); | |
self.items().remove(); | |
self.getEl('body').innerHTML = ''; | |
self.add(items); | |
self.renderNew(); | |
self.fire('loaded'); | |
}); | |
}, | |
/** | |
* Hide menu and all sub menus. | |
* | |
* @method hideAll | |
*/ | |
hideAll: function () { | |
var self = this; | |
this.find('menuitem').exec('hideMenu'); | |
return self._super(); | |
}, | |
/** | |
* Invoked before the menu is rendered. | |
* | |
* @method preRender | |
*/ | |
preRender: function () { | |
var self = this; | |
self.items().each(function (ctrl) { | |
var settings = ctrl.settings; | |
if (settings.icon || settings.image || settings.selectable) { | |
self._hasIcons = true; | |
return false; | |
} | |
}); | |
if (self.settings.itemsFactory) { | |
self.on('postrender', function () { | |
if (self.settings.itemsFactory) { | |
self.load(); | |
} | |
}); | |
} | |
self.on('show hide', function (e) { | |
if (e.control === self) { | |
if (e.type === 'show') { | |
Delay.setTimeout(function () { | |
self.classes.add('in'); | |
}, 0); | |
} else { | |
self.classes.remove('in'); | |
} | |
} | |
}); | |
return self._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* ListBox.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new list box control. | |
* | |
* @-x-less ListBox.less | |
* @class tinymce.ui.ListBox | |
* @extends tinymce.ui.MenuButton | |
*/ | |
define( | |
'tinymce.ui.ListBox', | |
[ | |
"tinymce.ui.MenuButton", | |
"tinymce.ui.Menu" | |
], | |
function (MenuButton, Menu) { | |
"use strict"; | |
return MenuButton.extend({ | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {Array} values Array with values to add to list box. | |
*/ | |
init: function (settings) { | |
var self = this, values, selected, selectedText, lastItemCtrl; | |
function setSelected(menuValues) { | |
// Try to find a selected value | |
for (var i = 0; i < menuValues.length; i++) { | |
selected = menuValues[i].selected || settings.value === menuValues[i].value; | |
if (selected) { | |
selectedText = selectedText || menuValues[i].text; | |
self.state.set('value', menuValues[i].value); | |
return true; | |
} | |
// If the value has a submenu, try to find the selected values in that menu | |
if (menuValues[i].menu) { | |
if (setSelected(menuValues[i].menu)) { | |
return true; | |
} | |
} | |
} | |
} | |
self._super(settings); | |
settings = self.settings; | |
self._values = values = settings.values; | |
if (values) { | |
if (typeof settings.value != "undefined") { | |
setSelected(values); | |
} | |
// Default with first item | |
if (!selected && values.length > 0) { | |
selectedText = values[0].text; | |
self.state.set('value', values[0].value); | |
} | |
self.state.set('menu', values); | |
} | |
self.state.set('text', settings.text || selectedText); | |
self.classes.add('listbox'); | |
self.on('select', function (e) { | |
var ctrl = e.control; | |
if (lastItemCtrl) { | |
e.lastControl = lastItemCtrl; | |
} | |
if (settings.multiple) { | |
ctrl.active(!ctrl.active()); | |
} else { | |
self.value(e.control.value()); | |
} | |
lastItemCtrl = ctrl; | |
}); | |
}, | |
/** | |
* Getter/setter function for the control value. | |
* | |
* @method value | |
* @param {String} [value] Value to be set. | |
* @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation. | |
*/ | |
bindStates: function () { | |
var self = this; | |
function activateMenuItemsByValue(menu, value) { | |
if (menu instanceof Menu) { | |
menu.items().each(function (ctrl) { | |
if (!ctrl.hasMenus()) { | |
ctrl.active(ctrl.value() === value); | |
} | |
}); | |
} | |
} | |
function getSelectedItem(menuValues, value) { | |
var selectedItem; | |
if (!menuValues) { | |
return; | |
} | |
for (var i = 0; i < menuValues.length; i++) { | |
if (menuValues[i].value === value) { | |
return menuValues[i]; | |
} | |
if (menuValues[i].menu) { | |
selectedItem = getSelectedItem(menuValues[i].menu, value); | |
if (selectedItem) { | |
return selectedItem; | |
} | |
} | |
} | |
} | |
self.on('show', function (e) { | |
activateMenuItemsByValue(e.control, self.value()); | |
}); | |
self.state.on('change:value', function (e) { | |
var selectedItem = getSelectedItem(self.state.get('menu'), e.value); | |
if (selectedItem) { | |
self.text(selectedItem.text); | |
} else { | |
self.text(self.settings.text); | |
} | |
}); | |
return self._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* Radio.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new radio button. | |
* | |
* @-x-less Radio.less | |
* @class tinymce.ui.Radio | |
* @extends tinymce.ui.Checkbox | |
*/ | |
define( | |
'tinymce.ui.Radio', | |
[ | |
"tinymce.ui.Checkbox" | |
], | |
function (Checkbox) { | |
"use strict"; | |
return Checkbox.extend({ | |
Defaults: { | |
classes: "radio", | |
role: "radio" | |
} | |
}); | |
} | |
); | |
/** | |
* ResizeHandle.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events. | |
* | |
* @-x-less ResizeHandle.less | |
* @class tinymce.ui.ResizeHandle | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.ResizeHandle', | |
[ | |
"tinymce.ui.Widget", | |
"tinymce.ui.DragHelper" | |
], | |
function (Widget, DragHelper) { | |
"use strict"; | |
return Widget.extend({ | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, prefix = self.classPrefix; | |
self.classes.add('resizehandle'); | |
if (self.settings.direction == "both") { | |
self.classes.add('resizehandle-both'); | |
} | |
self.canFocus = false; | |
return ( | |
'<div id="' + self._id + '" class="' + self.classes + '">' + | |
'<i class="' + prefix + 'ico ' + prefix + 'i-resize"></i>' + | |
'</div>' | |
); | |
}, | |
/** | |
* Called after the control has been rendered. | |
* | |
* @method postRender | |
*/ | |
postRender: function () { | |
var self = this; | |
self._super(); | |
self.resizeDragHelper = new DragHelper(this._id, { | |
start: function () { | |
self.fire('ResizeStart'); | |
}, | |
drag: function (e) { | |
if (self.settings.direction != "both") { | |
e.deltaX = 0; | |
} | |
self.fire('Resize', e); | |
}, | |
stop: function () { | |
self.fire('ResizeEnd'); | |
} | |
}); | |
}, | |
remove: function () { | |
if (this.resizeDragHelper) { | |
this.resizeDragHelper.destroy(); | |
} | |
return this._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* SelectBox.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new select box control. | |
* | |
* @-x-less SelectBox.less | |
* @class tinymce.ui.SelectBox | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.SelectBox', | |
[ | |
"tinymce.ui.Widget" | |
], | |
function (Widget) { | |
"use strict"; | |
function createOptions(options) { | |
var strOptions = ''; | |
if (options) { | |
for (var i = 0; i < options.length; i++) { | |
strOptions += '<option value="' + options[i] + '">' + options[i] + '</option>'; | |
} | |
} | |
return strOptions; | |
} | |
return Widget.extend({ | |
Defaults: { | |
classes: "selectbox", | |
role: "selectbox", | |
options: [] | |
}, | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {Array} options Array with options to add to the select box. | |
*/ | |
init: function (settings) { | |
var self = this; | |
self._super(settings); | |
if (self.settings.size) { | |
self.size = self.settings.size; | |
} | |
if (self.settings.options) { | |
self._options = self.settings.options; | |
} | |
self.on('keydown', function (e) { | |
var rootControl; | |
if (e.keyCode == 13) { | |
e.preventDefault(); | |
// Find root control that we can do toJSON on | |
self.parents().reverse().each(function (ctrl) { | |
if (ctrl.toJSON) { | |
rootControl = ctrl; | |
return false; | |
} | |
}); | |
// Fire event on current text box with the serialized data of the whole form | |
self.fire('submit', { data: rootControl.toJSON() }); | |
} | |
}); | |
}, | |
/** | |
* Getter/setter function for the options state. | |
* | |
* @method options | |
* @param {Array} [state] State to be set. | |
* @return {Array|tinymce.ui.SelectBox} Array of string options. | |
*/ | |
options: function (state) { | |
if (!arguments.length) { | |
return this.state.get('options'); | |
} | |
this.state.set('options', state); | |
return this; | |
}, | |
renderHtml: function () { | |
var self = this, options, size = ''; | |
options = createOptions(self._options); | |
if (self.size) { | |
size = ' size = "' + self.size + '"'; | |
} | |
return ( | |
'<select id="' + self._id + '" class="' + self.classes + '"' + size + '>' + | |
options + | |
'</select>' | |
); | |
}, | |
bindStates: function () { | |
var self = this; | |
self.state.on('change:options', function (e) { | |
self.getEl().innerHTML = createOptions(e.value); | |
}); | |
return self._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* Slider.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Slider control. | |
* | |
* @-x-less Slider.less | |
* @class tinymce.ui.Slider | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.Slider', | |
[ | |
"tinymce.ui.Widget", | |
"tinymce.ui.DragHelper", | |
"tinymce.ui.DomUtils" | |
], | |
function (Widget, DragHelper, DomUtils) { | |
"use strict"; | |
function constrain(value, minVal, maxVal) { | |
if (value < minVal) { | |
value = minVal; | |
} | |
if (value > maxVal) { | |
value = maxVal; | |
} | |
return value; | |
} | |
function setAriaProp(el, name, value) { | |
el.setAttribute('aria-' + name, value); | |
} | |
function updateSliderHandle(ctrl, value) { | |
var maxHandlePos, shortSizeName, sizeName, stylePosName, styleValue, handleEl; | |
if (ctrl.settings.orientation == "v") { | |
stylePosName = "top"; | |
sizeName = "height"; | |
shortSizeName = "h"; | |
} else { | |
stylePosName = "left"; | |
sizeName = "width"; | |
shortSizeName = "w"; | |
} | |
handleEl = ctrl.getEl('handle'); | |
maxHandlePos = (ctrl.layoutRect()[shortSizeName] || 100) - DomUtils.getSize(handleEl)[sizeName]; | |
styleValue = (maxHandlePos * ((value - ctrl._minValue) / (ctrl._maxValue - ctrl._minValue))) + 'px'; | |
handleEl.style[stylePosName] = styleValue; | |
handleEl.style.height = ctrl.layoutRect().h + 'px'; | |
setAriaProp(handleEl, 'valuenow', value); | |
setAriaProp(handleEl, 'valuetext', '' + ctrl.settings.previewFilter(value)); | |
setAriaProp(handleEl, 'valuemin', ctrl._minValue); | |
setAriaProp(handleEl, 'valuemax', ctrl._maxValue); | |
} | |
return Widget.extend({ | |
init: function (settings) { | |
var self = this; | |
if (!settings.previewFilter) { | |
settings.previewFilter = function (value) { | |
return Math.round(value * 100) / 100.0; | |
}; | |
} | |
self._super(settings); | |
self.classes.add('slider'); | |
if (settings.orientation == "v") { | |
self.classes.add('vertical'); | |
} | |
self._minValue = settings.minValue || 0; | |
self._maxValue = settings.maxValue || 100; | |
self._initValue = self.state.get('value'); | |
}, | |
renderHtml: function () { | |
var self = this, id = self._id, prefix = self.classPrefix; | |
return ( | |
'<div id="' + id + '" class="' + self.classes + '">' + | |
'<div id="' + id + '-handle" class="' + prefix + 'slider-handle" role="slider" tabindex="-1"></div>' + | |
'</div>' | |
); | |
}, | |
reset: function () { | |
this.value(this._initValue).repaint(); | |
}, | |
postRender: function () { | |
var self = this, minValue, maxValue, screenCordName, | |
stylePosName, sizeName, shortSizeName; | |
function toFraction(min, max, val) { | |
return (val + min) / (max - min); | |
} | |
function fromFraction(min, max, val) { | |
return (val * (max - min)) - min; | |
} | |
function handleKeyboard(minValue, maxValue) { | |
function alter(delta) { | |
var value; | |
value = self.value(); | |
value = fromFraction(minValue, maxValue, toFraction(minValue, maxValue, value) + (delta * 0.05)); | |
value = constrain(value, minValue, maxValue); | |
self.value(value); | |
self.fire('dragstart', { value: value }); | |
self.fire('drag', { value: value }); | |
self.fire('dragend', { value: value }); | |
} | |
self.on('keydown', function (e) { | |
switch (e.keyCode) { | |
case 37: | |
case 38: | |
alter(-1); | |
break; | |
case 39: | |
case 40: | |
alter(1); | |
break; | |
} | |
}); | |
} | |
function handleDrag(minValue, maxValue, handleEl) { | |
var startPos, startHandlePos, maxHandlePos, handlePos, value; | |
self._dragHelper = new DragHelper(self._id, { | |
handle: self._id + "-handle", | |
start: function (e) { | |
startPos = e[screenCordName]; | |
startHandlePos = parseInt(self.getEl('handle').style[stylePosName], 10); | |
maxHandlePos = (self.layoutRect()[shortSizeName] || 100) - DomUtils.getSize(handleEl)[sizeName]; | |
self.fire('dragstart', { value: value }); | |
}, | |
drag: function (e) { | |
var delta = e[screenCordName] - startPos; | |
handlePos = constrain(startHandlePos + delta, 0, maxHandlePos); | |
handleEl.style[stylePosName] = handlePos + 'px'; | |
value = minValue + (handlePos / maxHandlePos) * (maxValue - minValue); | |
self.value(value); | |
self.tooltip().text('' + self.settings.previewFilter(value)).show().moveRel(handleEl, 'bc tc'); | |
self.fire('drag', { value: value }); | |
}, | |
stop: function () { | |
self.tooltip().hide(); | |
self.fire('dragend', { value: value }); | |
} | |
}); | |
} | |
minValue = self._minValue; | |
maxValue = self._maxValue; | |
if (self.settings.orientation == "v") { | |
screenCordName = "screenY"; | |
stylePosName = "top"; | |
sizeName = "height"; | |
shortSizeName = "h"; | |
} else { | |
screenCordName = "screenX"; | |
stylePosName = "left"; | |
sizeName = "width"; | |
shortSizeName = "w"; | |
} | |
self._super(); | |
handleKeyboard(minValue, maxValue, self.getEl('handle')); | |
handleDrag(minValue, maxValue, self.getEl('handle')); | |
}, | |
repaint: function () { | |
this._super(); | |
updateSliderHandle(this, this.value()); | |
}, | |
bindStates: function () { | |
var self = this; | |
self.state.on('change:value', function (e) { | |
updateSliderHandle(self, e.value); | |
}); | |
return self._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* Spacer.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a spacer. This control is used in flex layouts for example. | |
* | |
* @-x-less Spacer.less | |
* @class tinymce.ui.Spacer | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.Spacer', | |
[ | |
"tinymce.ui.Widget" | |
], | |
function (Widget) { | |
"use strict"; | |
return Widget.extend({ | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this; | |
self.classes.add('spacer'); | |
self.canFocus = false; | |
return '<div id="' + self._id + '" class="' + self.classes + '"></div>'; | |
} | |
}); | |
} | |
); | |
/** | |
* SplitButton.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a split button. | |
* | |
* @-x-less SplitButton.less | |
* @class tinymce.ui.SplitButton | |
* @extends tinymce.ui.Button | |
*/ | |
define( | |
'tinymce.ui.SplitButton', | |
[ | |
'global!window', | |
'tinymce.core.dom.DomQuery', | |
'tinymce.ui.DomUtils', | |
'tinymce.ui.MenuButton' | |
], | |
function (window, DomQuery, DomUtils, MenuButton) { | |
return MenuButton.extend({ | |
Defaults: { | |
classes: "widget btn splitbtn", | |
role: "button" | |
}, | |
/** | |
* Repaints the control after a layout operation. | |
* | |
* @method repaint | |
*/ | |
repaint: function () { | |
var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm; | |
self._super(); | |
mainButtonElm = elm.firstChild; | |
menuButtonElm = elm.lastChild; | |
DomQuery(mainButtonElm).css({ | |
width: rect.w - DomUtils.getSize(menuButtonElm).width, | |
height: rect.h - 2 | |
}); | |
DomQuery(menuButtonElm).css({ | |
height: rect.h - 2 | |
}); | |
return self; | |
}, | |
/** | |
* Sets the active menu state. | |
* | |
* @private | |
*/ | |
activeMenu: function (state) { | |
var self = this; | |
DomQuery(self.getEl().lastChild).toggleClass(self.classPrefix + 'active', state); | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, id = self._id, prefix = self.classPrefix, image; | |
var icon = self.state.get('icon'), text = self.state.get('text'); | |
var settings = self.settings, textHtml = '', ariaPressed; | |
image = settings.image; | |
if (image) { | |
icon = 'none'; | |
// Support for [high dpi, low dpi] image sources | |
if (typeof image != "string") { | |
image = window.getSelection ? image[0] : image[1]; | |
} | |
image = ' style="background-image: url(\'' + image + '\')"'; | |
} else { | |
image = ''; | |
} | |
icon = settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : ''; | |
if (text) { | |
self.classes.add('btn-has-text'); | |
textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>'; | |
} | |
ariaPressed = typeof settings.active === 'boolean' ? ' aria-pressed="' + settings.active + '"' : ''; | |
return ( | |
'<div id="' + id + '" class="' + self.classes + '" role="button"' + ariaPressed + ' tabindex="-1">' + | |
'<button type="button" hidefocus="1" tabindex="-1">' + | |
(icon ? '<i class="' + icon + '"' + image + '></i>' : '') + | |
textHtml + | |
'</button>' + | |
'<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' + | |
//(icon ? '<i class="' + icon + '"></i>' : '') + | |
(self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') + | |
' <i class="' + prefix + 'caret"></i>' + | |
'</button>' + | |
'</div>' | |
); | |
}, | |
/** | |
* Called after the control has been rendered. | |
* | |
* @method postRender | |
*/ | |
postRender: function () { | |
var self = this, onClickHandler = self.settings.onclick; | |
self.on('click', function (e) { | |
var node = e.target; | |
if (e.control == this) { | |
// Find clicks that is on the main button | |
while (node) { | |
if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) { | |
e.stopImmediatePropagation(); | |
if (onClickHandler) { | |
onClickHandler.call(this, e); | |
} | |
return; | |
} | |
node = node.parentNode; | |
} | |
} | |
}); | |
delete self.settings.onclick; | |
return self._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* StackLayout.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* This layout uses the browsers layout when the items are blocks. | |
* | |
* @-x-less StackLayout.less | |
* @class tinymce.ui.StackLayout | |
* @extends tinymce.ui.FlowLayout | |
*/ | |
define( | |
'tinymce.ui.StackLayout', | |
[ | |
"tinymce.ui.FlowLayout" | |
], | |
function (FlowLayout) { | |
"use strict"; | |
return FlowLayout.extend({ | |
Defaults: { | |
containerClass: 'stack-layout', | |
controlClass: 'stack-layout-item', | |
endClass: 'break' | |
}, | |
isNative: function () { | |
return true; | |
} | |
}); | |
} | |
); | |
/** | |
* TabPanel.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a tab panel control. | |
* | |
* @-x-less TabPanel.less | |
* @class tinymce.ui.TabPanel | |
* @extends tinymce.ui.Panel | |
* | |
* @setting {Number} activeTab Active tab index. | |
*/ | |
define( | |
'tinymce.ui.TabPanel', | |
[ | |
"tinymce.ui.Panel", | |
"tinymce.core.dom.DomQuery", | |
"tinymce.ui.DomUtils" | |
], | |
function (Panel, $, DomUtils) { | |
"use strict"; | |
return Panel.extend({ | |
Defaults: { | |
layout: 'absolute', | |
defaults: { | |
type: 'panel' | |
} | |
}, | |
/** | |
* Activates the specified tab by index. | |
* | |
* @method activateTab | |
* @param {Number} idx Index of the tab to activate. | |
*/ | |
activateTab: function (idx) { | |
var activeTabElm; | |
if (this.activeTabId) { | |
activeTabElm = this.getEl(this.activeTabId); | |
$(activeTabElm).removeClass(this.classPrefix + 'active'); | |
activeTabElm.setAttribute('aria-selected', "false"); | |
} | |
this.activeTabId = 't' + idx; | |
activeTabElm = this.getEl('t' + idx); | |
activeTabElm.setAttribute('aria-selected', "true"); | |
$(activeTabElm).addClass(this.classPrefix + 'active'); | |
this.items()[idx].show().fire('showtab'); | |
this.reflow(); | |
this.items().each(function (item, i) { | |
if (idx != i) { | |
item.hide(); | |
} | |
}); | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix; | |
self.preRender(); | |
layout.preRender(self); | |
self.items().each(function (ctrl, i) { | |
var id = self._id + '-t' + i; | |
ctrl.aria('role', 'tabpanel'); | |
ctrl.aria('labelledby', id); | |
tabsHtml += ( | |
'<div id="' + id + '" class="' + prefix + 'tab" ' + | |
'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' + | |
self.encode(ctrl.settings.title) + | |
'</div>' | |
); | |
}); | |
return ( | |
'<div id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1">' + | |
'<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' + | |
tabsHtml + | |
'</div>' + | |
'<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' + | |
layout.renderHtml(self) + | |
'</div>' + | |
'</div>' | |
); | |
}, | |
/** | |
* Called after the control has been rendered. | |
* | |
* @method postRender | |
*/ | |
postRender: function () { | |
var self = this; | |
self._super(); | |
self.settings.activeTab = self.settings.activeTab || 0; | |
self.activateTab(self.settings.activeTab); | |
this.on('click', function (e) { | |
var targetParent = e.target.parentNode; | |
if (targetParent && targetParent.id == self._id + '-head') { | |
var i = targetParent.childNodes.length; | |
while (i--) { | |
if (targetParent.childNodes[i] == e.target) { | |
self.activateTab(i); | |
} | |
} | |
} | |
}); | |
}, | |
/** | |
* Initializes the current controls layout rect. | |
* This will be executed by the layout managers to determine the | |
* default minWidth/minHeight etc. | |
* | |
* @method initLayoutRect | |
* @return {Object} Layout rect instance. | |
*/ | |
initLayoutRect: function () { | |
var self = this, rect, minW, minH; | |
minW = DomUtils.getSize(self.getEl('head')).width; | |
minW = minW < 0 ? 0 : minW; | |
minH = 0; | |
self.items().each(function (item) { | |
minW = Math.max(minW, item.layoutRect().minW); | |
minH = Math.max(minH, item.layoutRect().minH); | |
}); | |
self.items().each(function (ctrl) { | |
ctrl.settings.x = 0; | |
ctrl.settings.y = 0; | |
ctrl.settings.w = minW; | |
ctrl.settings.h = minH; | |
ctrl.layoutRect({ | |
x: 0, | |
y: 0, | |
w: minW, | |
h: minH | |
}); | |
}); | |
var headH = DomUtils.getSize(self.getEl('head')).height; | |
self.settings.minWidth = minW; | |
self.settings.minHeight = minH + headH; | |
rect = self._super(); | |
rect.deltaH += headH; | |
rect.innerH = rect.h - rect.deltaH; | |
return rect; | |
} | |
}); | |
} | |
); | |
/** | |
* TextBox.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
/** | |
* Creates a new textbox. | |
* | |
* @-x-less TextBox.less | |
* @class tinymce.ui.TextBox | |
* @extends tinymce.ui.Widget | |
*/ | |
define( | |
'tinymce.ui.TextBox', | |
[ | |
'global!document', | |
'tinymce.core.util.Tools', | |
'tinymce.ui.DomUtils', | |
'tinymce.ui.Widget' | |
], | |
function (document, Tools, DomUtils, Widget) { | |
return Widget.extend({ | |
/** | |
* Constructs a instance with the specified settings. | |
* | |
* @constructor | |
* @param {Object} settings Name/value object with settings. | |
* @setting {Boolean} multiline True if the textbox is a multiline control. | |
* @setting {Number} maxLength Max length for the textbox. | |
* @setting {Number} size Size of the textbox in characters. | |
*/ | |
init: function (settings) { | |
var self = this; | |
self._super(settings); | |
self.classes.add('textbox'); | |
if (settings.multiline) { | |
self.classes.add('multiline'); | |
} else { | |
self.on('keydown', function (e) { | |
var rootControl; | |
if (e.keyCode == 13) { | |
e.preventDefault(); | |
// Find root control that we can do toJSON on | |
self.parents().reverse().each(function (ctrl) { | |
if (ctrl.toJSON) { | |
rootControl = ctrl; | |
return false; | |
} | |
}); | |
// Fire event on current text box with the serialized data of the whole form | |
self.fire('submit', { data: rootControl.toJSON() }); | |
} | |
}); | |
self.on('keyup', function (e) { | |
self.state.set('value', e.target.value); | |
}); | |
} | |
}, | |
/** | |
* Repaints the control after a layout operation. | |
* | |
* @method repaint | |
*/ | |
repaint: function () { | |
var self = this, style, rect, borderBox, borderW, borderH = 0, lastRepaintRect; | |
style = self.getEl().style; | |
rect = self._layoutRect; | |
lastRepaintRect = self._lastRepaintRect || {}; | |
// Detect old IE 7+8 add lineHeight to align caret vertically in the middle | |
var doc = document; | |
if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) { | |
style.lineHeight = (rect.h - borderH) + 'px'; | |
} | |
borderBox = self.borderBox; | |
borderW = borderBox.left + borderBox.right + 8; | |
borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0); | |
if (rect.x !== lastRepaintRect.x) { | |
style.left = rect.x + 'px'; | |
lastRepaintRect.x = rect.x; | |
} | |
if (rect.y !== lastRepaintRect.y) { | |
style.top = rect.y + 'px'; | |
lastRepaintRect.y = rect.y; | |
} | |
if (rect.w !== lastRepaintRect.w) { | |
style.width = (rect.w - borderW) + 'px'; | |
lastRepaintRect.w = rect.w; | |
} | |
if (rect.h !== lastRepaintRect.h) { | |
style.height = (rect.h - borderH) + 'px'; | |
lastRepaintRect.h = rect.h; | |
} | |
self._lastRepaintRect = lastRepaintRect; | |
self.fire('repaint', {}, false); | |
return self; | |
}, | |
/** | |
* Renders the control as a HTML string. | |
* | |
* @method renderHtml | |
* @return {String} HTML representing the control. | |
*/ | |
renderHtml: function () { | |
var self = this, settings = self.settings, attrs, elm; | |
attrs = { | |
id: self._id, | |
hidefocus: '1' | |
}; | |
Tools.each([ | |
'rows', 'spellcheck', 'maxLength', 'size', 'readonly', 'min', | |
'max', 'step', 'list', 'pattern', 'placeholder', 'required', 'multiple' | |
], function (name) { | |
attrs[name] = settings[name]; | |
}); | |
if (self.disabled()) { | |
attrs.disabled = 'disabled'; | |
} | |
if (settings.subtype) { | |
attrs.type = settings.subtype; | |
} | |
elm = DomUtils.create(settings.multiline ? 'textarea' : 'input', attrs); | |
elm.value = self.state.get('value'); | |
elm.className = self.classes; | |
return elm.outerHTML; | |
}, | |
value: function (value) { | |
if (arguments.length) { | |
this.state.set('value', value); | |
return this; | |
} | |
// Make sure the real state is in sync | |
if (this.state.get('rendered')) { | |
this.state.set('value', this.getEl().value); | |
} | |
return this.state.get('value'); | |
}, | |
/** | |
* Called after the control has been rendered. | |
* | |
* @method postRender | |
*/ | |
postRender: function () { | |
var self = this; | |
self.getEl().value = self.state.get('value'); | |
self._super(); | |
self.$el.on('change', function (e) { | |
self.state.set('value', e.target.value); | |
self.fire('change', e); | |
}); | |
}, | |
bindStates: function () { | |
var self = this; | |
self.state.on('change:value', function (e) { | |
if (self.getEl().value != e.value) { | |
self.getEl().value = e.value; | |
} | |
}); | |
self.state.on('change:disabled', function (e) { | |
self.getEl().disabled = e.value; | |
}); | |
return self._super(); | |
}, | |
remove: function () { | |
this.$el.off(); | |
this._super(); | |
} | |
}); | |
} | |
); | |
/** | |
* Api.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.ui.Api', | |
[ | |
'tinymce.core.ui.Factory', | |
'tinymce.core.util.Tools', | |
'tinymce.ui.AbsoluteLayout', | |
'tinymce.ui.BrowseButton', | |
'tinymce.ui.Button', | |
'tinymce.ui.ButtonGroup', | |
'tinymce.ui.Checkbox', | |
'tinymce.ui.Collection', | |
'tinymce.ui.ColorBox', | |
'tinymce.ui.ColorButton', | |
'tinymce.ui.ColorPicker', | |
'tinymce.ui.ComboBox', | |
'tinymce.ui.Container', | |
'tinymce.ui.Control', | |
'tinymce.ui.DragHelper', | |
'tinymce.ui.DropZone', | |
'tinymce.ui.ElementPath', | |
'tinymce.ui.FieldSet', | |
'tinymce.ui.FilePicker', | |
'tinymce.ui.FitLayout', | |
'tinymce.ui.FlexLayout', | |
'tinymce.ui.FloatPanel', | |
'tinymce.ui.FlowLayout', | |
'tinymce.ui.Form', | |
'tinymce.ui.FormatControls', | |
'tinymce.ui.FormItem', | |
'tinymce.ui.GridLayout', | |
'tinymce.ui.Iframe', | |
'tinymce.ui.InfoBox', | |
'tinymce.ui.KeyboardNavigation', | |
'tinymce.ui.Label', | |
'tinymce.ui.Layout', | |
'tinymce.ui.ListBox', | |
'tinymce.ui.Menu', | |
'tinymce.ui.MenuBar', | |
'tinymce.ui.MenuButton', | |
'tinymce.ui.MenuItem', | |
'tinymce.ui.MessageBox', | |
'tinymce.ui.Movable', | |
'tinymce.ui.Notification', | |
'tinymce.ui.Panel', | |
'tinymce.ui.PanelButton', | |
'tinymce.ui.Path', | |
'tinymce.ui.Progress', | |
'tinymce.ui.Radio', | |
'tinymce.ui.ReflowQueue', | |
'tinymce.ui.Resizable', | |
'tinymce.ui.ResizeHandle', | |
'tinymce.ui.Scrollable', | |
'tinymce.ui.SelectBox', | |
'tinymce.ui.Selector', | |
'tinymce.ui.Slider', | |
'tinymce.ui.Spacer', | |
'tinymce.ui.SplitButton', | |
'tinymce.ui.StackLayout', | |
'tinymce.ui.TabPanel', | |
'tinymce.ui.TextBox', | |
'tinymce.ui.Throbber', | |
'tinymce.ui.Toolbar', | |
'tinymce.ui.Tooltip', | |
'tinymce.ui.Widget', | |
'tinymce.ui.Window' | |
], | |
function ( | |
Factory, Tools, AbsoluteLayout, BrowseButton, Button, ButtonGroup, Checkbox, Collection, ColorBox, ColorButton, ColorPicker, ComboBox, Container, Control, | |
DragHelper, DropZone, ElementPath, FieldSet, FilePicker, FitLayout, FlexLayout, FloatPanel, FlowLayout, Form, FormatControls, FormItem, GridLayout, Iframe, | |
InfoBox, KeyboardNavigation, Label, Layout, ListBox, Menu, MenuBar, MenuButton, MenuItem, MessageBox, Movable, Notification, Panel, PanelButton, Path, Progress, | |
Radio, ReflowQueue, Resizable, ResizeHandle, Scrollable, SelectBox, Selector, Slider, Spacer, SplitButton, StackLayout, TabPanel, TextBox, Throbber, Toolbar, | |
Tooltip, Widget, Window | |
) { | |
var getApi = function () { | |
return { | |
Selector: Selector, | |
Collection: Collection, | |
ReflowQueue: ReflowQueue, | |
Control: Control, | |
Factory: Factory, | |
KeyboardNavigation: KeyboardNavigation, | |
Container: Container, | |
DragHelper: DragHelper, | |
Scrollable: Scrollable, | |
Panel: Panel, | |
Movable: Movable, | |
Resizable: Resizable, | |
FloatPanel: FloatPanel, | |
Window: Window, | |
MessageBox: MessageBox, | |
Tooltip: Tooltip, | |
Widget: Widget, | |
Progress: Progress, | |
Notification: Notification, | |
Layout: Layout, | |
AbsoluteLayout: AbsoluteLayout, | |
Button: Button, | |
ButtonGroup: ButtonGroup, | |
Checkbox: Checkbox, | |
ComboBox: ComboBox, | |
ColorBox: ColorBox, | |
PanelButton: PanelButton, | |
ColorButton: ColorButton, | |
ColorPicker: ColorPicker, | |
Path: Path, | |
ElementPath: ElementPath, | |
FormItem: FormItem, | |
Form: Form, | |
FieldSet: FieldSet, | |
FilePicker: FilePicker, | |
FitLayout: FitLayout, | |
FlexLayout: FlexLayout, | |
FlowLayout: FlowLayout, | |
FormatControls: FormatControls, | |
GridLayout: GridLayout, | |
Iframe: Iframe, | |
InfoBox: InfoBox, | |
Label: Label, | |
Toolbar: Toolbar, | |
MenuBar: MenuBar, | |
MenuButton: MenuButton, | |
MenuItem: MenuItem, | |
Throbber: Throbber, | |
Menu: Menu, | |
ListBox: ListBox, | |
Radio: Radio, | |
ResizeHandle: ResizeHandle, | |
SelectBox: SelectBox, | |
Slider: Slider, | |
Spacer: Spacer, | |
SplitButton: SplitButton, | |
StackLayout: StackLayout, | |
TabPanel: TabPanel, | |
TextBox: TextBox, | |
DropZone: DropZone, | |
BrowseButton: BrowseButton | |
}; | |
}; | |
var appendTo = function (target) { | |
if (target.ui) { | |
Tools.each(getApi(), function (ref, key) { | |
target.ui[key] = ref; | |
}); | |
} else { | |
target.ui = getApi(); | |
} | |
}; | |
var registerToFactory = function () { | |
Tools.each(getApi(), function (ref, key) { | |
Factory.add(key, ref); | |
}); | |
}; | |
var Api = { | |
appendTo: appendTo, | |
registerToFactory: registerToFactory | |
}; | |
return Api; | |
} | |
); | |
/** | |
* Theme.js | |
* | |
* Released under LGPL License. | |
* Copyright (c) 1999-2016 Ephox Corp. All rights reserved | |
* | |
* License: http://www.tinymce.com/license | |
* Contributing: http://www.tinymce.com/contributing | |
*/ | |
define( | |
'tinymce.themes.modern.Theme', | |
[ | |
'global!window', | |
'tinymce.core.ThemeManager', | |
'tinymce.themes.modern.api.ThemeApi', | |
'tinymce.ui.Api', | |
'tinymce.ui.FormatControls' | |
], | |
function (window, ThemeManager, ThemeApi, Api, FormatControls) { | |
Api.registerToFactory(); | |
Api.appendTo(window.tinymce ? window.tinymce : {}); | |
ThemeManager.add('modern', function (editor) { | |
FormatControls.setup(editor); | |
return ThemeApi.get(editor); | |
}); | |
return function () { }; | |
} | |
); | |
dem('tinymce.themes.modern.Theme')(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment