Added drag&drop ability for Redactor
authorAlexander Ebert <ebert@woltlab.com>
Thu, 24 Jul 2014 19:20:31 +0000 (21:20 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 24 Jul 2014 19:20:31 +0000 (21:20 +0200)
com.woltlab.wcf/templates/wysiwyg.tpl
wcfsetup/install/files/js/3rdParty/redactor/plugins/wupload.js [new file with mode: 0644]
wcfsetup/install/files/js/3rdParty/slimbox2.js
wcfsetup/install/files/js/3rdParty/slimbox2.min.js
wcfsetup/install/files/js/WCF.Attachment.js
wcfsetup/install/files/js/WCF.js
wcfsetup/install/files/style/redactor.less
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index ded918779b637a4627f616106419e3f84494421a..26e6abc36491bdb05471f5bb1612b53c21f9094c 100644 (file)
@@ -25,7 +25,7 @@ $(function() {
                        lang: '{@$__wcf->getLanguage()->getFixedLanguageCode()}',
                        minHeight: 200,
                        imageResizable: false,
-                       plugins: [ 'wutil',  'wmonkeypatch', 'wbutton', 'wbbcode',  'wfontcolor', 'wfontfamily', 'wfontsize' ],
+                       plugins: [ 'wutil',  'wmonkeypatch', 'wbutton', 'wbbcode',  'wfontcolor', 'wfontfamily', 'wfontsize', 'wupload' ],
                        wautosave: {
                                active: ($autosave) ? true : false,
                                key: ($autosave) ? '{@$__wcf->getAutosavePrefix()}_' + $autosave : '',
@@ -34,6 +34,15 @@ $(function() {
                        wOriginalValue: $textarea.val()
                };
                
+               {if MODULE_ATTACHMENT && !$attachmentHandler|empty && $attachmentHandler->canUpload()}
+                       WCF.Language.addObject({
+                               'wcf.attachment.dragAndDrop.dropHere': '{lang}wcf.attachment.dragAndDrop.dropHere{/lang}',
+                               'wcf.attachment.dragAndDrop.dropNow': '{lang}wcf.attachment.dragAndDrop.dropNow{/lang}'
+                       });
+                       
+                       $config.plugins.push('wupload');
+               {/if}
+               
                {event name='javascriptInit'}
                
                $textarea.redactor($config);
@@ -52,6 +61,7 @@ $(function() {
                        '{@$__wcf->getPath()}js/3rdParty/redactor/plugins/wfontsize.js?v={@$__wcfVersion}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor/plugins/wmonkeypatch.js?v={@$__wcfVersion}',
                        '{@$__wcf->getPath()}js/3rdParty/redactor/plugins/wutil.js?v={@$__wcfVersion}',
+                       '{@$__wcf->getPath()}js/3rdParty/redactor/plugins/wupload.js?v={@$__wcfVersion}'
                {/if}
                {event name='javascriptFiles'}
        ], function() {
diff --git a/wcfsetup/install/files/js/3rdParty/redactor/plugins/wupload.js b/wcfsetup/install/files/js/3rdParty/redactor/plugins/wupload.js
new file mode 100644 (file)
index 0000000..84b16cb
--- /dev/null
@@ -0,0 +1,147 @@
+if (!RedactorPlugins) var RedactorPlugins = {};
+
+/**
+ * Handles drag&drop upload using the attachment system for Redactor.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ */
+RedactorPlugins.wupload = {
+       _boundGlobalUploadEvents: false,
+       _wUploadDropArea: { },
+       _timer: null,
+       _isDragging: false,
+       
+       /**
+        * Initializes the RedactorPlugins.wupload plugin.
+        */
+       init: function() {
+               var $namespace = '.redactor_' + this.$source.wcfIdentify();
+               $(document).on('dragover' + $namespace, $.proxy(this._dragOver, this));
+               $(document).on('dragleave' + $namespace, $.proxy(this._dragLeave, this));
+               $(document).on('drop' + $namespace, $.proxy(function(event) {
+                       event.preventDefault();
+                       
+                       this._revertDropArea(undefined, this.$source.wcfIdentify());
+               }, this));
+               
+               if (!this._boundGlobalUploadEvents) {
+                       this._boundGlobalUploadEvents = true;
+                       
+                       $(document).on('dragend', function(event) { event.preventDefault(); });
+               }
+       },
+       
+       /**
+        * Handles an actively dragged object.
+        * 
+        * @param       object          event
+        */
+       _dragOver: function(event) {
+               event = event.originalEvent;
+               
+               if (!event.dataTransfer || !event.dataTransfer.types) {
+                       return;
+               }
+               
+               // IE and WebKit set 'Files', Firefox sets 'application/x-moz-file' for files being dragged
+               if (event.dataTransfer.types[0] !== 'Files' && event.dataTransfer.types[0] !== 'application/x-moz-file') {
+                       return;
+               }
+               
+               event.preventDefault();
+               
+               if (!this._isDragging) {
+                       var $containerID = this.$source.wcfIdentify();
+                       
+                       if (this._wUploadDropArea[$containerID] === undefined) {
+                               this._wUploadDropArea[$containerID] = $('<div class="redactorDropArea">' + WCF.Language.get('wcf.attachment.dragAndDrop.dropHere') + '</div>').hide().appendTo(document.body);
+                               this._wUploadDropArea[$containerID].on('dragover', $.proxy(this._hoverDropArea, this)).on('dragleave', $.proxy(this._revertDropArea, this)).on('drop', $.proxy(this._drop, this));
+                       }
+                       
+                       // adjust dimensions
+                       var $dimensions = (this.inWysiwygMode()) ? this.$editor.getDimensions('outer') : this.$source.getDimensions('outer');
+                       var $position = (this.inWysiwygMode()) ? this.$editor.getOffsets('offset') : this.$source.getOffsets('offset');
+                       
+                       this._wUploadDropArea[$containerID].css({
+                               height: $dimensions.height + 'px',
+                               left: $position.left + 'px',
+                               lineHeight: $dimensions.height + 'px',
+                               top: $position.top + 'px',
+                               width: $dimensions.width + 'px'
+                       }).show();
+                       
+                       this._isDragging = true;
+               }
+               
+               event.preventDefault();
+       },
+       
+       /**
+        * Visualizes the drop area being hovered.
+        * 
+        * @param       object          event
+        */
+       _hoverDropArea: function(event) {
+               this._wUploadDropArea[this.$source.wcfIdentify()].addClass('active').text(WCF.Language.get('wcf.attachment.dragAndDrop.dropNow'));
+       },
+       
+       /**
+        * Reverts the drop area into the initial state.
+        * 
+        * @param       object          event
+        * @param       string          containerID
+        */
+       _revertDropArea: function(event, containerID) {
+               var $containerID = containerID || this.$source.wcfIdentify();
+               this._wUploadDropArea[$containerID].removeClass('active').text(WCF.Language.get('wcf.attachment.dragAndDrop.dropHere'));
+               
+               if (containerID) {
+                       this._wUploadDropArea[$containerID].hide();
+               }
+       },
+       
+       /**
+        * Handles the object no longer being dragged.
+        * 
+        * This event can fires whenever an object is hovering over a different element, there is
+        * a delay of 100ms before the dragging will be checked again to prevent flicker.
+        */
+       _dragLeave: function() {
+               if (this._timer === null) {
+                       var self = this;
+                       this._timer = new WCF.PeriodicalExecuter(function(pe) {
+                               pe.stop();
+                               
+                               if (!self._isDragging) {
+                                       self._wUploadDropArea[self.$source.wcfIdentify()].hide();
+                               }
+                       }, 100);
+               }
+               else {
+                       this._timer.resume();
+               }
+               
+               this._isDragging = false;
+       },
+       
+       /**
+        * Handles the drop of the dragged object.
+        * 
+        * @param       object          event
+        */
+       _drop: function(event) {
+               event = event.originalEvent || event;
+               
+               if (event.dataTransfer && event.dataTransfer.files.length == 1) {
+                       event.preventDefault();
+                       
+                       // reset overlay
+                       var $containerID = this.$source.wcfIdentify();
+                       this._revertDropArea(undefined, $containerID);
+                       
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'upload_' + $containerID, { file: event.dataTransfer.files[0] });
+               }
+       }
+};
index 795892ba2efec01adbca448c577f0791bd9a11b6..d8cbe048cccc478d1b64e775a9f8502f2028dbba 100644 (file)
@@ -78,6 +78,8 @@
                
                // WoltLab modifications        BEGIN
                if (activeImage == -1) {
+                       WCF.System.DisableScrolling.disable();
+                       
                        middle = win.scrollTop() + (win.height() / 2);
                        centerWidth = options.initialWidth;
                        centerHeight = options.initialHeight;
@@ -85,9 +87,7 @@
                        compatibleOverlay = ie6 || (overlay.currentStyle && (overlay.currentStyle.position != "fixed"));
                        if (compatibleOverlay) overlay.style.position = "absolute";
                        $(overlay).css("opacity", options.overlayOpacity).fadeIn(options.overlayFadeDuration);
-               
-                       WCF.System.DisableScrolling.disable();
-               
+                       
                        position();
                        setup(1);
                }
index b6bb1574e354f43c62083e6d3bb1616f8da5f248..2977d1bd1870b35f64180822847907d2972c2004 100644 (file)
@@ -5,4 +5,4 @@
 
        Modified by WoltLab GmbH to support large images.
 */
-(function(e){function L(){var n=t.scrollLeft(),r=t.width();e([b,T]).css("left",n+r/2);if(a)e(y).css({left:n,top:t.scrollTop(),width:r,height:t.height()})}function A(n){if(n){e("object").add(h?"select":"embed").each(function(e,t){p[e]=[t,t.style.visibility];t.style.visibility="hidden"})}else{e.each(p,function(e,t){t[0].style.visibility=t[1]});p=[]}var r=n?"bind":"unbind";t[r]("scroll resize",L);e(document)[r]("keydown",O)}function O(t){var r=t.which,i=e.inArray;return i(r,n.closeKeys)>=0?j():i(r,n.nextKeys)>=0?_():i(r,n.previousKeys)>=0?M():null}function M(){return D(o)}function _(){return D(u)}function D(t){if(t>=0){i=t;s=r[i][0];o=(i||(n.loop?r.length:0))-1;u=(i+1)%r.length||(n.loop?0:-1);B();e('<span class="icon icon48 icon-spinner" />').appendTo(b);v=new Image;v.onload=P;v.src=s}return false}function P(){e(w).css({backgroundImage:"url("+s+")",visibility:"hidden",display:""});e(E).width(v.width);e([E,S,x]).height(v.height);e(C).html(r[i][1]||"");e(k).html((r.length>1&&n.counterText||"").replace(/{x}/,i+1).replace(/{y}/,r.length));if(o>=0)m.src=r[o][0];if(u>=0)g.src=r[u][0];l=w.offsetWidth;c=w.offsetHeight;var t=e(window).getDimensions();var a=Math.round(t.height*.8);var h=Math.round(t.width*.8);if(c>a||l>h){var p=a/c;var d=h/l;if(p<d){l=Math.round(l*(a/c));c=a}else{c=Math.round(c*(h/l));l=h}e(E).width(l-20);e([E,S,x]).height(c-20)}var y=Math.max(0,f-c/2);if(b.offsetHeight!=c){e(b).animate({height:c,top:y},n.resizeDuration,n.resizeEasing)}if(b.offsetWidth!=l){e(b).animate({width:l,marginLeft:-l/2},n.resizeDuration,n.resizeEasing)}e(b).queue(function(){e(T).css({width:l,top:y+c,marginLeft:-l/2,visibility:"hidden",display:""});e(b).children(".icon-spinner").remove();e(w).css({display:"none",visibility:"",opacity:""}).fadeIn(n.imageFadeDuration,H)})}function H(){if(o>=0)e(S).show();if(u>=0)e(x).show();e(N).css("marginTop",-N.offsetHeight).animate({marginTop:0},n.captionAnimationDuration);T.style.visibility=""}function B(){v.onload=null;v.src=m.src=g.src=s;e([b,w,N]).stop(true);e([S,x,w,T]).hide()}function j(){if(i>=0){B();i=o=u=-1;e(b).hide();e(y).stop().fadeOut(n.overlayFadeDuration,A);WCF.System.DisableScrolling.enable()}return false}var t=e(window),n,r,i=-1,s,o,u,a,f,l,c,h=!window.XMLHttpRequest,p=[],d=document.documentElement,v={},m=new Image,g=new Image,y,b,w,E,S,x,T,N,C,k;e(function(){e("body").append(e([y=e('<div id="lbOverlay" />').click(j)[0],b=e('<div id="lbCenter" />')[0],T=e('<div id="lbBottomContainer" />')[0]]).css("display","none"));w=e('<div id="lbImage" />').appendTo(b).append(E=e('<div style="position: relative;" />').append([S=e('<a id="lbPrevLink" href="#" />').click(M)[0],x=e('<a id="lbNextLink" href="#" />').click(_)[0]])[0])[0];N=e('<div id="lbBottom" />').appendTo(T).append([e('<a id="lbCloseLink" href="#" />').click(j)[0],C=e('<div id="lbCaption" />')[0],k=e('<div id="lbNumber" />')[0],e('<div style="clear: both;" />')[0]])[0]});e.slimbox=function(i,s,o){n=e.extend({loop:false,overlayOpacity:.8,overlayFadeDuration:400,resizeDuration:400,resizeEasing:"swing",initialWidth:250,initialHeight:250,imageFadeDuration:400,captionAnimationDuration:400,counterText:"Image {x} of {y}",closeKeys:[27,88,67],previousKeys:[37,80],nextKeys:[39,78]},o);if(typeof i=="string"){i=[[i,s]];s=0}f=t.scrollTop()+t.height()/2;l=n.initialWidth;c=n.initialHeight;e(b).css({top:Math.max(0,f-c/2),width:l,height:c,marginLeft:-l/2}).show();a=h||y.currentStyle&&y.currentStyle.position!="fixed";if(a)y.style.position="absolute";e(y).css("opacity",n.overlayOpacity).fadeIn(n.overlayFadeDuration);WCF.System.DisableScrolling.disable();L();A(1);r=i;n.loop=n.loop&&r.length>1;return D(s)};e.fn.slimbox=function(t,n,r){n=n||function(e){return[e.href,e.title]};r=r||function(){return true};var i=this;return i.unbind("click").click(function(){var s=this,o=0,u,a=0,f;u=e.grep(i,function(e,t){return r.call(s,e,t)});for(f=u.length;a<f;++a){if(u[a]==s)o=a;u[a]=n(u[a],a)}return e.slimbox(u,o,t)})}})(jQuery)
\ No newline at end of file
+(function(e){function L(){var n=t.scrollLeft(),r=t.width();e([b,T]).css("left",n+r/2);if(a)e(y).css({left:n,top:t.scrollTop(),width:r,height:t.height()})}function A(n){if(n){e("object").add(h?"select":"embed").each(function(e,t){p[e]=[t,t.style.visibility];t.style.visibility="hidden"})}else{e.each(p,function(e,t){t[0].style.visibility=t[1]});p=[]}var r=n?"bind":"unbind";t[r]("scroll resize",L);e(document)[r]("keydown",O)}function O(t){var r=t.which,i=e.inArray;return i(r,n.closeKeys)>=0?j():i(r,n.nextKeys)>=0?_():i(r,n.previousKeys)>=0?M():null}function M(){return D(o)}function _(){return D(u)}function D(t){if(t>=0){i=t;s=r[i][0];o=(i||(n.loop?r.length:0))-1;u=(i+1)%r.length||(n.loop?0:-1);B();e('<span class="icon icon48 icon-spinner" />').appendTo(b);v=new Image;v.onload=P;v.src=s}return false}function P(){e(w).css({backgroundImage:"url("+s+")",visibility:"hidden",display:""});e(E).width(v.width);e([E,S,x]).height(v.height);e(C).html(r[i][1]||"");e(k).html((r.length>1&&n.counterText||"").replace(/{x}/,i+1).replace(/{y}/,r.length));if(o>=0)m.src=r[o][0];if(u>=0)g.src=r[u][0];l=w.offsetWidth;c=w.offsetHeight;var t=e(window).getDimensions();var a=Math.round(t.height*.8);var h=Math.round(t.width*.8);if(c>a||l>h){var p=a/c;var d=h/l;if(p<d){l=Math.round(l*(a/c));c=a}else{c=Math.round(c*(h/l));l=h}e(E).width(l-20);e([E,S,x]).height(c-20)}var y=Math.max(0,f-c/2);if(b.offsetHeight!=c){e(b).animate({height:c,top:y},n.resizeDuration,n.resizeEasing)}if(b.offsetWidth!=l){e(b).animate({width:l,marginLeft:-l/2},n.resizeDuration,n.resizeEasing)}e(b).queue(function(){e(T).css({width:l,top:y+c,marginLeft:-l/2,visibility:"hidden",display:""});e(b).children(".icon-spinner").remove();e(w).css({display:"none",visibility:"",opacity:""}).fadeIn(n.imageFadeDuration,H)})}function H(){if(o>=0)e(S).show();if(u>=0)e(x).show();e(N).css("marginTop",-N.offsetHeight).animate({marginTop:0},n.captionAnimationDuration);T.style.visibility=""}function B(){v.onload=null;v.src=m.src=g.src=s;e([b,w,N]).stop(true);e([S,x,w,T]).hide()}function j(){if(i>=0){B();i=o=u=-1;e(b).hide();e(y).stop().fadeOut(n.overlayFadeDuration,A);WCF.System.DisableScrolling.enable()}return false}var t=e(window),n,r,i=-1,s,o,u,a,f,l,c,h=!window.XMLHttpRequest,p=[],d=document.documentElement,v={},m=new Image,g=new Image,y,b,w,E,S,x,T,N,C,k;e(function(){e("body").append(e([y=e('<div id="lbOverlay" />').click(j)[0],b=e('<div id="lbCenter" />')[0],T=e('<div id="lbBottomContainer" />')[0]]).css("display","none"));w=e('<div id="lbImage" />').appendTo(b).append(E=e('<div style="position: relative;" />').append([S=e('<a id="lbPrevLink" href="#" />').click(M)[0],x=e('<a id="lbNextLink" href="#" />').click(_)[0]])[0])[0];N=e('<div id="lbBottom" />').appendTo(T).append([e('<a id="lbCloseLink" href="#" />').click(j)[0],C=e('<div id="lbCaption" />')[0],k=e('<div id="lbNumber" />')[0],e('<div style="clear: both;" />')[0]])[0]});e.slimbox=function(s,o,u){n=e.extend({loop:false,overlayOpacity:.8,overlayFadeDuration:400,resizeDuration:400,resizeEasing:"swing",initialWidth:250,initialHeight:250,imageFadeDuration:400,captionAnimationDuration:400,counterText:"Image {x} of {y}",closeKeys:[27,88,67],previousKeys:[37,80],nextKeys:[39,78]},u);if(typeof s=="string"){s=[[s,o]];o=0}if(i==-1){WCF.System.DisableScrolling.disable();f=t.scrollTop()+t.height()/2;l=n.initialWidth;c=n.initialHeight;e(b).css({top:Math.max(0,f-c/2),width:l,height:c,marginLeft:-l/2}).show();a=h||y.currentStyle&&y.currentStyle.position!="fixed";if(a)y.style.position="absolute";e(y).css("opacity",n.overlayOpacity).fadeIn(n.overlayFadeDuration);L();A(1)}r=s;n.loop=n.loop&&r.length>1;return D(o)};e.fn.slimbox=function(t,n,r){n=n||function(e){return[e.href,e.title]};r=r||function(){return true};var i=this;return i.unbind("click").click(function(){var s=this,o=0,u,a=0,f;u=e.grep(i,function(e,t){return r.call(s,e,t)});for(f=u.length;a<f;++a){if(u[a]==s)o=a;u[a]=n(u[a],a)}return e.slimbox(u,o,t)})}})(jQuery)
\ No newline at end of file
index fedf10c8c3eebb4befcc004908bed66c163f22eb..74e6daf5d01c7ded30175cfd23a6bff39a31558b 100644 (file)
@@ -74,9 +74,18 @@ WCF.Attachment.Upload = WCF.Upload.extend({
                if (this._wysiwygContainerID) {
                        WCF.System.Event.addListener('com.woltlab.wcf.messageOptionsInline', 'submit_' + this._wysiwygContainerID, $.proxy(this._submitInline, this));
                        WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'reset', $.proxy(this._reset, this));
+                       WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'upload_' + this._wysiwygContainerID, $.proxy(this._editorUpload, this));
                }
        },
        
+       _editorUpload: function(data) {
+               // show tab
+               var $tabMenuContainer = this._fileListSelector.closest('.messageTabMenu')
+               $tabMenuContainer.messageTabMenu('showTab', 'attachments', true);
+               
+               this._upload(undefined, [ data.file ]);
+       },
+       
        /**
         * Adds parameters for the inline editor.
         * 
@@ -145,9 +154,9 @@ WCF.Attachment.Upload = WCF.Upload.extend({
        /**
         * @see WCF.Upload._upload()
         */
-       _upload: function() {
+       _upload: function(event, files) {
                if (this._validateLimit()) {
-                       this._super();
+                       this._super(event, files);
                }
                
                if (this._fileUpload) {
index 75c657c0831546dffb63e24c65e9def319ddf2ac..248a00562a86fb42be73c5033cd260619a57bc68 100755 (executable)
@@ -8396,9 +8396,12 @@ WCF.Upload = Class.extend({
        
        /**
         * Callback for file uploads.
+        * 
+        * @param       object          event
+        * @param       array<File>     files
         */
-       _upload: function() {
-               var $files = this._fileUpload.prop('files');
+       _upload: function(event, files) {
+               var $files = files || this._fileUpload.prop('files');
                if ($files.length) {
                        var $fd = new FormData();
                        var $uploadID = this._createUploadMatrix($files);
index 13987b62dd4c965a9265054c9db49297dd16c494..74dc887af4d8e4c3d01b8d68de1d0cb4ed748475 100644 (file)
                        width: 20px;
                }
        }
-}
\ No newline at end of file
+}
+
+.redactorDropArea {
+       background-color: rgba(255, 255, 204, 1);
+       border: 5px dashed rgba(255, 204, 0);
+       box-sizing: border-box;
+       font-size: 1.4rem;
+       position: absolute;
+       text-align: center;
+       vertical-align: middle;
+       z-index: 1000;
+       
+       &.active {
+               background-color: #CEF6CE;
+               border-color: #04B404;
+       }
+}
index 117262b889aabe056983ea59db57815c09bc9ace..477dd37d9bcc88269de6874a1cf33efbe7bc8660 100644 (file)
@@ -1662,6 +1662,8 @@ Erlaubte Dateiendungen: {', '|implode:$attachmentHandler->getFormattedAllowedExt
                <item name="wcf.attachment.downloads"><![CDATA[Downloads]]></item>
                <item name="wcf.attachment.lastDownloadTime"><![CDATA[Letzter Download]]></item>
                <item name="wcf.attachment.fileType"><![CDATA[Dateityp]]></item>
+               <item name="wcf.attachment.dragAndDrop.dropHere"><![CDATA[Hierhin ziehen und loslassen um Dateien hochzuladen]]></item>
+               <item name="wcf.attachment.dragAndDrop.dropNow"><![CDATA[Jetzt loslassen um Dateien hochzuladen]]></item>
        </category>
        
        <category name="wcf.bbcode">
index a8d5dfbe73bd941847a94ea75ea4fbce65ef308d..2182943b07c716417a2bd2825f3dff5ac799a6a5 100644 (file)
@@ -1629,6 +1629,8 @@ Allowed extensions: {', '|implode:$attachmentHandler->getFormattedAllowedExtensi
                <item name="wcf.attachment.downloads"><![CDATA[Downloads]]></item>
                <item name="wcf.attachment.lastDownloadTime"><![CDATA[Last Download]]></item>
                <item name="wcf.attachment.fileType"><![CDATA[Type]]></item>
+               <item name="wcf.attachment.dragAndDrop.dropHere"><![CDATA[Drag and Drop here to upload]]></item>
+               <item name="wcf.attachment.dragAndDrop.dropNow"><![CDATA[Drop now to upload]]></item>
        </category>
        
        <category name="wcf.bbcode">