/**
* theme.js
*
- * Copyright, Moxiecode Systems AB
* Released under LGPL License.
+ * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
/*global tinymce:true */
tinymce.ThemeManager.add('modern', function(editor) {
- var self = this, settings = editor.settings, Factory = tinymce.ui.Factory, each = tinymce.each, DOM = tinymce.DOM;
+ var self = this, settings = editor.settings, Factory = tinymce.ui.Factory,
+ each = tinymce.each, DOM = tinymce.DOM, Rect = tinymce.ui.Rect, FloatPanel = tinymce.ui.FloatPanel;
// Default menus
var defaultMenus = {
var defaultToolbar = "undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | " +
"bullist numlist outdent indent | link image";
- /**
- * Creates the toolbars from config and returns a toolbar array.
- *
- * @return {Array} Array with toolbars.
- */
- function createToolbars() {
- var toolbars = [];
-
- function addToolbar(items) {
- var toolbarItems = [], buttonGroup;
+ function createToolbar(items) {
+ var toolbarItems = [], buttonGroup;
- if (!items) {
- return;
- }
+ if (!items) {
+ return;
+ }
- each(items.split(/[ ,]/), function(item) {
- var itemName;
+ each(items.split(/[ ,]/), function(item) {
+ var itemName;
- function bindSelectorChanged() {
- var selection = editor.selection;
+ function bindSelectorChanged() {
+ var selection = editor.selection;
- if (itemName == "bullist") {
- selection.selectorChanged('ul > li', function(state, args) {
- var nodeName, i = args.parents.length;
+ if (itemName == "bullist") {
+ selection.selectorChanged('ul > li', function(state, args) {
+ var nodeName, i = args.parents.length;
- while (i--) {
- nodeName = args.parents[i].nodeName;
- if (nodeName == "OL" || nodeName == "UL") {
- break;
- }
+ while (i--) {
+ nodeName = args.parents[i].nodeName;
+ if (nodeName == "OL" || nodeName == "UL") {
+ break;
}
+ }
- item.active(state && nodeName == "UL");
- });
- }
+ item.active(state && nodeName == "UL");
+ });
+ }
- if (itemName == "numlist") {
- selection.selectorChanged('ol > li', function(state, args) {
- var nodeName, i = args.parents.length;
+ if (itemName == "numlist") {
+ selection.selectorChanged('ol > li', function(state, args) {
+ var nodeName, i = args.parents.length;
- while (i--) {
- nodeName = args.parents[i].nodeName;
- if (nodeName == "OL" || nodeName == "UL") {
- break;
- }
+ while (i--) {
+ nodeName = args.parents[i].nodeName;
+ if (nodeName == "OL" || nodeName == "UL") {
+ break;
}
+ }
- item.active(state && nodeName == "OL");
- });
- }
+ item.active(state && nodeName == "OL");
+ });
+ }
- if (item.settings.stateSelector) {
- selection.selectorChanged(item.settings.stateSelector, function(state) {
- item.active(state);
- }, true);
- }
+ 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.settings.disabledStateSelector) {
+ selection.selectorChanged(item.settings.disabledStateSelector, function(state) {
+ item.disabled(state);
+ });
}
+ }
+
+ if (item == "|") {
+ buttonGroup = null;
+ } else {
+ if (Factory.has(item)) {
+ item = {type: item};
- if (item == "|") {
+ if (settings.toolbar_items_size) {
+ item.size = settings.toolbar_items_size;
+ }
+
+ toolbarItems.push(item);
buttonGroup = null;
} else {
- if (Factory.has(item)) {
- item = {type: item};
+ if (!buttonGroup) {
+ buttonGroup = {type: 'buttongroup', items: []};
+ toolbarItems.push(buttonGroup);
+ }
- if (settings.toolbar_items_size) {
- item.size = settings.toolbar_items_size;
- }
+ if (editor.buttons[item]) {
+ // TODO: Move control creation to some UI class
+ itemName = item;
+ item = editor.buttons[itemName];
- toolbarItems.push(item);
- buttonGroup = null;
- } else {
- if (!buttonGroup) {
- buttonGroup = {type: 'buttongroup', items: []};
- toolbarItems.push(buttonGroup);
+ if (typeof item == "function") {
+ item = item();
}
- 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.type = item.type || 'button';
- if (settings.toolbar_items_size) {
- item.size = settings.toolbar_items_size;
- }
+ if (settings.toolbar_items_size) {
+ item.size = settings.toolbar_items_size;
+ }
- item = Factory.create(item);
- buttonGroup.items.push(item);
+ item = Factory.create(item);
+ buttonGroup.items.push(item);
- if (editor.initialized) {
- bindSelectorChanged();
- } else {
- editor.on('init', bindSelectorChanged);
- }
+ if (editor.initialized) {
+ bindSelectorChanged();
+ } else {
+ editor.on('init', bindSelectorChanged);
}
}
}
- });
+ }
+ });
- toolbars.push({type: 'toolbar', layout: 'flow', items: toolbarItems});
+ return {
+ type: 'toolbar',
+ layout: 'flow',
+ items: toolbarItems
+ };
+ }
- return true;
+ /**
+ * Creates the toolbars from config and returns a toolbar array.
+ *
+ * @return {Array} Array with toolbars.
+ */
+ function createToolbars() {
+ var toolbars = [];
+
+ function addToolbar(items) {
+ if (items) {
+ toolbars.push(createToolbar(items));
+ return true;
+ }
}
// Convert toolbar array to multiple options
self.resizeTo(elm.clientWidth + dw, elm.clientHeight + dh);
}
+ /**
+ * Handles contextual toolbars.
+ */
+ function addContextualToolbars() {
+ var scrollContainer;
+
+ function getContextToolbars() {
+ return editor.contextToolbars || [];
+ }
+
+ function getElementRect(elm) {
+ var pos, targetRect, root;
+
+ pos = tinymce.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;
+ }
+
+ function hideAllFloatingPanels() {
+ each(editor.contextToolbars, function(toolbar) {
+ if (toolbar.panel) {
+ toolbar.panel.hide();
+ }
+ });
+ }
+
+ function reposition(match) {
+ var relPos, panelRect, elementRect, contentAreaRect, panel, relRect, testPositions;
+
+ if (editor.removed) {
+ return;
+ }
+
+ if (!match || !match.toolbar.panel) {
+ hideAllFloatingPanels();
+ return;
+ }
+
+ testPositions = [
+ 'tc-bc', 'bc-tc',
+ 'tl-bl', 'bl-tl',
+ 'tr-br', 'br-tr'
+ ];
+
+ panel = match.toolbar.panel;
+ panel.show();
+
+ elementRect = getElementRect(match.element);
+ panelRect = tinymce.DOM.getRect(panel.getEl());
+ contentAreaRect = tinymce.DOM.getRect(editor.getContentAreaContainer() || editor.getBody());
+
+ 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 = Rect.inflate(elementRect, 0, 7);
+ }
+
+ relPos = Rect.findBestRelativePosition(panelRect, elementRect, contentAreaRect, testPositions);
+
+ if (relPos) {
+ each(testPositions.concat('inside'), function(pos) {
+ panel.classes.toggle('tinymce-inline-' + pos, pos == relPos);
+ });
+
+ relRect = Rect.relativePosition(panelRect, elementRect, relPos);
+ panel.moveTo(relRect.x, relRect.y);
+ } else {
+ each(testPositions, function(pos) {
+ panel.classes.toggle('tinymce-inline-' + pos, false);
+ });
+
+ panel.classes.toggle('tinymce-inline-inside', true);
+
+ elementRect = Rect.intersect(contentAreaRect, elementRect);
+
+ if (elementRect) {
+ relPos = Rect.findBestRelativePosition(panelRect, elementRect, contentAreaRect, [
+ 'tc-tc', 'tl-tl', 'tr-tr'
+ ]);
+
+ if (relPos) {
+ relRect = Rect.relativePosition(panelRect, elementRect, relPos);
+ panel.moveTo(relRect.x, relRect.y);
+ } else {
+ panel.moveTo(elementRect.x, elementRect.y);
+ }
+ } else {
+ panel.hide();
+ }
+ }
+
+ //drawRect(contentAreaRect, 'blue');
+ //drawRect(elementRect, 'red');
+ //drawRect(panelRect, 'green');
+ }
+
+ function repositionHandler() {
+ function execute() {
+ if (editor.selection) {
+ reposition(findFrontMostMatch(editor.selection.getNode()));
+ }
+ }
+
+ if (window.requestAnimationFrame) {
+ window.requestAnimationFrame(execute);
+ } else {
+ execute();
+ }
+ }
+
+ function bindScrollEvent() {
+ if (!scrollContainer) {
+ scrollContainer = editor.selection.getScrollContainer() || editor.getWin();
+ tinymce.$(scrollContainer).on('scroll', repositionHandler);
+
+ editor.on('remove', function() {
+ tinymce.$(scrollContainer).off('scroll');
+ });
+ }
+ }
+
+ function showContextToolbar(match) {
+ var panel;
+
+ if (match.toolbar.panel) {
+ match.toolbar.panel.show();
+ reposition(match);
+ return;
+ }
+
+ bindScrollEvent();
+
+ panel = Factory.create({
+ type: 'floatpanel',
+ role: 'application',
+ classes: 'tinymce tinymce-inline',
+ layout: 'flex',
+ direction: 'column',
+ align: 'stretch',
+ autohide: false,
+ autofix: true,
+ fixed: true,
+ border: 1,
+ items: createToolbar(match.toolbar.items)
+ });
+
+ match.toolbar.panel = panel;
+ panel.renderTo(document.body).reflow();
+ reposition(match);
+ }
+
+ function hideAllContextToolbars() {
+ tinymce.each(getContextToolbars(), function(toolbar) {
+ if (toolbar.panel) {
+ toolbar.panel.hide();
+ }
+ });
+ }
+
+ function findFrontMostMatch(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 blur', function() {
+ // Needs to be delayed to avoid Chrome img focus out bug
+ window.setTimeout(function() {
+ var match;
+
+ if (editor.removed) {
+ return;
+ }
+
+ match = findFrontMostMatch(editor.selection.getNode());
+ if (match) {
+ showContextToolbar(match);
+ } else {
+ hideAllContextToolbars();
+ }
+ }, 0);
+ });
+
+ editor.on('ObjectResizeStart', function() {
+ var match = findFrontMostMatch(editor.selection.getNode());
+
+ if (match && match.toolbar.panel) {
+ match.toolbar.panel.hide();
+ }
+ });
+
+ editor.on('nodeChange ResizeEditor ResizeWindow', repositionHandler);
+
+ editor.on('remove', function() {
+ tinymce.each(getContextToolbars(), function(toolbar) {
+ if (toolbar.panel) {
+ toolbar.panel.remove();
+ }
+ });
+
+ editor.contextToolbars = {};
+ });
+ }
+
/**
* Renders the inline editor UI.
*
function hide() {
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');
}
}
addAccessibilityKeys(panel);
show();
+ addContextualToolbars();
editor.on('nodeChange', reposition);
editor.on('activate', show);
panel = null;
});
- // Add accesibility shortkuts
+ // Add accesibility shortcuts
addAccessibilityKeys(panel);
+ addContextualToolbars();
return {
iframeContainer: panel.find('#iframe')[0].getEl(),