diff --git a/customize.dist/src/less2/include/markdown.less b/customize.dist/src/less2/include/markdown.less index 631fe8f12..867dbacc9 100644 --- a/customize.dist/src/less2/include/markdown.less +++ b/customize.dist/src/less2/include/markdown.less @@ -90,6 +90,11 @@ border: 1px solid #BBB; } + pre.mermaid { + svg { + max-width: 100%; + } + } } .markdown_preformatted-code (@color: #333) { diff --git a/customize.dist/src/less2/include/modal.less b/customize.dist/src/less2/include/modal.less index 044763c59..635a65b71 100644 --- a/customize.dist/src/less2/include/modal.less +++ b/customize.dist/src/less2/include/modal.less @@ -24,6 +24,9 @@ .cp-modal-container { display: none; + align-items: center; + justify-content: center; + z-index: 100000; //Z modal container position: absolute; top: 0; @@ -39,9 +42,11 @@ padding: @variables_padding; - position: absolute; - top: 15vh; bottom: 15vh; - left: 10vw; right: 10vw; + position: relative; + //top: 15vh; bottom: 15vh; + //left: 10vw; right: 10vw; + width: 90vw; + max-height: 95vh; overflow: auto; diff --git a/customize.dist/src/less2/include/modals-ui-elements.less b/customize.dist/src/less2/include/modals-ui-elements.less index 855142a28..62b175f0e 100644 --- a/customize.dist/src/less2/include/modals-ui-elements.less +++ b/customize.dist/src/less2/include/modals-ui-elements.less @@ -106,4 +106,60 @@ .cp-teams-help { margin-left: 10px; } + + // mediatag preview + #cp-mediatag-preview-modal { + .cp-modal { + display: flex; + justify-content: center; + .cp-mediatag-container { + width: 100%; + flex: 1; + min-width: 0; + overflow: auto; + media-tag { + & > * { + max-width: 100%; + max-height: 100%; + } + video, iframe { + margin-bottom: -5px; + } + & > iframe { + width: 100%; + height: 100%; + min-height: 75vh; + } + & > .plain-text-reader { + white-space: pre-wrap; + text-align: left; + word-break: break-word; + color: @cryptpad_text_col; + padding: 5px; + } + } + pre.mermaid { + overflow: unset; + margin-bottom: 0; + } + .cp-spinner { + border-color: @colortheme_logo-1; + border-top-color: transparent; + } + } + .cp-mediatag-control { + align-self: center; + .fa { + margin: 10px; + cursor: pointer; + } + } + .cp-mediatag-outer { + display: flex; + height: 100%; + width: 100%; + align-items: center; + } + } + } } diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 3c59218f4..49654dc56 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -371,7 +371,7 @@ width: 200px; display: flex; align-items: center; - .fa { + .fa, .cptools { font-size: 32px; min-width: 50px; } diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 735d3fb37..4c4518434 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -462,6 +462,42 @@ define([ return frame; }; + UI.createModal = function (cfg) { + var $body = cfg.$body || $('body'); + var $blockContainer = $body.find('#'+cfg.id); + if (!$blockContainer.length) { + $blockContainer = $(h('div.cp-modal-container#'+cfg.id, { + tabindex: 1 + })); + } + var hide = function () { + if (cfg.onClose) { return void cfg.onClose(); } + $blockContainer.hide(); + if (cfg.onClosed) { cfg.onClosed(); } + }; + $blockContainer.html('').appendTo($body); + var $block = $(h('div.cp-modal')).appendTo($blockContainer); + $(h('span.cp-modal-close.fa.fa-times', { + title: Messages.filePicker_close + })).click(hide).appendTo($block); + $body.click(hide); + $block.click(function (e) { + e.stopPropagation(); + }); + $body.keydown(function (e) { + if (e.which === 27) { + hide(); + } + }); + return { + $modal: $blockContainer, + show: function () { + $blockContainer.css('display', 'flex'); + }, + hide: hide + }; + }; + UI.alert = function (msg, cb, opt) { var force = false; if (typeof(opt) === 'object') { @@ -1254,5 +1290,65 @@ define([ }; }; + UI.createContextMenu = function (menu) { + var $menu = $(menu).appendTo($('body')); + + var display = function (e) { + $menu.css({ display: "block" }); + var h = $menu.outerHeight(); + var w = $menu.outerWidth(); + var wH = window.innerHeight; + var wW = window.innerWidth; + if (h > wH) { + $menu.css({ + top: '0px', + bottom: '' + }); + } else if (e.pageY + h <= wH) { + $menu.css({ + top: e.pageY+'px', + bottom: '' + }); + } else { + $menu.css({ + bottom: '0px', + top: '' + }); + } + if(w > wW) { + $menu.css({ + left: '0px', + right: '' + }); + } else if (e.pageX + w <= wW) { + $menu.css({ + left: e.pageX+'px', + right: '' + }); + } else { + $menu.css({ + left: '', + right: '0px', + }); + } + }; + + var hide = function () { + $menu.hide(); + }; + var remove = function () { + $menu.remove(); + }; + + $('body').click(hide); + + return { + menu: menu, + show: display, + hide: hide, + remove: remove + }; + }; + return UI; }); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 60a3bcfc5..aae6b09b9 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -8,7 +8,6 @@ define([ '/common/common-constants.js', '/common/common-feedback.js', '/common/hyperscript.js', - '/common/media-tag.js', '/common/clipboard.js', '/customize/messages.js', '/customize/application_config.js', @@ -18,19 +17,10 @@ define([ '/common/visible.js', 'css!/customize/fonts/cptools/style.css', - '/bower_components/croppie/croppie.min.js', - 'css!/bower_components/croppie/croppie.css', -], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, MediaTag, Clipboard, +], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, Clipboard, Messages, AppConfig, Pages, NThen, InviteInner, Visible) { var UIElements = {}; - // Configure MediaTags to use our local viewer - if (MediaTag) { - MediaTag.setDefaultConfig('pdf', { - viewer: '/common/pdfjs/web/viewer.html' - }); - } - UIElements.prettySize = function (bytes) { var kB = Util.bytesToKilobytes(bytes); if (kB < 1024) { return kB + Messages.KB; } @@ -110,7 +100,7 @@ define([ var data = users[key]; var name = data.displayName || data.name || Messages.anonymous; var avatar = h('span.cp-usergrid-avatar.cp-avatar'); - UIElements.displayAvatar(common, $(avatar), data.avatar, name); + common.displayAvatar($(avatar), data.avatar, name); var removeBtn, el; if (config.remove) { removeBtn = h('span.fa.fa-times'); @@ -1930,205 +1920,6 @@ define([ }; }; - // Avatars - - UIElements.displayMediatagImage = function (Common, $tag, cb) { - if (!$tag.length || !$tag.is('media-tag')) { return void cb('NOT_MEDIATAG'); } - var observer = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { - if (mutation.addedNodes.length) { - if (mutation.addedNodes.length > 1 || - mutation.addedNodes[0].nodeName !== 'IMG') { - return void cb('NOT_IMAGE'); - } - var $image = $tag.find('img'); - var onLoad = function () { - var img = new Image(); - img.onload = function () { - var _cb = cb; - cb = $.noop; - _cb(null, $image, img); - }; - img.src = $image.attr('src'); - }; - if ($image[0].complete) { onLoad(); } - $image.on('load', onLoad); - } - }); - }); - observer.observe($tag[0], { - attributes: false, - childList: true, - characterData: false - }); - MediaTag($tag[0]).on('error', function (data) { - console.error(data); - }); - }; - - var emoji_patt = /([\uD800-\uDBFF][\uDC00-\uDFFF])/; - var isEmoji = function (str) { - return emoji_patt.test(str); - }; - var emojiStringToArray = function (str) { - var split = str.split(emoji_patt); - var arr = []; - for (var i=0; i', {'class': 'cp-avatar-default'}).text(text); - $container.append($avatar); - if (cb) { cb(); } - }; - if (!window.Symbol) { return void displayDefault(); } // IE doesn't have Symbol - if (!href || href.length === 1) { return void displayDefault(); } - - var centerImage = function ($img, $image, img) { - var w = img.width; - var h = img.height; - if (w>h) { - $image.css('max-height', '100%'); - $img.css('flex-direction', 'column'); - if (cb) { cb($img); } - return; - } - $image.css('max-width', '100%'); - $img.css('flex-direction', 'row'); - if (cb) { cb($img); } - }; - - var parsed = Hash.parsePadUrl(href); - if (parsed.type !== "file" || parsed.hashData.type !== "file") { - var $img = $('').appendTo($container); - var img = new Image(); - $(img).attr('src', href); - img.onload = function () { - centerImage($img, $(img), img); - $(img).appendTo($img); - }; - return; - } - // No password for avatars - var privateData = common.getMetadataMgr().getPrivateData(); - var origin = privateData.fileHost || privateData.origin; - var secret = Hash.getSecrets('file', parsed.hash); - if (secret.keys && secret.channel) { - var hexFileName = secret.channel; - var cryptKey = Hash.encodeBase64(secret.keys && secret.keys.cryptKey); - var src = origin + Hash.getBlobPathFromHex(hexFileName); - common.getFileSize(hexFileName, function (e, data) { - if (e || !data) { return void displayDefault(); } - if (typeof data !== "number") { return void displayDefault(); } - if (Util.bytesToMegabytes(data) > 0.5) { return void displayDefault(); } - var $img = $('').appendTo($container); - $img.attr('src', src); - $img.attr('data-crypto-key', 'cryptpad:' + cryptKey); - UIElements.displayMediatagImage(common, $img, function (err, $image, img) { - if (err) { return void console.error(err); } - centerImage($img, $image, img); - }); - }); - } - }; - var transformAvatar = function (file, cb) { - if (file.type === 'image/gif') { return void cb(file); } - var $croppie = $('
', { - 'class': 'cp-app-profile-resizer' - }); - - if (typeof ($croppie.croppie) !== "function") { - console.warn('fuck'); - return void cb(file); - } - - var todo = function () { - UI.confirm($croppie[0], function (yes) { - if (!yes) { return; } - $croppie.croppie('result', { - type: 'blob', - size: {width: 300, height: 300} - }).then(function(blob) { - blob.lastModifiedDate = new Date(); - blob.name = 'avatar'; - cb(blob); - }); - }); - }; - - var reader = new FileReader(); - reader.onload = function(e) { - $croppie.croppie({ - url: e.target.result, - viewport: { width: 100, height: 100 }, - boundary: { width: 400, height: 300 }, - }); - todo(); - }; - reader.readAsDataURL(file); - }; - UIElements.addAvatar = function (common, cb) { - var AVATAR_SIZE_LIMIT = 0.5; - var allowedMediaTypes = [ - 'image/png', - 'image/jpeg', - 'image/jpg', - 'image/gif', - ]; - var fmConfig = { - noHandlers: true, - noStore: true, - body: $('body'), - onUploaded: cb - }; - var FM = common.createFileManager(fmConfig); - var accepted = ".gif,.jpg,.jpeg,.png"; - var data = { - FM: FM, - filter: function (file) { - var sizeMB = Util.bytesToMegabytes(file.size); - var type = file.type; - // We can't resize .gif so we have to display an error if it is too big - if (sizeMB > AVATAR_SIZE_LIMIT && type === 'image/gif') { - UI.log(Messages._getKey('profile_uploadSizeError', [ - Messages._getKey('formattedMB', [AVATAR_SIZE_LIMIT]) - ])); - return false; - } - // Display an error if the image type is not allowed - if (allowedMediaTypes.indexOf(type) === -1) { - UI.log(Messages._getKey('profile_uploadTypeError', [ - accepted.split(',').join(', ') - ])); - return false; - } - return true; - }, - transformer: transformAvatar, - accept: accepted - }; - return data; - }; - /* Create a usage bar which keeps track of how much storage space is used by your CryptDrive. The getPinnedUsage RPC is one of the heavier calls, so we throttle its usage. Clients will not update more than once per @@ -2640,7 +2431,7 @@ define([ $displayName.text(newName || Messages.anonymous); if (accountName && oldUrl !== url) { $avatar.html(''); - UIElements.displayAvatar(Common, $avatar, url, + Common.displayAvatar($avatar, url, newName || Messages.anonymous, function ($img) { oldUrl = url; $userAdmin.find('> button').removeClass('cp-avatar'); @@ -2747,37 +2538,6 @@ define([ return $block; }; - UIElements.createModal = function (cfg) { - var $body = cfg.$body || $('body'); - var $blockContainer = $body.find('#'+cfg.id); - if (!$blockContainer.length) { - $blockContainer = $('
', { - 'class': 'cp-modal-container', - tabindex: 1, - 'id': cfg.id - }); - } - var hide = function () { - if (cfg.onClose) { return void cfg.onClose(); } - $blockContainer.hide(); - }; - $blockContainer.html('').appendTo($body); - var $block = $('
', {'class': 'cp-modal'}).appendTo($blockContainer); - $('', { - 'class': 'cp-modal-close fa fa-times', - 'title': Messages.filePicker_close - }).click(hide).appendTo($block); - $body.click(hide); - $block.click(function (e) { - e.stopPropagation(); - }); - $body.keydown(function (e) { - if (e.which === 27) { - hide(); - } - }); - return $blockContainer; - }; UIElements.createNewPadModal = function (common) { // if in drive, show new pad modal instead @@ -2785,10 +2545,11 @@ define([ return void $(".cp-app-drive-element-row.cp-app-drive-new-ghost").click(); } - var $modal = UIElements.createModal({ + var modal = UI.createModal({ id: 'cp-app-toolbar-creation-dialog', $body: $('body') }); + var $modal = modal.$modal; var $title = $('

