| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- /**
- * ControlSelection.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class handles control selection of elements. Controls are elements
- * that can be resized and needs to be selected as a whole. It adds custom resize handles
- * to all browser engines that support properly disabling the built in resize logic.
- *
- * @class tinymce.dom.ControlSelection
- */
- define("tinymce/dom/ControlSelection", [
- "tinymce/util/VK",
- "tinymce/util/Tools",
- "tinymce/Env"
- ], function(VK, Tools, Env) {
- return function(selection, editor) {
- var dom = editor.dom, each = Tools.each;
- var selectedElm, selectedElmGhost, resizeHandles, selectedHandle, lastMouseDownEvent;
- var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
- var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11;
- // Details about each resize handle how to scale etc
- resizeHandles = {
- // Name: x multiplier, y multiplier, delta size x, delta size y
- n: [0.5, 0, 0, -1],
- e: [1, 0.5, 1, 0],
- s: [0.5, 1, 0, 1],
- w: [0, 0.5, -1, 0],
- nw: [0, 0, -1, -1],
- ne: [1, 0, 1, -1],
- se: [1, 1, 1, 1],
- sw: [0, 1, -1, 1]
- };
- // Add CSS for resize handles, cloned element and selected
- var rootClass = '.mce-content-body';
- editor.contentStyles.push(
- rootClass + ' div.mce-resizehandle {' +
- 'position: absolute;' +
- 'border: 1px solid black;' +
- 'background: #FFF;' +
- 'width: 5px;' +
- 'height: 5px;' +
- 'z-index: 10000' +
- '}' +
- rootClass + ' .mce-resizehandle:hover {' +
- 'background: #000' +
- '}' +
- rootClass + ' img[data-mce-selected], hr[data-mce-selected] {' +
- 'outline: 1px solid black;' +
- 'resize: none' + // Have been talks about implementing this in browsers
- '}' +
- rootClass + ' .mce-clonedresizable {' +
- 'position: absolute;' +
- (Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing
- 'opacity: .5;' +
- 'filter: alpha(opacity=50);' +
- 'z-index: 10000' +
- '}'
- );
- function isResizable(elm) {
- var selector = editor.settings.object_resizing;
- if (selector === false || Env.iOS) {
- return false;
- }
- if (typeof selector != 'string') {
- selector = 'table,img,div';
- }
- if (elm.getAttribute('data-mce-resize') === 'false') {
- return false;
- }
- return editor.dom.is(elm, selector);
- }
- function resizeGhostElement(e) {
- var deltaX, deltaY;
- // Calc new width/height
- deltaX = e.screenX - startX;
- deltaY = e.screenY - startY;
- // Calc new size
- width = deltaX * selectedHandle[2] + startW;
- height = deltaY * selectedHandle[3] + startH;
- // Never scale down lower than 5 pixels
- width = width < 5 ? 5 : width;
- height = height < 5 ? 5 : height;
- // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image
- if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) {
- width = Math.round(height / ratio);
- height = Math.round(width * ratio);
- }
- // Update ghost size
- dom.setStyles(selectedElmGhost, {
- width: width,
- height: height
- });
- // Update ghost X position if needed
- if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
- dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
- }
- // Update ghost Y position if needed
- if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
- dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
- }
- if (!resizeStarted) {
- editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH});
- resizeStarted = true;
- }
- }
- function endGhostResize() {
- resizeStarted = false;
- function setSizeProp(name, value) {
- if (value) {
- // Resize by using style or attribute
- if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
- dom.setStyle(selectedElm, name, value);
- } else {
- dom.setAttrib(selectedElm, name, value);
- }
- }
- }
- // Set width/height properties
- setSizeProp('width', width);
- setSizeProp('height', height);
- dom.unbind(editableDoc, 'mousemove', resizeGhostElement);
- dom.unbind(editableDoc, 'mouseup', endGhostResize);
- if (rootDocument != editableDoc) {
- dom.unbind(rootDocument, 'mousemove', resizeGhostElement);
- dom.unbind(rootDocument, 'mouseup', endGhostResize);
- }
- // Remove ghost and update resize handle positions
- dom.remove(selectedElmGhost);
- if (!isIE || selectedElm.nodeName == "TABLE") {
- showResizeRect(selectedElm);
- }
- editor.fire('ObjectResized', {target: selectedElm, width: width, height: height});
- editor.nodeChanged();
- }
- function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
- var position, targetWidth, targetHeight, e, rect, offsetParent = editor.getBody();
- unbindResizeHandleEvents();
- // Get position and size of target
- position = dom.getPos(targetElm, offsetParent);
- selectedElmX = position.x;
- selectedElmY = position.y;
- rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
- targetWidth = rect.width || (rect.right - rect.left);
- targetHeight = rect.height || (rect.bottom - rect.top);
- // Reset width/height if user selects a new image/table
- if (selectedElm != targetElm) {
- detachResizeStartListener();
- selectedElm = targetElm;
- width = height = 0;
- }
- // Makes it possible to disable resizing
- e = editor.fire('ObjectSelected', {target: targetElm});
- if (isResizable(targetElm) && !e.isDefaultPrevented()) {
- each(resizeHandles, function(handle, name) {
- var handleElm, handlerContainerElm;
- function startDrag(e) {
- startX = e.screenX;
- startY = e.screenY;
- startW = selectedElm.clientWidth;
- startH = selectedElm.clientHeight;
- ratio = startH / startW;
- selectedHandle = handle;
- selectedElmGhost = selectedElm.cloneNode(true);
- dom.addClass(selectedElmGhost, 'mce-clonedresizable');
- selectedElmGhost.contentEditable = false; // Hides IE move layer cursor
- selectedElmGhost.unSelectabe = true;
- dom.setStyles(selectedElmGhost, {
- left: selectedElmX,
- top: selectedElmY,
- margin: 0
- });
- selectedElmGhost.removeAttribute('data-mce-selected');
- editor.getBody().appendChild(selectedElmGhost);
- dom.bind(editableDoc, 'mousemove', resizeGhostElement);
- dom.bind(editableDoc, 'mouseup', endGhostResize);
- if (rootDocument != editableDoc) {
- dom.bind(rootDocument, 'mousemove', resizeGhostElement);
- dom.bind(rootDocument, 'mouseup', endGhostResize);
- }
- }
- if (mouseDownHandleName) {
- // Drag started by IE native resizestart
- if (name == mouseDownHandleName) {
- startDrag(mouseDownEvent);
- }
- return;
- }
- // Get existing or render resize handle
- handleElm = dom.get('mceResizeHandle' + name);
- if (!handleElm) {
- handlerContainerElm = editor.getBody();
- handleElm = dom.add(handlerContainerElm, 'div', {
- id: 'mceResizeHandle' + name,
- 'data-mce-bogus': true,
- 'class': 'mce-resizehandle',
- unselectable: true,
- style: 'cursor:' + name + '-resize; margin:0; padding:0'
- });
- // Hides IE move layer cursor
- // If we set it on Chrome we get this wounderful bug: #6725
- if (Env.ie) {
- handleElm.contentEditable = false;
- }
- } else {
- dom.show(handleElm);
- }
- if (!handle.elm) {
- dom.bind(handleElm, 'mousedown', function(e) {
- e.stopImmediatePropagation();
- e.preventDefault();
- startDrag(e);
- });
- handle.elm = handleElm;
- }
- /*
- var halfHandleW = handleElm.offsetWidth / 2;
- var halfHandleH = handleElm.offsetHeight / 2;
- // Position element
- dom.setStyles(handleElm, {
- left: Math.floor((targetWidth * handle[0] + selectedElmX) - halfHandleW + (handle[2] * halfHandleW)),
- top: Math.floor((targetHeight * handle[1] + selectedElmY) - halfHandleH + (handle[3] * halfHandleH))
- });
- */
- // Position element
- dom.setStyles(handleElm, {
- left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
- top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
- });
- });
- } else {
- hideResizeRect();
- }
- selectedElm.setAttribute('data-mce-selected', '1');
- }
- function hideResizeRect() {
- var name, handleElm;
- unbindResizeHandleEvents();
- if (selectedElm) {
- selectedElm.removeAttribute('data-mce-selected');
- }
- for (name in resizeHandles) {
- handleElm = dom.get('mceResizeHandle' + name);
- if (handleElm) {
- dom.unbind(handleElm);
- dom.remove(handleElm);
- }
- }
- }
- function updateResizeRect(e) {
- var controlElm;
- function isChildOrEqual(node, parent) {
- if (node) {
- do {
- if (node === parent) {
- return true;
- }
- } while ((node = node.parentNode));
- }
- }
- // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
- each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) {
- img.removeAttribute('data-mce-selected');
- });
- controlElm = e.type == 'mousedown' ? e.target : selection.getNode();
- controlElm = dom.getParent(controlElm, isIE ? 'table' : 'table,img,hr');
- if (isChildOrEqual(controlElm, editor.getBody())) {
- disableGeckoResize();
- if (isChildOrEqual(selection.getStart(), controlElm) && isChildOrEqual(selection.getEnd(), controlElm)) {
- if (!isIE || (controlElm != selection.getStart() && selection.getStart().nodeName !== 'IMG')) {
- showResizeRect(controlElm);
- return;
- }
- }
- }
- hideResizeRect();
- }
- function attachEvent(elm, name, func) {
- if (elm && elm.attachEvent) {
- elm.attachEvent('on' + name, func);
- }
- }
- function detachEvent(elm, name, func) {
- if (elm && elm.detachEvent) {
- elm.detachEvent('on' + name, func);
- }
- }
- function resizeNativeStart(e) {
- var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY;
- pos = target.getBoundingClientRect();
- relativeX = lastMouseDownEvent.clientX - pos.left;
- relativeY = lastMouseDownEvent.clientY - pos.top;
- // Figure out what corner we are draging on
- for (name in resizeHandles) {
- corner = resizeHandles[name];
- cornerX = target.offsetWidth * corner[0];
- cornerY = target.offsetHeight * corner[1];
- if (Math.abs(cornerX - relativeX) < 8 && Math.abs(cornerY - relativeY) < 8) {
- selectedHandle = corner;
- break;
- }
- }
- // Remove native selection and let the magic begin
- resizeStarted = true;
- editor.getDoc().selection.empty();
- showResizeRect(target, name, lastMouseDownEvent);
- }
- function nativeControlSelect(e) {
- var target = e.srcElement;
- if (target != selectedElm) {
- detachResizeStartListener();
- if (target.id.indexOf('mceResizeHandle') === 0) {
- e.returnValue = false;
- return;
- }
- if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') {
- hideResizeRect();
- selectedElm = target;
- attachEvent(target, 'resizestart', resizeNativeStart);
- }
- }
- }
- function detachResizeStartListener() {
- detachEvent(selectedElm, 'resizestart', resizeNativeStart);
- }
- function unbindResizeHandleEvents() {
- for (var name in resizeHandles) {
- var handle = resizeHandles[name];
- if (handle.elm) {
- dom.unbind(handle.elm);
- delete handle.elm;
- }
- }
- }
- function disableGeckoResize() {
- try {
- // Disable object resizing on Gecko
- editor.getDoc().execCommand('enableObjectResizing', false, false);
- } catch (ex) {
- // Ignore
- }
- }
- function controlSelect(elm) {
- var ctrlRng;
- if (!isIE) {
- return;
- }
- ctrlRng = editableDoc.body.createControlRange();
- try {
- ctrlRng.addElement(elm);
- ctrlRng.select();
- return true;
- } catch (ex) {
- // Ignore since the element can't be control selected for example a P tag
- }
- }
- editor.on('init', function() {
- if (isIE) {
- // Hide the resize rect on resize and reselect the image
- editor.on('ObjectResized', function(e) {
- if (e.target.nodeName != 'TABLE') {
- hideResizeRect();
- controlSelect(e.target);
- }
- });
- attachEvent(editor.getBody(), 'controlselect', nativeControlSelect);
- editor.on('mousedown', function(e) {
- lastMouseDownEvent = e;
- });
- } else {
- disableGeckoResize();
- if (Env.ie >= 11) {
- // TODO: Drag/drop doesn't work
- editor.on('mouseup', function(e) {
- var nodeName = e.target.nodeName;
- if (/^(TABLE|IMG|HR)$/.test(nodeName)) {
- editor.selection.select(e.target, nodeName == 'TABLE');
- editor.nodeChanged();
- }
- });
- editor.dom.bind(editor.getBody(), 'mscontrolselect', function(e) {
- if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) {
- e.preventDefault();
- // This moves the selection from being a control selection to a text like selection like in WebKit #6753
- // TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections.
- if (e.target.tagName == 'IMG') {
- window.setTimeout(function() {
- editor.selection.select(e.target);
- }, 0);
- }
- }
- });
- }
- }
- editor.on('nodechange mousedown mouseup ResizeEditor', updateResizeRect);
- // Update resize rect while typing in a table
- editor.on('keydown keyup', function(e) {
- if (selectedElm && selectedElm.nodeName == "TABLE") {
- updateResizeRect(e);
- }
- });
- // Hide rect on focusout since it would float on top of windows otherwise
- //editor.on('focusout', hideResizeRect);
- });
- editor.on('remove', unbindResizeHandleEvents);
- function destroy() {
- selectedElm = selectedElmGhost = null;
- if (isIE) {
- detachResizeStartListener();
- detachEvent(editor.getBody(), 'controlselect', nativeControlSelect);
- }
- }
- return {
- isResizable: isResizable,
- showResizeRect: showResizeRect,
- hideResizeRect: hideResizeRect,
- updateResizeRect: updateResizeRect,
- controlSelect: controlSelect,
- destroy: destroy
- };
- };
- });
|