+ /**
+ * 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 = {};
+ });
+ }
+