Added clipboard paste for images, live preview of attachments
authorAlexander Ebert <ebert@woltlab.com>
Sat, 26 Jul 2014 12:08:35 +0000 (14:08 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sat, 26 Jul 2014 12:08:35 +0000 (14:08 +0200)
com.woltlab.wcf/templates/wysiwyg.tpl
wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/wupload.js
wcfsetup/install/files/js/WCF.Attachment.js
wcfsetup/install/files/js/WCF.js

index 26e6abc36491bdb05471f5bb1612b53c21f9094c..3fda90d2b784c57e6c6e39f9c36e6dd104eb95d3 100644 (file)
@@ -41,6 +41,7 @@ $(function() {
                        });
                        
                        $config.plugins.push('wupload');
+                       $config.wAttachmentUrl = '{link controller='Attachment' id=987654321}thumbnail=1{/link}';
                {/if}
                
                {event name='javascriptInit'}
index c22207976da188a48c726ce22910f5bcf1f3342e..96c9c761d0a4d563a6b435bdf256bde4cb32949a 100644 (file)
@@ -235,6 +235,9 @@ RedactorPlugins.wbbcode = {
                html = html.replace(/<img [^>]*?alt="([^"]+?)" class="smiley".*?>/gi, '$1'); // firefox
                html = html.replace(/<img [^>]*?class="smiley" alt="([^"]+?)".*?>/gi, '$1'); // chrome, ie
                
+               // attachments
+               html = html.replace(/<img [^>]*?class="redactorEmbeddedAttachment" data-attachment-id="(\d+)".*?>/gi, '[attach=$1][/attach]');
+               
                // [img]
                html = html.replace(/<img [^>]*?src=(["'])([^"']+?)\1 style="float: (left|right)[^"]*".*?>/gi, "[img='$2',$3][/img]");
                html = html.replace(/<img [^>]*?src=(["'])([^"']+?)\1.*?>/gi, '[img]$2[/img]');
@@ -525,6 +528,14 @@ RedactorPlugins.wbbcode = {
                        return '<td>' + $tdContent + '</td>';
                });
                
+               // attachments
+               var $attachmentUrl = this.getOption('wAttachmentUrl');
+               if ($attachmentUrl) {
+                       data = data.replace(/\[attach=(\d+)\]\[\/attach\]/, function(match, attachmentID) {
+                               return '<img src="' + $attachmentUrl.replace(/987654321/, attachmentID) + '" class="redactorEmbeddedAttachment" data-attachment-id="' + attachmentID + '" />';
+                       });
+               }
+               
                // smileys
                for (var smileyCode in __REDACTOR_SMILIES) {
                        $smileyCode = smileyCode.replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -631,6 +642,32 @@ RedactorPlugins.wbbcode = {
                html = html.replace(/\[size=(\d+)\]/g, '<p><br></p><p><inline style="font-size: $1pt">');
                html = html.replace(/\[\/size\]/g, '</inline></p><p><br></p>');
                
+               // handle pasting of images in Firefox
+               html = html.replace(/<img([^>]+)>/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(
+                               '<img src="' + $attachmentUrl.replace(/987654321/, attachmentID) + '" class="redactorEmbeddedAttachment" data-attachment-id="' + attachmentID + '" />',
+                               $bbcode
+                       );
+               }
+               else {
+                       this.insertDynamic($bbcode);
+               }
        }
 };
index 84b16cb976f79960633acf427a77489ee30503df..5cfeb54810ddb528225a91de5321e6e58a7efeaa 100644 (file)
@@ -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('<span class="redactor-pastedImageFromClipboard-' + $eventData.uploadID + '" />');
+               }, 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();
        }
 };
index 74e6daf5d01c7ded30175cfd23a6bff39a31558b..b1ed25edb412b2350eb2ac5d3ba50dddf2fb7690 100644 (file)
@@ -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<integer>
+        */
+       _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);
                }
        },
        
index 248a00562a86fb42be73c5033cd260619a57bc68..4db5c04e678c52f5d47cbc9a30c02a8f82a8f145 100755 (executable)
@@ -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<File>     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;
        },
        
        /**