From 07d1932efc2c8d40ce59924e6445f3ca8f2edc02 Mon Sep 17 00:00:00 2001 From: rabuzarus Date: Fri, 1 Feb 2019 19:18:08 +0100 Subject: [PATCH 1/9] attachment preview: frontend work (works with frio) --- mod/parse_url.php | 40 ++- view/js/linkPreview.js | 419 +++++++++++++++++++++++ view/theme/frio/css/style.css | 63 +++- view/theme/frio/js/jot.js | 94 +++++ view/theme/frio/templates/jot-header.tpl | 25 +- 5 files changed, 613 insertions(+), 28 deletions(-) create mode 100644 view/js/linkPreview.js create mode 100644 view/theme/frio/js/jot.js diff --git a/mod/parse_url.php b/mod/parse_url.php index b982ccf084..c0a0a1336a 100644 --- a/mod/parse_url.php +++ b/mod/parse_url.php @@ -12,6 +12,7 @@ use Friendica\App; use Friendica\Core\Hook; use Friendica\Core\Logger; +use Friendica\Core\System; use Friendica\Util\Network; use Friendica\Util\ParseUrl; @@ -19,6 +20,8 @@ function parse_url_content(App $a) { $text = null; $str_tags = ''; + $format = ''; + $ret= ['success' => false, 'contentType' => '']; $br = "\n"; @@ -43,6 +46,10 @@ function parse_url_content(App $a) } } + if (isset($_GET['dataType']) && $_GET['dataType'] == 'json') { + $format = 'json'; + } + // Add url scheme if it is missing $arrurl = parse_url($url); if (empty($arrurl['scheme'])) { @@ -73,23 +80,36 @@ function parse_url_content(App $a) } } $type = null; + $content_type = ''; + $bbcode = ''; if (array_key_exists('Content-Type', $hdrs)) { $type = $hdrs['Content-Type']; } if ($type) { if (stripos($type, 'image/') !== false) { - echo $br . '[img]' . $url . '[/img]' . $br; - exit(); + $content_type = 'image'; + $bbcode = $br . '[img]' . $url . '[/img]' . $br; } if (stripos($type, 'video/') !== false) { - echo $br . '[video]' . $url . '[/video]' . $br; - exit(); + $content_type = 'video'; + $bbcode = $br . '[video]' . $url . '[/video]' . $br; } if (stripos($type, 'audio/') !== false) { - echo $br . '[audio]' . $url . '[/audio]' . $br; - exit(); + $content_type = 'audio'; + $bbcode = $br . '[audio]' . $url . '[/audio]' . $br; } } + if (!empty($content_type)) { + if ($format == 'json') { + $ret['contentType'] = $content_type; + $ret['data'] = ['url' => $url]; + $ret['success'] = true; + System::jsonExit($ret); + } + + echo $bbcode; + exit(); + } } @@ -130,6 +150,14 @@ function parse_url_content(App $a) exit(); } + if ($format == 'json') { + $ret['data'] = $siteinfo; + $ret['contentType'] = 'attachment'; + $ret['success'] = true; + + System::jsonExit($ret); + } + // Format it as BBCode attachment $info = add_page_info_data($siteinfo); diff --git a/view/js/linkPreview.js b/view/js/linkPreview.js new file mode 100644 index 0000000000..dcc1677cc5 --- /dev/null +++ b/view/js/linkPreview.js @@ -0,0 +1,419 @@ +/** + * Copyright (c) 2014 Leonardo Cardoso (http://leocardz.com) + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * + * restructured from rabzuarus (https://friendica.kommune4.de/profile/rabuzarus) + * for the decental social network Friendica. + * + * Version: 1.4.0 + */ +(function ($) { + $.fn.linkPreview = function (options) { + var opts = jQuery.extend({}, $.fn.linkPreview.defaults, options); + + var selector = $(this).selector; + selector = selector.substr(1); + + var previewTpl = '\ +
{1}
\ +
'; + var attachmentTpl = '\ +
\ +
\ + \ +
\ +
\ +
\ + \ +
\ +
\ +
\ + \ +
\ +
\ +