').text(Messages.fm_newFile); var $description = $('

').html(Messages.creation_newPadModalDescription); $modal.find('.cp-modal').append($title); @@ -2874,7 +2635,7 @@ define([ $modal.find('.cp-modal').append($container).append($advancedContainer); window.setTimeout(function () { - $modal.show(); + modal.show(); $modal.focus(); }); }; @@ -3028,7 +2789,7 @@ define([ var teams = Object.keys(privateData.teams).map(function (id) { var data = privateData.teams[id]; var avatar = h('span.cp-creation-team-avatar.cp-avatar'); - UIElements.displayAvatar(common, $(avatar), data.avatar, data.name); + common.displayAvatar($(avatar), data.avatar, data.name); return h('div.cp-creation-team', { 'data-id': id, title: data.name, @@ -3612,119 +3373,6 @@ define([ }; - var createContextMenu = function (menu) { - var $menu = $(menu).appendTo($('body')); - - var display = function (e) { - $menu.css({ display: "block" }); - var h = $menu.outerHeight(); - var w = $menu.outerWidth(); - var wH = window.innerHeight; - var wW = window.innerWidth; - if (h > wH) { - $menu.css({ - top: '0px', - bottom: '' - }); - } else if (e.pageY + h <= wH) { - $menu.css({ - top: e.pageY+'px', - bottom: '' - }); - } else { - $menu.css({ - bottom: '0px', - top: '' - }); - } - if(w > wW) { - $menu.css({ - left: '0px', - right: '' - }); - } else if (e.pageX + w <= wW) { - $menu.css({ - left: e.pageX+'px', - right: '' - }); - } else { - $menu.css({ - left: '', - right: '0px', - }); - } - }; - - var hide = function () { - $menu.hide(); - }; - var remove = function () { - $menu.remove(); - }; - - $('body').click(hide); - - return { - menu: menu, - show: display, - hide: hide, - remove: remove - }; - }; - - var mediatagContextMenu; - UIElements.importMediaTagMenu = function (common) { - if (mediatagContextMenu) { return mediatagContextMenu; } - - // Create context menu - var menu = h('div.cp-contextmenu.dropdown.cp-unselectable', [ - h('ul.dropdown-menu', { - 'role': 'menu', - 'aria-labelledBy': 'dropdownMenu', - 'style': 'display:block;position:static;margin-bottom:5px;' - }, [ - h('li', h('a.cp-app-code-context-saveindrive.dropdown-item', { - 'tabindex': '-1', - 'data-icon': "fa-cloud-upload", - }, Messages.pad_mediatagImport)), - h('li', h('a.cp-app-code-context-download.dropdown-item', { - 'tabindex': '-1', - 'data-icon': "fa-download", - }, Messages.download_mt_button)), - ]) - ]); - // create the icon for each contextmenu option - $(menu).find("li a.dropdown-item").each(function (i, el) { - var $icon = $(""); - if ($(el).attr('data-icon')) { - var font = $(el).attr('data-icon').indexOf('cptools') === 0 ? 'cptools' : 'fa'; - $icon.addClass(font).addClass($(el).attr('data-icon')); - } else { - $icon.text($(el).text()); - } - $(el).prepend($icon); - }); - var m = createContextMenu(menu); - - mediatagContextMenu = m; - - var $menu = $(m.menu); - $menu.on('click', 'a', function (e) { - e.stopPropagation(); - m.hide(); - var $mt = $menu.data('mediatag'); - if ($(this).hasClass("cp-app-code-context-saveindrive")) { - common.importMediaTag($mt); - } - else if ($(this).hasClass("cp-app-code-context-download")) { - var media = $mt[0]._mediaObject; - window.saveAs(media._blob.content, media.name); - } - }); - - return m; - }; - UIElements.displayFriendRequestModal = function (common, data) { var msg = data.content.msg; var userData = msg.content.user; diff --git a/www/common/common-util.js b/www/common/common-util.js index cf7d5d97e..424dbfdd3 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -467,6 +467,27 @@ return false; }; + var emoji_patt = /([\uD800-\uDBFF][\uDC00-\uDFFF])/; + var isEmoji = function (str) { + return emoji_patt.test(str); + }; + var emojiStringToArray = function (str) { + var split = str.split(emoji_patt); + var arr = []; + for (var i=0; i

