From 75e5d3cc429849a24196b77e9eeacb8b001cc698 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 30 Mar 2020 12:09:12 +0200 Subject: [PATCH 01/18] Move the mediatag and avatar code outsite of common-ui-elements --- www/common/common-ui-elements.js | 221 +------------------------ www/common/common-util.js | 21 +++ www/common/messenger-ui.js | 2 +- www/common/sframe-common-codemirror.js | 10 +- www/common/sframe-common.js | 7 +- www/common/toolbar3.js | 15 +- www/kanban/inner.js | 10 +- www/pad/cursor.js | 6 - www/profile/inner.js | 4 +- www/teams/inner.js | 6 +- www/whiteboard/inner.js | 2 + 11 files changed, 55 insertions(+), 249 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 60a3bcfc5..4d487f779 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'); @@ -2879,6 +2670,7 @@ define([ }); }; + // XXX add txid UIElements.initFilePicker = function (common, cfg) { var onSelect = cfg.onSelect || $.noop; var sframeChan = common.getSframeChannel(); @@ -3028,7 +2820,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, @@ -3725,6 +3517,9 @@ define([ return m; }; + UIElements.getMediaTagPreview = function () { + }; + 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 da373f1ad..f9c64eb0e 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -445,6 +445,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'; - 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..e10a90976 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); @@ -103,6 +105,7 @@ define([ funcs.createNewPadModal = callWithCommon(UIElements.createNewPadModal); funcs.onServerError = callWithCommon(UIElements.onServerError); funcs.importMediaTagMenu = callWithCommon(UIElements.importMediaTagMenu); + funcs.getMediaTagPreview = callWithCommon(UIElements.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/kanban/inner.js b/www/kanban/inner.js index 310219e75..6a8595821 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..4dccf34d1 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, @@ -966,7 +968,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 +1053,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..80ab8408f 100644 --- a/www/whiteboard/inner.js +++ b/www/whiteboard/inner.js @@ -404,6 +404,8 @@ define([ if (data.type === 'file') { var mt = ''; framework._.sfCommon.displayMediatagImage($(mt), function (err, $image) { + // Convert src from blob URL to base64 data URL + // XXX base64 is heavy... Util.blobURLToImage($image.attr('src'), function (imgSrc) { var img = new Image(); img.onload = function () { addImageToCanvas(img); }; From 179e7d68b4c7502f514564fff7cd0a0c7cb9fd19 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 31 Mar 2020 10:38:27 +0200 Subject: [PATCH 02/18] Add new common-mediatag file --- www/common/inner/common-mediatag.js | 238 ++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 www/common/inner/common-mediatag.js diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js new file mode 100644 index 000000000..6a6fbdb49 --- /dev/null +++ b/www/common/inner/common-mediatag.js @@ -0,0 +1,238 @@ +define([ + 'jquery', + '/common/common-util.js', + '/common/common-hash.js', + '/common/common-interface.js', + '/common/media-tag.js', + '/customize/messages.js', + + '/bower_components/croppie/croppie.min.js', + 'css!/bower_components/croppie/croppie.css', +], function ($, Util, Hash, UI, MediaTag, Messages) { + var MT = {}; + + // 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); } + }; + + // XXX Drop support for external URLs + 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); + 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.previewMediaTag = function (common, config) { + config = config || {}; + + var metadataMgr = common.getMetadataMgr(); + var priv = metadataMgr.getPrivateData(); + + var src = config.src; + var key = config.key; + if (config.href) { + var parsed = Hash.parsePadUrl(config.href); + var secret = Hash.getSecrets(parsed.type, parsed.hash, config.password); + var host = priv.fileHost || priv.origin || ''; + src = host + Hash.getBlobPathFromHex(secret.channel); + key = secret.keys && secret.keys.cryptKey; + } + if (!src || !key) { + // XXX + return; + } + + var tag = h('media-tag', { + src: src, + 'data-crypto-key': 'cryptpad:' + key + }); + $img.attr('src', src); + $img.attr('data-crypto-key', 'cryptpad:' + cryptKey); + + + }; + + return MT; +}); From 817309d602fcada8271cf0cc01687c76b51d4fbe Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 31 Mar 2020 10:38:42 +0200 Subject: [PATCH 03/18] Move code from ui-elements to common-interface --- www/common/common-interface.js | 29 ++++++++++++++++++++++++++++ www/common/common-ui-elements.js | 33 +------------------------------- www/common/drive-ui.js | 2 +- www/filepicker/inner.js | 2 +- www/teams/inner.js | 2 +- 5 files changed, 33 insertions(+), 35 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 735d3fb37..4fa409604 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -462,6 +462,35 @@ 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(); + }; + $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 $blockContainer; + }; + UI.alert = function (msg, cb, opt) { var force = false; if (typeof(opt) === 'object') { diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 4d487f779..6377e2fb8 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2538,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 @@ -2576,7 +2545,7 @@ 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') }); diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 81f7a6e54..787caa6b4 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -2892,7 +2892,7 @@ define([ $element.append($('', {'class': 'cp-app-drive-element-name'}) .text(Messages.fm_newFile)); $element.click(function () { - var $modal = UIElements.createModal({ + var $modal = UI.createModal({ id: 'cp-app-drive-new-ghost-dialog', $body: $('body') }); diff --git a/www/filepicker/inner.js b/www/filepicker/inner.js index aba1ed77b..2eb9b4120 100644 --- a/www/filepicker/inner.js +++ b/www/filepicker/inner.js @@ -81,7 +81,7 @@ define([ var createFileDialog = function () { var types = filters.types || []; // Create modal - var $blockContainer = UIElements.createModal({ + var $blockContainer = UI.createModal({ id: 'cp-filepicker-dialog', $body: $body, onClose: hideFileDialog diff --git a/www/teams/inner.js b/www/teams/inner.js index 4dccf34d1..ae67c6f6e 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -535,7 +535,7 @@ define([ }; var makePermissions = function () { - var $blockContainer = UIElements.createModal({ + var $blockContainer = UI.createModal({ id: 'cp-teams-roster-dialog', }).show(); From 6a10ec711adb46ac2fe6c033aa1c9711af0f3baa Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 31 Mar 2020 16:43:00 +0200 Subject: [PATCH 04/18] Preview mediatag in the drive and in diffMarked --- .../src/less2/include/modals-ui-elements.less | 29 ++++ www/common/common-interface.js | 60 ++++++++ www/common/common-ui-elements.js | 116 ---------------- www/common/diffMarked.js | 14 +- www/common/drive-ui.js | 40 ++++-- www/common/inner/common-mediatag.js | 129 ++++++++++++++++-- www/common/sframe-common.js | 4 +- 7 files changed, 253 insertions(+), 139 deletions(-) diff --git a/customize.dist/src/less2/include/modals-ui-elements.less b/customize.dist/src/less2/include/modals-ui-elements.less index 855142a28..b8f7cb90f 100644 --- a/customize.dist/src/less2/include/modals-ui-elements.less +++ b/customize.dist/src/less2/include/modals-ui-elements.less @@ -106,4 +106,33 @@ .cp-teams-help { margin-left: 10px; } + + // mediatag preview + #cp-mediatag-preview-modal { + .cp-modal { + display: flex; + align-items: center; + justify-content: center; + .cp-mediatag-container { + height: 100%; + width: 100%; + media-tag { + & > * { + max-width: 100%; + max-height: 100%; + } + & > iframe { + width: 100%; + height: 100%; + } + & > .plain-text-reader { + white-space: pre-wrap; + text-align: left; + word-break: break-word; + color: @cryptpad_text_col; + } + } + } + } + } } diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 4fa409604..5a7cf8754 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1283,5 +1283,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 6377e2fb8..9908dcae6 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -3373,122 +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.getMediaTagPreview = function () { - }; - UIElements.displayFriendRequestModal = function (common, data) { var msg = data.content.msg; var userData = msg.content.user; diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index f8e006216..7268e1c87 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -5,13 +5,14 @@ define([ '/common/common-hash.js', '/common/common-util.js', '/common/hyperscript.js', + '/common/inner/common-mediatag.js', '/common/media-tag.js', '/common/highlight/highlight.pack.js', '/customize/messages.js', '/bower_components/diff-dom/diffDOM.js', '/bower_components/tweetnacl/nacl-fast.min.js', 'css!/common/highlight/styles/github.css' -],function ($, ApiConfig, Marked, Hash, Util, h, MediaTag, Highlight, Messages) { +],function ($, ApiConfig, Marked, Hash, Util, h, MT, MediaTag, Highlight, Messages) { var DiffMd = {}; var DiffDOM = window.diffDOM; @@ -355,7 +356,7 @@ define([ DD.apply($content[0], patch); var $mts = $content.find('media-tag:not(:has(*))'); $mts.each(function (i, el) { - $(el).contextmenu(function (e) { + var $mt = $(el).contextmenu(function (e) { e.preventDefault(); $(contextMenu.menu).data('mediatag', $(el)); contextMenu.show(e); @@ -371,6 +372,15 @@ define([ observer.disconnect(); } }); + $mt.off('dblclick'); + if ($mt.find('img').length) { + $mt.on('dblclick', function () { + common.getMediaTagPreview({ + src: $mt.attr('src'), + key: $mt.attr('data-crypto-key') + }); + }); + } }); observer.observe(el, { attributes: false, diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 787caa6b4..aaeda303d 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -80,6 +80,7 @@ define([ var faCollapseAll = 'fa-minus-square-o'; var faShared = 'fa-shhare-alt'; var faReadOnly = 'fa-eye'; + var faPreview = 'fa-eye'; var faOpenInCode = 'cptools-code'; var faRename = 'fa-pencil'; var faColor = 'cptools-palette'; @@ -317,6 +318,10 @@ define([ 'style': 'display:block;position:static;margin-bottom:5px;' }, [ h('span.cp-app-drive-context-noAction.dropdown-item.disabled', Messages.fc_noAction || "No action possible"), + h('li', h('a.cp-app-drive-context-preview.dropdown-item', { + 'tabindex': '-1', + 'data-icon': faPreview, + }, 'PREVIEW')), // XXX h('li', h('a.cp-app-drive-context-open.dropdown-item', { 'tabindex': '-1', 'data-icon': faFolderOpen, @@ -1042,12 +1047,23 @@ define([ return ret; }; - var openFile = function (el, isRo) { + var openFile = function (el, isRo, app) { var data = manager.getFileData(el); if (!data || (!data.href && !data.roHref)) { return void logError("Missing data for the file", el, data); } + var href = isRo ? data.roHref : (data.href || data.roHref); + var parsed = Hash.parsePadUrl(href); + + if (parsed.hashData && parsed.hashData.type === 'file' && !app) { + common.getMediaTagPreview({ + href: data.href, + password: data.password + }); + return; + } + var priv = metadataMgr.getPrivateData(); var useUnsafe = Util.find(priv, ['settings', 'security', 'unsafeLinks']); if (useUnsafe !== false) { // true of undefined: use unsafe links @@ -1055,7 +1071,6 @@ define([ } // Get hidden hash - var parsed = Hash.parsePadUrl(href); var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password); var opts = {}; if (isRo) { opts.view = true; } @@ -1175,6 +1190,7 @@ define([ if (!$element.is('.cp-border-color-file')) { //hide.push('download'); hide.push('openincode'); + hide.push('preview'); } if ($element.is('.cp-border-color-sheet')) { hide.push('download'); @@ -1192,6 +1208,9 @@ define([ if (!metadata || !Util.isPlainTextFile(metadata.fileType, metadata.title)) { hide.push('openincode'); } + if (metadata.channel && metadata.channel.length < 48) { + hide.push('preview'); + } if (!metadata.channel || metadata.channel.length > 32 || metadata.rtChannel) { hide.push('makeacopy'); // Not for blobs } @@ -1260,6 +1279,7 @@ define([ hide.push('savelocal'); hide.push('openincode'); // can't because of race condition hide.push('makeacopy'); + hide.push('preview'); } if (containsFolder && paths.length > 1) { // Cannot open multiple folders @@ -1276,12 +1296,12 @@ define([ show = ['newfolder', 'newsharedfolder', 'uploadfiles', 'uploadfolder', 'newdoc']; break; case 'tree': - show = ['open', 'openro', 'openincode', 'expandall', 'collapseall', + show = ['open', 'openro', 'preview', 'openincode', 'expandall', 'collapseall', 'color', 'download', 'share', 'savelocal', 'rename', 'delete', 'makeacopy', 'deleteowned', 'removesf', 'access', 'properties', 'hashtag']; break; case 'default': - show = ['open', 'openro', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy']; + show = ['open', 'openro', 'preview', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy']; break; case 'trashtree': { show = ['empty']; @@ -3980,11 +4000,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); + } 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 +4023,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 index 6a6fbdb49..6349893b6 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -3,14 +3,18 @@ define([ '/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, MediaTag, Messages) { +], 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', { @@ -204,7 +208,7 @@ define([ return data; }; - MT.previewMediaTag = function (common, config) { + MT.getMediaTagPreview = function (common, config) { config = config || {}; var metadataMgr = common.getMetadataMgr(); @@ -217,21 +221,124 @@ define([ var secret = Hash.getSecrets(parsed.type, parsed.hash, config.password); var host = priv.fileHost || priv.origin || ''; src = host + Hash.getBlobPathFromHex(secret.channel); - key = secret.keys && secret.keys.cryptKey; - } - if (!src || !key) { - // XXX - return; + var _key = secret.keys && secret.keys.cryptKey; + if (_key) { key = 'cryptpad:' + Nacl.util.encodeBase64(_key); } } + if (!src || !key) { return void UI.log(Messages.error); } var tag = h('media-tag', { src: src, - 'data-crypto-key': 'cryptpad:' + key + 'data-crypto-key': key + }); + + var $modal = UI.createModal({ + id: 'cp-mediatag-preview-modal', + $body: $('body') + }).show().focus(); + + var $container = $modal.find('.cp-modal').append(h('div.cp-mediatag-container', [ + tag + ])); + + var el; + var checkSize = function () { + if (!el) { return; } + $container.find('.cp-mediatag-container').css('height', ''); + var size = el.naturalHeight || el.videoHeight; + // Center small images and videos + if (size && size < $container.height()) { + $container.find('.cp-mediatag-container').css('height', 'auto'); + } + }; + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.addedNodes.length === 1) { + el = mutation.addedNodes[0]; + if (el.readyState === 0) { + // Wait for the video to be ready before checking the size + el.onloadedmetadata = checkSize; + return; + } + if (el.complete === false) { + el.onload = checkSize; + return; + } + setTimeout(checkSize); + } + }); + }); + observer.observe(tag, { + attributes: false, + childList: true, + characterData: false + }); + MediaTag(tag).on('error', function () { + UI.log(Messages.error); + $modal.hide(); + }); + }; + + 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', h('a.cp-app-code-context-open.dropdown-item', { + 'tabindex': '-1', + 'data-icon': "fa-eye", + }, Messages.fc_open)), // XXX + 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")) { + common.getMediaTagPreview({ + src: $mt.attr('src'), + key: $mt.attr('data-crypto-key') + }); + } }); - $img.attr('src', src); - $img.attr('data-crypto-key', 'cryptpad:' + cryptKey); - + return m; }; return MT; diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index e10a90976..ebacf3b71 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -104,8 +104,8 @@ define([ funcs.getBurnAfterReadingWarning = callWithCommon(UIElements.getBurnAfterReadingWarning); funcs.createNewPadModal = callWithCommon(UIElements.createNewPadModal); funcs.onServerError = callWithCommon(UIElements.onServerError); - funcs.importMediaTagMenu = callWithCommon(UIElements.importMediaTagMenu); - funcs.getMediaTagPreview = callWithCommon(UIElements.getMediaTagPreview); + funcs.importMediaTagMenu = callWithCommon(MT.importMediaTagMenu); + funcs.getMediaTagPreview = callWithCommon(MT.getMediaTagPreview); // Thumb funcs.displayThumbnail = callWithCommon(Thumb.displayThumbnail); From d8d515a450f14a8a298ad19602fa9723291f5265 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 31 Mar 2020 17:03:16 +0200 Subject: [PATCH 05/18] Add spinner while loading preview --- .../src/less2/include/modals-ui-elements.less | 5 ++++- www/common/inner/common-mediatag.js | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/customize.dist/src/less2/include/modals-ui-elements.less b/customize.dist/src/less2/include/modals-ui-elements.less index b8f7cb90f..4a26920ab 100644 --- a/customize.dist/src/less2/include/modals-ui-elements.less +++ b/customize.dist/src/less2/include/modals-ui-elements.less @@ -114,7 +114,6 @@ align-items: center; justify-content: center; .cp-mediatag-container { - height: 100%; width: 100%; media-tag { & > * { @@ -132,6 +131,10 @@ color: @cryptpad_text_col; } } + .cp-spinner { + border-color: @colortheme_logo-1; + border-top-color: transparent; + } } } } diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 6349893b6..cce892290 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -237,21 +237,27 @@ define([ }).show().focus(); var $container = $modal.find('.cp-modal').append(h('div.cp-mediatag-container', [ + h('div.cp-loading-spinner-container', h('span.cp-spinner')), tag ])); var el; var checkSize = function () { if (!el) { return; } - $container.find('.cp-mediatag-container').css('height', ''); var size = el.naturalHeight || el.videoHeight; + if (el.nodeName !== 'IMG' && el.nodeName !== 'VIDEO') { + $container.find('.cp-mediatag-container').css('height', '100%'); + } + if (!size) { return; } // Center small images and videos - if (size && size < $container.height()) { + $container.find('.cp-mediatag-container').css('height', '100%'); + if (size < $container.height()) { $container.find('.cp-mediatag-container').css('height', 'auto'); } }; var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { + $container.find('.cp-loading-spinner-container').remove(); if (mutation.addedNodes.length === 1) { el = mutation.addedNodes[0]; if (el.readyState === 0) { From 81b460abd8252d7a10c0e6086c31d0d974daeaca Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 31 Mar 2020 17:55:31 +0200 Subject: [PATCH 06/18] Open pdf in the file app --- www/common/drive-ui.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index aaeda303d..482c4cf9c 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1056,7 +1056,8 @@ define([ var href = isRo ? data.roHref : (data.href || data.roHref); var parsed = Hash.parsePadUrl(href); - if (parsed.hashData && parsed.hashData.type === 'file' && !app) { + if (parsed.hashData && parsed.hashData.type === 'file' && !app + && data.fileType !== "application/pdf") { common.getMediaTagPreview({ href: data.href, password: data.password From 572db0098745ff919f0d84b10d949f19248d3b60 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 1 Apr 2020 12:04:31 +0200 Subject: [PATCH 07/18] Preview multiple mediatags --- .../src/less2/include/modals-ui-elements.less | 13 ++ www/common/diffMarked.js | 13 +- www/common/drive-ui.js | 32 ++- www/common/inner/common-mediatag.js | 183 +++++++++++++----- 4 files changed, 184 insertions(+), 57 deletions(-) diff --git a/customize.dist/src/less2/include/modals-ui-elements.less b/customize.dist/src/less2/include/modals-ui-elements.less index 4a26920ab..968fa6f70 100644 --- a/customize.dist/src/less2/include/modals-ui-elements.less +++ b/customize.dist/src/less2/include/modals-ui-elements.less @@ -129,6 +129,7 @@ text-align: left; word-break: break-word; color: @cryptpad_text_col; + padding: 5px; } } .cp-spinner { @@ -136,6 +137,18 @@ border-top-color: transparent; } } + .cp-mediatag-outer { + display: flex; + height: 100%; + width: 100%; + align-items: center; + .cp-mediatag-control { + .fa { + margin: 10px; + cursor: pointer; + } + } + } } } } diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index 7268e1c87..d4fad71ea 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -289,7 +289,7 @@ define([ }; DiffMd.apply = function (newHtml, $content, common) { - var contextMenu = common.importMediaTagMenu(); + var contextMenu = common.importMediaTagMenu($content); var id = $content.attr('id'); if (!id) { throw new Error("The element must have a valid id"); } var pattern = /()<\/media-tag>/g; @@ -375,10 +375,19 @@ define([ $mt.off('dblclick'); if ($mt.find('img').length) { $mt.on('dblclick', function () { - common.getMediaTagPreview({ + var mts = [{ src: $mt.attr('src'), key: $mt.attr('data-crypto-key') + }]; + $content.find('media-tag').each(function (i, el) { + var $el = $(el); + if ($el.attr('src') === $mt.attr('src')) { return; } + mts.push({ + src: $el.attr('src'), + key: $el.attr('data-crypto-key') + }); }); + common.getMediaTagPreview(mts); }); } }); diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 482c4cf9c..adac4e353 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1047,6 +1047,28 @@ define([ return ret; }; + var previewMediaTag = function (data) { + var mts = [{ + href: data.href, + password: data.password + }]; + $content.find('.cp-app-drive-element.cp-border-color-file').each(function (i, el) { + var path = $(el).data('path'); + var id = manager.find(path); + if (!id) { return; } + var _data = manager.getFileData(id); + if (!_data || _data.channel < 48 || _data.channel === data.channel) { return; } + mts.push({ + href: _data.href, + password: _data.password + }); + }); + common.getMediaTagPreview(mts); + }; + + // `app`: true (force open wiht the app), false (force open in preview), + // falsy (open in preview if default is not using the app) + var defaultInApp = ['application/pdf']; var openFile = function (el, isRo, app) { var data = manager.getFileData(el); if (!data || (!data.href && !data.roHref)) { @@ -1057,12 +1079,8 @@ define([ var parsed = Hash.parsePadUrl(href); if (parsed.hashData && parsed.hashData.type === 'file' && !app - && data.fileType !== "application/pdf") { - common.getMediaTagPreview({ - href: data.href, - password: data.password - }); - return; + && (defaultInApp.indexOf(data.fileType) === -1 || app === false)) { + return void previewMediaTag(data); } var priv = metadataMgr.getPrivateData(); @@ -4004,7 +4022,7 @@ define([ else if ($this.hasClass('cp-app-drive-context-preview')) { if (paths.length !== 1) { return; } el = manager.find(paths[0].path); - openFile(el); + openFile(el, null, false); } else if ($this.hasClass('cp-app-drive-context-open')) { paths.forEach(function (p) { diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index cce892290..74d54a180 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -208,38 +208,29 @@ define([ return data; }; - MT.getMediaTagPreview = function (common, config) { - config = config || {}; + MT.getMediaTagPreview = function (common, tags) { + if (!Array.isArray(tags) || !tags.length) { return; } + var i = 0; var metadataMgr = common.getMetadataMgr(); var priv = metadataMgr.getPrivateData(); - var src = config.src; - var key = config.key; - if (config.href) { - var parsed = Hash.parsePadUrl(config.href); - var secret = Hash.getSecrets(parsed.type, parsed.hash, config.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) { return void UI.log(Messages.error); } - - var tag = h('media-tag', { - src: src, - 'data-crypto-key': key - }); + var left, right; var $modal = UI.createModal({ id: 'cp-mediatag-preview-modal', $body: $('body') }).show().focus(); - - var $container = $modal.find('.cp-modal').append(h('div.cp-mediatag-container', [ - h('div.cp-loading-spinner-container', h('span.cp-spinner')), - tag + var $container = $modal.find('.cp-modal').append(h('div.cp-mediatag-outer', [ + 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 el; var checkSize = function () { @@ -255,37 +246,124 @@ define([ $container.find('.cp-mediatag-container').css('height', 'auto'); } }; - var observer = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { - $container.find('.cp-loading-spinner-container').remove(); - if (mutation.addedNodes.length === 1) { - el = mutation.addedNodes[0]; - if (el.readyState === 0) { - // Wait for the video to be ready before checking the size - el.onloadedmetadata = checkSize; - return; - } - if (el.complete === false) { - el.onload = checkSize; - return; + + 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').remove(); + $spinner.show(); + + // Check src and cryptkey + var cfg = tags[i]; + 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(); + // XXX show error + 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(mutation) { + locked = false; + $spinner.hide(); + if (mutation.addedNodes.length === 1) { + el = mutation.addedNodes[0]; + if (el.readyState === 0) { + // Wait for the video to be ready before checking the size + el.onloadedmetadata = checkSize; + return; + } + if (el.complete === false) { + el.onload = checkSize; + return; + } + setTimeout(checkSize); } - setTimeout(checkSize); - } + }); }); - }); - observer.observe(tag, { - attributes: false, - childList: true, - characterData: false - }); - MediaTag(tag).on('error', function () { - UI.log(Messages.error); - $modal.hide(); + observer.observe(tag, { + attributes: false, + childList: true, + characterData: false + }); + MediaTag(tag).on('error', function () { + locked = false; + $spinner.hide(); + UI.log(Messages.error); + // XXX show error + }); + }; + + show(i); + var previous = function () { + show(i - 1); + }; + var next = function () { + show(i + 1); + }; + $left.click(previous); + $right.click(next); + + $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) { + MT.importMediaTagMenu = function (common, $container) { if (mediatagContextMenu) { return mediatagContextMenu; } // Create context menu @@ -337,10 +415,19 @@ define([ window.saveAs(media._blob.content, media.name); } else if ($(this).hasClass("cp-app-code-context-open")) { - common.getMediaTagPreview({ + var mts = [{ src: $mt.attr('src'), key: $mt.attr('data-crypto-key') + }]; + $container.find('media-tag').each(function (i, el) { + var $el = $(el); + if ($el.attr('src') === $mt.attr('src')) { return; } + mts.push({ + src: $el.attr('src'), + key: $el.attr('data-crypto-key') + }); }); + common.getMediaTagPreview(mts); } }); From b4c61ee75367ace2bb06d5f62930c5e1952787dd Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 3 Apr 2020 12:31:39 +0200 Subject: [PATCH 08/18] Fix preview modal not stopping keydown propagation in drive --- www/common/inner/common-mediatag.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 74d54a180..22e4d9cfc 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -338,6 +338,9 @@ define([ $left.click(previous); $right.click(next); + $modal.on('keydown', function (e) { + e.stopPropagation(); + }); $modal.on('keyup', function (e) { //if (!Slide.shown) { return; } e.stopPropagation(); From 3bc32f6085591e222c8799dd3b19f8184c43681e Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 3 Apr 2020 12:32:25 +0200 Subject: [PATCH 09/18] Start at the correct index when previewing multiple mediatags --- www/common/diffMarked.js | 25 +++++++++++++++++++------ www/common/drive-ui.js | 27 +++++++++++++++++++++------ www/common/inner/common-mediatag.js | 29 +++++++++++++++++++++-------- 3 files changed, 61 insertions(+), 20 deletions(-) diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index d4fad71ea..852fa2599 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -375,19 +375,32 @@ define([ $mt.off('dblclick'); if ($mt.find('img').length) { $mt.on('dblclick', function () { - var mts = [{ - src: $mt.attr('src'), - key: $mt.attr('data-crypto-key') - }]; + var mts = []; $content.find('media-tag').each(function (i, el) { var $el = $(el); - if ($el.attr('src') === $mt.attr('src')) { return; } mts.push({ src: $el.attr('src'), key: $el.attr('data-crypto-key') }); }); - common.getMediaTagPreview(mts); + + // Find initial position + var idx = -1; + mts.some(function (obj, i) { + if (obj.src === $mt.attr('src')) { + idx = i; + return true; + } + }); + if (idx === -1) { + mts.unshift({ + src: $mt.attr('src'), + key: $mt.attr('data-crypto-key') + }); + idx = 0; + } + + common.getMediaTagPreview(mts, idx); }); } }); diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index adac4e353..4523c99f5 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1048,22 +1048,37 @@ define([ }; var previewMediaTag = function (data) { - var mts = [{ - href: data.href, - password: data.password - }]; + var mts = []; $content.find('.cp-app-drive-element.cp-border-color-file').each(function (i, el) { var path = $(el).data('path'); var id = manager.find(path); if (!id) { return; } var _data = manager.getFileData(id); - if (!_data || _data.channel < 48 || _data.channel === data.channel) { return; } + if (!_data || _data.channel < 48) { return; } mts.push({ + channel: _data.channel, href: _data.href, password: _data.password }); }); - common.getMediaTagPreview(mts); + + // Find initial position + var idx = -1; + mts.some(function (obj, i) { + if (obj.channel === data.channel) { + idx = i; + return true; + } + }); + if (idx === -1) { + mts.unshift({ + href: data.href, + password: data.password + }); + idx = 0; + } + + common.getMediaTagPreview(mts, idx); }; // `app`: true (force open wiht the app), false (force open in preview), diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 22e4d9cfc..a97f05618 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -208,10 +208,10 @@ define([ return data; }; - MT.getMediaTagPreview = function (common, tags) { + MT.getMediaTagPreview = function (common, tags, start) { if (!Array.isArray(tags) || !tags.length) { return; } - var i = 0; + var i = start; var metadataMgr = common.getMetadataMgr(); var priv = metadataMgr.getPrivateData(); @@ -418,19 +418,32 @@ define([ window.saveAs(media._blob.content, media.name); } else if ($(this).hasClass("cp-app-code-context-open")) { - var mts = [{ - src: $mt.attr('src'), - key: $mt.attr('data-crypto-key') - }]; + var mts = []; $container.find('media-tag').each(function (i, el) { var $el = $(el); - if ($el.attr('src') === $mt.attr('src')) { return; } mts.push({ src: $el.attr('src'), key: $el.attr('data-crypto-key') }); }); - common.getMediaTagPreview(mts); + + // Find initial position + var idx = -1; + mts.some(function (obj, i) { + if (obj.src === $mt.attr('src')) { + idx = i; + return true; + } + }); + if (idx === -1) { + mts.unshift({ + src: $mt.attr('src'), + key: $mt.attr('data-crypto-key') + }); + idx = 0; + } + + common.getMediaTagPreview(mts, idx); } }); From 7ca45eac7269b731f36608391d87ca825b14e286 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 3 Apr 2020 15:01:52 +0200 Subject: [PATCH 10/18] Add preview for mermaid graphs in code --- .../src/less2/include/modals-ui-elements.less | 2 + www/common/diffMarked.js | 82 ++++++++++++------- www/common/drive-ui.js | 2 +- www/common/inner/common-mediatag.js | 68 +++++++-------- 4 files changed, 91 insertions(+), 63 deletions(-) diff --git a/customize.dist/src/less2/include/modals-ui-elements.less b/customize.dist/src/less2/include/modals-ui-elements.less index 968fa6f70..a5869aa6a 100644 --- a/customize.dist/src/less2/include/modals-ui-elements.less +++ b/customize.dist/src/less2/include/modals-ui-elements.less @@ -115,6 +115,8 @@ justify-content: center; .cp-mediatag-container { width: 100%; + flex: 1; + min-width: 0; media-tag { & > * { max-width: 100%; diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index 852fa2599..ccf240685 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -289,7 +289,7 @@ define([ }; DiffMd.apply = function (newHtml, $content, common) { - var contextMenu = common.importMediaTagMenu($content); + var contextMenu = common.importMediaTagMenu(); var id = $content.attr('id'); if (!id) { throw new Error("The element must have a valid id"); } var pattern = /()<\/media-tag>/g; @@ -349,6 +349,42 @@ define([ var oldDom = domFromHTML($content[0].outerHTML); + var onPreview = function ($mt) { + return function () { + var mts = []; + $content.find('media-tag, pre.mermaid').each(function (i, el) { + if (el.nodeName.toLowerCase() === "pre") { + return void mts.push({ + svg: el.cloneNode(true) + }); + } + var $el = $(el); + mts.push({ + src: $el.attr('src'), + key: $el.attr('data-crypto-key') + }); + }); + + // Find initial position + var idx = -1; + mts.some(function (obj, i) { + if (obj.src === $mt.attr('src')) { + idx = i; + return true; + } + }); + if (idx === -1) { + mts.unshift({ + src: $mt.attr('src'), + key: $mt.attr('data-crypto-key') + }); + idx = 0; + } + + common.getMediaTagPreview(mts, idx); + }; + }; + var patch = makeDiff(oldDom, Dom, id); if (typeof(patch) === 'string') { throw new Error(patch); @@ -359,6 +395,7 @@ define([ var $mt = $(el).contextmenu(function (e) { e.preventDefault(); $(contextMenu.menu).data('mediatag', $(el)); + $(contextMenu.menu).find('li').show(); contextMenu.show(e); }); MediaTag(el); @@ -372,35 +409,11 @@ define([ observer.disconnect(); } }); - $mt.off('dblclick'); + $mt.off('dblclick preview'); + $mt.on('preview', onPreview($mt)); if ($mt.find('img').length) { $mt.on('dblclick', function () { - var mts = []; - $content.find('media-tag').each(function (i, el) { - var $el = $(el); - mts.push({ - src: $el.attr('src'), - key: $el.attr('data-crypto-key') - }); - }); - - // Find initial position - var idx = -1; - mts.some(function (obj, i) { - if (obj.src === $mt.attr('src')) { - idx = i; - return true; - } - }); - if (idx === -1) { - mts.unshift({ - src: $mt.attr('src'), - key: $mt.attr('data-crypto-key') - }); - idx = 0; - } - - common.getMediaTagPreview(mts, idx); + $mt.trigger('preview'); }); } }); @@ -422,6 +435,19 @@ define([ // loop over mermaid elements in the rendered content $content.find('pre.mermaid').each(function (index, el) { + var $el = $(el); + $el.off('contextmenu').on('contextmenu', function (e) { + e.preventDefault(); + $(contextMenu.menu).data('mediatag', $el); + $(contextMenu.menu).find('li:not(.cp-svg)').hide(); + contextMenu.show(e); + }); + $el.off('dblclick preview'); + $el.on('preview', onPreview($el)); + $el.on('dblclick', function () { + $el.trigger('preview'); + }); + // since you've simply drawn the content that was supplied via markdown // you can assume that the index of your rendered charts matches that // of those in the markdown source. diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 4523c99f5..7c76d3255 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -321,7 +321,7 @@ define([ h('li', h('a.cp-app-drive-context-preview.dropdown-item', { 'tabindex': '-1', 'data-icon': faPreview, - }, 'PREVIEW')), // XXX + }, Messages.pad_mediatagPreview)), h('li', h('a.cp-app-drive-context-open.dropdown-item', { 'tabindex': '-1', 'data-icon': faFolderOpen, diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index a97f05618..a4145c6f9 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -216,9 +216,13 @@ define([ var priv = metadataMgr.getPrivateData(); var left, right; + var checkSize = function () {}; var $modal = UI.createModal({ id: 'cp-mediatag-preview-modal', + onClose: function () { + $(window).off('resize', checkSize); + }, $body: $('body') }).show().focus(); var $container = $modal.find('.cp-modal').append(h('div.cp-mediatag-outer', [ @@ -233,9 +237,18 @@ define([ var $inner = $container.find('.cp-mediatag-container'); var el; - var checkSize = function () { + checkSize = function () { if (!el) { return; } + + if (el.nodeName === 'BUTTON') { + return $container.find('.cp-mediatag-container').css('height', 'auto'); + } + var size = el.naturalHeight || el.videoHeight; + if ($(el).find('svg').length) { + var h = $(el).find('svg').prop('height'); + size = Number(h) || (h.baseVal && h.baseVal.value); + } if (el.nodeName !== 'IMG' && el.nodeName !== 'VIDEO') { $container.find('.cp-mediatag-container').css('height', '100%'); } @@ -247,6 +260,8 @@ define([ } }; + $(window).on('resize', checkSize); + var $spinner = $container.find('.cp-loading-spinner-container'); var locked = false; @@ -268,11 +283,21 @@ define([ } // Reset modal - $inner.find('media-tag').remove(); + $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); + el = cfg.svg; + checkSize(); + locked = false; + return; + } + var src = cfg.src; var key = cfg.key; if (cfg.href) { @@ -286,7 +311,6 @@ define([ if (!src || !key) { locked = false; $spinner.hide(); - // XXX show error return void UI.log(Messages.error); } @@ -311,7 +335,9 @@ define([ el.onload = checkSize; return; } - setTimeout(checkSize); + setTimeout(function () { + checkSize(); + }); } }); }); @@ -324,7 +350,6 @@ define([ locked = false; $spinner.hide(); UI.log(Messages.error); - // XXX show error }); }; @@ -366,7 +391,7 @@ define([ }; var mediatagContextMenu; - MT.importMediaTagMenu = function (common, $container) { + MT.importMediaTagMenu = function (common) { if (mediatagContextMenu) { return mediatagContextMenu; } // Create context menu @@ -376,10 +401,10 @@ define([ 'aria-labelledBy': 'dropdownMenu', 'style': 'display:block;position:static;margin-bottom:5px;' }, [ - h('li', h('a.cp-app-code-context-open.dropdown-item', { + h('li.cp-svg', h('a.cp-app-code-context-open.dropdown-item', { 'tabindex': '-1', 'data-icon': "fa-eye", - }, Messages.fc_open)), // XXX + }, Messages.pad_mediatagPreview)), h('li', h('a.cp-app-code-context-saveindrive.dropdown-item', { 'tabindex': '-1', 'data-icon': "fa-cloud-upload", @@ -418,32 +443,7 @@ define([ window.saveAs(media._blob.content, media.name); } else if ($(this).hasClass("cp-app-code-context-open")) { - var mts = []; - $container.find('media-tag').each(function (i, el) { - var $el = $(el); - mts.push({ - src: $el.attr('src'), - key: $el.attr('data-crypto-key') - }); - }); - - // Find initial position - var idx = -1; - mts.some(function (obj, i) { - if (obj.src === $mt.attr('src')) { - idx = i; - return true; - } - }); - if (idx === -1) { - mts.unshift({ - src: $mt.attr('src'), - key: $mt.attr('data-crypto-key') - }); - idx = 0; - } - - common.getMediaTagPreview(mts, idx); + $mt.trigger('preview'); } }); From 18a4d2a72c360c95b40fc836a5df189a67c7ca3d Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 3 Apr 2020 15:31:42 +0200 Subject: [PATCH 11/18] Merge duplicated code between framework and whiteboard --- www/common/common-ui-elements.js | 1 - www/common/sframe-app-framework.js | 11 +++++--- www/whiteboard/inner.js | 40 +++++++++--------------------- 3 files changed, 20 insertions(+), 32 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 9908dcae6..d73b91692 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2639,7 +2639,6 @@ define([ }); }; - // XXX add txid UIElements.initFilePicker = function (common, cfg) { var onSelect = cfg.onSelect || $.noop; var sframeChan = common.getSframeChannel(); 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/whiteboard/inner.js b/www/whiteboard/inner.js index 80ab8408f..5dd436909 100644 --- a/www/whiteboard/inner.js +++ b/www/whiteboard/inner.js @@ -399,34 +399,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) { - // Convert src from blob URL to base64 data URL - // XXX base64 is heavy... - 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 () { From 35394476a6b05d652fb3f6e0ee8b4b7c4bee4c2e Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 6 Apr 2020 11:42:47 +0200 Subject: [PATCH 12/18] Improve scrollbars and modal size --- .../src/less2/include/markdown.less | 5 ++ customize.dist/src/less2/include/modal.less | 11 +++- .../src/less2/include/modals-ui-elements.less | 22 ++++--- www/common/common-interface.js | 9 ++- www/common/common-ui-elements.js | 5 +- www/common/diffMarked.js | 21 +++++-- www/common/drive-ui.js | 5 +- www/common/inner/common-mediatag.js | 59 +++---------------- www/filepicker/inner.js | 6 +- www/teams/inner.js | 6 +- 10 files changed, 73 insertions(+), 76 deletions(-) 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 a5869aa6a..24906d576 100644 --- a/customize.dist/src/less2/include/modals-ui-elements.less +++ b/customize.dist/src/less2/include/modals-ui-elements.less @@ -111,20 +111,24 @@ #cp-mediatag-preview-modal { .cp-modal { display: flex; - align-items: center; 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; @@ -134,22 +138,26 @@ padding: 5px; } } + pre.mermaid { + overflow: unset; + } .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; - .cp-mediatag-control { - .fa { - margin: 10px; - cursor: pointer; - } - } } } } diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 5a7cf8754..4c4518434 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -473,6 +473,7 @@ define([ 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); @@ -488,7 +489,13 @@ define([ hide(); } }); - return $blockContainer; + return { + $modal: $blockContainer, + show: function () { + $blockContainer.css('display', 'flex'); + }, + hide: hide + }; }; UI.alert = function (msg, cb, opt) { diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index d73b91692..aae6b09b9 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2545,10 +2545,11 @@ define([ return void $(".cp-app-drive-element-row.cp-app-drive-new-ghost").click(); } - var $modal = UI.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); @@ -2634,7 +2635,7 @@ define([ $modal.find('.cp-modal').append($container).append($advancedContainer); window.setTimeout(function () { - $modal.show(); + modal.show(); $modal.focus(); }); }; diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index ccf240685..c385a606c 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -351,6 +351,7 @@ define([ var onPreview = function ($mt) { return function () { + var isSvg = $mt.is('pre.mermaid'); var mts = []; $content.find('media-tag, pre.mermaid').each(function (i, el) { if (el.nodeName.toLowerCase() === "pre") { @@ -368,16 +369,26 @@ define([ // Find initial position var idx = -1; mts.some(function (obj, i) { - if (obj.src === $mt.attr('src')) { + if (isSvg && $mt.find('svg').attr('id') === $(obj.svg).find('svg').attr('id')) { + idx = i; + return true; + } + if (!isSvg && obj.src === $mt.attr('src')) { idx = i; return true; } }); if (idx === -1) { - mts.unshift({ - src: $mt.attr('src'), - key: $mt.attr('data-crypto-key') - }); + if (isSvg) { + mts.unshift({ + svg: $mt[0].cloneNode(true) + }); + } else { + mts.unshift({ + src: $mt.attr('src'), + key: $mt.attr('data-crypto-key') + }); + } idx = 0; } diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 7c76d3255..899405fa1 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -2946,17 +2946,18 @@ define([ $element.append($('', {'class': 'cp-app-drive-element-name'}) .text(Messages.fm_newFile)); $element.click(function () { - var $modal = UI.createModal({ + var modal = UI.createModal({ id: 'cp-app-drive-new-ghost-dialog', $body: $('body') }); + var $modal = modal.$modal; var $title = $('

').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); }); }; diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index a4145c6f9..001bae062 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -216,52 +216,24 @@ define([ var priv = metadataMgr.getPrivateData(); var left, right; - var checkSize = function () {}; - var $modal = UI.createModal({ + var modal = UI.createModal({ id: 'cp-mediatag-preview-modal', - onClose: function () { - $(window).off('resize', checkSize); - }, $body: $('body') - }).show().focus(); - var $container = $modal.find('.cp-modal').append(h('div.cp-mediatag-outer', [ + }); + 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 el; - checkSize = function () { - if (!el) { return; } - - if (el.nodeName === 'BUTTON') { - return $container.find('.cp-mediatag-container').css('height', 'auto'); - } - - var size = el.naturalHeight || el.videoHeight; - if ($(el).find('svg').length) { - var h = $(el).find('svg').prop('height'); - size = Number(h) || (h.baseVal && h.baseVal.value); - } - if (el.nodeName !== 'IMG' && el.nodeName !== 'VIDEO') { - $container.find('.cp-mediatag-container').css('height', '100%'); - } - if (!size) { return; } - // Center small images and videos - $container.find('.cp-mediatag-container').css('height', '100%'); - if (size < $container.height()) { - $container.find('.cp-mediatag-container').css('height', 'auto'); - } - }; - - $(window).on('resize', checkSize); - var $spinner = $container.find('.cp-loading-spinner-container'); var locked = false; @@ -292,8 +264,6 @@ define([ if (cfg.svg) { $spinner.hide(); $inner.append(cfg.svg); - el = cfg.svg; - checkSize(); locked = false; return; } @@ -321,24 +291,9 @@ define([ $inner.append(tag); var observer = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { + mutations.forEach(function() { locked = false; $spinner.hide(); - if (mutation.addedNodes.length === 1) { - el = mutation.addedNodes[0]; - if (el.readyState === 0) { - // Wait for the video to be ready before checking the size - el.onloadedmetadata = checkSize; - return; - } - if (el.complete === false) { - el.onload = checkSize; - return; - } - setTimeout(function () { - checkSize(); - }); - } }); }); observer.observe(tag, { diff --git a/www/filepicker/inner.js b/www/filepicker/inner.js index 2eb9b4120..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 = UI.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/teams/inner.js b/www/teams/inner.js index ae67c6f6e..af011167d 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -535,9 +535,11 @@ define([ }; var makePermissions = function () { - var $blockContainer = UI.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) { From 033b784576c967983f08970b479d408da86494e8 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 6 Apr 2020 13:21:08 +0200 Subject: [PATCH 13/18] Add max file size in whiteboard --- www/whiteboard/inner.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/www/whiteboard/inner.js b/www/whiteboard/inner.js index 5dd436909..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 () { From 9da47ebac9d7dd2b47e63d598194a3bdc40e83c7 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 6 Apr 2020 13:22:22 +0200 Subject: [PATCH 14/18] lint compliance --- www/common/diffMarked.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index 5a8a594a1..f53828e17 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -486,7 +486,6 @@ define([ // check if you had cached a pre-rendered instance of the supplied source if (typeof(cached) !== 'object') { try { - var $el = $(el); Mermaid.init(undefined, $el); // clickable elements in mermaid don't work well with our sandboxing setup // the function below strips clickable elements but still leaves behind some artifacts From 20111e8416b71958b9f4538b5c6e07bd9b7c12bd Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 6 Apr 2020 13:22:56 +0200 Subject: [PATCH 15/18] Drop support for external URL as avatar --- www/common/inner/common-mediatag.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 001bae062..217eb3ed1 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -96,18 +96,6 @@ define([ if (cb) { cb($img); } }; - // XXX Drop support for external URLs - 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; From c29b7d33893c37340fb02cb4d1d7baa312a326e5 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 6 Apr 2020 15:52:13 +0200 Subject: [PATCH 16/18] Fix reference error --- www/common/inner/common-mediatag.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 217eb3ed1..0d6f768af 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -99,6 +99,7 @@ define([ // 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; From 0f21f3118bedee89097294ea3dda9607a3717e6e Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 6 Apr 2020 16:00:01 +0200 Subject: [PATCH 17/18] Fix lightbox errors --- customize.dist/src/less2/include/modals-ui-elements.less | 1 + www/common/inner/common-mediatag.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/customize.dist/src/less2/include/modals-ui-elements.less b/customize.dist/src/less2/include/modals-ui-elements.less index 24906d576..62b175f0e 100644 --- a/customize.dist/src/less2/include/modals-ui-elements.less +++ b/customize.dist/src/less2/include/modals-ui-elements.less @@ -140,6 +140,7 @@ } pre.mermaid { overflow: unset; + margin-bottom: 0; } .cp-spinner { border-color: @colortheme_logo-1; diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 0d6f768af..d86b481cd 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -299,9 +299,11 @@ define([ 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); From 3aefc3a8936c90de783f4e754f2626362ea1dbe1 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 6 Apr 2020 16:13:51 +0200 Subject: [PATCH 18/18] Fix icon size in the new pad modal --- customize.dist/src/less2/include/toolbar.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; }