\ +
\ +
\ + \ +
\ +
\ +
'; + var text; + 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; + var binurl; + var block = false; + var blockTitle = false; + var blockDescription = false; + var cache = {}; + var images = ""; + var isExtern = false; + var photoNumber = 0; + var firstPosted = false; + var isActive = false; + var isCrawling = false; + var defaultTitle = opts.defaultTitle; + var defaultDescription = opts.defaultDescription; + + var init = function() { + $('#' + selector).bind({ + paste: function () { + setTimeout(function () { + crawlText(); + }, 100); + }, + keyup: function (e) { + // on enter, space, ctrl + if ((e.which === 13 || e.which === 32 || e.which === 17)) { + crawlText(); + } + } + }); + }; + var resetPreview = function() { + $('#previewChangeImg_' + selector).removeClass('buttonChangeActive'); + $('#previewChangeImg_' + selector).addClass('buttonChangeDeactive'); + photoNumber = 0; + images = ""; + } + + var crawlText = function (text) { + block = false; + images = ''; + isExtern = false; + + if (typeof text === 'undefined') { + text = getPrevWord(selector); + } else { + isExtern = true; + } + + if (trim(text) !== "") { + if (block === false && urlRegex.test(text)) { + binurl = bin2hex(text); + block = true; + + isCrawling = true; + $('#profile-rotator').show(); + + if (binurl in cache) { + isCrawling = false; + processContentData(cache[binurl]); + } else { + getContentData(binurl, processContentData); + } + } + } + }; + + var processContentData = function(result) { + if (result.contentType === 'image') { + insertImage(result.data); + } + if (result.contentType === 'attachment') { + insertAttachment(result.data); + } + $('#profile-rotator').hide(); + } + + var getContentData = function(binurl, callback) { + $.get('parse_url?binurl='+ binurl + '&dataType=json', function (answer) { + if (typeof answer.contentType === 'undefined' + || answer.contentType === null) + { + answer.contentType = ""; + } + if (typeof answer.data.url === 'undefined' + || answer.data.url === null) + { + answer.data.url = ""; + } + if (typeof answer.data.title === 'undefined' + || answer.data.title === null + || answer.data.title === "") + { + answer.data.title = defaultTitle; + } + if (typeof answer.data.text === 'undefined' + || answer.data.text === null + || answer.data.text === "") + { + answer.data.text = ""; + } + if (typeof answer.data.images === 'undefined' + || answer.data.images === null) + { + answer.data.images = ""; + } + + // Put the data into a cache + cache[binurl] = answer; + + callback(answer); + + isCrawling = false; + }); + } + + var insertImage = function(json) { + if (!isExtern) { + return + } + var bbcode = '\n[img]' + json.url + '[/img]\n'; + addeditortext(bbcode); + }; + + var insertAudio = function(json) { + if (!isExtern) { + return + } + var bbcode = '\n[audio]' + json.url + '[/audio]\n'; + addeditortext(bbcode); + }; + + var insertVideo = function(json) { + if (!isExtern) { + return + } + var bbcode = '\n[video]' + json.url + '[/video]\n'; + addeditortext(bbcode); + }; + + var insertAttachment = function(json) { + // If we have already a preview, leaver here. + // Note: if we finish the Preview of other media content type, + // we can move this condition to the beggining of crawlText(); + if (isActive) { + return; + } + + if (json.type != 'link' && json.type != 'video' && json.type != 'photo' || json.url == json.title) { + return; + } + + $('#photoNumber_' + selector).val(0); + resetPreview(); + + var typeClass = 'type-' + json.type; + var imageClass = 'attachment-preview'; + var urlHost = ""; + var description = json.text; + + // Load and add the template if it isn't allready loaded. + if ($('#preview_' + selector).length == 0) { + var tpl = previewTpl.format(typeClass, attachmentTpl); + $('#' + selector).after(tpl); + } + + isActive = true; + + if (description === '') { + description = defaultDescription; + } + + $('#previewTitle_' + selector).html("\ + " + escapeHTML(json.title) + "\ + " + ); + + + $('#previewDescription_' + selector).html("\ + " + escapeHTML(description) + "\n\ + " + ); + + if (json.url) { + var regexpr = "(https?://)([^:^/]*)(:\\d*)?(.*)?"; + var regResult = json.url.match(regexpr); + var urlHost = regResult[1] + regResult[2]; + $('#previewUrl_' + selector).html("" + urlHost + ""); + } + + images = json.images; + + if (Array.isArray(images)) { + $('#previewImages_' + selector).show(); + } else { + $('#previewImages_' + selector).hide(); + } + + images.length = parseInt(images.length); + var appendImage = ""; + + for (i = 0; i < images.length; i++) { + // For small preview images we use a smaller attachment format. +// if (Array.isArray(images) && typeof images[i].width !== 'undefined') { + ///@todo here we need to add a check for !Config::get('system', 'always_show_preview'). + if (images[i].width >= 500 && images[i].width >= images[i].height) { + imageClass = 'attachment-image'; + } +// } + if (i === 0) { + appendImage += ""; + } else { + appendImage += ""; + } + } + + $('#previewImage_' + selector).html(appendImage + ""); + + // more than just one image. + if (images.length > 1) { + // Enable the the button to change the preview pictures. + $('#previewChangeImg_' + selector).show(); + + if (firstPosted === false) { + firstPosted = true; + + $('#previewChangeImg_' + selector).unbind('click').click(function (e) { + e.stopPropagation(); + if (images.length > 1) { +// photoNumber = parseInt($('#photoNumber_' + selector).val()); + $('#imagePreview_' + selector + '_' + photoNumber).css({ + 'display': 'none' + }); + photoNumber += 1; + + // If have reached the last image, begin with the first image. + if (photoNumber === images.length) { + photoNumber = 0; + } + + $('#imagePreview_' + selector + '_' + photoNumber).css({ + 'display': 'block' + }); + $('#photoNumber_' + selector).val(photoNumber); + } + }); + } + } + + processEventListener(); + $('#profile-rotator').hide(); + }; + + var processEventListener = function() { + $('#previewSpanTitle_' + selector).unbind('click').click(function (e) { + e.stopPropagation(); + if (blockTitle === false) { + blockTitle = true; + $('#previewSpanTitle_' + selector).hide(); + $('#previewInputTitle_' + selector).show(); + $('#previewInputTitle_' + selector).val($('#previewInputTitle_' + selector).val()); + $('#previewInputTitle_' + selector).focus().select(); + } + }); + + $('#previewInputTitle_' + selector).blur(function () { + blockTitle = false; + $('#previewSpanTitle_' + selector).html($('#previewInputTitle_' + selector).val()); + $('#previewSpanTitle_' + selector).show(); + $('#previewInputTitle_' + selector).hide(); + }); + + $('#previewInputTitle_' + selector).keypress(function (e) { + if (e.which === 13) { + blockTitle = false; + $('#previewSpanTitle_' + selector).html($('#previewInputTitle_' + selector).val()); + $('#previewSpanTitle_' + selector).show(); + $('#previewInputTitle_' + selector).hide(); + } + }); + + $('#previewSpanDescription_' + selector).unbind('click').click(function (e) { + e.stopPropagation(); + if (blockDescription === false) { + blockDescription = true; + $('#previewSpanDescription_' + selector).hide(); + $('#previewInputDescription_' + selector).show(); + $('#previewInputDescription_' + selector).val($('#previewInputDescription_' + selector).val()); + $('#previewInputDescription_' + selector).focus().select(); + } + }); + + $('#previewInputDescription_' + selector).blur(function () { + blockDescription = false; + $('#previewSpanDescription_' + selector).html($('#previewInputDescription_' + selector).val()); + $('#previewSpanDescription_' + selector).show(); + $('#previewInputDescription_' + selector).hide(); + }); + + $('#previewInputDescription_' + selector).keypress(function (e) { + if (e.which === 13) { + blockDescription = false; + $('#previewSpanDescription_' + selector).html($('#previewInputDescription_' + selector).val()); + $('#previewSpanDescription_' + selector).show(); + $('#previewInputDescription_' + selector).hide(); + } + }); + + $('#previewSpanTitle_' + selector).mouseover(function () { + $('#previewSpanTitle_' + selector).css({ + "background-color": "#ff9" + }); + }); + + $('#previewSpanTitle_' + selector).mouseout(function () { + $('#previewSpanTitle_' + selector).css({ + "background-color": "transparent" + }); + }); + + $('#previewSpanDescription_' + selector).mouseover(function () { + $('#previewSpanDescription_' + selector).css({ + "background-color": "#ff9" + }); + }); + + $('#previewSpanDescription_' + selector).mouseout(function () { + $('#previewSpanDescription_' + selector).css({ + "background-color": "transparent" + }); + }); + + $('#closePreview_' + selector).unbind('click').click(function (e) { + e.stopPropagation(); + block = false; + images = ''; + isActive = false; + firstPosted = false; + $('#preview_' + selector).fadeOut("fast", function () { + $('#preview_' + selector).remove(); + $('#profile-rotator').hide(); + }); + + }); + }; + + var trim = function(str) { + return str.replace(/^\s+|\s+$/g, ""); + }; + var escapeHTML = function(unsafe_str) { + return unsafe_str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/\"/g, '"') + .replace(/\[/g, '[') + .replace(/\]/g, ']') + .replace(/\'/g, '''); // ''' is not valid HTML 4 + } + + // Initialize LinkPreview + init(); + + return { + // make crawlText() accessable from the outside. + crawlText: function (text) { + crawlText(text); + } + }; + }; + + $.fn.linkPreview.defaults = { + defaultDescription: "Enter a description", + defaultTitle: "Enter a title" + }; +})(jQuery); diff --git a/view/theme/frio/css/style.css b/view/theme/frio/css/style.css index 896892531d..c26264c4ac 100644 --- a/view/theme/frio/css/style.css +++ b/view/theme/frio/css/style.css @@ -1340,6 +1340,67 @@ section #jotOpen { #jot-text-wrap textarea { min-height: 100px; } +/*#jot-attachment-preview { + display: none; +}*/ +#jot-text-wrap .preview textarea { + width: 100%; +} +#preview_profile-jot-text { + position: relative; + padding: 0px 10px; + margin-top: -2px; + border: 2px solid #ededed; + border-top: none; + box-shadow: none; + border-radius: 0 0 4px 4px; + background: #fff; + color: #555; +} +textarea#profile-jot-text:focus + #preview_profile-jot-text { + border: 2px solid #6fdbe8; + border-top: none; +} +.preview hr.previewseparator { + margin-top: 0px; + border-color: #D2D2D2; +} +#previewImgBtn_profile-jot-text, +.closePreview { + position: absolute; + top: 15px; +} +.closePreview { + right: 15px; + z-index: 1; +} +.previewImgBtn { + left: 15px; +} +.preview button.previewActionBtn { + display:block; + height: 25px; + width: 25px; + border-radius: 50%; + color: #fff; + border: 2px solid #fff; + box-shadow: 0 0 3px gray; + background: #777; + text-align: center; + line-height: 2px; + text-decoration: none; + padding: 0 0 1px 1px; + opacity: 0.7; +} +.preview button.previewActionBtn:hover { + opacity: 1; +} +.preview .closePreview button.previewActionBtn { + font-size: 25px; +} +#previewInputTitle_profile-jot-text { + width: 100%; +} #profile-jot-wrapper button#profile-jot-submit { margin-top: 5px; } @@ -1347,7 +1408,7 @@ section #jotOpen { padding: 10px 15px; } -/* ACL */ +/* ACL /*#jot-modal-body { height: auto; max-height: calc(100vh - 130px); diff --git a/view/theme/frio/js/jot.js b/view/theme/frio/js/jot.js new file mode 100644 index 0000000000..f63ed53569 --- /dev/null +++ b/view/theme/frio/js/jot.js @@ -0,0 +1,94 @@ +var linkPreview; + +$(document).ready(function() { + linkPreview = $('#profile-jot-text').linkPreview(); +}); + + +/** + * Insert a link into friendica jot. + * + * @returns {void} + */ +function jotGetLink() { + var currentText = $("#profile-jot-text").val(); + var noAttachment = ''; + reply = prompt(aStr.linkurl); + if(reply && reply.length) { + // There should be only one attachment per post. + // So we need to remove the old one. + $('#jot-attachment-preview').empty(); + $('#profile-rotator').show(); + if (currentText.includes("[attachment") && currentText.includes("[/attachment]")) { + noAttachment = '&noAttachment=1'; + } + + // We use the linkPreview library to have a preview + // of the attachments. + linkPreview.crawlText(reply + noAttachment); + autosize.update($("#profile-jot-text")); + } +} + +/** + * Get in a textarea the previous word before the cursor. + * + * @param {object} text Textarea elemet. + * @param {integer} caretPos Cursor position. + * + * @returns {string} Previous word. + */ +function returnWord(text, caretPos) { + var index = text.indexOf(caretPos); + var preText = text.substring(0, caretPos); + // If the last charachter is a space remove the one space + // We need this in friendica for the url preview. + if (preText.slice(-1) == " ") { + preText = preText.substring(0, preText.length -1); + } +// preText = preText.replace(/^\s+|\s+$/g, ""); + if (preText.indexOf(" ") > 0) { + var words = preText.split(" "); + return words[words.length - 1]; //return last word + } + else { + return preText; + } +} + +/** + * Get in a textarea the previous word before the cursor. + * + * @param {string} id The ID of a textarea element. + * @returns {sting|null} Previous word or null if no word is available. + */ +function getPrevWord(id) { + var text = document.getElementById(id); + var caretPos = getCaretPosition(text); + var word = returnWord(text.value, caretPos); + if (word != null) { + return word + } + +} + +/** + * Get the cursor posiotion in an text element. + * + * @param {object} ctrl Textarea elemet. + * @returns {integer} Position of the cursor. + */ +function getCaretPosition(ctrl) { + var CaretPos = 0; // IE Support + if (document.selection) { + ctrl.focus(); + var Sel = document.selection.createRange(); + Sel.moveStart('character', -ctrl.value.length); + CaretPos = Sel.text.length; + } + // Firefox support + else if (ctrl.selectionStart || ctrl.selectionStart == '0') { + CaretPos = ctrl.selectionStart; + } + return (CaretPos); +} diff --git a/view/theme/frio/templates/jot-header.tpl b/view/theme/frio/templates/jot-header.tpl index 70370e42eb..548ff264ab 100644 --- a/view/theme/frio/templates/jot-header.tpl +++ b/view/theme/frio/templates/jot-header.tpl @@ -1,5 +1,7 @@ - + + +