attachment preview: handle content with attachment bbcode inside (show preview of...
[friendica.git/.git] / view / js / linkPreview.js
1 /**\r
2  * Copyright (c) 2014 Leonardo Cardoso (http://leocardz.com)\r
3  * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)\r
4  * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.\r
5  * \r
6  * Restructured from Rabzuarus (https://friendica.kommune4.de/profile/rabuzarus)\r
7  * to use it for the decental social network Friendica (https://friendi.ca).\r
8  * \r
9  * Version: 1.4.0\r
10  */\r
11 (function ($) {\r
12         $.fn.linkPreview = function (options) {\r
13                 var opts = jQuery.extend({}, $.fn.linkPreview.defaults, options);\r
14 \r
15                 var selector = $(this).selector;\r
16                 selector = selector.substr(1);\r
17 \r
18                 var previewTpl = '\\r
19                         <div id="preview_' + selector + '" class="preview {0}">\\r
20                                 {1}\\r
21                                 <input type="hidden" name="has_attachment" id="hasAttachment_' + selector + '" value="{2}" />\\r
22                                 <input type="hidden" name="attachment_url" id="attachmentUrl_' + selector + '" value="{3}" />\\r
23                                 <input type="hidden" name="attachment_type" id="attachmentType_' + selector + '" value="{4}" />\\r
24                         </div>';\r
25 \r
26                 var attachmentTpl = '\\r
27                         <hr class="previewseparator">\\r
28                         <div id="closePreview_' + selector + '" title="Remove" class="closePreview" >\\r
29                                 <button type="button" class="previewActionBtn">×</button>\\r
30                         </div>\\r
31                         <div id="previewImages_' + selector + '" class="previewImages">\\r
32                                 <div id="previewImgBtn_' + selector + '" class="previewImgBtn">\\r
33                                         <button type="button" id="previewChangeImg_' + selector + '" class="buttonChangeDeactive previewActionBtn" style="display: none">\\r
34                                                 <i class="fa fa-exchange" aria-hidden="true"></i>\\r
35                                         </button>\\r
36                                 </div>\\r
37                                 <div id="previewImage_' + selector + '" class="previewImage">\\r
38                                 </div>\\r
39                                 <input type="hidden" id="photoNumber_' + selector + '" class="photoNumber" value="0" />\\r
40                                 <input type="hidden" name="attachment_img_src" id="attachmentImageSrc_' + selector + '" value="" />\\r
41                                 <input type="hidden" name="attachment_img_width" id="attachmentImageWidth_' + selector + '" value="0" />\\r
42                                 <input type="hidden" name="attachment_img_height" id="attachmentImageHeight_' + selector + '" value="0" />\\r
43                         </div>\\r
44                         <div id="previewContent_' + selector + '" class="previewContent">\\r
45                                 <h4 id="previewTitle_' + selector + '" class="previewTitle"></h4>\\r
46                                 <blockquote id="previewDescription_' + selector + '" class="previewDescription"></blockquote>\\r
47                                 <div id="hiddenDescription_' + selector + '" class="hiddenDescription"></div>\\r
48                                 <sup id="previewUrl_' + selector + '" class="previewUrl"></sup>\\r
49                         </div>\\r
50                         <div class="clear"></div>\\r
51                         <hr class="previewseparator">';\r
52                 var text;\r
53                 var urlRegex = /(https?\:\/\/|\s)[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})(\/+[a-z0-9_.\:\;-]*)*(\?[\&\%\|\+a-z0-9_=,\.\:\;-]*)?([\&\%\|\+&a-z0-9_=,\:\;\.-]*)([\!\#\/\&\%\|\+a-z0-9_=,\:\;\.-]*)}*/i;\r
54                 var binurl;\r
55                 var block = false;\r
56                 var blockTitle = false;\r
57                 var blockDescription = false;\r
58                 var cache = {};\r
59                 var images = "";\r
60                 var isExtern = false;\r
61                 var photoNumber = 0;\r
62                 var firstPosted = false;\r
63                 var isActive = false;\r
64                 var isCrawling = false;\r
65                 var defaultTitle = opts.defaultTitle;\r
66                 var defaultDescription = opts.defaultDescription;\r
67 \r
68                 /**\r
69                  * Initialize the plugin\r
70                  * \r
71                  * @returns {void}\r
72                  */\r
73                 var init = function() {\r
74                         $('#' + selector).bind({\r
75                                 paste: function () {\r
76                                         setTimeout(function () {\r
77                                                 crawlText();\r
78                                         }, 100);\r
79                                 },\r
80                                 keyup: function (e) {\r
81                                         // on enter, space, ctrl\r
82                                         if ((e.which === 13 || e.which === 32 || e.which === 17)) {\r
83                                                 crawlText();\r
84                                         }\r
85                                 }\r
86                         });\r
87 \r
88                         // Check if we have already attachment bbcode in the textarea\r
89                         // and add it to the attachment preview.\r
90                         var content = $('#' + selector).val();\r
91                         addBBCodeToPreview(content);\r
92                 };\r
93 \r
94                 /**\r
95                  * Reset some values.\r
96                  * \r
97                  * @returns {void}\r
98                  */\r
99                 var resetPreview = function() {\r
100                         $('#hasAttachment_' + selector).val(0);\r
101                         photoNumber = 0;\r
102                         images = "";\r
103                 };\r
104 \r
105                 /**\r
106                  * Crawl a text string if it contains an url and try\r
107                  * to attach it.\r
108                  * \r
109                  * If no text is passed to crawlText() we take\r
110                  * the previous word before the cursor of the textarea.\r
111                  * \r
112                  * @param {string} text (optional)\r
113                  * @returns {void}\r
114                  */\r
115                 var crawlText = function (text) {\r
116                         block = false;\r
117                         images = '';\r
118                         isExtern = false;\r
119 \r
120                         // If no text is passed to crawlText() we \r
121                         // take the previous word before the cursor.\r
122                         if (typeof text === 'undefined') {\r
123                                 text = getPrevWord(selector);\r
124                         } else {\r
125                                 isExtern = true;\r
126                         }\r
127 \r
128                         // Don't procces the textarea input if we have already\r
129                         // an attachment preview.\r
130                         if (!isExtern && isActive) {\r
131                                 return;\r
132                         }\r
133 \r
134                         if (trim(text) !== "") {\r
135                                 if (block === false && urlRegex.test(text)) {\r
136                                         binurl = bin2hex(text);\r
137                                         block = true;\r
138 \r
139                                         isCrawling = true;\r
140                                         $('#profile-rotator').show();\r
141 \r
142                                         if (binurl in cache) {\r
143                                                 isCrawling = false;\r
144                                                 processContentData(cache[binurl]);\r
145                                         } else {\r
146                                                 getContentData(binurl, processContentData);\r
147                                         }\r
148                                 }\r
149                         }\r
150                 };\r
151 \r
152                 /**\r
153                  * Process the attachment data according to\r
154                  * its content type (image, audio, video, attachment)\r
155                  * \r
156                  * @param {object} result\r
157                  * @returns {void}\r
158                  */\r
159                 var processContentData = function(result) {\r
160                         if (result.contentType === 'image') {\r
161                                 insertImage(result.data);\r
162                         }\r
163                         if (result.contentType === 'audio') {\r
164                                 insertAudio(result.data);\r
165                         }\r
166                         if (result.contentType === 'video') {\r
167                                 insertVideo(result.data);\r
168                         }\r
169                         if (result.contentType === 'attachment') {\r
170                                 insertAttachment(result.data);\r
171                         }\r
172                         $('#profile-rotator').hide();\r
173                 };\r
174 \r
175                 /**\r
176                  * Fetch the content of link which should be attached.\r
177                  * \r
178                  * @param {string} binurl Link which should be attached as hexadecimal string.\r
179                  * @param {type} callback\r
180                  * @returns {void}\r
181                  */\r
182                 var getContentData = function(binurl, callback) {\r
183                         $.get('parse_url?binurl='+ binurl + '&dataType=json', function (answer) {\r
184                                 obj = sanitizeInputData(answer);\r
185 \r
186                                 // Put the data into a cache\r
187                                 cache[binurl] = obj;\r
188 \r
189                                 callback(obj);\r
190 \r
191                                 isCrawling = false;\r
192                         });\r
193                 };\r
194 \r
195                 /*\r
196                  * Add a [img] bbtag with the image url to the jot editor.\r
197                  * \r
198                  * @param {type} data\r
199                  * @returns {void}\r
200                  */\r
201                 var insertImage = function(data) {\r
202                         if (!isExtern) {\r
203                                 return;\r
204                         }\r
205                         var bbcode = '\n[img]' + data.url + '[/img]\n';\r
206                         addeditortext(bbcode);\r
207                 };\r
208 \r
209                 /*\r
210                  * Add a [audio] bbtag with the audio url to the jot editor.\r
211                  * \r
212                  * @param {type} data\r
213                  * @returns {void}\r
214                  */\r
215                 var insertAudio = function(data) {\r
216                         if (!isExtern) {\r
217                                 return;\r
218                         }\r
219                         var bbcode = '\n[audio]' + data.url + '[/audio]\n';\r
220                         addeditortext(bbcode);\r
221                 };\r
222 \r
223                 /*\r
224                  * Add a [video] bbtag with the video url to the jot editor.\r
225                  * \r
226                  * @param {type} data\r
227                  * @returns {void}\r
228                  */\r
229                 var insertVideo = function(data) {\r
230                         if (!isExtern) {\r
231                                 return;\r
232                         }\r
233                         var bbcode = '\n[video]' + json.url + '[/video]\n';\r
234                         addeditortext(bbcode);\r
235                 };\r
236 \r
237                 /**\r
238                  * Proccess all attachment data and show up a html\r
239                  * attachment preview.\r
240                  * \r
241                  * @param {obj} data Attachment data.\r
242                  * @returns {void}\r
243                  */\r
244                 var insertAttachment = function(data) {\r
245                         // If we have already a preview, leaver here.\r
246                         // Note: if we finish the Preview of other media content type,\r
247                         // we can move this condition to the beggining of crawlText();\r
248                         if (isActive) {\r
249                                 $('#profile-rotator').hide();\r
250                                 return;\r
251                         }\r
252 \r
253                         if (data.type !== 'link' && data.type !== 'video' && data.type !== 'photo' || data.url === data.title) {\r
254                                 $('#profile-rotator').hide();\r
255                                 return;\r
256                         }\r
257 \r
258                         $('#photoNumber_' + selector).val(0);\r
259                         resetPreview();\r
260 \r
261                         processAttachmentTpl(data, 'type-' + data.type);\r
262                         addTitleDescription(data);\r
263                         addHostToAttachment(data.url);\r
264                         addImagesToAttachment(data.images);\r
265 \r
266                         processEventListener();\r
267                         $('#profile-rotator').hide();\r
268                 };\r
269 \r
270                 /**\r
271                  * Construct the attachment html from the attachment template and\r
272                  * add it to the DOM.\r
273                  * \r
274                  * @param {object} data Attachment data.\r
275                  * @returns {void}\r
276                  */\r
277                 var processAttachmentTpl = function(data) {\r
278                         // Load and add the template if it isn't allready loaded.\r
279                         if ($('#preview_' + selector).length === 0) {\r
280                                 var tpl = previewTpl.format(\r
281                                         'type-' + data.type,\r
282                                         attachmentTpl,\r
283                                         1,\r
284                                         bin2hex(data.url),\r
285                                         data.type\r
286                                 );\r
287                                 $('#' + selector).after(tpl);\r
288                         }\r
289 \r
290                         isActive = true;\r
291                 };\r
292 \r
293                 /**\r
294                  * Add the attachment title and the description\r
295                  * to the attachment preview.\r
296                  * \r
297                  * @param {object} data Attachment data.\r
298                  * @returns {void}\r
299                  */\r
300                 var addTitleDescription = function(data) {\r
301                         var description = data.text;\r
302 \r
303                         if (description === '') {\r
304                                 description = defaultDescription;\r
305                         }\r
306 \r
307                         $('#previewTitle_' + selector).html("\\r
308                                 <span id='previewSpanTitle_" + selector + "' class='previewSpanTitle' >" + escapeHTML(data.title) + "</span>\\r
309                                 <input type='text' name='attachment_title' value='" + escapeHTML(data.title) + "' id='previewInputTitle_" + selector + "' class='previewInputTitle inputPreview' style='display: none;'/>"\r
310                         );\r
311 \r
312                         $('#previewDescription_' + selector).html("\\r
313                                 <span id='previewSpanDescription_" + selector + "' class='previewSpanDescription' >" + escapeHTML(description) + "</span>\n\\r
314                                 <textarea id='previewInputDescription_" + selector + "' name='attachment_text' class='previewInputDescription' style='display: none;' class='inputPreview' >" + escapeHTML(data.text) + "</textarea>"\r
315                         );\r
316                 };\r
317 \r
318                 /**\r
319                  * Add the host to the attachment preview.\r
320                  * \r
321                  * @param {string} url The url of the link attachment.\r
322                  * @returns {void}\r
323                  */\r
324                 var addHostToAttachment = function(url) {\r
325                         if (url) {\r
326                                 var regexpr = "(https?://)([^:^/]*)(:\\d*)?(.*)?";\r
327                                 var regResult = url.match(regexpr);\r
328                                 var urlHost = regResult[1] + regResult[2];\r
329                                 $('#previewUrl_' + selector).html("<a href='" + url + "'>" + urlHost + "</a>");\r
330                         }\r
331                 };\r
332 \r
333                 /**\r
334                  * Add preview images to the attachment.\r
335                  * \r
336                  * @param {array} images\r
337                  * \r
338                  * @returns {void}\r
339                  */\r
340                 var addImagesToAttachment = function(images) {\r
341                         var imageClass = 'attachment-preview';\r
342         \r
343                         if (Array.isArray(images)) {\r
344                                 $('#previewImages_' + selector).show();\r
345                                 $('#attachmentImageSrc_' + selector).val(bin2hex(images[photoNumber].src));\r
346                                 $('#attachmentImageWidth_' + selector).val(images[photoNumber].width);\r
347                                 $('#attachmentImageHeight_' + selector).val(images[photoNumber].height);\r
348                         } else {\r
349                                 $('#previewImages_' + selector).hide();\r
350                         }\r
351 \r
352                         images.length = parseInt(images.length);\r
353                         var appendImage = "";\r
354 \r
355                         for (i = 0; i < images.length; i++) {\r
356                                 // For small preview images we use a smaller attachment format.\r
357                                 ///@todo here we need to add a check for !Config::get('system', 'always_show_preview').\r
358                                 if (images[i].width >= 500 && images[i].width >= images[i].height) {\r
359                                                 imageClass = 'attachment-image';\r
360                                 }\r
361 \r
362                                 if (i === 0) {\r
363                                         appendImage += "<img id='imagePreview_" + selector + "_" + i + "' src='" + images[i].src + "' class='" + imageClass + "' ></img>";\r
364                                 } else {\r
365                                         appendImage += "<img id='imagePreview_" + selector + "_" + i + "' src='" + images[i].src + "' class='" + imageClass + "' style='display: none;'></img>";\r
366                                 }\r
367                         }\r
368 \r
369                         $('#previewImage_' + selector).html(appendImage + "<div id='whiteImage' style='color: transparent; display:none;'>...</div>");\r
370 \r
371                         // More than just one image.\r
372                         if (images.length > 1) {\r
373                                 // Enable the the button to change the preview pictures.\r
374                                 $('#previewChangeImg_' + selector).show();\r
375 \r
376                                 if (firstPosted === false) {\r
377                                         firstPosted = true;\r
378 \r
379                                         $('#previewChangeImg_' + selector).unbind('click').click(function (e) {\r
380                                                 e.stopPropagation();\r
381                                                 if (images.length > 1) {\r
382                                                         $('#imagePreview_' + selector + '_' + photoNumber).css({\r
383                                                                 'display': 'none'\r
384                                                         });\r
385                                                         photoNumber += 1;\r
386 \r
387                                                         // If have reached the last image, begin with the first image.\r
388                                                         if (photoNumber === images.length) {\r
389                                                                 photoNumber = 0;\r
390                                                         }\r
391 \r
392                                                         $('#imagePreview_' + selector + '_' + photoNumber).css({\r
393                                                                 'display': 'block'\r
394                                                         });\r
395                                                         $('#photoNumber_' + selector).val(photoNumber);\r
396                                                         $('#attachmentImageSrc_' + selector).val(bin2hex(images[photoNumber].src));\r
397                                                         $('#attachmentImageWidth_' + selector).val(images[photoNumber].width);\r
398                                                         $('#attachmentImageHeight_' + selector).val(images[photoNumber].height);\r
399                                                 }\r
400                                         });\r
401                                 }\r
402                         }\r
403                 };\r
404 \r
405                 /**\r
406                  * Add event listener to control the attachment preview.\r
407                  * \r
408                  * @returns {void}\r
409                  */\r
410                 var processEventListener = function() {\r
411                         $('#previewSpanTitle_' + selector).unbind('click').click(function (e) {\r
412                                 e.stopPropagation();\r
413                                 if (blockTitle === false) {\r
414                                         blockTitle = true;\r
415                                         $('#previewSpanTitle_' + selector).hide();\r
416                                         $('#previewInputTitle_' + selector).show();\r
417                                         $('#previewInputTitle_' + selector).val($('#previewInputTitle_' + selector).val());\r
418                                         $('#previewInputTitle_' + selector).focus().select();\r
419                                 }\r
420                         });\r
421 \r
422                         $('#previewInputTitle_' + selector).blur(function () {\r
423                                 blockTitle = false;\r
424                                 $('#previewSpanTitle_' + selector).html($('#previewInputTitle_' + selector).val());\r
425                                 $('#previewSpanTitle_' + selector).show();\r
426                                 $('#previewInputTitle_' + selector).hide();\r
427                         });\r
428 \r
429                         $('#previewInputTitle_' + selector).keypress(function (e) {\r
430                                 if (e.which === 13) {\r
431                                         blockTitle = false;\r
432                                         $('#previewSpanTitle_' + selector).html($('#previewInputTitle_' + selector).val());\r
433                                         $('#previewSpanTitle_' + selector).show();\r
434                                         $('#previewInputTitle_' + selector).hide();\r
435                                 }\r
436                         });\r
437 \r
438                         $('#previewSpanDescription_' + selector).unbind('click').click(function (e) {\r
439                                 e.stopPropagation();\r
440                                 if (blockDescription === false) {\r
441                                         blockDescription = true;\r
442                                         $('#previewSpanDescription_' + selector).hide();\r
443                                         $('#previewInputDescription_' + selector).show();\r
444                                         $('#previewInputDescription_' + selector).val($('#previewInputDescription_' + selector).val());\r
445                                         $('#previewInputDescription_' + selector).focus().select();\r
446                                 }\r
447                         });\r
448 \r
449                         $('#previewInputDescription_' + selector).blur(function () {\r
450                                 blockDescription = false;\r
451                                 $('#previewSpanDescription_' + selector).html($('#previewInputDescription_' + selector).val());\r
452                                 $('#previewSpanDescription_' + selector).show();\r
453                                 $('#previewInputDescription_' + selector).hide();\r
454                         });\r
455 \r
456                         $('#previewInputDescription_' + selector).keypress(function (e) {\r
457                                 if (e.which === 13) {\r
458                                         blockDescription = false;\r
459                                         $('#previewSpanDescription_' + selector).html($('#previewInputDescription_' + selector).val());\r
460                                         $('#previewSpanDescription_' + selector).show();\r
461                                         $('#previewInputDescription_' + selector).hide();\r
462                                 }\r
463                         });\r
464 \r
465                         $('#previewSpanTitle_' + selector).mouseover(function () {\r
466                                 $('#previewSpanTitle_' + selector).css({\r
467                                         "background-color": "#ff9"\r
468                                 });\r
469                         });\r
470 \r
471                         $('#previewSpanTitle_' + selector).mouseout(function () {\r
472                                 $('#previewSpanTitle_' + selector).css({\r
473                                         "background-color": "transparent"\r
474                                 });\r
475                         });\r
476 \r
477                         $('#previewSpanDescription_' + selector).mouseover(function () {\r
478                                 $('#previewSpanDescription_' + selector).css({\r
479                                         "background-color": "#ff9"\r
480                                 });\r
481                         });\r
482 \r
483                         $('#previewSpanDescription_' + selector).mouseout(function () {\r
484                                 $('#previewSpanDescription_' + selector).css({\r
485                                         "background-color": "transparent"\r
486                                 });\r
487                         });\r
488 \r
489                         $('#closePreview_' + selector).unbind('click').click(function (e) {\r
490                                 e.stopPropagation();\r
491                                 block = false;\r
492                                 images = '';\r
493                                 isActive = false;\r
494                                 firstPosted = false;\r
495                                 $('#preview_' + selector).fadeOut("fast", function () {\r
496                                         $('#preview_' + selector).remove();\r
497                                         $('#profile-rotator').hide();\r
498                                         $('#' + selector).focus();\r
499                                 });\r
500 \r
501                         });\r
502                 };\r
503 \r
504                 /**\r
505                  * Convert attachmant bbcode into an array.\r
506                  * \r
507                  * @param {string} content Text content with the attachment bbcode.\r
508                  * @returns {object || null}\r
509                  */\r
510                 var getAttachmentData = function(content) {\r
511                         var data = {};\r
512 \r
513                         var match = content.match(/(.*)\[attachment(.*?)\](.*?)\[\/attachment\](.*)/ism);\r
514                         if (match === null || match.length < 5) {\r
515                                 return null;\r
516                         }\r
517 \r
518                         var attributes = match[2];\r
519                         data.text = trim(match[1]);\r
520 \r
521                         var type = '';\r
522                         var matches = attributes.match(/type='(.*?)'/ism);\r
523                         if (matches !== null && typeof matches[1] !== 'undefined') {\r
524                                 type = matches[1].toLowerCase();\r
525                         }\r
526 \r
527                         matches = attributes.match(/type="(.*?)"/ism);\r
528                         if (matches !== null && typeof matches[1] !== 'undefined') {\r
529                                 type = matches[1].toLowerCase();\r
530                         }\r
531 \r
532                         if (type === '') {\r
533                                 return null;\r
534                         }\r
535 \r
536                         if (\r
537                                 type !== 'link'\r
538                                 && type !== 'audio'\r
539                                 && type !== 'photo'\r
540                                 && type !== 'video')\r
541                         {\r
542                                 return null;\r
543                         }\r
544 \r
545                         if (type !== '') {\r
546                                 data.type = type;\r
547                         }\r
548 \r
549                         var url = '';\r
550 \r
551                         matches = attributes.match(/url='(.*?)'/ism);\r
552                         if (matches !== null && typeof matches[1] !== 'undefined') {\r
553                                 url = matches[1].toLowerCase();\r
554                         }\r
555 \r
556                         matches = attributes.match(/url="(.*?)"/ism);\r
557                         if (matches !== null && typeof matches[1] !== 'undefined') {\r
558                                 url = matches[1].toLowerCase();\r
559                         }\r
560 \r
561                         if(url !== '') {\r
562                                 data.url = escapeHTML(url);\r
563                         }\r
564 \r
565                         var title = '';\r
566 \r
567                         matches = attributes.match(/title='(.*?)'/ism);\r
568                         if (matches !== null && typeof matches[1] !== 'undefined') {\r
569                                 title = matches[1].toLowerCase();\r
570                         }\r
571 \r
572                         matches = attributes.match(/title="(.*?)"/ism);\r
573                         if (matches !== null && typeof matches[1] !== 'undefined') {\r
574                                 title = matches[1].toLowerCase();\r
575                         }\r
576 \r
577                         if (title !== '') {\r
578                                 data.title = escapeHTML(title);\r
579                         }\r
580 \r
581                         var image = '';\r
582 \r
583                         matches = attributes.match(/image='(.*?)'/ism);\r
584                         if (matches !== null && typeof matches[1] !== 'undefined') {\r
585                                 image = matches[1].toLowerCase();\r
586                         }\r
587 \r
588                         matches = attributes.match(/image="(.*?)"/ism);\r
589                         if (matches !== null && typeof matches[1] !== 'undefined') {\r
590                                 image = matches[1].toLowerCase();\r
591                         }\r
592 \r
593                         if (image !== '') {\r
594                                 data.image = escapeHTML(image);\r
595                         }\r
596 \r
597                         var preview = '';\r
598 \r
599                         matches = attributes.match(/preview='(.*?)'/ism);\r
600                         if (matches !== null && typeof matches[1] !== 'undefined') {\r
601                                 preview = matches[1].toLowerCase();\r
602                         }\r
603 \r
604                         matches = attributes.match(/preview="(.*?)"/ism);\r
605                         if (matches !== null && typeof matches[1] !== 'undefined') {\r
606                                 preview = matches[1].toLowerCase();\r
607                         }\r
608 \r
609                         if (preview !== '') {\r
610                                 data.preview = escapeHTML(preview);\r
611                         }\r
612 \r
613                         data.text = trim(match[3]);\r
614                         data.after = trim(match[4]);\r
615 \r
616                         return data;\r
617                 };\r
618 \r
619                 /**\r
620                  * Process txt content and if it contains attachment bbcode\r
621                  * add it to the attachment preview .\r
622                  * \r
623                  * @param {string} content\r
624                  * @returns {void}\r
625                  */\r
626                 var addBBCodeToPreview =function(content) {\r
627                         var attachmentData = getAttachmentData(content);\r
628                         if (attachmentData) {\r
629                                 reAddAttachment(attachmentData);\r
630                                 // Remove the attachment bbcode from the textarea.\r
631                                 var content = content.replace(/\[attachment.*\[\/attachment]/ism, '');\r
632                                 $('#' + selector).val(content);\r
633                                 $('#' + selector).focus();\r
634                         }\r
635                 };\r
636 \r
637                 /**\r
638                  * Add an Attachment with data from an old bbcode\r
639                  * generated attachment.\r
640                  * \r
641                  * @param {object} json The attachment data.\r
642                  * @returns {void}\r
643                  */\r
644                 var reAddAttachment = function(json) {\r
645                         if (isActive) {\r
646                                 $('#profile-rotator').hide();\r
647                                 return;\r
648                         }\r
649 \r
650                         if (json.type !== 'link' && json.type !== 'video' && json.type !== 'photo' || json.url === json.title) {\r
651                                 $('#profile-rotator').hide();\r
652                                 return;\r
653                         }\r
654 \r
655                         var obj = {data: json};\r
656                         obj = sanitizeInputData(obj);\r
657 \r
658                         var data = obj.data;\r
659 \r
660                         resetPreview();\r
661 \r
662                         processAttachmentTpl(data);\r
663                         addTitleDescription(data);\r
664                         addHostToAttachment(data.url);\r
665 \r
666                         // Since we don't have an array of image data,\r
667                         // we need to add the preview images in a different way\r
668                         // than in function addImagesToAttachment().\r
669                         var imageClass = 'attachment-preview';\r
670                         var image = '';\r
671 \r
672                         if (data.image !== '') {\r
673                                 imageClass = 'attachment-image';\r
674                                 image = data.image;\r
675                         } else {\r
676                                 image = data.preview;\r
677                         }\r
678 \r
679                         if (image !== '') {\r
680                                 var appendImage = "<img id='imagePreview_" + selector + "' src='" + image + "' class='" + imageClass + "' ></img>"\r
681                                 $('#previewImage_' + selector).html(appendImage);\r
682                                 $('#attachmentImageSrc_' + selector).val(bin2hex(image));\r
683 \r
684                                 // We need to add the image widht and height when it is \r
685                                 // loaded.\r
686                                 $('<img/>' ,{\r
687                                         load : function(){\r
688                                                 $('#attachmentImageWidth_' + selector).val(this.width);\r
689                                                 $('#attachmentImageHeight_' + selector).val(this.height);\r
690                                         },\r
691                                         src  : image\r
692                                 });\r
693                         }\r
694 \r
695                         processEventListener();\r
696                         $('#profile-rotator').hide();\r
697                 };\r
698 \r
699                 /**\r
700                  * Add missing default properties to the input data object.\r
701                  * \r
702                  * @param {object} obj Input data.\r
703                  * @returns {object}\r
704                  */\r
705                 var sanitizeInputData = function(obj) {\r
706                         if (typeof obj.contentType === 'undefined'\r
707                                 || obj.contentType === null)\r
708                         {\r
709                                 obj.contentType = "";\r
710                         }\r
711                         if (typeof obj.data.url === 'undefined'\r
712                                 || obj.data.url === null)\r
713                         {\r
714                                 obj.data.url = "";\r
715                         }\r
716                         if (typeof obj.data.title === 'undefined'\r
717                                 || obj.data.title === null\r
718                                 || obj.data.title === "")\r
719                         {\r
720                                 obj.data.title = defaultTitle;\r
721                         }\r
722                         if (typeof obj.data.text === 'undefined'\r
723                                 || obj.data.text === null\r
724                                 || obj.data.text === "")\r
725                         {\r
726                                 obj.data.text = "";\r
727                         }\r
728                         if (typeof obj.data.images === 'undefined'\r
729                                 || obj.data.images === null)\r
730                         {\r
731                                 obj.data.images = "";\r
732                         }\r
733 \r
734                         if (typeof obj.data.image === 'undefined'\r
735                                 || obj.data.image === null)\r
736                         {\r
737                                 obj.data.image = "";\r
738                         }\r
739 \r
740                         if (typeof obj.data.preview === 'undefined'\r
741                                 || obj.data.preview === null)\r
742                         {\r
743                                 obj.data.preview = "";\r
744                         }\r
745 \r
746                         return obj;\r
747                 };\r
748 \r
749                 /**\r
750                  * Destroy the plugin.\r
751                  * \r
752                  * @returns {void}\r
753                  */\r
754                 var destroy = function() {\r
755                         $('#' + selector).unbind();\r
756                         $('#preview_' + selector).remove();\r
757                         binurl;\r
758                         block = false;\r
759                         blockTitle = false;\r
760                         blockDescription = false;\r
761                         cache = {};\r
762                         images = "";\r
763                         isExtern = false;\r
764                         photoNumber = 0;\r
765                         firstPosted = false;\r
766                         isActive = false;\r
767                         isCrawling = false;\r
768                         selector = "";\r
769                 };\r
770 \r
771                 var trim = function(str) {\r
772                         return str.replace(/^\s+|\s+$/g, "");\r
773                 };\r
774                 var escapeHTML = function(unsafe_str) {\r
775                         return unsafe_str\r
776                                 .replace(/&/g, '&amp;')\r
777                                 .replace(/</g, '&lt;')\r
778                                 .replace(/>/g, '&gt;')\r
779                                 .replace(/\"/g, '&quot;')\r
780                                 .replace(/\[/g, '&#91;')\r
781                                 .replace(/\]/g, '&#93;')\r
782                                 .replace(/\'/g, '&#39;'); // '&apos;' is not valid HTML 4\r
783                 };\r
784 \r
785                 // Initialize LinkPreview \r
786                 init();\r
787 \r
788                 return {\r
789                         // make crawlText() accessable from the outside.\r
790                         crawlText: function(text) {\r
791                                 crawlText(text);\r
792                         },\r
793                         addBBCodeToPreview: function(content) {\r
794                                 addBBCodeToPreview(content);\r
795                         },\r
796                         destroy: function() {\r
797                                 destroy();\r
798                         }\r
799                 };\r
800         };\r
801 \r
802         $.fn.linkPreview.defaults = {\r
803                 defaultDescription: "Enter a description",\r
804                 defaultTitle: "Enter a title"\r
805         };\r
806 \r
807         /**\r
808         * Get in a textarea the previous word before the cursor.\r
809         * \r
810         * @param {object} text Textarea elemet.\r
811         * @param {integer} caretPos Cursor position.\r
812         * \r
813         * @returns {string} Previous word.\r
814         */\r
815         function returnWord(text, caretPos) {\r
816                 var index = text.indexOf(caretPos);\r
817                 var preText = text.substring(0, caretPos);\r
818                 // If the last charachter is a space remove the one space\r
819                 // We need this in friendica for the url  preview.\r
820                 if (preText.slice(-1) == " ") {\r
821                         preText = preText.substring(0, preText.length -1);\r
822                 }\r
823 \r
824                 if (preText.indexOf(" ") > 0) {\r
825                         var words = preText.split(" ");\r
826                         return words[words.length - 1]; //return last word\r
827                 }\r
828                 else {\r
829                         return preText;\r
830                 }\r
831         }\r
832 \r
833         /**\r
834          * Get in a textarea the previous word before the cursor.\r
835          * \r
836          * @param {string} id The ID of a textarea element.\r
837          * @returns {sting|null} Previous word or null if no word is available.\r
838          */\r
839         function getPrevWord(id) {\r
840                 var text = document.getElementById(id);\r
841                 var caretPos = getCaretPosition(text);\r
842                 var word = returnWord(text.value, caretPos);\r
843                 if (word != null) {\r
844                         return word\r
845                 }\r
846 \r
847         }\r
848 \r
849         /**\r
850          * Get the cursor posiotion in an text element.\r
851          * \r
852          * @param {object} ctrl Textarea elemet.\r
853          * @returns {integer} Position of the cursor.\r
854          */\r
855         function getCaretPosition(ctrl) {\r
856                 var CaretPos = 0;   // IE Support\r
857                 if (document.selection) {\r
858                         ctrl.focus();\r
859                         var Sel = document.selection.createRange();\r
860                         Sel.moveStart('character', -ctrl.value.length);\r
861                         CaretPos = Sel.text.length;\r
862                 }\r
863                 // Firefox support\r
864                 else if (ctrl.selectionStart || ctrl.selectionStart == '0') {\r
865                         CaretPos = ctrl.selectionStart;\r
866                 }\r
867                 return (CaretPos);\r
868         }\r
869 })(jQuery);\r