wysiwyg-media.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /**
  2. * @file
  3. * Attach Media WYSIWYG behaviors.
  4. */
  5. (function ($) {
  6. Drupal.media = Drupal.media || {};
  7. // Define the behavior.
  8. Drupal.wysiwyg.plugins.media = {
  9. /**
  10. * Initializes the tag map.
  11. */
  12. initializeTagMap: function () {
  13. if (typeof Drupal.settings.tagmap == 'undefined') {
  14. Drupal.settings.tagmap = { };
  15. }
  16. },
  17. /**
  18. * Execute the button.
  19. * @TODO: Debug calls from this are never called. What's its function?
  20. */
  21. invoke: function (data, settings, instanceId) {
  22. if (data.format == 'html') {
  23. Drupal.media.popups.mediaBrowser(function (mediaFiles) {
  24. Drupal.wysiwyg.plugins.media.mediaBrowserOnSelect(mediaFiles, instanceId);
  25. }, settings['global']);
  26. }
  27. },
  28. /**
  29. * Respond to the mediaBrowser's onSelect event.
  30. * @TODO: Debug calls from this are never called. What's its function?
  31. */
  32. mediaBrowserOnSelect: function (mediaFiles, instanceId) {
  33. var mediaFile = mediaFiles[0];
  34. var options = {};
  35. Drupal.media.popups.mediaStyleSelector(mediaFile, function (formattedMedia) {
  36. Drupal.wysiwyg.plugins.media.insertMediaFile(mediaFile, formattedMedia.type, formattedMedia.html, formattedMedia.options, Drupal.wysiwyg.instances[instanceId]);
  37. }, options);
  38. return;
  39. },
  40. insertMediaFile: function (mediaFile, viewMode, formattedMedia, options, wysiwygInstance) {
  41. this.initializeTagMap();
  42. // @TODO: the folks @ ckeditor have told us that there is no way
  43. // to reliably add wrapper divs via normal HTML.
  44. // There is some method of adding a "fake element"
  45. // But until then, we're just going to embed to img.
  46. // This is pretty hacked for now.
  47. //
  48. var imgElement = $(this.stripDivs(formattedMedia));
  49. this.addImageAttributes(imgElement, mediaFile.fid, viewMode, options);
  50. var toInsert = this.outerHTML(imgElement);
  51. // Create an inline tag
  52. var inlineTag = Drupal.wysiwyg.plugins.media.createTag(imgElement);
  53. // Add it to the tag map in case the user switches input formats
  54. Drupal.settings.tagmap[inlineTag] = toInsert;
  55. wysiwygInstance.insert(toInsert);
  56. },
  57. /**
  58. * Gets the HTML content of an element
  59. *
  60. * @param jQuery element
  61. */
  62. outerHTML: function (element) {
  63. return $('<div>').append( element.eq(0).clone() ).html();
  64. },
  65. addImageAttributes: function (imgElement, fid, view_mode, additional) {
  66. // imgElement.attr('fid', fid);
  67. // imgElement.attr('view_mode', view_mode);
  68. // Class so we can find this image later.
  69. imgElement.addClass('media-image');
  70. this.forceAttributesIntoClass(imgElement, fid, view_mode, additional);
  71. if (additional) {
  72. for (k in additional) {
  73. if (additional.hasOwnProperty(k)) {
  74. if (k === 'attr') {
  75. imgElement.attr(k, additional[k]);
  76. }
  77. }
  78. }
  79. }
  80. },
  81. /**
  82. * Due to problems handling wrapping divs in ckeditor, this is needed.
  83. *
  84. * Going forward, if we don't care about supporting other editors
  85. * we can use the fakeobjects plugin to ckeditor to provide cleaner
  86. * transparency between what Drupal will output <div class="field..."><img></div>
  87. * instead of just <img>, for now though, we're going to remove all the stuff surrounding the images.
  88. *
  89. * @param String formattedMedia
  90. * Element containing the image
  91. *
  92. * @return HTML of <img> tag inside formattedMedia
  93. */
  94. stripDivs: function (formattedMedia) {
  95. // Check to see if the image tag has divs to strip
  96. var stripped = null;
  97. if ($(formattedMedia).is('img')) {
  98. stripped = this.outerHTML($(formattedMedia));
  99. } else {
  100. stripped = this.outerHTML($('img', $(formattedMedia)));
  101. }
  102. // This will fail if we pass the img tag without anything wrapping it, like we do when re-enabling WYSIWYG
  103. return stripped;
  104. },
  105. /**
  106. * Attach function, called when a rich text editor loads.
  107. * This finds all [[tags]] and replaces them with the html
  108. * that needs to show in the editor.
  109. *
  110. */
  111. attach: function (content, settings, instanceId) {
  112. var matches = content.match(/\[\[.*?\]\]/g);
  113. this.initializeTagMap();
  114. var tagmap = Drupal.settings.tagmap;
  115. if (matches) {
  116. var inlineTag = "";
  117. for (i = 0; i < matches.length; i++) {
  118. inlineTag = matches[i];
  119. if (tagmap[inlineTag]) {
  120. // This probably needs some work...
  121. // We need to somehow get the fid propogated here.
  122. // We really want to
  123. var tagContent = tagmap[inlineTag];
  124. var mediaMarkup = this.stripDivs(tagContent); // THis is <div>..<img>
  125. var _tag = inlineTag;
  126. _tag = _tag.replace('[[','');
  127. _tag = _tag.replace(']]','');
  128. try {
  129. mediaObj = JSON.parse(_tag);
  130. }
  131. catch(err) {
  132. mediaObj = null;
  133. }
  134. if(mediaObj) {
  135. var imgElement = $(mediaMarkup);
  136. this.addImageAttributes(imgElement, mediaObj.fid, mediaObj.view_mode);
  137. var toInsert = this.outerHTML(imgElement);
  138. content = content.replace(inlineTag, toInsert);
  139. }
  140. }
  141. else {
  142. debug.debug("Could not find content for " + inlineTag);
  143. }
  144. }
  145. }
  146. return content;
  147. },
  148. /**
  149. * Detach function, called when a rich text editor detaches
  150. */
  151. detach: function (content, settings, instanceId) {
  152. // Replace all Media placeholder images with the appropriate inline json
  153. // string. Using a regular expression instead of jQuery manipulation to
  154. // prevent <script> tags from being displaced.
  155. // @see http://drupal.org/node/1280758.
  156. if (matches = content.match(/<img[^>]+class=([\'"])media-image[^>]*>/gi)) {
  157. for (var i = 0; i < matches.length; i++) {
  158. var imageTag = matches[i];
  159. var inlineTag = Drupal.wysiwyg.plugins.media.createTag($(imageTag));
  160. Drupal.settings.tagmap[inlineTag] = imageTag;
  161. content = content.replace(imageTag, inlineTag);
  162. }
  163. }
  164. return content;
  165. },
  166. /**
  167. * @param jQuery imgNode
  168. * Image node to create tag from
  169. */
  170. createTag: function (imgNode) {
  171. // Currently this is the <img> itself
  172. // Collect all attribs to be stashed into tagContent
  173. var mediaAttributes = {};
  174. var imgElement = imgNode[0];
  175. var sorter = [];
  176. // @todo: this does not work in IE, width and height are always 0.
  177. for (i=0; i< imgElement.attributes.length; i++) {
  178. var attr = imgElement.attributes[i];
  179. if (attr.specified == true) {
  180. if (attr.name !== 'class') {
  181. sorter.push(attr);
  182. }
  183. else {
  184. // Exctract expando properties from the class field.
  185. var attributes = this.getAttributesFromClass(attr.value);
  186. for (var name in attributes) {
  187. if (attributes.hasOwnProperty(name)) {
  188. var value = attributes[name];
  189. if (value.type && value.type === 'attr') {
  190. sorter.push(value);
  191. }
  192. }
  193. }
  194. }
  195. }
  196. }
  197. sorter.sort(this.sortAttributes);
  198. for (var prop in sorter) {
  199. mediaAttributes[sorter[prop].name] = sorter[prop].value;
  200. }
  201. // The following 5 ifs are dedicated to IE7
  202. // If the style is null, it is because IE7 can't read values from itself
  203. if (jQuery.browser.msie && jQuery.browser.version == '7.0') {
  204. if (mediaAttributes.style === "null") {
  205. var imgHeight = imgNode.css('height');
  206. var imgWidth = imgNode.css('width');
  207. mediaAttributes.style = {
  208. height: imgHeight,
  209. width: imgWidth
  210. }
  211. if (!mediaAttributes['width']) {
  212. mediaAttributes['width'] = imgWidth;
  213. }
  214. if (!mediaAttributes['height']) {
  215. mediaAttributes['height'] = imgHeight;
  216. }
  217. }
  218. // If the attribute width is zero, get the CSS width
  219. if (Number(mediaAttributes['width']) === 0) {
  220. mediaAttributes['width'] = imgNode.css('width');
  221. }
  222. // IE7 does support 'auto' as a value of the width attribute. It will not
  223. // display the image if this value is allowed to pass through
  224. if (mediaAttributes['width'] === 'auto') {
  225. delete mediaAttributes['width'];
  226. }
  227. // If the attribute height is zero, get the CSS height
  228. if (Number(mediaAttributes['height']) === 0) {
  229. mediaAttributes['height'] = imgNode.css('height');
  230. }
  231. // IE7 does support 'auto' as a value of the height attribute. It will not
  232. // display the image if this value is allowed to pass through
  233. if (mediaAttributes['height'] === 'auto') {
  234. delete mediaAttributes['height'];
  235. }
  236. }
  237. // Remove elements from attribs using the blacklist
  238. for (var blackList in Drupal.settings.media.blacklist) {
  239. delete mediaAttributes[Drupal.settings.media.blacklist[blackList]];
  240. }
  241. tagContent = {
  242. "type": 'media',
  243. // @todo: This will be selected from the format form
  244. "view_mode": attributes['view_mode'].value,
  245. "fid" : attributes['fid'].value,
  246. "attributes": mediaAttributes
  247. };
  248. return '[[' + JSON.stringify(tagContent) + ']]';
  249. },
  250. /**
  251. * Forces custom attributes into the class field of the specified image.
  252. *
  253. * Due to a bug in some versions of Firefox
  254. * (http://forums.mozillazine.org/viewtopic.php?f=9&t=1991855), the
  255. * custom attributes used to share information about the image are
  256. * being stripped as the image markup is set into the rich text
  257. * editor. Here we encode these attributes into the class field so
  258. * the data survives.
  259. *
  260. * @param imgElement
  261. * The image
  262. * @fid
  263. * The file id.
  264. * @param view_mode
  265. * The view mode.
  266. * @param additional
  267. * Additional attributes to add to the image.
  268. */
  269. forceAttributesIntoClass: function (imgElement, fid, view_mode, additional) {
  270. var wysiwyg = imgElement.attr('wysiwyg');
  271. if (wysiwyg) {
  272. imgElement.addClass('attr__wysiwyg__' + wysiwyg);
  273. }
  274. var format = imgElement.attr('format');
  275. if (format) {
  276. imgElement.addClass('attr__format__' + format);
  277. }
  278. var typeOf = imgElement.attr('typeof');
  279. if (typeOf) {
  280. imgElement.addClass('attr__typeof__' + typeOf);
  281. }
  282. if (fid) {
  283. imgElement.addClass('img__fid__' + fid);
  284. }
  285. if (view_mode) {
  286. imgElement.addClass('img__view_mode__' + view_mode);
  287. }
  288. if (additional) {
  289. for (var name in additional) {
  290. if (additional.hasOwnProperty(name)) {
  291. if (name !== 'alt') {
  292. imgElement.addClass('attr__' + name + '__' + additional[name]);
  293. }
  294. }
  295. }
  296. }
  297. },
  298. /**
  299. * Retrieves encoded attributes from the specified class string.
  300. *
  301. * @param classString
  302. * A string containing the value of the class attribute.
  303. * @return
  304. * An array containing the attribute names as keys, and an object
  305. * with the name, value, and attribute type (either 'attr' or
  306. * 'img', depending on whether it is an image attribute or should
  307. * be it the attributes section)
  308. */
  309. getAttributesFromClass: function (classString) {
  310. var actualClasses = [];
  311. var otherAttributes = [];
  312. var classes = classString.split(' ');
  313. var regexp = new RegExp('^(attr|img)__([^\S]*)__([^\S]*)$');
  314. for (var index = 0; index < classes.length; index++) {
  315. var matches = classes[index].match(regexp);
  316. if (matches && matches.length === 4) {
  317. otherAttributes[matches[2]] = {name: matches[2], value: matches[3], type: matches[1]};
  318. }
  319. else {
  320. actualClasses.push(classes[index]);
  321. }
  322. }
  323. if (actualClasses.length > 0) {
  324. otherAttributes['class'] = {name: 'class', value: actualClasses.join(' '), type: 'attr'};
  325. }
  326. return otherAttributes;
  327. },
  328. /*
  329. *
  330. */
  331. sortAttributes: function (a, b) {
  332. var nameA = a.name.toLowerCase();
  333. var nameB = b.name.toLowerCase();
  334. if (nameA < nameB) {
  335. return -1;
  336. }
  337. if (nameA > nameB) {
  338. return 1;
  339. }
  340. return 0;
  341. }
  342. };
  343. })(jQuery);