4 * Released under LGPL License.
5 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
7 * License: http://www.tinymce.com/license
8 * Contributing: http://www.tinymce.com/contributing
11 /*global tinymce:true */
13 tinymce.PluginManager.add('image', function(editor) {
14 function getImageSize(url, callback) {
15 var img = document.createElement('img');
17 function done(width, height) {
19 img.parentNode.removeChild(img);
22 callback({width: width, height: height});
25 img.onload = function() {
26 done(Math.max(img.width, img.clientWidth), Math.max(img.height, img.clientHeight));
29 img.onerror = function() {
33 var style = img.style;
34 style.visibility = 'hidden';
35 style.position = 'fixed';
36 style.bottom = style.left = 0;
37 style.width = style.height = 'auto';
39 document.body.appendChild(img);
43 function buildListItems(inputList, itemCallback, startItems) {
44 function appendItems(values, output) {
45 output = output || [];
47 tinymce.each(values, function(item) {
48 var menuItem = {text: item.text || item.title};
51 menuItem.menu = appendItems(item.menu);
53 menuItem.value = item.value;
54 itemCallback(menuItem);
57 output.push(menuItem);
63 return appendItems(inputList, startItems || []);
66 function createImageList(callback) {
68 var imageList = editor.settings.image_list;
70 if (typeof imageList == "string") {
71 tinymce.util.XHR.send({
73 success: function(text) {
74 callback(tinymce.util.JSON.parse(text));
77 } else if (typeof imageList == "function") {
85 function showDialog(imageList) {
86 var win, data = {}, dom = editor.dom, imgElm = editor.selection.getNode();
87 var width, height, imageListCtrl, classListCtrl, imageDimensions = editor.settings.image_dimensions !== false;
89 function recalcSize() {
90 var widthCtrl, heightCtrl, newWidth, newHeight;
92 widthCtrl = win.find('#width')[0];
93 heightCtrl = win.find('#height')[0];
95 if (!widthCtrl || !heightCtrl) {
99 newWidth = widthCtrl.value();
100 newHeight = heightCtrl.value();
102 if (win.find('#constrain')[0].checked() && width && height && newWidth && newHeight) {
103 if (width != newWidth) {
104 newHeight = Math.round((newWidth / width) * newHeight);
106 if (!isNaN(newHeight)) {
107 heightCtrl.value(newHeight);
110 newWidth = Math.round((newHeight / height) * newWidth);
112 if (!isNaN(newWidth)) {
113 widthCtrl.value(newWidth);
122 function onSubmitForm() {
123 function waitLoad(imgElm) {
124 function selectImage() {
125 imgElm.onload = imgElm.onerror = null;
127 if (editor.selection) {
128 editor.selection.select(imgElm);
129 editor.nodeChanged();
133 imgElm.onload = function() {
134 if (!data.width && !data.height && imageDimensions) {
135 dom.setAttribs(imgElm, {
136 width: imgElm.clientWidth,
137 height: imgElm.clientHeight
140 editor.fire( 'wpNewImageRefresh', { node: imgElm } );
146 imgElm.onerror = selectImage;
152 data = tinymce.extend(data, win.toJSON());
153 var caption = data.caption; // WP
163 if (data.width === '') {
167 if (data.height === '') {
175 // Setup new data excluding style properties
176 /*eslint dot-notation: 0*/
184 "class": data["class"]
187 editor.undoManager.transact(function() {
189 var eventData = { node: imgElm, data: data, caption: caption };
191 editor.fire( 'wpImageFormSubmit', { imgData: eventData } );
193 if ( eventData.cancel ) {
194 waitLoad( eventData.node );
203 editor.nodeChanged();
209 if (data.title === "") {
214 data.id = '__mcenew';
216 editor.selection.setContent(dom.createHTML('img', data));
217 imgElm = dom.get('__mcenew');
218 dom.setAttrib(imgElm, 'id', null);
220 dom.setAttribs(imgElm, data);
221 editor.editorUpload.uploadImagesAuto();
228 function removePixelSuffix(value) {
230 value = value.replace(/px$/, '');
236 function srcChange(e) {
237 var srcURL, prependURL, absoluteURLPattern, meta = e.meta || {};
240 imageListCtrl.value(editor.convertURL(this.value(), 'src'));
243 tinymce.each(meta, function(value, key) {
244 win.find('#' + key).value(value);
247 if (!meta.width && !meta.height) {
248 srcURL = editor.convertURL(this.value(), 'src');
250 // Pattern test the src url and make sure we haven't already prepended the url
251 prependURL = editor.settings.image_prepend_url;
252 absoluteURLPattern = new RegExp('^(?:[a-z]+:)?//', 'i');
253 if (prependURL && !absoluteURLPattern.test(srcURL) && srcURL.substring(0, prependURL.length) !== prependURL) {
254 srcURL = prependURL + srcURL;
259 getImageSize(editor.documentBaseURI.toAbsolute(this.value()), function(data) {
260 if (data.width && data.height && imageDimensions) {
262 height = data.height;
264 win.find('#width').value(width);
265 win.find('#height').value(height);
271 width = dom.getAttrib(imgElm, 'width');
272 height = dom.getAttrib(imgElm, 'height');
274 if (imgElm.nodeName == 'IMG' && !imgElm.getAttribute('data-mce-object') && !imgElm.getAttribute('data-mce-placeholder')) {
276 src: dom.getAttrib(imgElm, 'src'),
277 alt: dom.getAttrib(imgElm, 'alt'),
278 title: dom.getAttrib(imgElm, 'title'),
279 "class": dom.getAttrib(imgElm, 'class'),
285 editor.fire( 'wpLoadImageData', { imgData: { data: data, node: imgElm } } );
294 values: buildListItems(
297 item.value = editor.convertURL(item.value || item.url, 'src');
299 [{text: 'None', value: ''}]
301 value: data.src && editor.convertURL(data.src, 'src'),
302 onselect: function(e) {
303 var altCtrl = win.find('#alt');
305 if (!altCtrl.value() || (e.lastControl && altCtrl.value() == e.lastControl.text())) {
306 altCtrl.value(e.control.text());
309 win.find('#src').value(e.control.value()).fire('change');
311 onPostRender: function() {
312 /*eslint consistent-this: 0*/
313 imageListCtrl = this;
318 if (editor.settings.image_class_list) {
323 values: buildListItems(
324 editor.settings.image_class_list,
327 item.textStyle = function() {
328 return editor.formatter.getCssText({inline: 'img', classes: [item.value]});
336 // General settings shared between simple and advanced dialogs
337 var generalFormItems = [
349 if (editor.settings.image_description !== false) {
350 generalFormItems.push({name: 'alt', type: 'textbox', label: 'Image description'});
353 if (editor.settings.image_title) {
354 generalFormItems.push({name: 'title', type: 'textbox', label: 'Image Title'});
357 if (imageDimensions) {
358 generalFormItems.push({
366 {name: 'width', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Width'},
367 {type: 'label', text: 'x'},
368 {name: 'height', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Height'},
369 {name: 'constrain', type: 'checkbox', checked: true, text: 'Constrain proportions'}
374 generalFormItems.push(classListCtrl);
377 editor.fire( 'wpLoadImageForm', { data: generalFormItems } );
379 function mergeMargins(css) {
382 var splitMargin = css.margin.split(" ");
384 switch (splitMargin.length) {
385 case 1: //margin: toprightbottomleft;
386 css['margin-top'] = css['margin-top'] || splitMargin[0];
387 css['margin-right'] = css['margin-right'] || splitMargin[0];
388 css['margin-bottom'] = css['margin-bottom'] || splitMargin[0];
389 css['margin-left'] = css['margin-left'] || splitMargin[0];
391 case 2: //margin: topbottom rightleft;
392 css['margin-top'] = css['margin-top'] || splitMargin[0];
393 css['margin-right'] = css['margin-right'] || splitMargin[1];
394 css['margin-bottom'] = css['margin-bottom'] || splitMargin[0];
395 css['margin-left'] = css['margin-left'] || splitMargin[1];
397 case 3: //margin: top rightleft bottom;
398 css['margin-top'] = css['margin-top'] || splitMargin[0];
399 css['margin-right'] = css['margin-right'] || splitMargin[1];
400 css['margin-bottom'] = css['margin-bottom'] || splitMargin[2];
401 css['margin-left'] = css['margin-left'] || splitMargin[1];
403 case 4: //margin: top right bottom left;
404 css['margin-top'] = css['margin-top'] || splitMargin[0];
405 css['margin-right'] = css['margin-right'] || splitMargin[1];
406 css['margin-bottom'] = css['margin-bottom'] || splitMargin[2];
407 css['margin-left'] = css['margin-left'] || splitMargin[3];
414 function updateStyle() {
415 function addPixelSuffix(value) {
416 if (value.length > 0 && /^[0-9]+$/.test(value)) {
423 if (!editor.settings.image_advtab) {
427 var data = win.toJSON(),
428 css = dom.parseStyle(data.style);
430 css = mergeMargins(css);
433 css['margin-top'] = css['margin-bottom'] = addPixelSuffix(data.vspace);
436 css['margin-left'] = css['margin-right'] = addPixelSuffix(data.hspace);
439 css['border-width'] = addPixelSuffix(data.border);
442 win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css))));
445 function updateVSpaceHSpaceBorder() {
446 if (!editor.settings.image_advtab) {
450 var data = win.toJSON(),
451 css = dom.parseStyle(data.style);
453 win.find('#vspace').value("");
454 win.find('#hspace').value("");
456 css = mergeMargins(css);
458 //Move opposite equal margins to vspace/hspace field
459 if ((css['margin-top'] && css['margin-bottom']) || (css['margin-right'] && css['margin-left'])) {
460 if (css['margin-top'] === css['margin-bottom']) {
461 win.find('#vspace').value(removePixelSuffix(css['margin-top']));
463 win.find('#vspace').value('');
465 if (css['margin-right'] === css['margin-left']) {
466 win.find('#hspace').value(removePixelSuffix(css['margin-right']));
468 win.find('#hspace').value('');
473 if (css['border-width']) {
474 win.find('#border').value(removePixelSuffix(css['border-width']));
477 win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css))));
481 if (editor.settings.image_advtab) {
482 // Parse styles from img
484 if (imgElm.style.marginLeft && imgElm.style.marginRight && imgElm.style.marginLeft === imgElm.style.marginRight) {
485 data.hspace = removePixelSuffix(imgElm.style.marginLeft);
487 if (imgElm.style.marginTop && imgElm.style.marginBottom && imgElm.style.marginTop === imgElm.style.marginBottom) {
488 data.vspace = removePixelSuffix(imgElm.style.marginTop);
490 if (imgElm.style.borderWidth) {
491 data.border = removePixelSuffix(imgElm.style.borderWidth);
494 data.style = editor.dom.serializeStyle(editor.dom.parseStyle(editor.dom.getAttrib(imgElm, 'style')));
497 // Advanced dialog shows general+advanced tabs
498 win = editor.windowManager.open({
499 title: 'Insert/edit image',
501 bodyType: 'tabpanel',
506 items: generalFormItems
518 onchange: updateVSpaceHSpaceBorder
526 alignH: ['left', 'right'],
530 onchange: updateStyle
533 {label: 'Vertical space', name: 'vspace'},
534 {label: 'Horizontal space', name: 'hspace'},
535 {label: 'Border', name: 'border'}
541 onSubmit: onSubmitForm
544 // Simple default dialog
545 win = editor.windowManager.open({
546 title: 'Insert/edit image',
548 body: generalFormItems,
549 onSubmit: onSubmitForm
554 editor.addButton('image', {
556 tooltip: 'Insert/edit image',
557 onclick: createImageList(showDialog),
558 stateSelector: 'img:not([data-mce-object],[data-mce-placeholder])'
561 editor.addMenuItem('image', {
563 text: 'Insert/edit image',
564 onclick: createImageList(showDialog),
566 prependToContext: true
569 editor.addCommand('mceImage', createImageList(showDialog));