4 * Copyright, Moxiecode Systems AB
5 * Released under LGPL License.
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(img.clientWidth, 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);
227 function removePixelSuffix(value) {
229 value = value.replace(/px$/, '');
235 function srcChange(e) {
236 var srcURL, prependURL, absoluteURLPattern, meta = e.meta || {};
239 imageListCtrl.value(editor.convertURL(this.value(), 'src'));
242 tinymce.each(meta, function(value, key) {
243 win.find('#' + key).value(value);
246 if (!meta.width && !meta.height) {
247 srcURL = editor.convertURL(this.value(), 'src');
249 // Pattern test the src url and make sure we haven't already prepended the url
250 prependURL = editor.settings.image_prepend_url;
251 absoluteURLPattern = new RegExp('^(?:[a-z]+:)?//', 'i');
252 if (prependURL && !absoluteURLPattern.test(srcURL) && srcURL.substring(0, prependURL.length) !== prependURL) {
253 srcURL = prependURL + srcURL;
258 getImageSize(editor.documentBaseURI.toAbsolute(this.value()), function(data) {
259 if (data.width && data.height && imageDimensions) {
261 height = data.height;
263 win.find('#width').value(width);
264 win.find('#height').value(height);
270 width = dom.getAttrib(imgElm, 'width');
271 height = dom.getAttrib(imgElm, 'height');
273 if (imgElm.nodeName == 'IMG' && !imgElm.getAttribute('data-mce-object') && !imgElm.getAttribute('data-mce-placeholder')) {
275 src: dom.getAttrib(imgElm, 'src'),
276 alt: dom.getAttrib(imgElm, 'alt'),
277 title: dom.getAttrib(imgElm, 'title'),
278 "class": dom.getAttrib(imgElm, 'class'),
284 editor.fire( 'wpLoadImageData', { imgData: { data: data, node: imgElm } } );
293 values: buildListItems(
296 item.value = editor.convertURL(item.value || item.url, 'src');
298 [{text: 'None', value: ''}]
300 value: data.src && editor.convertURL(data.src, 'src'),
301 onselect: function(e) {
302 var altCtrl = win.find('#alt');
304 if (!altCtrl.value() || (e.lastControl && altCtrl.value() == e.lastControl.text())) {
305 altCtrl.value(e.control.text());
308 win.find('#src').value(e.control.value()).fire('change');
310 onPostRender: function() {
311 imageListCtrl = this;
316 if (editor.settings.image_class_list) {
321 values: buildListItems(
322 editor.settings.image_class_list,
325 item.textStyle = function() {
326 return editor.formatter.getCssText({inline: 'img', classes: [item.value]});
334 // General settings shared between simple and advanced dialogs
335 var generalFormItems = [
347 if (editor.settings.image_description !== false) {
348 generalFormItems.push({name: 'alt', type: 'textbox', label: 'Image description'});
351 if (editor.settings.image_title) {
352 generalFormItems.push({name: 'title', type: 'textbox', label: 'Image Title'});
355 if (imageDimensions) {
356 generalFormItems.push({
364 {name: 'width', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Width'},
365 {type: 'label', text: 'x'},
366 {name: 'height', type: 'textbox', maxLength: 5, size: 3, onchange: recalcSize, ariaLabel: 'Height'},
367 {name: 'constrain', type: 'checkbox', checked: true, text: 'Constrain proportions'}
372 generalFormItems.push(classListCtrl);
375 editor.fire( 'wpLoadImageForm', { data: generalFormItems } );
377 function mergeMargins(css) {
380 var splitMargin = css.margin.split(" ");
382 switch (splitMargin.length) {
383 case 1: //margin: toprightbottomleft;
384 css['margin-top'] = css['margin-top'] || splitMargin[0];
385 css['margin-right'] = css['margin-right'] || splitMargin[0];
386 css['margin-bottom'] = css['margin-bottom'] || splitMargin[0];
387 css['margin-left'] = css['margin-left'] || splitMargin[0];
389 case 2: //margin: topbottom rightleft;
390 css['margin-top'] = css['margin-top'] || splitMargin[0];
391 css['margin-right'] = css['margin-right'] || splitMargin[1];
392 css['margin-bottom'] = css['margin-bottom'] || splitMargin[0];
393 css['margin-left'] = css['margin-left'] || splitMargin[1];
395 case 3: //margin: top rightleft bottom;
396 css['margin-top'] = css['margin-top'] || splitMargin[0];
397 css['margin-right'] = css['margin-right'] || splitMargin[1];
398 css['margin-bottom'] = css['margin-bottom'] || splitMargin[2];
399 css['margin-left'] = css['margin-left'] || splitMargin[1];
401 case 4: //margin: top right bottom left;
402 css['margin-top'] = css['margin-top'] || splitMargin[0];
403 css['margin-right'] = css['margin-right'] || splitMargin[1];
404 css['margin-bottom'] = css['margin-bottom'] || splitMargin[2];
405 css['margin-left'] = css['margin-left'] || splitMargin[3];
412 function updateStyle() {
413 function addPixelSuffix(value) {
414 if (value.length > 0 && /^[0-9]+$/.test(value)) {
421 if (!editor.settings.image_advtab) {
425 var data = win.toJSON(),
426 css = dom.parseStyle(data.style);
428 css = mergeMargins(css);
431 css['margin-top'] = css['margin-bottom'] = addPixelSuffix(data.vspace);
434 css['margin-left'] = css['margin-right'] = addPixelSuffix(data.hspace);
437 css['border-width'] = addPixelSuffix(data.border);
440 win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css))));
443 function updateVSpaceHSpaceBorder() {
444 if (!editor.settings.image_advtab) {
448 var data = win.toJSON(),
449 css = dom.parseStyle(data.style);
451 win.find('#vspace').value("");
452 win.find('#hspace').value("");
454 css = mergeMargins(css);
456 //Move opposite equal margins to vspace/hspace field
457 if ((css['margin-top'] && css['margin-bottom']) || (css['margin-right'] && css['margin-left'])) {
458 if (css['margin-top'] === css['margin-bottom']) {
459 win.find('#vspace').value(removePixelSuffix(css['margin-top']));
461 win.find('#vspace').value('');
463 if (css['margin-right'] === css['margin-left']) {
464 win.find('#hspace').value(removePixelSuffix(css['margin-right']));
466 win.find('#hspace').value('');
471 if (css['border-width']) {
472 win.find('#border').value(removePixelSuffix(css['border-width']));
475 win.find('#style').value(dom.serializeStyle(dom.parseStyle(dom.serializeStyle(css))));
479 if (editor.settings.image_advtab) {
480 // Parse styles from img
482 if (imgElm.style.marginLeft && imgElm.style.marginRight && imgElm.style.marginLeft === imgElm.style.marginRight) {
483 data.hspace = removePixelSuffix(imgElm.style.marginLeft);
485 if (imgElm.style.marginTop && imgElm.style.marginBottom && imgElm.style.marginTop === imgElm.style.marginBottom) {
486 data.vspace = removePixelSuffix(imgElm.style.marginTop);
488 if (imgElm.style.borderWidth) {
489 data.border = removePixelSuffix(imgElm.style.borderWidth);
492 data.style = editor.dom.serializeStyle(editor.dom.parseStyle(editor.dom.getAttrib(imgElm, 'style')));
495 // Advanced dialog shows general+advanced tabs
496 win = editor.windowManager.open({
497 title: 'Insert/edit image',
499 bodyType: 'tabpanel',
504 items: generalFormItems
516 onchange: updateVSpaceHSpaceBorder
524 alignH: ['left', 'right'],
528 onchange: updateStyle
531 {label: 'Vertical space', name: 'vspace'},
532 {label: 'Horizontal space', name: 'hspace'},
533 {label: 'Border', name: 'border'}
539 onSubmit: onSubmitForm
542 // Simple default dialog
543 win = editor.windowManager.open({
544 title: 'Insert/edit image',
546 body: generalFormItems,
547 onSubmit: onSubmitForm
552 editor.addButton('image', {
554 tooltip: 'Insert/edit image',
555 onclick: createImageList(showDialog),
556 stateSelector: 'img:not([data-mce-object],[data-mce-placeholder])'
559 editor.addMenuItem('image', {
561 text: 'Insert/edit image',
562 onclick: createImageList(showDialog),
564 prependToContext: true
567 editor.addCommand('mceImage', createImageList(showDialog));