').text(Messages.fm_newFile); var $description = $('

').text(Messages.fm_newButtonTitle); $modal.find('.cp-modal').append($title); $modal.find('.cp-modal').append($description); var $content = createNewPadIcons($modal, isInRoot); $modal.find('.cp-modal').append($content); - window.setTimeout(function () { $modal.show(); }); + window.setTimeout(function () { modal.show(); }); addNewPadHandlers($modal, isInRoot); }); }; @@ -3980,11 +4035,15 @@ define([ else if ($this.hasClass('cp-app-drive-context-deleteowned')) { deleteOwnedPaths(paths); } + else if ($this.hasClass('cp-app-drive-context-preview')) { + if (paths.length !== 1) { return; } + el = manager.find(paths[0].path); + openFile(el, null, false); + } else if ($this.hasClass('cp-app-drive-context-open')) { paths.forEach(function (p) { - var $element = p.element; - $element.click(); - $element.dblclick(); + var el = manager.find(p.path); + openFile(el, false, true); }); } else if ($this.hasClass('cp-app-drive-context-openro')) { @@ -3999,7 +4058,7 @@ define([ } else { if (!el || manager.isFolder(el)) { return; } } - openFile(el, true); + openFile(el, true, true); }); } else if ($this.hasClass('cp-app-drive-context-makeacopy')) { diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js new file mode 100644 index 000000000..d86b481cd --- /dev/null +++ b/www/common/inner/common-mediatag.js @@ -0,0 +1,400 @@ +define([ + 'jquery', + '/common/common-util.js', + '/common/common-hash.js', + '/common/common-interface.js', + '/common/hyperscript.js', + '/common/media-tag.js', + '/customize/messages.js', + + '/bower_components/croppie/croppie.min.js', + '/bower_components/file-saver/FileSaver.min.js', + 'css!/bower_components/croppie/croppie.css', +], function ($, Util, Hash, UI, h, MediaTag, Messages) { + var MT = {}; + + var Nacl = window.nacl; + + // Configure MediaTags to use our local viewer + if (MediaTag) { + MediaTag.setDefaultConfig('pdf', { + viewer: '/common/pdfjs/web/viewer.html' + }); + } + + // Cache of the avatars outer html (including ) + var avatars = {}; + + MT.getCursorAvatar = function (cursor) { + var html = ''; + html += (cursor.avatar && avatars[cursor.avatar]) || ''; + html += cursor.name + ''; + return html; + }; + + MT.displayMediatagImage = function (Common, $tag, _cb) { + var cb = Util.once(_cb); + if (!$tag.length || !$tag.is('media-tag')) { return void cb('NOT_MEDIATAG'); } + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.addedNodes.length) { + if (mutation.addedNodes.length > 1 || + mutation.addedNodes[0].nodeName !== 'IMG') { + return void cb('NOT_IMAGE'); + } + var $image = $tag.find('img'); + var onLoad = function () { + cb(null, $image); + }; + if ($image[0].complete) { onLoad(); } + $image.on('load', onLoad); + } + }); + }); + observer.observe($tag[0], { + attributes: false, + childList: true, + characterData: false + }); + MediaTag($tag[0]).on('error', function (data) { + console.error(data); + }); + }; + + MT.displayAvatar = function (common, $container, href, name, _cb) { + var cb = Util.once(Util.mkAsync(_cb || function () {})); + var displayDefault = function () { + var text = (href && typeof(href) === "string") ? href : Util.getFirstCharacter(name); + var $avatar = $('', {'class': 'cp-avatar-default'}).text(text); + $container.append($avatar); + if (cb) { cb(); } + }; + if (!window.Symbol) { return void displayDefault(); } // IE doesn't have Symbol + if (!href || href.length === 1) { return void displayDefault(); } + + if (avatars[href]) { + var nodes = $.parseHTML(avatars[href]); + var $el = $(nodes[0]); + $container.append($el); + return void cb($el); + } + + var centerImage = function ($img, $image) { + var img = $image[0]; + var w = img.width; + var h = img.height; + if (w>h) { + $image.css('max-height', '100%'); + $img.css('flex-direction', 'column'); + avatars[href] = $img[0].outerHTML; + if (cb) { cb($img); } + return; + } + $image.css('max-width', '100%'); + $img.css('flex-direction', 'row'); + avatars[href] = $img[0].outerHTML; + if (cb) { cb($img); } + }; + + // No password for avatars + var privateData = common.getMetadataMgr().getPrivateData(); + var origin = privateData.fileHost || privateData.origin; + var parsed = Hash.parsePadUrl(href); + var secret = Hash.getSecrets('file', parsed.hash); + if (secret.keys && secret.channel) { + var hexFileName = secret.channel; + var cryptKey = Hash.encodeBase64(secret.keys && secret.keys.cryptKey); + var src = origin + Hash.getBlobPathFromHex(hexFileName); + common.getFileSize(hexFileName, function (e, data) { + if (e || !data) { return void displayDefault(); } + if (typeof data !== "number") { return void displayDefault(); } + if (Util.bytesToMegabytes(data) > 0.5) { return void displayDefault(); } + var $img = $('').appendTo($container); + $img.attr('src', src); + $img.attr('data-crypto-key', 'cryptpad:' + cryptKey); + MT.displayMediatagImage(common, $img, function (err, $image) { + if (err) { return void console.error(err); } + centerImage($img, $image); + }); + }); + } + }; + var transformAvatar = function (file, cb) { + if (file.type === 'image/gif') { return void cb(file); } + var $croppie = $('

