diff --git a/src/converse-message-view.js b/src/converse-message-view.js index 39f6fab31d..435e2e27fe 100644 --- a/src/converse-message-view.js +++ b/src/converse-message-view.js @@ -199,10 +199,7 @@ converse.plugins.add('converse-message-view', { }, transformOOBURL (url) { - url = u.renderFileURL(_converse, url); - url = u.renderMovieURL(_converse, url); - url = u.renderAudioURL(_converse, url); - return u.renderImageURL(_converse, url); + return u.getOOBURLMarkup(_converse, url); }, async transformBodyText (text) { diff --git a/src/utils/html.js b/src/utils/html.js index abfaf07202..8ce876fcd1 100644 --- a/src/utils/html.js +++ b/src/utils/html.js @@ -8,6 +8,7 @@ import URI from "urijs"; import _ from "../headless/lodash.noconflict"; +import log from '@converse/headless/log'; import sizzle from "sizzle"; import tpl_audio from "../templates/audio.html"; import tpl_file from "../templates/file.html"; @@ -32,13 +33,6 @@ function getAutoCompleteProperty (name, options) { }[name]; } -const logger = _.assign({ - 'debug': _.get(console, 'log') ? console.log.bind(console) : function noop () {}, - 'error': _.get(console, 'log') ? console.log.bind(console) : function noop () {}, - 'info': _.get(console, 'log') ? console.log.bind(console) : function noop () {}, - 'warn': _.get(console, 'log') ? console.log.bind(console) : function noop () {} -}, console); - const XFORM_TYPE_MAP = { 'text-private': 'password', 'text-single': 'text', @@ -58,8 +52,7 @@ function slideOutWrapup (el) { el.style.height = ""; } - -const isImage = function (url) { +function isImage (url) { return new Promise((resolve, reject) => { var img = new Image(); var timer = window.setTimeout(function () { @@ -76,112 +69,93 @@ const isImage = function (url) { }; img.src = url; }); -}; - - -u.isAudioURL = function (url) { - if (!(url instanceof URI)) { - try { - url = new URI(url); - } catch (error) { - return false; - } - } - const filename = url.filename().toLowerCase(); - if (url.protocol().toLowerCase() !== "https") { - return false; - } - return filename.endsWith('.ogg') || filename.endsWith('.mp3') || filename.endsWith('.m4a'); } - -u.isImageURL = function (url) { - if (!(url instanceof URI)) { - try { - url = new URI(url); - } catch (error) { - return false; - } - } - const filename = url.filename().toLowerCase(); - if (window.location.protocol === 'https:' && url.protocol().toLowerCase() !== "https") { - return false; +function getURI (url) { + try { + return (url instanceof URI) ? url : (new URI(url)); + } catch (error) { + log.debug(error); + return null; } - return filename.endsWith('.jpg') || filename.endsWith('.jpeg') || - filename.endsWith('.png') || filename.endsWith('.gif') || - filename.endsWith('.bmp') || filename.endsWith('.tiff') || - filename.endsWith('.svg'); -}; +} +function checkTLS (uri) { + return window.location.protocol === 'http:' || + window.location.protocol === 'https:' && uri.protocol().toLowerCase() === "https"; +} -u.isVideoURL = function (url) { - if (!(url instanceof URI)) { - try { - url = new URI(url); - } catch (error) { - return false; - } - } - const filename = url.filename().toLowerCase(); - if (url.protocol().toLowerCase() !== "https") { +function checkFileTypes (types, url) { + const uri = getURI(url); + if (uri === null || !checkTLS(uri)) { return false; } - return filename.endsWith('.mp4') || filename.endsWith('.webm'); + const filename = uri.filename().toLowerCase(); + return !!types.filter(ext => filename.endsWith(ext)).length; } +const isAudioURL = url => checkFileTypes(['.ogg', '.mp3', '.m4a'], url); +const isImageURL = url => checkFileTypes(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.svg'], url); +const isVideoURL = url => checkFileTypes(['.mp4', '.webm'], url); -u.renderAudioURL = function (_converse, url) { +function getFileName (uri) { try { - const uri = new URI(url); - if (u.isAudioURL(uri)) { - const { __ } = _converse; - return tpl_audio({ - 'url': url, - 'label_download': __('Download audio file "%1$s"', decodeURI(uri.filename())) - }) - } + return decodeURI(uri.filename()); } catch (error) { - // decodeURI may throw error in case of malformed URIs + log.debug(error); + return uri.filename(); } - return url; -}; +} +function renderAudioURL (_converse, uri) { + const { __ } = _converse; + return tpl_audio({ + 'url': uri.toString(), + 'label_download': __('Download audio file "%1$s"', getFileName(uri)) + }) +} -u.renderFileURL = function (_converse, url) { - try { - const uri = new URI(url); - if (u.isImageURL(uri) || u.isVideoURL(uri) || u.isAudioURL(uri)) { - return url; - } - const { __ } = _converse, - filename = uri.filename(); - return tpl_file({ - 'url': url, - 'label_download': __('Download file "%1$s"', decodeURI(filename)) - }) - } catch (error) { - return url; +function renderImageURL (_converse, uri) { + if (!_converse.show_images_inline) { + return u.convertToHyperlink(uri); } -}; + const { __ } = _converse; + return tpl_image({ + 'url': uri.toString(), + 'label_download': __('Download image "%1$s"', getFileName(uri)) + }) +} -u.renderImageURL = function (_converse, url) { - if (!_converse.show_images_inline) { - return u.addHyperlinks(url); +function renderFileURL (_converse, uri) { + const { __ } = _converse; + return tpl_file({ + 'url': uri.toString(), + 'label_download': __('Download file "%1$s"', getFileName(uri)) + }) +} + +/** + * Returns the markup for a URL that points to a downloadable asset + * (such as a video, image or audio file). + * @method u#getOOBURLMarkup + * @param { String } url + * @returns { String } + */ +u.getOOBURLMarkup = function (_converse, url) { + const uri = getURI(url); + if (uri === null) { + return url; } - try { - const uri = new URI(url); - if (u.isImageURL(uri)) { - const { __ } = _converse; - return tpl_image({ - 'url': url, - 'label_download': __('Download image "%1$s"', decodeURI(uri.filename())) - }) - } - } catch (error) { - // decodeURI may throw error in case of malformed URIs + if (isVideoURL(uri)) { + return tpl_video({url}) + } else if (isAudioURL(uri)) { + return renderAudioURL(_converse, uri); + } else if (isImageURL(uri)) { + return renderImageURL(_converse, uri); + } else { + return renderFileURL(_converse, uri); } - return url; -}; +} /** @@ -223,7 +197,7 @@ u.renderImageURLs = function (_converse, el) { return Promise.all( list.map(url => new Promise((resolve) => { - if (u.isImageURL(url)) { + if (isImageURL(url)) { return isImage(url).then(img => { const i = new Image(); i.src = img.src; @@ -244,19 +218,6 @@ u.renderImageURLs = function (_converse, el) { }; -u.renderMovieURL = function (_converse, url) { - try { - const uri = new URI(url); - if (u.isVideoURL(uri)) { - return tpl_video({url}); - } - } catch (error) { - // decodeURI may throw error in case of malformed URIs - } - return url; -}; - - u.renderNewLines = function (text) { return text.replace(/\n\n+/g, '

').replace(/\n/g, '
'); }; @@ -411,25 +372,28 @@ u.addMentionsMarkup = function (text, references, chatbox) { }; +u.convertToHyperlink = function (url) { + const uri = getURI(url); + if (uri === null) { + return url; + } + url = uri.normalize()._string; + const pretty_url = uri._parts.urn ? url : uri.readable(); + if (!uri._parts.protocol && !url.startsWith('http://') && !url.startsWith('https://')) { + url = 'http://' + url; + } + if (uri._parts.protocol === 'xmpp' && uri._parts.query === 'join') { + return `${u.escapeHTML(pretty_url)}`; + } + return `${u.escapeHTML(pretty_url)}`; +} + + u.addHyperlinks = function (text) { - return URI.withinString(text, url => { - try { - const uri = new URI(url); - url = uri.normalize()._string; - const pretty_url = uri._parts.urn ? url : uri.readable(); - if (!uri._parts.protocol && !url.startsWith('http://') && !url.startsWith('https://')) { - url = 'http://' + url; - } - if (uri._parts.protocol === 'xmpp' && uri._parts.query === 'join') { - return `${u.escapeHTML(pretty_url)}`; - } - return `${u.escapeHTML(pretty_url)}`; - } catch (error) { - return url; - } - }, { + const parse_options = { 'start': /\b(?:([a-z][a-z0-9.+-]*:\/\/)|xmpp:|mailto:|www\.)/gi - }); + }; + return URI.withinString(text, url => u.convertToHyperlink(url), parse_options); }; @@ -462,7 +426,7 @@ u.slideOut = function (el, duration=200) { return new Promise((resolve, reject) => { if (!el) { const err = "An element needs to be passed in to slideOut" - logger.warn(err); + log.warn(err); reject(new Error(err)); return; } @@ -521,7 +485,7 @@ u.slideIn = function (el, duration=200) { return new Promise((resolve, reject) => { if (!el) { const err = "An element needs to be passed in to slideIn"; - logger.warn(err); + log.warn(err); return reject(new Error(err)); } else if (_.includes(el.classList, 'collapsed')) { return resolve(el); @@ -588,7 +552,7 @@ u.isVisible = function (el) { u.fadeIn = function (el, callback) { if (!el) { - logger.warn("An element needs to be passed in to fadeIn"); + log.warn("An element needs to be passed in to fadeIn"); } if (window.converse_disable_effects) { el.classList.remove('hidden');