linkpreview.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /**
  2. * (c) 2010 StatusNet, Inc.
  3. */
  4. (function() {
  5. /**
  6. * Quickie wrapper around ooembed JSON lookup
  7. */
  8. var oEmbed = {
  9. api: 'https://noembed.com/embed',
  10. width: 100,
  11. height: 75,
  12. cache: {},
  13. callbacks: {},
  14. /**
  15. * Do a cached oEmbed lookup for the given URL.
  16. *
  17. * @param {String} url
  18. * @param {function} callback
  19. */
  20. lookup: function(url, callback)
  21. {
  22. if (typeof oEmbed.cache[url] == "object") {
  23. // We already have a successful lookup.
  24. callback(oEmbed.cache[url]);
  25. } else if (typeof oEmbed.callbacks[url] == "undefined") {
  26. // No lookup yet... Start it!
  27. oEmbed.callbacks[url] = [callback];
  28. oEmbed.rawLookup(url, function(data) {
  29. oEmbed.cache[url] = data;
  30. var callbacks = oEmbed.callbacks[url];
  31. oEmbed.callbacks[url] = undefined;
  32. for (var i = 0; i < callbacks.length; i++) {
  33. callbacks[i](data);
  34. }
  35. });
  36. } else {
  37. // A lookup is in progress.
  38. oEmbed.callbacks[url].push(callback);
  39. }
  40. },
  41. /**
  42. * Do an oEmbed lookup for the given URL.
  43. *
  44. * @fixme proxy through ourselves if possible?
  45. * @fixme use the global thumbnail size settings
  46. *
  47. * @param {String} url
  48. * @param {function} callback
  49. */
  50. rawLookup: function(url, callback)
  51. {
  52. var params = {
  53. url: url,
  54. format: 'json',
  55. maxwidth: oEmbed.width,
  56. maxheight: oEmbed.height,
  57. token: $('#token').val()
  58. };
  59. $.ajax({
  60. url: oEmbed.api,
  61. data: params,
  62. dataType: 'json',
  63. success: function(data, xhr) {
  64. callback(data);
  65. },
  66. error: function(xhr, textStatus, errorThrown) {
  67. callback(null);
  68. }
  69. });
  70. }
  71. };
  72. SN.Init.LinkPreview = function(params) {
  73. if (params.api) oEmbed.api = params.api;
  74. if (params.width) oEmbed.width = params.width;
  75. if (params.height) oEmbed.height = params.height;
  76. }
  77. // Piggyback on the counter update...
  78. var origCounter = SN.U.Counter;
  79. SN.U.Counter = function(form) {
  80. var preview = form.data('LinkPreview');
  81. if (preview) {
  82. preview.previewLinks(form.find('.notice_data-text:first').val());
  83. }
  84. return origCounter(form);
  85. }
  86. // Customize notice form init...
  87. var origSetup = SN.Init.NoticeFormSetup;
  88. SN.Init.NoticeFormSetup = function(form) {
  89. origSetup(form);
  90. form
  91. .bind('reset', function() {
  92. LinkPreview.clear();
  93. });
  94. var LinkPreview = {
  95. links: [],
  96. state: [],
  97. refresh: [],
  98. /**
  99. * Find URL links from the source text that may be interesting.
  100. *
  101. * @param {String} text
  102. * @return {Array} list of URLs
  103. */
  104. findLinks: function (text)
  105. {
  106. // @fixme match this to core code
  107. var re = /(?:^| )(https?:\/\/.+?\/.+?)(?= |$)/mg;
  108. var links = [];
  109. var matches;
  110. while ((matches = re.exec(text)) !== null) {
  111. links.push(matches[1]);
  112. }
  113. return links;
  114. },
  115. ensureArea: function() {
  116. if (form.find('.link-preview').length < 1) {
  117. form.append('<div class="notice-status link-preview thumbnails"></div>');
  118. }
  119. },
  120. /**
  121. * Start looking up info for a link preview...
  122. * May start async data loads.
  123. *
  124. * @param {number} col: column number to insert preview into
  125. */
  126. prepLinkPreview: function(col)
  127. {
  128. var id = 'link-preview-' + col;
  129. var url = LinkPreview.links[col];
  130. LinkPreview.refresh[col] = false;
  131. LinkPreview.markLoading(col);
  132. oEmbed.lookup(url, function(data) {
  133. var thumb = null;
  134. var width = 100;
  135. if (data && typeof data.thumbnail_url == "string") {
  136. thumb = data.thumbnail_url;
  137. if (typeof data.thumbnail_width !== "undefined") {
  138. if (data.thumbnail_width < width) {
  139. width = data.thumbnail_width;
  140. }
  141. }
  142. } else if (data && data.type == 'photo' && typeof data.url == "string") {
  143. thumb = data.url;
  144. if (typeof data.width !== "undefined") {
  145. if (data.width < width) {
  146. width = data.width;
  147. }
  148. }
  149. }
  150. if (thumb) {
  151. LinkPreview.ensureArea();
  152. var link = $('<span class="inline-attachment"><a><img/></a></span>');
  153. link.find('a')
  154. .attr('href', url)
  155. .attr('target', '_blank')
  156. .last()
  157. .find('img')
  158. .attr('src', thumb)
  159. .attr('width', width)
  160. .attr('title', data.title || data.url || url);
  161. form.find('.' + id)
  162. .empty()
  163. .append(link);
  164. } else {
  165. // No thumbnail available or error retriving it.
  166. LinkPreview.clearLink(col);
  167. }
  168. if (LinkPreview.refresh[col]) {
  169. // Darn user has typed more characters.
  170. // Go fetch another link!
  171. LinkPreview.prepLinkPreview(col);
  172. } else {
  173. LinkPreview.markDone(col);
  174. }
  175. });
  176. },
  177. /**
  178. * Update the live preview section with links found in the given text.
  179. * May start async data loads.
  180. *
  181. * @param {String} text: free-form input text
  182. */
  183. previewLinks: function(text)
  184. {
  185. var i;
  186. var old = LinkPreview.links;
  187. var links = LinkPreview.findLinks(text);
  188. LinkPreview.links = links;
  189. // Check for existing common elements...
  190. for (i = 0; i < old.length && i < links.length; i++) {
  191. if (links[i] != old[i]) {
  192. if (LinkPreview.state[i] == "loading") {
  193. // Slate this column for a refresh when this one's done.
  194. LinkPreview.refresh[i] = true;
  195. } else {
  196. // Change an existing entry!
  197. LinkPreview.prepLinkPreview(i);
  198. }
  199. }
  200. }
  201. if (links.length > old.length) {
  202. // Adding new entries, whee!
  203. for (i = old.length; i < links.length; i++) {
  204. LinkPreview.addPreviewArea(i);
  205. LinkPreview.prepLinkPreview(i);
  206. }
  207. } else if (old.length > links.length) {
  208. // Remove preview entries for links that have been removed.
  209. for (i = links.length; i < old.length; i++) {
  210. LinkPreview.clearLink(i);
  211. }
  212. }
  213. if (links.length == 0) {
  214. LinkPreview.clear();
  215. }
  216. },
  217. addPreviewArea: function(col) {
  218. LinkPreview.ensureArea();
  219. var id = 'link-preview-' + col;
  220. if (form.find('.' + id).length < 1) {
  221. form.find('.link-preview').append('<span class="' + id + '"></span>');
  222. }
  223. },
  224. clearLink: function(col) {
  225. var id = 'link-preview-' + col;
  226. form.find('.' + id).html('');
  227. },
  228. markLoading: function(col) {
  229. LinkPreview.state[col] = "loading";
  230. var id = 'link-preview-' + col;
  231. form.find('.' + id).attr('style', 'opacity: 0.5');
  232. },
  233. markDone: function(col) {
  234. LinkPreview.state[col] = "done";
  235. var id = 'link-preview-' + col;
  236. form.find('.' + id).removeAttr('style');
  237. },
  238. /**
  239. * Clear out any link preview data.
  240. */
  241. clear: function() {
  242. LinkPreview.links = [];
  243. form.find('.link-preview').remove();
  244. }
  245. };
  246. form.data('LinkPreview', LinkPreview);
  247. }
  248. })();