]> git.mxchange.org Git - friendica.git/blob - view/js/linkPreview.js
attachment preview: mv some js functions from frio to the linkPreview to make linkPre...
[friendica.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  * for the decental social network Friendica.\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                 var init = function() {\r
69                         $('#' + selector).bind({\r
70                                 paste: function () {\r
71                                         setTimeout(function () {\r
72                                                 crawlText();\r
73                                         }, 100);\r
74                                 },\r
75                                 keyup: function (e) {\r
76                                         // on enter, space, ctrl\r
77                                         if ((e.which === 13 || e.which === 32 || e.which === 17)) {\r
78                                                 crawlText();\r
79                                         }\r
80                                 }\r
81                         });\r
82                 };\r
83                 var resetPreview = function() {\r
84                         $('#hasAttachment_' + selector).val(0);\r
85                         photoNumber = 0;\r
86                         images = "";\r
87                 }\r
88 \r
89                 var crawlText = function (text) {\r
90                         block = false;\r
91                         images = '';\r
92                         isExtern = false;\r
93 \r
94                         if (typeof text === 'undefined') {\r
95                                 text = getPrevWord(selector);\r
96                         } else {\r
97                                 isExtern = true;\r
98                         }\r
99 \r
100                         // Don't procces the textarea input if we have already\r
101                         // an attachment preview.\r
102                         if (!isExtern && isActive) {\r
103                                 return;\r
104                         }\r
105 \r
106                         if (trim(text) !== "") {\r
107                                 if (block === false && urlRegex.test(text)) {\r
108                                         binurl = bin2hex(text);\r
109                                         block = true;\r
110 \r
111                                         isCrawling = true;\r
112                                         $('#profile-rotator').show();\r
113 \r
114                                         if (binurl in cache) {\r
115                                                 isCrawling = false;\r
116                                                 processContentData(cache[binurl]);\r
117                                         } else {\r
118                                                 getContentData(binurl, processContentData);\r
119                                         }\r
120                                 }\r
121                         }\r
122                 };\r
123 \r
124                 var processContentData = function(result) {\r
125                         if (result.contentType === 'image') {\r
126                                 insertImage(result.data);\r
127                         }\r
128                         if (result.contentType === 'attachment') {\r
129                                 insertAttachment(result.data);\r
130                         }\r
131                         $('#profile-rotator').hide();\r
132                 }\r
133 \r
134                 var getContentData = function(binurl, callback) {\r
135                         $.get('parse_url?binurl='+ binurl + '&dataType=json', function (answer) {\r
136                                 if (typeof answer.contentType === 'undefined'\r
137                                         || answer.contentType === null)\r
138                                 {\r
139                                         answer.contentType = "";\r
140                                 }\r
141                                 if (typeof answer.data.url === 'undefined'\r
142                                         || answer.data.url === null)\r
143                                 {\r
144                                         answer.data.url = "";\r
145                                 }\r
146                                 if (typeof answer.data.title === 'undefined'\r
147                                         || answer.data.title === null\r
148                                         || answer.data.title === "")\r
149                                 {\r
150                                         answer.data.title = defaultTitle;\r
151                                 }\r
152                                 if (typeof answer.data.text === 'undefined'\r
153                                         || answer.data.text === null\r
154                                         || answer.data.text === "")\r
155                                 {\r
156                                         answer.data.text = "";\r
157                                 }\r
158                                 if (typeof answer.data.images === 'undefined'\r
159                                         || answer.data.images === null)\r
160                                 {\r
161                                         answer.data.images = "";\r
162                                 }\r
163 \r
164                                 // Put the data into a cache\r
165                                 cache[binurl] = answer;\r
166 \r
167                                 callback(answer);\r
168 \r
169                                 isCrawling = false;\r
170                         });\r
171                 }\r
172 \r
173                 var insertImage = function(json) {\r
174                         if (!isExtern) {\r
175                                 return\r
176                         }\r
177                         var bbcode = '\n[img]' + json.url + '[/img]\n';\r
178                         addeditortext(bbcode);\r
179                 };\r
180 \r
181                 var insertAudio = function(json) {\r
182                         if (!isExtern) {\r
183                                 return\r
184                         }\r
185                         var bbcode = '\n[audio]' + json.url + '[/audio]\n';\r
186                         addeditortext(bbcode);\r
187                 };\r
188 \r
189                 var insertVideo = function(json) {\r
190                         if (!isExtern) {\r
191                                 return\r
192                         }\r
193                         var bbcode = '\n[video]' + json.url + '[/video]\n';\r
194                         addeditortext(bbcode);\r
195                 };\r
196 \r
197                 var insertAttachment = function(json) {\r
198                         // If we have already a preview, leaver here.\r
199                         // Note: if we finish the Preview of other media content type,\r
200                         // we can move this condition to the beggining of crawlText();\r
201                         if (isActive) {\r
202                                 $('#profile-rotator').hide();\r
203                                 return;\r
204                         }\r
205 \r
206                         if (json.type != 'link' && json.type != 'video' && json.type != 'photo' || json.url == json.title) {\r
207                                 $('#profile-rotator').hide();\r
208                                 return;\r
209                         }\r
210 \r
211                         $('#photoNumber_' + selector).val(0);\r
212                         resetPreview();\r
213 \r
214                         var typeClass = 'type-' + json.type;\r
215                         var imageClass = 'attachment-preview';\r
216                         var urlHost = "";\r
217                         var description = json.text;\r
218 \r
219                         // Load and add the template if it isn't allready loaded.\r
220                         if ($('#preview_' + selector).length == 0) {\r
221                                 var tpl = previewTpl.format(\r
222                                         typeClass,\r
223                                         attachmentTpl,\r
224                                         1,\r
225                                         bin2hex(json.url),\r
226                                         json.type\r
227                                 );\r
228                                 $('#' + selector).after(tpl);\r
229                         }\r
230 \r
231                         isActive = true;\r
232 \r
233                         if (description === '') {\r
234                                 description = defaultDescription;\r
235                         }\r
236 \r
237                         $('#previewTitle_' + selector).html("\\r
238                                 <span id='previewSpanTitle_" + selector + "' class='previewSpanTitle' >" + escapeHTML(json.title) + "</span>\\r
239                                 <input type='text' name='attachment_title' value='" + escapeHTML(json.title) + "' id='previewInputTitle_" + selector + "' class='previewInputTitle inputPreview' style='display: none;'/>"\r
240                         );\r
241 \r
242                         $('#previewDescription_' + selector).html("\\r
243                                 <span id='previewSpanDescription_" + selector + "' class='previewSpanDescription' >" + escapeHTML(description) + "</span>\n\\r
244                                 <textarea id='previewInputDescription_" + selector + "' name='attachment_text' class='previewInputDescription' style='display: none;' class='inputPreview' >" + escapeHTML(json.text) + "</textarea>"\r
245                         );\r
246 \r
247                         if (json.url) {\r
248                                 var regexpr = "(https?://)([^:^/]*)(:\\d*)?(.*)?";\r
249                                 var regResult = json.url.match(regexpr);\r
250                                 var urlHost = regResult[1] + regResult[2];\r
251                                 $('#previewUrl_' + selector).html("<a href='" + json.url + "'>" + urlHost + "</a>");\r
252                         }\r
253 \r
254                         images = json.images;\r
255 \r
256                         if (Array.isArray(images)) {\r
257                                 $('#previewImages_' + selector).show();\r
258                                 $('#attachmentImageSrc_' + selector).val(bin2hex(images[photoNumber].src));\r
259                                 $('#attachmentImageWidth_' + selector).val(images[photoNumber].width);\r
260                                 $('#attachmentImageHeight_' + selector).val(images[photoNumber].height);\r
261                         } else {\r
262                                 $('#previewImages_' + selector).hide();\r
263                         }\r
264 \r
265                         images.length = parseInt(images.length);\r
266                         var appendImage = "";\r
267 \r
268                         for (i = 0; i < images.length; i++) {\r
269                                 // For small preview images we use a smaller attachment format.\r
270 //                              if (Array.isArray(images) && typeof images[i].width !== 'undefined') {\r
271                                         ///@todo here we need to add a check for !Config::get('system', 'always_show_preview').\r
272                                         if (images[i].width >= 500 && images[i].width >= images[i].height) {\r
273                                                         imageClass = 'attachment-image';\r
274                                         }\r
275 //                              }\r
276                                 if (i === 0) {\r
277                                         appendImage += "<img id='imagePreview_" + selector + "_" + i + "' src='" + images[i].src + "' class='" + imageClass + "' ></img>";\r
278                                 } else {\r
279                                         appendImage += "<img id='imagePreview_" + selector + "_" + i + "' src='" + images[i].src + "' class='" + imageClass + "' style='display: none;'></img>";\r
280                                 }\r
281                         }\r
282 \r
283                         $('#previewImage_' + selector).html(appendImage + "<div id='whiteImage' style='color: transparent; display:none;'>...</div>");\r
284 \r
285                         // More than just one image.\r
286                         if (images.length > 1) {\r
287                                 // Enable the the button to change the preview pictures.\r
288                                 $('#previewChangeImg_' + selector).show();\r
289 \r
290                                 if (firstPosted === false) {\r
291                                         firstPosted = true;\r
292 \r
293                                         $('#previewChangeImg_' + selector).unbind('click').click(function (e) {\r
294                                                 e.stopPropagation();\r
295                                                 if (images.length > 1) {\r
296                                                         $('#imagePreview_' + selector + '_' + photoNumber).css({\r
297                                                                 'display': 'none'\r
298                                                         });\r
299                                                         photoNumber += 1;\r
300 \r
301                                                         // If have reached the last image, begin with the first image.\r
302                                                         if (photoNumber === images.length) {\r
303                                                                 photoNumber = 0;\r
304                                                         }\r
305 \r
306                                                         $('#imagePreview_' + selector + '_' + photoNumber).css({\r
307                                                                 'display': 'block'\r
308                                                         });\r
309                                                         $('#photoNumber_' + selector).val(photoNumber);\r
310                                                         $('#attachmentImageSrc_' + selector).val(bin2hex(images[photoNumber].src));\r
311                                                         $('#attachmentImageWidth_' + selector).val(images[photoNumber].width);\r
312                                                         $('#attachmentImageHeight_' + selector).val(images[photoNumber].height);\r
313                                                 }\r
314                                         });\r
315                                 }\r
316                         }\r
317 \r
318                         processEventListener();\r
319                         $('#profile-rotator').hide();\r
320                 };\r
321 \r
322                 var processEventListener = function() {\r
323                         $('#previewSpanTitle_' + selector).unbind('click').click(function (e) {\r
324                                 e.stopPropagation();\r
325                                 if (blockTitle === false) {\r
326                                         blockTitle = true;\r
327                                         $('#previewSpanTitle_' + selector).hide();\r
328                                         $('#previewInputTitle_' + selector).show();\r
329                                         $('#previewInputTitle_' + selector).val($('#previewInputTitle_' + selector).val());\r
330                                         $('#previewInputTitle_' + selector).focus().select();\r
331                                 }\r
332                         });\r
333 \r
334                         $('#previewInputTitle_' + selector).blur(function () {\r
335                                 blockTitle = false;\r
336                                 $('#previewSpanTitle_' + selector).html($('#previewInputTitle_' + selector).val());\r
337                                 $('#previewSpanTitle_' + selector).show();\r
338                                 $('#previewInputTitle_' + selector).hide();\r
339                         });\r
340 \r
341                         $('#previewInputTitle_' + selector).keypress(function (e) {\r
342                                 if (e.which === 13) {\r
343                                         blockTitle = false;\r
344                                         $('#previewSpanTitle_' + selector).html($('#previewInputTitle_' + selector).val());\r
345                                         $('#previewSpanTitle_' + selector).show();\r
346                                         $('#previewInputTitle_' + selector).hide();\r
347                                 }\r
348                         });\r
349 \r
350                         $('#previewSpanDescription_' + selector).unbind('click').click(function (e) {\r
351                                 e.stopPropagation();\r
352                                 if (blockDescription === false) {\r
353                                         blockDescription = true;\r
354                                         $('#previewSpanDescription_' + selector).hide();\r
355                                         $('#previewInputDescription_' + selector).show();\r
356                                         $('#previewInputDescription_' + selector).val($('#previewInputDescription_' + selector).val());\r
357                                         $('#previewInputDescription_' + selector).focus().select();\r
358                                 }\r
359                         });\r
360 \r
361                         $('#previewInputDescription_' + selector).blur(function () {\r
362                                 blockDescription = false;\r
363                                 $('#previewSpanDescription_' + selector).html($('#previewInputDescription_' + selector).val());\r
364                                 $('#previewSpanDescription_' + selector).show();\r
365                                 $('#previewInputDescription_' + selector).hide();\r
366                         });\r
367 \r
368                         $('#previewInputDescription_' + selector).keypress(function (e) {\r
369                                 if (e.which === 13) {\r
370                                         blockDescription = false;\r
371                                         $('#previewSpanDescription_' + selector).html($('#previewInputDescription_' + selector).val());\r
372                                         $('#previewSpanDescription_' + selector).show();\r
373                                         $('#previewInputDescription_' + selector).hide();\r
374                                 }\r
375                         });\r
376 \r
377                         $('#previewSpanTitle_' + selector).mouseover(function () {\r
378                                 $('#previewSpanTitle_' + selector).css({\r
379                                         "background-color": "#ff9"\r
380                                 });\r
381                         });\r
382 \r
383                         $('#previewSpanTitle_' + selector).mouseout(function () {\r
384                                 $('#previewSpanTitle_' + selector).css({\r
385                                         "background-color": "transparent"\r
386                                 });\r
387                         });\r
388 \r
389                         $('#previewSpanDescription_' + selector).mouseover(function () {\r
390                                 $('#previewSpanDescription_' + selector).css({\r
391                                         "background-color": "#ff9"\r
392                                 });\r
393                         });\r
394 \r
395                         $('#previewSpanDescription_' + selector).mouseout(function () {\r
396                                 $('#previewSpanDescription_' + selector).css({\r
397                                         "background-color": "transparent"\r
398                                 });\r
399                         });\r
400 \r
401                         $('#closePreview_' + selector).unbind('click').click(function (e) {\r
402                                 e.stopPropagation();\r
403                                 block = false;\r
404                                 images = '';\r
405                                 isActive = false;\r
406                                 firstPosted = false;\r
407                                 $('#preview_' + selector).fadeOut("fast", function () {\r
408                                         $('#preview_' + selector).remove();\r
409                                         $('#profile-rotator').hide();\r
410                                 });\r
411 \r
412                         });\r
413                 };\r
414 \r
415                 var trim = function(str) {\r
416                         return str.replace(/^\s+|\s+$/g, "");\r
417                 };\r
418                 var escapeHTML = function(unsafe_str) {\r
419                         return unsafe_str\r
420                                 .replace(/&/g, '&amp;')\r
421                                 .replace(/</g, '&lt;')\r
422                                 .replace(/>/g, '&gt;')\r
423                                 .replace(/\"/g, '&quot;')\r
424                                 .replace(/\[/g, '&#91;')\r
425                                 .replace(/\]/g, '&#93;')\r
426                                 .replace(/\'/g, '&#39;'); // '&apos;' is not valid HTML 4\r
427                 }\r
428 \r
429                 // Initialize LinkPreview \r
430                 init();\r
431 \r
432                 return {\r
433                         // make crawlText() accessable from the outside.\r
434                         crawlText: function (text) {\r
435                                 crawlText(text);\r
436                         }\r
437                 };\r
438         };\r
439 \r
440         $.fn.linkPreview.defaults = {\r
441                 defaultDescription: "Enter a description",\r
442                 defaultTitle: "Enter a title"\r
443         };\r
444 \r
445         /**\r
446         * Get in a textarea the previous word before the cursor.\r
447         * \r
448         * @param {object} text Textarea elemet.\r
449         * @param {integer} caretPos Cursor position.\r
450         * \r
451         * @returns {string} Previous word.\r
452         */\r
453         function returnWord(text, caretPos) {\r
454                 var index = text.indexOf(caretPos);\r
455                 var preText = text.substring(0, caretPos);\r
456                 // If the last charachter is a space remove the one space\r
457                 // We need this in friendica for the url  preview.\r
458                 if (preText.slice(-1) == " ") {\r
459                         preText = preText.substring(0, preText.length -1);\r
460                 }\r
461 \r
462                 if (preText.indexOf(" ") > 0) {\r
463                         var words = preText.split(" ");\r
464                         return words[words.length - 1]; //return last word\r
465                 }\r
466                 else {\r
467                         return preText;\r
468                 }\r
469         }\r
470 \r
471         /**\r
472          * Get in a textarea the previous word before the cursor.\r
473          * \r
474          * @param {string} id The ID of a textarea element.\r
475          * @returns {sting|null} Previous word or null if no word is available.\r
476          */\r
477         function getPrevWord(id) {\r
478                 var text = document.getElementById(id);\r
479                 var caretPos = getCaretPosition(text);\r
480                 var word = returnWord(text.value, caretPos);\r
481                 if (word != null) {\r
482                         return word\r
483                 }\r
484 \r
485         }\r
486 \r
487         /**\r
488          * Get the cursor posiotion in an text element.\r
489          * \r
490          * @param {object} ctrl Textarea elemet.\r
491          * @returns {integer} Position of the cursor.\r
492          */\r
493         function getCaretPosition(ctrl) {\r
494                 var CaretPos = 0;   // IE Support\r
495                 if (document.selection) {\r
496                         ctrl.focus();\r
497                         var Sel = document.selection.createRange();\r
498                         Sel.moveStart('character', -ctrl.value.length);\r
499                         CaretPos = Sel.text.length;\r
500                 }\r
501                 // Firefox support\r
502                 else if (ctrl.selectionStart || ctrl.selectionStart == '0') {\r
503                         CaretPos = ctrl.selectionStart;\r
504                 }\r
505                 return (CaretPos);\r
506         }\r
507 })(jQuery);\r