', { + 'class': 'cp-app-profile-resizer' + }); + + if (typeof ($croppie.croppie) !== "function") { + return void cb(file); + } + + var todo = function () { + UI.confirm($croppie[0], function (yes) { + if (!yes) { return; } + $croppie.croppie('result', { + type: 'blob', + size: {width: 300, height: 300} + }).then(function(blob) { + blob.lastModifiedDate = new Date(); + blob.name = 'avatar'; + cb(blob); + }); + }); + }; + + var reader = new FileReader(); + reader.onload = function(e) { + $croppie.croppie({ + url: e.target.result, + viewport: { width: 100, height: 100 }, + boundary: { width: 400, height: 300 }, + }); + todo(); + }; + reader.readAsDataURL(file); + }; + MT.addAvatar = function (common, cb) { + var AVATAR_SIZE_LIMIT = 0.5; + var allowedMediaTypes = [ + 'image/png', + 'image/jpeg', + 'image/jpg', + 'image/gif', + ]; + var fmConfig = { + noHandlers: true, + noStore: true, + body: $('body'), + onUploaded: cb + }; + var FM = common.createFileManager(fmConfig); + var accepted = ".gif,.jpg,.jpeg,.png"; + var data = { + FM: FM, + filter: function (file) { + var sizeMB = Util.bytesToMegabytes(file.size); + var type = file.type; + // We can't resize .gif so we have to display an error if it is too big + if (sizeMB > AVATAR_SIZE_LIMIT && type === 'image/gif') { + UI.log(Messages._getKey('profile_uploadSizeError', [ + Messages._getKey('formattedMB', [AVATAR_SIZE_LIMIT]) + ])); + return false; + } + // Display an error if the image type is not allowed + if (allowedMediaTypes.indexOf(type) === -1) { + UI.log(Messages._getKey('profile_uploadTypeError', [ + accepted.split(',').join(', ') + ])); + return false; + } + return true; + }, + transformer: transformAvatar, + accept: accepted + }; + return data; + }; + + MT.getMediaTagPreview = function (common, tags, start) { + if (!Array.isArray(tags) || !tags.length) { return; } + + var i = start; + var metadataMgr = common.getMetadataMgr(); + var priv = metadataMgr.getPrivateData(); + + var left, right; + + var modal = UI.createModal({ + id: 'cp-mediatag-preview-modal', + $body: $('body') + }); + modal.show(); + var $modal = modal.$modal.focus(); + var $container = $modal.find('.cp-modal').append([ + h('div.cp-mediatag-control', left = h('span.fa.fa-chevron-left')), + h('div.cp-mediatag-container', [ + h('div.cp-loading-spinner-container', h('span.cp-spinner')), + ]), + h('div.cp-mediatag-control', right = h('span.fa.fa-chevron-right')), + ]); + var $left = $(left); + var $right = $(right); + var $inner = $container.find('.cp-mediatag-container'); + + var $spinner = $container.find('.cp-loading-spinner-container'); + + var locked = false; + var show = function (_i) { + if (locked) { return; } + locked = true; + if (_i < 0) { i = 0; } + else if (_i > tags.length -1) { i = tags.length - 1; } + else { i = _i; } + + // Show/hide controls + $left.css('visibility', ''); + $right.css('visibility', ''); + if (i === 0) { + $left.css('visibility', 'hidden'); + } + if (i === tags.length - 1) { + $right.css('visibility', 'hidden'); + } + + // Reset modal + $inner.find('media-tag, pre.mermaid').detach(); + $spinner.show(); + + // Check src and cryptkey + var cfg = tags[i]; + + if (cfg.svg) { + $spinner.hide(); + $inner.append(cfg.svg); + locked = false; + return; + } + + var src = cfg.src; + var key = cfg.key; + if (cfg.href) { + var parsed = Hash.parsePadUrl(cfg.href); + var secret = Hash.getSecrets(parsed.type, parsed.hash, cfg.password); + var host = priv.fileHost || priv.origin || ''; + src = host + Hash.getBlobPathFromHex(secret.channel); + var _key = secret.keys && secret.keys.cryptKey; + if (_key) { key = 'cryptpad:' + Nacl.util.encodeBase64(_key); } + } + if (!src || !key) { + locked = false; + $spinner.hide(); + return void UI.log(Messages.error); + } + + var tag = h('media-tag', { + src: src, + 'data-crypto-key': key + }); + $inner.append(tag); + + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function() { + locked = false; + $spinner.hide(); + }); + }); + observer.observe(tag, { + attributes: false, + childList: true, + characterData: false + }); + MediaTag(tag).on('error', function () { + locked = false; + $spinner.hide(); + UI.log(Messages.error); + }); + }; + + show(i); + var previous = function () { + if (i === 0) { return; } + show(i - 1); + }; + var next = function () { + if (i === tags.length - 1) { return; } + show(i + 1); + }; + $left.click(previous); + $right.click(next); + + $modal.on('keydown', function (e) { + e.stopPropagation(); + }); + $modal.on('keyup', function (e) { + //if (!Slide.shown) { return; } + e.stopPropagation(); + if (e.ctrlKey) { return; } + switch(e.which) { + case 33: // pageup + case 38: // up + case 37: // left + previous(); + break; + case 34: // pagedown + case 32: // space + case 40: // down + case 39: // right + next(); + break; + case 27: // esc + $modal.hide(); + break; + default: + } + }); + }; + + var mediatagContextMenu; + MT.importMediaTagMenu = function (common) { + if (mediatagContextMenu) { return mediatagContextMenu; } + + // Create context menu + var menu = h('div.cp-contextmenu.dropdown.cp-unselectable', [ + h('ul.dropdown-menu', { + 'role': 'menu', + 'aria-labelledBy': 'dropdownMenu', + 'style': 'display:block;position:static;margin-bottom:5px;' + }, [ + h('li.cp-svg', h('a.cp-app-code-context-open.dropdown-item', { + 'tabindex': '-1', + 'data-icon': "fa-eye", + }, Messages.pad_mediatagPreview)), + h('li', h('a.cp-app-code-context-saveindrive.dropdown-item', { + 'tabindex': '-1', + 'data-icon': "fa-cloud-upload", + }, Messages.pad_mediatagImport)), + h('li', h('a.cp-app-code-context-download.dropdown-item', { + 'tabindex': '-1', + 'data-icon': "fa-download", + }, Messages.download_mt_button)), + ]) + ]); + // create the icon for each contextmenu option + $(menu).find("li a.dropdown-item").each(function (i, el) { + var $icon = $(""); + if ($(el).attr('data-icon')) { + var font = $(el).attr('data-icon').indexOf('cptools') === 0 ? 'cptools' : 'fa'; + $icon.addClass(font).addClass($(el).attr('data-icon')); + } else { + $icon.text($(el).text()); + } + $(el).prepend($icon); + }); + var m = UI.createContextMenu(menu); + + mediatagContextMenu = m; + + var $menu = $(m.menu); + $menu.on('click', 'a', function (e) { + e.stopPropagation(); + m.hide(); + var $mt = $menu.data('mediatag'); + if ($(this).hasClass("cp-app-code-context-saveindrive")) { + common.importMediaTag($mt); + } + else if ($(this).hasClass("cp-app-code-context-download")) { + var media = $mt[0]._mediaObject; + window.saveAs(media._blob.content, media.name); + } + else if ($(this).hasClass("cp-app-code-context-open")) { + $mt.trigger('preview'); + } + }); + + return m; + }; + + return MT; +}); diff --git a/www/common/messenger-ui.js b/www/common/messenger-ui.js index 5ba92f6a6..4a64f186e 100644 --- a/www/common/messenger-ui.js +++ b/www/common/messenger-ui.js @@ -879,7 +879,7 @@ define([ h('i.fa.fa-bell'), Messages.contacts_unmute || 'unmute' ]); - UIElements.displayAvatar(common, $(avatar), data.avatar, data.name); + common.displayAvatar($(avatar), data.avatar, data.name); $(button).click(function () { unmuteUser(curve, button); execCommand('UNMUTE_USER', curve, function (e, data) { diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index a90877ff1..ff3de520d 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -525,18 +525,23 @@ define([ } }); $embedButton = common.createButton('mediatag', true).click(function () { - common.openFilePicker({ + var cfg = { types: ['file'], where: ['root'] - }); + }; + if ($embedButton.data('filter')) { + cfg.filter = $embedButton.data('filter'); + } + common.openFilePicker(cfg); }).appendTo(toolbar.$rightside).hide(); }; - var setMediaTagEmbedder = function (mte) { + var setMediaTagEmbedder = function (mte, filter) { if (!common.isLoggedIn()) { return; } if (!mte || readOnly) { $embedButton.hide(); return; } + if (filter) { $embedButton.data('filter', filter); } $embedButton.show(); mediaTagEmbedder = mte; }; diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js index 5ae5a4246..2192f168c 100644 --- a/www/common/sframe-common-codemirror.js +++ b/www/common/sframe-common-codemirror.js @@ -4,11 +4,12 @@ define([ '/common/themes.js', '/customize/messages.js', '/common/common-ui-elements.js', + '/common/inner/common-mediatag.js', '/common/common-hash.js', '/common/common-util.js', '/common/text-cursor.js', '/bower_components/chainpad/chainpad.dist.js', -], function ($, Modes, Themes, Messages, UIElements, Hash, Util, TextCursor, ChainPad) { +], function ($, Modes, Themes, Messages, UIElements, MT, Hash, Util, TextCursor, ChainPad) { var module = {}; var cursorToPos = function(cursor, oldText) { @@ -454,12 +455,7 @@ define([ })[0]; }; var makeTippy = function (cursor) { - var html = ''; - if (cursor.avatar && UIElements.getAvatar(cursor.avatar)) { - html += UIElements.getAvatar(cursor.avatar); - } - html += cursor.name + ''; - return html; + return MT.getCursorAvatar(cursor); }; var marks = {}; exp.removeCursors = function () { diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 8d04c6263..ebacf3b71 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -11,6 +11,7 @@ define([ '/common/sframe-common-codemirror.js', '/common/sframe-common-cursor.js', '/common/sframe-common-mailbox.js', + '/common/inner/common-mediatag.js', '/common/metadata-manager.js', '/customize/application_config.js', @@ -35,6 +36,7 @@ define([ CodeMirror, Cursor, Mailbox, + MT, MetadataMgr, AppConfig, CommonRealtime, @@ -90,8 +92,8 @@ define([ funcs.initFilePicker = callWithCommon(UIElements.initFilePicker); funcs.openFilePicker = callWithCommon(UIElements.openFilePicker); funcs.openTemplatePicker = callWithCommon(UIElements.openTemplatePicker); - funcs.displayMediatagImage = callWithCommon(UIElements.displayMediatagImage); - funcs.displayAvatar = callWithCommon(UIElements.displayAvatar); + funcs.displayMediatagImage = callWithCommon(MT.displayMediatagImage); + funcs.displayAvatar = callWithCommon(MT.displayAvatar); funcs.createButton = callWithCommon(UIElements.createButton); funcs.createUsageBar = callWithCommon(UIElements.createUsageBar); funcs.updateTags = callWithCommon(UIElements.updateTags); @@ -102,7 +104,8 @@ define([ funcs.getBurnAfterReadingWarning = callWithCommon(UIElements.getBurnAfterReadingWarning); funcs.createNewPadModal = callWithCommon(UIElements.createNewPadModal); funcs.onServerError = callWithCommon(UIElements.onServerError); - funcs.importMediaTagMenu = callWithCommon(UIElements.importMediaTagMenu); + funcs.importMediaTagMenu = callWithCommon(MT.importMediaTagMenu); + funcs.getMediaTagPreview = callWithCommon(MT.getMediaTagPreview); // Thumb funcs.displayThumbnail = callWithCommon(Thumb.displayThumbnail); diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 13f7b9f50..09f7784a6 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -7,10 +7,11 @@ define([ '/common/common-hash.js', '/common/common-util.js', '/common/common-feedback.js', + '/common/inner/common-mediatag.js', '/common/hyperscript.js', '/common/messenger-ui.js', '/customize/messages.js', -], function ($, Config, ApiConfig, UIElements, UI, Hash, Util, Feedback, h, +], function ($, Config, ApiConfig, UIElements, UI, Hash, Util, Feedback, MT, h, MessengerUI, Messages) { var Common; @@ -345,17 +346,9 @@ MessengerUI, Messages) { window.open(origin+'/profile/#' + data.profile); }); } - if (data.avatar && UIElements.getAvatar(data.avatar)) { - $span.append(UIElements.getAvatar(data.avatar)); + Common.displayAvatar($span, data.avatar, name, function () { $span.append($rightCol); - } else { - Common.displayAvatar($span, data.avatar, name, function ($img) { - if (data.avatar && $img && $img.length) { - UIElements.setAvatar(data.avatar, $img[0].outerHTML); - } - $span.append($rightCol); - }); - } + }); $span.data('uid', data.uid); $editUsersList.append($span); }); diff --git a/www/filepicker/inner.js b/www/filepicker/inner.js index aba1ed77b..205a0b8db 100644 --- a/www/filepicker/inner.js +++ b/www/filepicker/inner.js @@ -81,11 +81,13 @@ define([ var createFileDialog = function () { var types = filters.types || []; // Create modal - var $blockContainer = UIElements.createModal({ + var modal = UI.createModal({ id: 'cp-filepicker-dialog', $body: $body, onClose: hideFileDialog - }).show(); + }); + modal.show(); + var $blockContainer = modal.$modal; // Set the fixed content var $block = $blockContainer.find('.cp-modal'); diff --git a/www/kanban/inner.js b/www/kanban/inner.js index e99b61767..51c4e9de2 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -9,6 +9,7 @@ define([ '/common/common-hash.js', '/common/common-interface.js', '/common/common-ui-elements.js', + '/common/inner/common-mediatag.js', '/common/modes.js', '/customize/messages.js', '/common/hyperscript.js', @@ -40,6 +41,7 @@ define([ Hash, UI, UIElements, + MT, Modes, Messages, h, @@ -90,13 +92,9 @@ define([ var getAvatar = function (cursor, noClear) { // Tippy - var html = ''; - if (cursor.avatar && UIElements.getAvatar(cursor.avatar)) { - html += UIElements.getAvatar(cursor.avatar); - } - html += cursor.name + ''; + var html = MT.getCursorAvatar(cursor); - var l = UIElements.getFirstCharacter(cursor.name || Messages.anonymous); + var l = Util.getFirstCharacter(cursor.name || Messages.anonymous); var text = ''; if (cursor.color) { diff --git a/www/pad/cursor.js b/www/pad/cursor.js index 33308c24c..e8753ed4b 100644 --- a/www/pad/cursor.js +++ b/www/pad/cursor.js @@ -41,12 +41,6 @@ define([ var cursors = {}; var makeTippy = function (cursor) { - /*var html = ''; - if (cursor.avatar && UIElements.getAvatar(cursor.avatar)) { - html += UIElements.getAvatar(cursor.avatar); - } - html += cursor.name + ''; - return html;*/ return cursor.name; }; diff --git a/www/profile/inner.js b/www/profile/inner.js index c97521407..7bc6c4022 100644 --- a/www/profile/inner.js +++ b/www/profile/inner.js @@ -10,6 +10,7 @@ define([ '/common/common-ui-elements.js', '/common/common-realtime.js', '/common/clipboard.js', + '/common/inner/common-mediatag.js', '/common/hyperscript.js', '/customize/messages.js', '/customize/application_config.js', @@ -36,6 +37,7 @@ define([ UIElements, Realtime, Clipboard, + MT, h, Messages, AppConfig, @@ -351,7 +353,7 @@ define([ displayAvatar(); if (APP.readOnly) { return; } - var data = UIElements.addAvatar(common, function (ev, data) { + var data = MT.addAvatar(common, function (ev, data) { var old = common.getMetadataMgr().getUserData().avatar; var todo = function () { APP.module.execCommand("SET", { diff --git a/www/teams/inner.js b/www/teams/inner.js index 0eb649252..af011167d 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -12,6 +12,7 @@ define([ '/common/sframe-common.js', '/common/proxy-manager.js', '/common/userObject.js', + '/common/inner/common-mediatag.js', '/common/hyperscript.js', '/customize/application_config.js', '/common/messenger-ui.js', @@ -35,6 +36,7 @@ define([ SFCommon, ProxyManager, UserObject, + MT, h, AppConfig, MessengerUI, @@ -533,9 +535,11 @@ define([ }; var makePermissions = function () { - var $blockContainer = UIElements.createModal({ + var modal= UI.createModal({ id: 'cp-teams-roster-dialog', - }).show(); + }); + modal.show(); + var $blockContainer = modal.$modal; var makeRow = function (arr, first) { return arr.map(function (val) { @@ -966,7 +970,7 @@ define([ // Upload var avatar = h('div.cp-team-avatar.cp-avatar'); var $avatar = $(avatar); - var data = UIElements.addAvatar(common, function (ev, data) { + var data = MT.addAvatar(common, function (ev, data) { if (!data.url) { return void UI.warn(Messages.error); } APP.module.execCommand('GET_TEAM_METADATA', { teamId: APP.team @@ -1051,7 +1055,7 @@ define([ var displayUser = function (common, data) { var avatar = h('span.cp-teams-invite-from-avatar.cp-avatar'); - UIElements.displayAvatar(common, $(avatar), data.avatar, data.displayName); + common.displayAvatar($(avatar), data.avatar, data.displayName); return h('div.cp-teams-invite-from-author', [ avatar, h('span.cp-teams-invite-from-name', data.displayName) diff --git a/www/whiteboard/inner.js b/www/whiteboard/inner.js index 650fc1251..0af0b8ee1 100644 --- a/www/whiteboard/inner.js +++ b/www/whiteboard/inner.js @@ -363,6 +363,11 @@ define([ }); }; var addImageToCanvas = function (img) { + // 1 MB maximum + if (img.src && img.src.length > 1 * 1024 * 1024) { + UI.warn(Messages.upload_tooLargeBrief); + return; + } var w = img.width; var h = img.height; if (w 1 * 1024 * 1024) { + UI.warn(Messages.upload_tooLargeBrief); + return; + } reader.onload = function () { var img = new Image(); img.onload = function () { @@ -399,32 +409,18 @@ define([ }).appendTo($rightside); if (framework._.sfCommon.isLoggedIn()) { - var fileDialogCfg = { - onSelect: function (data) { - if (data.type === 'file') { - var mt = ''; - framework._.sfCommon.displayMediatagImage($(mt), function (err, $image) { - Util.blobURLToImage($image.attr('src'), function (imgSrc) { - var img = new Image(); - img.onload = function () { addImageToCanvas(img); }; - img.src = imgSrc; - }); - }); - return; - } - } - }; - framework._.sfCommon.initFilePicker(fileDialogCfg); - framework._.sfCommon.createButton('mediatag', true).click(function () { - var pickerCfg = { - types: ['file'], - where: ['root'], - filter: { - fileType: ['image/'] - } - }; - framework._.sfCommon.openFilePicker(pickerCfg); - }).appendTo($rightside); + framework.setMediaTagEmbedder(function ($mt) { + framework._.sfCommon.displayMediatagImage($mt, function (err, $image) { + // Convert src from blob URL to base64 data URL + Util.blobURLToImage($image.attr('src'), function (imgSrc) { + var img = new Image(); + img.onload = function () { addImageToCanvas(img); }; + img.src = imgSrc; + }); + }); + }, { + fileType: ['image/'] + }); // Export to drive as PNG framework._.sfCommon.createButton('savetodrive', true, {}).click(function () {