'
).appendTo(containerElm);
Tools.each(blockers, function (blocker) {
$('#' + id, containerElm).append(
'
'
);
});
Tools.each(handles, function (handle) {
$('#' + id, containerElm).append(
'
'
);
});
dragHelpers = Tools.map(handles, createDragHelper);
repaint(currentRect);
$(containerElm).on('focusin focusout', function (e) {
$(e.target).attr('aria-grabbed', e.type === 'focus');
});
$(containerElm).on('keydown', function (e) {
var activeHandle;
Tools.each(handles, function (handle) {
if (e.target.id == id + '-' + handle.name) {
activeHandle = handle;
return false;
}
});
function moveAndBlock(evt, handle, startRect, deltaX, deltaY) {
evt.stopPropagation();
evt.preventDefault();
moveRect(activeHandle, startRect, deltaX, deltaY);
}
switch (e.keyCode) {
case VK.LEFT:
moveAndBlock(e, activeHandle, currentRect, -10, 0);
break;
case VK.RIGHT:
moveAndBlock(e, activeHandle, currentRect, 10, 0);
break;
case VK.UP:
moveAndBlock(e, activeHandle, currentRect, 0, -10);
break;
case VK.DOWN:
moveAndBlock(e, activeHandle, currentRect, 0, 10);
break;
case VK.ENTER:
case VK.SPACEBAR:
e.preventDefault();
action();
break;
}
});
}
function toggleVisibility(state) {
var selectors;
selectors = Tools.map(handles, function (handle) {
return '#' + id + '-' + handle.name;
}).concat(Tools.map(blockers, function (blocker) {
return '#' + id + '-' + blocker;
})).join(',');
if (state) {
$(selectors, containerElm).show();
} else {
$(selectors, containerElm).hide();
}
}
function repaint(rect) {
function updateElementRect(name, rect) {
if (rect.h < 0) {
rect.h = 0;
}
if (rect.w < 0) {
rect.w = 0;
}
$('#' + id + '-' + name, containerElm).css({
left: rect.x,
top: rect.y,
width: rect.w,
height: rect.h
});
}
Tools.each(handles, function (handle) {
$('#' + id + '-' + handle.name, containerElm).css({
left: rect.w * handle.xMul + rect.x,
top: rect.h * handle.yMul + rect.y
});
});
updateElementRect('top', { x: viewPortRect.x, y: viewPortRect.y, w: viewPortRect.w, h: rect.y - viewPortRect.y });
updateElementRect('right', { x: rect.x + rect.w, y: rect.y, w: viewPortRect.w - rect.x - rect.w + viewPortRect.x, h: rect.h });
updateElementRect('bottom', {
x: viewPortRect.x,
y: rect.y + rect.h,
w: viewPortRect.w,
h: viewPortRect.h - rect.y - rect.h + viewPortRect.y
});
updateElementRect('left', { x: viewPortRect.x, y: rect.y, w: rect.x - viewPortRect.x, h: rect.h });
updateElementRect('move', rect);
}
function setRect(rect) {
currentRect = rect;
repaint(currentRect);
}
function setViewPortRect(rect) {
viewPortRect = rect;
repaint(currentRect);
}
function setInnerRect(rect) {
setRect(getAbsoluteRect(clampRect, rect));
}
function setClampRect(rect) {
clampRect = rect;
repaint(currentRect);
}
function destroy() {
Tools.each(dragHelpers, function (helper) {
helper.destroy();
});
dragHelpers = [];
}
render(containerElm);
instance = Tools.extend({
toggleVisibility: toggleVisibility,
setClampRect: setClampRect,
setRect: setRect,
getInnerRect: getInnerRect,
setInnerRect: setInnerRect,
setViewPortRect: setViewPortRect,
destroy: destroy
}, Observable);
return instance;
};
}
);
/**
* ImagePanel.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.plugins.imagetools.ui.ImagePanel',
[
'tinymce.core.geom.Rect',
'tinymce.core.ui.Control',
'tinymce.core.ui.DragHelper',
'tinymce.core.util.Promise',
'tinymce.core.util.Tools',
'tinymce.plugins.imagetools.ui.CropRect'
],
function (Rect, Control, DragHelper, Promise, Tools, CropRect) {
function loadImage(image) {
return new Promise(function (resolve) {
function loaded() {
image.removeEventListener('load', loaded);
resolve(image);
}
if (image.complete) {
resolve(image);
} else {
image.addEventListener('load', loaded);
}
});
}
return Control.extend({
Defaults: {
classes: "imagepanel"
},
selection: function (rect) {
if (arguments.length) {
this.state.set('rect', rect);
return this;
}
return this.state.get('rect');
},
imageSize: function () {
var viewRect = this.state.get('viewRect');
return {
w: viewRect.w,
h: viewRect.h
};
},
toggleCropRect: function (state) {
this.state.set('cropEnabled', state);
},
imageSrc: function (url) {
var self = this, img = new Image();
img.src = url;
loadImage(img).then(function () {
var rect, $img, lastRect = self.state.get('viewRect');
$img = self.$el.find('img');
if ($img[0]) {
$img.replaceWith(img);
} else {
var bg = document.createElement('div');
bg.className = 'mce-imagepanel-bg';
self.getEl().appendChild(bg);
self.getEl().appendChild(img);
}
rect = { x: 0, y: 0, w: img.naturalWidth, h: img.naturalHeight };
self.state.set('viewRect', rect);
self.state.set('rect', Rect.inflate(rect, -20, -20));
if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) {
self.zoomFit();
}
self.repaintImage();
self.fire('load');
});
},
zoom: function (value) {
if (arguments.length) {
this.state.set('zoom', value);
return this;
}
return this.state.get('zoom');
},
postRender: function () {
this.imageSrc(this.settings.imageSrc);
return this._super();
},
zoomFit: function () {
var self = this, $img, pw, ph, w, h, zoom, padding;
padding = 10;
$img = self.$el.find('img');
pw = self.getEl().clientWidth;
ph = self.getEl().clientHeight;
w = $img[0].naturalWidth;
h = $img[0].naturalHeight;
zoom = Math.min((pw - padding) / w, (ph - padding) / h);
if (zoom >= 1) {
zoom = 1;
}
self.zoom(zoom);
},
repaintImage: function () {
var x, y, w, h, pw, ph, $img, $bg, zoom, rect, elm;
elm = this.getEl();
zoom = this.zoom();
rect = this.state.get('rect');
$img = this.$el.find('img');
$bg = this.$el.find('.mce-imagepanel-bg');
pw = elm.offsetWidth;
ph = elm.offsetHeight;
w = $img[0].naturalWidth * zoom;
h = $img[0].naturalHeight * zoom;
x = Math.max(0, pw / 2 - w / 2);
y = Math.max(0, ph / 2 - h / 2);
$img.css({
left: x,
top: y,
width: w,
height: h
});
$bg.css({
left: x,
top: y,
width: w,
height: h
});
if (this.cropRect) {
this.cropRect.setRect({
x: rect.x * zoom + x,
y: rect.y * zoom + y,
w: rect.w * zoom,
h: rect.h * zoom
});
this.cropRect.setClampRect({
x: x,
y: y,
w: w,
h: h
});
this.cropRect.setViewPortRect({
x: 0,
y: 0,
w: pw,
h: ph
});
}
},
bindStates: function () {
var self = this;
function setupCropRect(rect) {
self.cropRect = new CropRect(
rect,
self.state.get('viewRect'),
self.state.get('viewRect'),
self.getEl(),
function () {
self.fire('crop');
}
);
self.cropRect.on('updateRect', function (e) {
var rect = e.rect, zoom = self.zoom();
rect = {
x: Math.round(rect.x / zoom),
y: Math.round(rect.y / zoom),
w: Math.round(rect.w / zoom),
h: Math.round(rect.h / zoom)
};
self.state.set('rect', rect);
});
self.on('remove', self.cropRect.destroy);
}
self.state.on('change:cropEnabled', function (e) {
self.cropRect.toggleVisibility(e.value);
self.repaintImage();
});
self.state.on('change:zoom', function () {
self.repaintImage();
});
self.state.on('change:rect', function (e) {
var rect = e.value;
if (!self.cropRect) {
setupCropRect(rect);
}
self.cropRect.setRect(rect);
});
}
});
}
);
/**
* UndoStack.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.plugins.imagetools.core.UndoStack',
[
],
function () {
return function () {
var data = [], index = -1;
function add(state) {
var removed;
removed = data.splice(++index);
data.push(state);
return {
state: state,
removed: removed
};
}
function undo() {
if (canUndo()) {
return data[--index];
}
}
function redo() {
if (canRedo()) {
return data[++index];
}
}
function canUndo() {
return index > 0;
}
function canRedo() {
return index != -1 && index < data.length - 1;
}
return {
data: data,
add: add,
undo: undo,
redo: redo,
canUndo: canUndo,
canRedo: canRedo
};
};
}
);
/**
* Dialog.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.plugins.imagetools.ui.Dialog',
[
'ephox.imagetools.api.BlobConversions',
'ephox.imagetools.api.ImageTransformations',
'tinymce.core.dom.DOMUtils',
'tinymce.core.ui.Container',
'tinymce.core.ui.Factory',
'tinymce.core.ui.Form',
'tinymce.core.util.Promise',
'tinymce.core.util.Tools',
'tinymce.plugins.imagetools.ui.ImagePanel',
'tinymce.plugins.imagetools.core.UndoStack'
],
function (
BlobConversions, ImageTransformations, DOMUtils, Container, Factory, Form, Promise,
Tools, ImagePanel, UndoStack
) {
function createState(blob) {
return {
blob: blob,
url: URL.createObjectURL(blob)
};
}
function destroyState(state) {
if (state) {
URL.revokeObjectURL(state.url);
}
}
function destroyStates(states) {
Tools.each(states, destroyState);
}
function open(currentState, resolve, reject) {
var win, undoStack = new UndoStack(), mainPanel, filtersPanel, tempState,
cropPanel, resizePanel, flipRotatePanel, imagePanel, sidePanel, mainViewContainer,
invertPanel, brightnessPanel, huePanel, saturatePanel, contrastPanel, grayscalePanel,
sepiaPanel, colorizePanel, sharpenPanel, embossPanel, gammaPanel, exposurePanel, panels,
width, height, ratioW, ratioH;
function recalcSize(e) {
var widthCtrl, heightCtrl, newWidth, newHeight;
widthCtrl = win.find('#w')[0];
heightCtrl = win.find('#h')[0];
newWidth = parseInt(widthCtrl.value(), 10);
newHeight = parseInt(heightCtrl.value(), 10);
if (win.find('#constrain')[0].checked() && width && height && newWidth && newHeight) {
if (e.control.settings.name == 'w') {
newHeight = Math.round(newWidth * ratioW);
heightCtrl.value(newHeight);
} else {
newWidth = Math.round(newHeight * ratioH);
widthCtrl.value(newWidth);
}
}
width = newWidth;
height = newHeight;
}
function floatToPercent(value) {
return Math.round(value * 100) + '%';
}
function updateButtonUndoStates() {
win.find('#undo').disabled(!undoStack.canUndo());
win.find('#redo').disabled(!undoStack.canRedo());
win.statusbar.find('#save').disabled(!undoStack.canUndo());
}
function disableUndoRedo() {
win.find('#undo').disabled(true);
win.find('#redo').disabled(true);
}
function displayState(state) {
if (state) {
imagePanel.imageSrc(state.url);
}
}
function switchPanel(targetPanel) {
return function () {
var hidePanels = Tools.grep(panels, function (panel) {
return panel.settings.name != targetPanel;
});
Tools.each(hidePanels, function (panel) {
panel.hide();
});
targetPanel.show();
targetPanel.focus();
};
}
function addTempState(blob) {
tempState = createState(blob);
displayState(tempState);
}
function addBlobState(blob) {
currentState = createState(blob);
displayState(currentState);
destroyStates(undoStack.add(currentState).removed);
updateButtonUndoStates();
}
function crop() {
var rect = imagePanel.selection();
BlobConversions.blobToImageResult(currentState.blob).
then(function (ir) {
ImageTransformations.crop(ir, rect.x, rect.y, rect.w, rect.h).
then(imageResultToBlob).
then(function (blob) {
addBlobState(blob);
cancel();
});
});
}
function tempAction(fn) {
var args = [].slice.call(arguments, 1);
return function () {
var state = tempState || currentState;
BlobConversions.blobToImageResult(state.blob).
then(function (ir) {
fn.apply(this, [ir].concat(args)).then(imageResultToBlob).then(addTempState);
});
};
}
function action(fn) {
var args = [].slice.call(arguments, 1);
return function () {
BlobConversions.blobToImageResult(currentState.blob).
then(function (ir) {
fn.apply(this, [ir].concat(args)).then(imageResultToBlob).then(addBlobState);
});
};
}
function cancel() {
displayState(currentState);
destroyState(tempState);
switchPanel(mainPanel)();
updateButtonUndoStates();
}
function applyTempState() {
if (tempState) {
addBlobState(tempState.blob);
cancel();
}
}
function zoomIn() {
var zoom = imagePanel.zoom();
if (zoom < 2) {
zoom += 0.1;
}
imagePanel.zoom(zoom);
}
function zoomOut() {
var zoom = imagePanel.zoom();
if (zoom > 0.1) {
zoom -= 0.1;
}
imagePanel.zoom(zoom);
}
function undo() {
currentState = undoStack.undo();
displayState(currentState);
updateButtonUndoStates();
}
function redo() {
currentState = undoStack.redo();
displayState(currentState);
updateButtonUndoStates();
}
function save() {
resolve(currentState.blob);
win.close();
}
function createPanel(items) {
return new Form({
layout: 'flex',
direction: 'row',
labelGap: 5,
border: '0 0 1 0',
align: 'center',
pack: 'center',
padding: '0 10 0 10',
spacing: 5,
flex: 0,
minHeight: 60,
defaults: {
classes: 'imagetool',
type: 'button'
},
items: items
});
}
var imageResultToBlob = function (ir) {
return ir.toBlob();
};
function createFilterPanel(title, filter) {
return createPanel([
{ text: 'Back', onclick: cancel },
{ type: 'spacer', flex: 1 },
{ text: 'Apply', subtype: 'primary', onclick: applyTempState }
]).hide().on('show', function () {
disableUndoRedo();
BlobConversions.blobToImageResult(currentState.blob).
then(function (ir) {
return filter(ir);
}).
then(imageResultToBlob).
then(function (blob) {
var newTempState = createState(blob);
displayState(newTempState);
destroyState(tempState);
tempState = newTempState;
});
});
}
function createVariableFilterPanel(title, filter, value, min, max) {
function update(value) {
BlobConversions.blobToImageResult(currentState.blob).
then(function (ir) {
return filter(ir, value);
}).
then(imageResultToBlob).
then(function (blob) {
var newTempState = createState(blob);
displayState(newTempState);
destroyState(tempState);
tempState = newTempState;
});
}
return createPanel([
{ text: 'Back', onclick: cancel },
{ type: 'spacer', flex: 1 },
{
type: 'slider',
flex: 1,
ondragend: function (e) {
update(e.value);
},
minValue: min,
maxValue: max,
value: value,
previewFilter: floatToPercent
},
{ type: 'spacer', flex: 1 },
{ text: 'Apply', subtype: 'primary', onclick: applyTempState }
]).hide().on('show', function () {
this.find('slider').value(value);
disableUndoRedo();
});
}
function createRgbFilterPanel(title, filter) {
function update() {
var r, g, b;
r = win.find('#r')[0].value();
g = win.find('#g')[0].value();
b = win.find('#b')[0].value();
BlobConversions.blobToImageResult(currentState.blob).
then(function (ir) {
return filter(ir, r, g, b);
}).
then(imageResultToBlob).
then(function (blob) {
var newTempState = createState(blob);
displayState(newTempState);
destroyState(tempState);
tempState = newTempState;
});
}
return createPanel([
{ text: 'Back', onclick: cancel },
{ type: 'spacer', flex: 1 },
{
type: 'slider', label: 'R', name: 'r', minValue: 0,
value: 1, maxValue: 2, ondragend: update, previewFilter: floatToPercent
},
{
type: 'slider', label: 'G', name: 'g', minValue: 0,
value: 1, maxValue: 2, ondragend: update, previewFilter: floatToPercent
},
{
type: 'slider', label: 'B', name: 'b', minValue: 0,
value: 1, maxValue: 2, ondragend: update, previewFilter: floatToPercent
},
{ type: 'spacer', flex: 1 },
{ text: 'Apply', subtype: 'primary', onclick: applyTempState }
]).hide().on('show', function () {
win.find('#r,#g,#b').value(1);
disableUndoRedo();
});
}
cropPanel = createPanel([
{ text: 'Back', onclick: cancel },
{ type: 'spacer', flex: 1 },
{ text: 'Apply', subtype: 'primary', onclick: crop }
]).hide().on('show hide', function (e) {
imagePanel.toggleCropRect(e.type == 'show');
}).on('show', disableUndoRedo);
function toggleConstrain(e) {
if (e.control.value() === true) {
ratioW = height / width;
ratioH = width / height;
}
}
resizePanel = createPanel([
{ text: 'Back', onclick: cancel },
{ type: 'spacer', flex: 1 },
{ type: 'textbox', name: 'w', label: 'Width', size: 4, onkeyup: recalcSize },
{ type: 'textbox', name: 'h', label: 'Height', size: 4, onkeyup: recalcSize },
{ type: 'checkbox', name: 'constrain', text: 'Constrain proportions', checked: true, onchange: toggleConstrain },
{ type: 'spacer', flex: 1 },
{ text: 'Apply', subtype: 'primary', onclick: 'submit' }
]).hide().on('submit', function (e) {
var width = parseInt(win.find('#w').value(), 10),
height = parseInt(win.find('#h').value(), 10);
e.preventDefault();
action(ImageTransformations.resize, width, height)();
cancel();
}).on('show', disableUndoRedo);
flipRotatePanel = createPanel([
{ text: 'Back', onclick: cancel },
{ type: 'spacer', flex: 1 },
{ icon: 'fliph', tooltip: 'Flip horizontally', onclick: tempAction(ImageTransformations.flip, 'h') },
{ icon: 'flipv', tooltip: 'Flip vertically', onclick: tempAction(ImageTransformations.flip, 'v') },
{ icon: 'rotateleft', tooltip: 'Rotate counterclockwise', onclick: tempAction(ImageTransformations.rotate, -90) },
{ icon: 'rotateright', tooltip: 'Rotate clockwise', onclick: tempAction(ImageTransformations.rotate, 90) },
{ type: 'spacer', flex: 1 },
{ text: 'Apply', subtype: 'primary', onclick: applyTempState }
]).hide().on('show', disableUndoRedo);
invertPanel = createFilterPanel("Invert", ImageTransformations.invert);
sharpenPanel = createFilterPanel("Sharpen", ImageTransformations.sharpen);
embossPanel = createFilterPanel("Emboss", ImageTransformations.emboss);
brightnessPanel = createVariableFilterPanel("Brightness", ImageTransformations.brightness, 0, -1, 1);
huePanel = createVariableFilterPanel("Hue", ImageTransformations.hue, 180, 0, 360);
saturatePanel = createVariableFilterPanel("Saturate", ImageTransformations.saturate, 0, -1, 1);
contrastPanel = createVariableFilterPanel("Contrast", ImageTransformations.contrast, 0, -1, 1);
grayscalePanel = createVariableFilterPanel("Grayscale", ImageTransformations.grayscale, 0, 0, 1);
sepiaPanel = createVariableFilterPanel("Sepia", ImageTransformations.sepia, 0, 0, 1);
colorizePanel = createRgbFilterPanel("Colorize", ImageTransformations.colorize);
gammaPanel = createVariableFilterPanel("Gamma", ImageTransformations.gamma, 0, -1, 1);
exposurePanel = createVariableFilterPanel("Exposure", ImageTransformations.exposure, 1, 0, 2);
filtersPanel = createPanel([
{ text: 'Back', onclick: cancel },
{ type: 'spacer', flex: 1 },
{ text: 'hue', icon: 'hue', onclick: switchPanel(huePanel) },
{ text: 'saturate', icon: 'saturate', onclick: switchPanel(saturatePanel) },
{ text: 'sepia', icon: 'sepia', onclick: switchPanel(sepiaPanel) },
{ text: 'emboss', icon: 'emboss', onclick: switchPanel(embossPanel) },
{ text: 'exposure', icon: 'exposure', onclick: switchPanel(exposurePanel) },
{ type: 'spacer', flex: 1 }
]).hide();
mainPanel = createPanel([
{ tooltip: 'Crop', icon: 'crop', onclick: switchPanel(cropPanel) },
{ tooltip: 'Resize', icon: 'resize2', onclick: switchPanel(resizePanel) },
{ tooltip: 'Orientation', icon: 'orientation', onclick: switchPanel(flipRotatePanel) },
{ tooltip: 'Brightness', icon: 'sun', onclick: switchPanel(brightnessPanel) },
{ tooltip: 'Sharpen', icon: 'sharpen', onclick: switchPanel(sharpenPanel) },
{ tooltip: 'Contrast', icon: 'contrast', onclick: switchPanel(contrastPanel) },
{ tooltip: 'Color levels', icon: 'drop', onclick: switchPanel(colorizePanel) },
{ tooltip: 'Gamma', icon: 'gamma', onclick: switchPanel(gammaPanel) },
{ tooltip: 'Invert', icon: 'invert', onclick: switchPanel(invertPanel) }
//{text: 'More', onclick: switchPanel(filtersPanel)}
]);
imagePanel = new ImagePanel({
flex: 1,
imageSrc: currentState.url
});
sidePanel = new Container({
layout: 'flex',
direction: 'column',
border: '0 1 0 0',
padding: 5,
spacing: 5,
items: [
{ type: 'button', icon: 'undo', tooltip: 'Undo', name: 'undo', onclick: undo },
{ type: 'button', icon: 'redo', tooltip: 'Redo', name: 'redo', onclick: redo },
{ type: 'button', icon: 'zoomin', tooltip: 'Zoom in', onclick: zoomIn },
{ type: 'button', icon: 'zoomout', tooltip: 'Zoom out', onclick: zoomOut }
]
});
mainViewContainer = new Container({
type: 'container',
layout: 'flex',
direction: 'row',
align: 'stretch',
flex: 1,
items: [sidePanel, imagePanel]
});
panels = [
mainPanel,
cropPanel,
resizePanel,
flipRotatePanel,
filtersPanel,
invertPanel,
brightnessPanel,
huePanel,
saturatePanel,
contrastPanel,
grayscalePanel,
sepiaPanel,
colorizePanel,
sharpenPanel,
embossPanel,
gammaPanel,
exposurePanel
];
win = Factory.create('window', {
layout: 'flex',
direction: 'column',
align: 'stretch',
minWidth: Math.min(DOMUtils.DOM.getViewPort().w, 800),
minHeight: Math.min(DOMUtils.DOM.getViewPort().h, 650),
title: 'Edit image',
items: panels.concat([mainViewContainer]),
buttons: [
{ text: 'Save', name: 'save', subtype: 'primary', onclick: save },
{ text: 'Cancel', onclick: 'close' }
]
});
win.renderTo(document.body).reflow();
win.on('close', function () {
reject();
destroyStates(undoStack.data);
undoStack = null;
tempState = null;
});
undoStack.add(currentState);
updateButtonUndoStates();
imagePanel.on('load', function () {
width = imagePanel.imageSize().w;
height = imagePanel.imageSize().h;
ratioW = height / width;
ratioH = width / height;
win.find('#w').value(width);
win.find('#h').value(height);
});
imagePanel.on('crop', crop);
}
function edit(imageResult) {
return new Promise(function (resolve, reject) {
return imageResult.toBlob().then(function (blob) {
open(createState(blob), resolve, reject);
});
});
}
//edit('img/dogleft.jpg');
return {
edit: edit
};
}
);
/**
* Plugin.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.plugins.imagetools.Plugin',
[
'ephox.imagetools.api.BlobConversions',
'ephox.imagetools.api.ImageTransformations',
'tinymce.core.Env',
'tinymce.core.PluginManager',
'tinymce.core.util.Delay',
'tinymce.core.util.Promise',
'tinymce.core.util.Tools',
'tinymce.core.util.URI',
'tinymce.plugins.imagetools.core.ImageSize',
'tinymce.plugins.imagetools.core.Proxy',
'tinymce.plugins.imagetools.ui.Dialog'
],
function (
BlobConversions, ImageTransformations, Env, PluginManager, Delay, Promise, Tools,
URI, ImageSize, Proxy, Dialog
) {
var plugin = function (editor) {
var count = 0, imageUploadTimer, lastSelectedImage;
if (!Env.fileApi) {
return;
}
function displayError(error) {
editor.notificationManager.open({
text: error,
type: 'error'
});
}
function getSelectedImage() {
return editor.selection.getNode();
}
function extractFilename(url) {
var m = url.match(/\/([^\/\?]+)?\.(?:jpeg|jpg|png|gif)(?:\?|$)/i);
if (m) {
return editor.dom.encode(m[1]);
}
return null;
}
function createId() {
return 'imagetools' + count++;
}
function isLocalImage(img) {
var url = img.src;
return url.indexOf('data:') === 0 || url.indexOf('blob:') === 0 || new URI(url).host === editor.documentBaseURI.host;
}
function isCorsImage(img) {
return Tools.inArray(editor.settings.imagetools_cors_hosts, new URI(img.src).host) !== -1;
}
function getApiKey() {
return editor.settings.api_key || editor.settings.imagetools_api_key;
}
function imageToBlob(img) {
var src = img.src, apiKey;
if (isCorsImage(img)) {
return Proxy.getUrl(img.src, null);
}
if (!isLocalImage(img)) {
src = editor.settings.imagetools_proxy;
src += (src.indexOf('?') === -1 ? '?' : '&') + 'url=' + encodeURIComponent(img.src);
apiKey = getApiKey();
return Proxy.getUrl(src, apiKey);
}
return BlobConversions.imageToBlob(img);
}
function findSelectedBlob() {
var blobInfo;
blobInfo = editor.editorUpload.blobCache.getByUri(getSelectedImage().src);
if (blobInfo) {
return Promise.resolve(blobInfo.blob());
}
return imageToBlob(getSelectedImage());
}
function startTimedUpload() {
imageUploadTimer = Delay.setEditorTimeout(editor, function () {
editor.editorUpload.uploadImagesAuto();
}, editor.settings.images_upload_timeout || 30000);
}
function cancelTimedUpload() {
clearTimeout(imageUploadTimer);
}
function updateSelectedImage(ir, uploadImmediately) {
return ir.toBlob().then(function (blob) {
var uri, name, blobCache, blobInfo, selectedImage;
blobCache = editor.editorUpload.blobCache;
selectedImage = getSelectedImage();
uri = selectedImage.src;
if (editor.settings.images_reuse_filename) {
blobInfo = blobCache.getByUri(uri);
if (blobInfo) {
uri = blobInfo.uri();
name = blobInfo.name();
} else {
name = extractFilename(uri);
}
}
blobInfo = blobCache.create({
id: createId(),
blob: blob,
base64: ir.toBase64(),
uri: uri,
name: name
});
blobCache.add(blobInfo);
editor.undoManager.transact(function () {
function imageLoadedHandler() {
editor.$(selectedImage).off('load', imageLoadedHandler);
editor.nodeChanged();
if (uploadImmediately) {
editor.editorUpload.uploadImagesAuto();
} else {
cancelTimedUpload();
startTimedUpload();
}
}
editor.$(selectedImage).on('load', imageLoadedHandler);
editor.$(selectedImage).attr({
src: blobInfo.blobUri()
}).removeAttr('data-mce-src');
});
return blobInfo;
});
}
function selectedImageOperation(fn) {
return function () {
return editor._scanForImages().
then(findSelectedBlob).
then(BlobConversions.blobToImageResult).
then(fn).
then(updateSelectedImage, displayError);
};
}
function rotate(angle) {
return function () {
return selectedImageOperation(function (imageResult) {
var size = ImageSize.getImageSize(getSelectedImage());
if (size) {
ImageSize.setImageSize(getSelectedImage(), {
w: size.h,
h: size.w
});
}
return ImageTransformations.rotate(imageResult, angle);
})();
};
}
function flip(axis) {
return function () {
return selectedImageOperation(function (imageResult) {
return ImageTransformations.flip(imageResult, axis);
})();
};
}
function editImageDialog() {
var img = getSelectedImage(), originalSize = ImageSize.getNaturalImageSize(img);
var handleDialogBlob = function (blob) {
return new Promise(function (resolve) {
BlobConversions.blobToImage(blob).
then(function (newImage) {
var newSize = ImageSize.getNaturalImageSize(newImage);
if (originalSize.w != newSize.w || originalSize.h != newSize.h) {
if (ImageSize.getImageSize(img)) {
ImageSize.setImageSize(img, newSize);
}
}
URL.revokeObjectURL(newImage.src);
resolve(blob);
});
});
};
var openDialog = function (imageResult) {
return Dialog.edit(imageResult).then(handleDialogBlob).
then(BlobConversions.blobToImageResult).
then(function (imageResult) {
return updateSelectedImage(imageResult, true);
}, function () {
// Close dialog
});
};
findSelectedBlob().
then(BlobConversions.blobToImageResult).
then(openDialog, displayError);
}
function addButtons() {
editor.addButton('rotateleft', {
title: 'Rotate counterclockwise',
cmd: 'mceImageRotateLeft'
});
editor.addButton('rotateright', {
title: 'Rotate clockwise',
cmd: 'mceImageRotateRight'
});
editor.addButton('flipv', {
title: 'Flip vertically',
cmd: 'mceImageFlipVertical'
});
editor.addButton('fliph', {
title: 'Flip horizontally',
cmd: 'mceImageFlipHorizontal'
});
editor.addButton('editimage', {
title: 'Edit image',
cmd: 'mceEditImage'
});
editor.addButton('imageoptions', {
title: 'Image options',
icon: 'options',
cmd: 'mceImage'
});
/*
editor.addButton('crop', {
title: 'Crop',
onclick: startCrop
});
*/
}
function addEvents() {
editor.on('NodeChange', function (e) {
// If the last node we selected was an image
// And had a source that doesn't match the current blob url
// We need to attempt to upload it
if (lastSelectedImage && lastSelectedImage.src != e.element.src) {
cancelTimedUpload();
editor.editorUpload.uploadImagesAuto();
lastSelectedImage = undefined;
}
// Set up the lastSelectedImage
if (isEditableImage(e.element)) {
lastSelectedImage = e.element;
}
});
}
function isEditableImage(img) {
var selectorMatched = editor.dom.is(img, 'img:not([data-mce-object],[data-mce-placeholder])');
return selectorMatched && (isLocalImage(img) || isCorsImage(img) || editor.settings.imagetools_proxy);
}
function addToolbars() {
var toolbarItems = editor.settings.imagetools_toolbar;
if (!toolbarItems) {
toolbarItems = 'rotateleft rotateright | flipv fliph | crop editimage imageoptions';
}
editor.addContextToolbar(
isEditableImage,
toolbarItems
);
}
Tools.each({
mceImageRotateLeft: rotate(-90),
mceImageRotateRight: rotate(90),
mceImageFlipVertical: flip('v'),
mceImageFlipHorizontal: flip('h'),
mceEditImage: editImageDialog
}, function (fn, cmd) {
editor.addCommand(cmd, fn);
});
addButtons();
addToolbars();
addEvents();
};
PluginManager.add('imagetools', plugin);
return function () { };
}
);
dem('tinymce.plugins.imagetools.Plugin')();
})();