library.js 12 KB

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