From b2cc4656f190934ec5b626089937673cd3a57a7d Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Sat, 26 Jul 2014 14:08:35 +0200 Subject: [PATCH] Added clipboard paste for images, live preview of attachments --- com.woltlab.wcf/templates/wysiwyg.tpl | 1 + .../js/3rdParty/redactor/plugins/wbbcode.js | 37 +++++++++ .../js/3rdParty/redactor/plugins/wupload.js | 36 +++++++++ wcfsetup/install/files/js/WCF.Attachment.js | 46 +++++++++-- wcfsetup/install/files/js/WCF.js | 80 +++++++++++++++++-- 5 files changed, 189 insertions(+), 11 deletions(-) diff --git a/com.woltlab.wcf/templates/wysiwyg.tpl b/com.woltlab.wcf/templates/wysiwyg.tpl index 26e6abc364..3fda90d2b7 100644 --- a/com.woltlab.wcf/templates/wysiwyg.tpl +++ b/com.woltlab.wcf/templates/wysiwyg.tpl @@ -41,6 +41,7 @@ $(function() { }); $config.plugins.push('wupload'); + $config.wAttachmentUrl = '{link controller='Attachment' id=987654321}thumbnail=1{/link}'; {/if} {event name='javascriptInit'} diff --git a/wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js b/wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js index c22207976d..96c9c761d0 100644 --- a/wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js +++ b/wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js @@ -235,6 +235,9 @@ RedactorPlugins.wbbcode = { html = html.replace(/]*?alt="([^"]+?)" class="smiley".*?>/gi, '$1'); // firefox html = html.replace(/]*?class="smiley" alt="([^"]+?)".*?>/gi, '$1'); // chrome, ie + // attachments + html = html.replace(/]*?class="redactorEmbeddedAttachment" data-attachment-id="(\d+)".*?>/gi, '[attach=$1][/attach]'); + // [img] html = html.replace(/]*?src=(["'])([^"']+?)\1 style="float: (left|right)[^"]*".*?>/gi, "[img='$2',$3][/img]"); html = html.replace(/]*?src=(["'])([^"']+?)\1.*?>/gi, '[img]$2[/img]'); @@ -525,6 +528,14 @@ RedactorPlugins.wbbcode = { return '' + $tdContent + ''; }); + // attachments + var $attachmentUrl = this.getOption('wAttachmentUrl'); + if ($attachmentUrl) { + data = data.replace(/\[attach=(\d+)\]\[\/attach\]/, function(match, attachmentID) { + return ''; + }); + } + // smileys for (var smileyCode in __REDACTOR_SMILIES) { $smileyCode = smileyCode.replace(//g, '>'); @@ -631,6 +642,32 @@ RedactorPlugins.wbbcode = { html = html.replace(/\[size=(\d+)\]/g, '


'); html = html.replace(/\[\/size\]/g, '


'); + // handle pasting of images in Firefox + html = html.replace(/]+)>/g, function(match, content) { + match = match.replace(/data-mozilla-paste-image="0"/, 'data-mozilla-paste-image="0" style="display:none"'); + return match; + }); + return html; + }, + + /** + * Inserts an attachment with live preview. + * + * @param integer attachmentID + */ + insertAttachment: function(attachmentID) { + var $attachmentUrl = this.getOption('wAttachmentUrl'); + var $bbcode = '[attach=' + attachmentID + '][/attach]'; + + if ($attachmentUrl) { + this.insertDynamic( + '', + $bbcode + ); + } + else { + this.insertDynamic($bbcode); + } } }; diff --git a/wcfsetup/install/files/js/3rdParty/redactor/plugins/wupload.js b/wcfsetup/install/files/js/3rdParty/redactor/plugins/wupload.js index 84b16cb976..5cfeb54810 100644 --- a/wcfsetup/install/files/js/3rdParty/redactor/plugins/wupload.js +++ b/wcfsetup/install/files/js/3rdParty/redactor/plugins/wupload.js @@ -31,6 +31,8 @@ RedactorPlugins.wupload = { $(document).on('dragend', function(event) { event.preventDefault(); }); } + + WCF.System.Event.addListener('com.woltlab.wcf.attachment', 'autoInsert_' + this.$source.wcfIdentify(), $.proxy(this.insertPastedImageAttachment, this)); }, /** @@ -143,5 +145,39 @@ RedactorPlugins.wupload = { WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'upload_' + $containerID, { file: event.dataTransfer.files[0] }); } + }, + + /** + * Overwrites $.Redactor.pasteClipboardUploadMozilla() to upload files as attachments. + * + * @see $.Redactor.pasteClipboardUploadMozilla() + */ + pasteClipboardUploadMozilla: function() { + this.$editor.find('img[data-mozilla-paste-image]').each($.proxy(function(index, image) { + var $image = $(image); + var $src = $image.prop('src').split(','); + var $contentType = $src[0].split(';')[0].split(':')[1]; + var $data = $src[1]; // raw base64 + + var $eventData = { + blob: WCF.base64toBlob($data, $contentType), + uploadID: null + }; + WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'upload_' + this.$source.wcfIdentify(), $eventData); + + // drop image + $image.replaceWith(''); + }, this)); + }, + + /** + * Inserts the attachment at the placeholder location. + * + * @param object data + */ + insertPastedImageAttachment: function(data) { + var $placeholder = this.$editor.find('span.redactor-pastedImageFromClipboard-' + data.uploadID); + $placeholder.before(data.attachment); + $placeholder.remove(); } }; diff --git a/wcfsetup/install/files/js/WCF.Attachment.js b/wcfsetup/install/files/js/WCF.Attachment.js index 74e6daf5d0..b1ed25edb4 100644 --- a/wcfsetup/install/files/js/WCF.Attachment.js +++ b/wcfsetup/install/files/js/WCF.Attachment.js @@ -9,6 +9,12 @@ WCF.Attachment = {}; * @see WCF.Upload */ WCF.Attachment.Upload = WCF.Upload.extend({ + /** + * list of upload ids which should be automatically inserted + * @var array + */ + _autoInsert: [ ], + /** * reference to 'Insert All' button * @var jQuery @@ -51,6 +57,7 @@ WCF.Attachment.Upload = WCF.Upload.extend({ init: function(buttonSelector, fileListSelector, objectType, objectID, tmpHash, parentObjectID, maxUploads, wysiwygContainerID) { this._super(buttonSelector, fileListSelector, 'wcf\\data\\attachment\\AttachmentAction', { multiple: true, maxUploads: maxUploads }); + this._autoInsert = [ ]; this._objectType = objectType; this._objectID = objectID; this._tmpHash = tmpHash; @@ -78,12 +85,27 @@ WCF.Attachment.Upload = WCF.Upload.extend({ } }, + /** + * Handles drag & drop uploads and clipboard paste. + * + * @param object data + */ _editorUpload: function(data) { + var $uploadID; + // show tab var $tabMenuContainer = this._fileListSelector.closest('.messageTabMenu') $tabMenuContainer.messageTabMenu('showTab', 'attachments', true); - this._upload(undefined, [ data.file ]); + if (data.file) { + $uploadID = this._upload(undefined, data.file); + } + else { + $uploadID = this._upload(undefined, undefined, data.blob); + } + + this._autoInsert.push($uploadID); + data.uploadID = $uploadID; }, /** @@ -154,9 +176,11 @@ WCF.Attachment.Upload = WCF.Upload.extend({ /** * @see WCF.Upload._upload() */ - _upload: function(event, files) { + _upload: function(event, file, blob) { + var $uploadID; + if (this._validateLimit()) { - this._super(event, files); + $uploadID = this._super(event, file, blob); } if (this._fileUpload) { @@ -165,6 +189,8 @@ WCF.Attachment.Upload = WCF.Upload.extend({ this._removeButton(); this._createButton(); } + + return $uploadID; }, /** @@ -281,6 +307,17 @@ WCF.Attachment.Upload = WCF.Upload.extend({ // fix webkit rendering bug $li.css('display', 'block'); + + if (WCF.inArray(uploadID, this._autoInsert)) { + this._autoInsert.splice(this._autoInsert.indexOf(uploadID), 1); + + if (!$li.hasClass('uploadFailed')) { + WCF.System.Event.fireEvent('com.woltlab.wcf.attachment', 'autoInsert_' + this._wysiwygContainerID, { + attachment: '[attach=' + data.returnValues.attachments[$internalFileID].attachmentID + '][/attach]', + uploadID: uploadID + }); + } + } } this._makeSortable(); @@ -303,10 +340,9 @@ WCF.Attachment.Upload = WCF.Upload.extend({ */ _insert: function(event, attachmentID) { var $attachmentID = (event === null) ? attachmentID : $(event.currentTarget).data('objectID'); - var $bbcode = '[attach=' + $attachmentID + '][/attach]'; if ($.browser.redactor) { - $('#' + this._wysiwygContainerID).redactor('insertDynamic', $bbcode); + $('#' + this._wysiwygContainerID).redactor('insertAttachment', $attachmentID); } }, diff --git a/wcfsetup/install/files/js/WCF.js b/wcfsetup/install/files/js/WCF.js index 248a00562a..4db5c04e67 100755 --- a/wcfsetup/install/files/js/WCF.js +++ b/wcfsetup/install/files/js/WCF.js @@ -758,6 +758,36 @@ $.extend(WCF, { var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); return v.toString(16); }); + }, + + /** + * Converts a base64 encoded file into a native Blob. + * + * @param string base64data + * @param string contentType + * @param integer sliceSize + * @return Blob + */ + base64toBlob: function(base64data, contentType, sliceSize) { + contentType = contentType || ''; + sliceSize = sliceSize || 512; + + var $byteCharacters = atob(base64data); + var $byteArrays = [ ]; + + for (var $offset = 0; $offset < $byteCharacters.length; $offset += sliceSize) { + var $slice = $byteCharacters.slice($offset, $offset + sliceSize); + + var $byteNumbers = new Array($slice.length); + for (var $i = 0; $i < $slice.length; $i++) { + $byteNumbers[$i] = $slice.charCodeAt($i); + } + + var $byteArray = new Uint8Array($byteNumbers); + $byteArrays.push($byteArray); + } + + return new Blob($byteArrays, { type: contentType }); } }); @@ -8398,23 +8428,59 @@ WCF.Upload = Class.extend({ * Callback for file uploads. * * @param object event - * @param array files + * @param File file + * @param Blob blob + * @return integer */ - _upload: function(event, files) { - var $files = files || this._fileUpload.prop('files'); + _upload: function(event, file, blob) { + var $uploadID = null; + var $files = [ ]; + if (file) { + $files.push(file); + } + else if (blob) { + var $ext = ''; + switch (blob.type) { + case 'image/png': + $ext = '.png'; + break; + + case 'image/jpeg': + $ext = '.jpg'; + break; + + case 'image/gif': + $ext = '.gif'; + break; + } + + $files.push({ + name: 'pasted-from-clipboard' + $ext + }); + } + else { + $files = this._fileUpload.prop('files'); + } + if ($files.length) { var $fd = new FormData(); - var $uploadID = this._createUploadMatrix($files); + $uploadID = this._createUploadMatrix($files); // no more files left, abort if (!this._uploadMatrix[$uploadID].length) { - return; + return null; } for (var $i = 0, $length = $files.length; $i < $length; $i++) { if (this._uploadMatrix[$uploadID][$i]) { var $internalFileID = this._uploadMatrix[$uploadID][$i].data('internalFileID'); - $fd.append('__files[' + $internalFileID + ']', $files[$i]); + + if (blob) { + $fd.append('__files[' + $internalFileID + ']', blob, $files[$i].name); + } + else { + $fd.append('__files[' + $internalFileID + ']', $files[$i]); + } } } @@ -8448,6 +8514,8 @@ WCF.Upload = Class.extend({ } }); } + + return $uploadID; }, /** -- 2.20.1