Media Manager: implemented insert link, insert image, and crop and insert features. Minor fixes in October core and third-party JS plugins.

This commit is contained in:
alekseybobkov 2015-04-11 18:55:02 -07:00
parent 790b464679
commit c1740c479c
34 changed files with 3250 additions and 39 deletions

View File

@ -143,6 +143,16 @@ body.slim-container .form-buttons{padding:0 20px 20px}
.select2-drop .select2-results .select2-no-results{padding:7px !important}
.select2-drop .select2-results .select2-highlighted{background:#4da7e8}
.select2-drop .select2-results > li > div{padding:5px 7px 5px}
[data-control=toolbar] .form-control{display:inline-block;margin-right:15px}
[data-control=toolbar] .form-control.width-50{width:50px}
[data-control=toolbar] .form-control.width-100{width:100px}
[data-control=toolbar] .form-control.width-150{width:150px}
[data-control=toolbar] input[type=text].form-control,[data-control=toolbar] label{position:relative;top:5px}
[data-control=toolbar] label{margin-right:7px}
[data-control=toolbar] label.standalone{margin-right:15px}
[data-control=toolbar] .select2-container{display:inline-block;width:auto;height:36px}
[data-control=toolbar] .select2-container .select2-choice{height:34px;line-height:34px}
[data-control=toolbar] select.form-control.custom-select{display:none}
table.table.data{font-size:12px}
table.table.data thead{background:#ffffff}
table.table.data thead td,table.table.data thead th{border-width:1px;border-top:1px solid #e2e2e2 !important;border-color:#e2e2e2;padding:0;font-weight:normal}
@ -566,6 +576,7 @@ html.cssanimations .cursor-loading-indicator.hide{display:none}
.control-toolbar .toolbar-item.layout-cell:last-child{padding-right:0}
.control-toolbar .toolbar-item .btn,.control-toolbar .toolbar-item .btn-group,.control-toolbar .toolbar-item .dropdown{white-space:nowrap;float:none;display:inline-block;margin-right:10px}
.control-toolbar .toolbar-item .btn:last-child,.control-toolbar .toolbar-item .btn-group:last-child,.control-toolbar .toolbar-item .dropdown:last-child{margin-right:0}
.control-toolbar .toolbar-item .btn.standalone,.control-toolbar .toolbar-item .btn-group.standalone,.control-toolbar .toolbar-item .dropdown.standalone{margin-right:15px}
.control-toolbar .toolbar-item .btn-group > .btn,.control-toolbar .toolbar-item .btn-group > .dropdown{margin-right:0;display:inline-block;float:none}
.control-toolbar .toolbar-item .btn-group .dropdown > .btn{margin-right:0;border-bottom-right-radius:0;border-top-right-radius:0}
.control-toolbar .toolbar-item .btn-group .dropdown.last > .btn{border-bottom-right-radius:2px !important;border-top-right-radius:2px !important}
@ -1179,4 +1190,4 @@ div.control-scrollpad > .scrollpad-scrollbar:hover{opacity:0.7;-webkit-transitio
div.control-scrollpad > .scrollpad-scrollbar[data-visible]{opacity:0.7}
div.control-scrollpad > .scrollpad-scrollbar[data-hidden]{display:none}
div.control-scrollpad[data-direction=horizontal] > .scrollpad-scrollbar{top:auto;left:0;width:auto;height:11px}
div.control-scrollpad[data-direction=horizontal] > .scrollpad-scrollbar .drag-handle{right:auto;top:2px;height:7px;min-height:0;min-width:10px;width:auto}
div.control-scrollpad[data-direction=horizontal] > .scrollpad-scrollbar .drag-handle{right:auto;top:2px;height:7px;min-height:0;min-width:10px;width:auto}

View File

@ -1,4 +1,4 @@
@import "../vendor/select2/select2.css"; html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
@import "../vendor/select2/select2.css";@import "../vendor/jcrop/css/jquery.Jcrop.min.css"; html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
body{margin:0}
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}
audio,canvas,progress,video{display:inline-block;vertical-align:baseline}

View File

@ -299,7 +299,9 @@ $(this.options.scrollMarkerContainer).append($('<span class="before scroll-marke
$el.mousewheel(function(event){if(!self.options.allowScroll)
return;var offset=self.options.vertical?((event.deltaFactor*event.deltaY)*-1):(event.deltaFactor*event.deltaX)
return!scrollWheel(offset)})
$el.on('mousedown.dragScroll',function(event){startDrag(event)
$el.on('mousedown.dragScroll',function(event){if(event.target&&event.target.tagName==='INPUT')
return
startDrag(event)
return false})
$el.on('touchstart.dragScroll',function(event){var touchEvent=event.originalEvent;if(touchEvent.touches.length==1){startDrag(touchEvent.touches[0])
event.stopPropagation()}})
@ -397,6 +399,7 @@ $(window).off('resize',this.proxy(this.fixScrollClasses))
this.el.off('.dragScroll')
this.scrollClassContainer=null
this.el.removeData('oc.dragScroll')
this.el=null
BaseProto.dispose.call(this)}
var old=$.fn.dragScroll
$.fn.dragScroll=function(option){var args=arguments;return this.each(function(){var $this=$(this)
@ -875,7 +878,7 @@ this.$modal.on('shown.bs.modal',function(){self.triggerEvent('shown.oc.popup')})
this.$modal.on('close.oc.popup',function(){self.hide()
return false})
this.init()}
Popup.DEFAULTS={ajax:null,handler:null,keyboard:true,extraData:{},content:null,size:null,adaptiveHeight:false}
Popup.DEFAULTS={ajax:null,handler:null,keyboard:true,extraData:{},content:null,size:null,adaptiveHeight:false,zIndex:null}
Popup.prototype.init=function(){var self=this
if(self.isOpen)
return
@ -897,6 +900,8 @@ if(this.options.size)
modalDialog.addClass('size-'+this.options.size)
if(this.options.adaptiveHeight)
modalDialog.addClass('adaptive-height')
if(this.options.zIndex!==null)
modal.css('z-index',this.options.zIndex+20)
return modal.append(modalDialog.append(modalContent))}
Popup.prototype.setContent=function(contents){this.$content.html(contents)
this.setLoading(false)
@ -904,7 +909,10 @@ this.show()
this.firstDiv=this.$content.find('>div:first')
if(this.firstDiv.length>0)
this.firstDiv.data('oc.popup',this)}
Popup.prototype.setBackdrop=function(val){if(val&&!this.$backdrop){this.$backdrop=$('<div class="popup-backdrop fade" />').appendTo(document.body)
Popup.prototype.setBackdrop=function(val){if(val&&!this.$backdrop){this.$backdrop=$('<div class="popup-backdrop fade" />')
if(this.options.zIndex!==null)
this.$backdrop.css('z-index',this.options.zIndex)
this.$backdrop.appendTo(document.body)
this.$backdrop.addClass('in')
this.$backdrop.append($('<div class="modal-content popup-loading-indicator" />'))}
else if(!val&&this.$backdrop){this.$backdrop.remove()

View File

@ -71,6 +71,9 @@
})
$el.on('mousedown.dragScroll', function(event){
if (event.target && event.target.tagName === 'INPUT')
return // Don't prevent clicking inputs in the toolbar
startDrag(event)
return false
})
@ -338,6 +341,8 @@
this.scrollClassContainer = null
this.el.removeData('oc.dragScroll')
this.el = null
BaseProto.dispose.call(this)
}

View File

@ -84,7 +84,8 @@
extraData: {},
content: null,
size: null,
adaptiveHeight: false
adaptiveHeight: false,
zIndex: null
}
Popup.prototype.init = function(){
@ -175,6 +176,9 @@
if (this.options.adaptiveHeight)
modalDialog.addClass('adaptive-height')
if (this.options.zIndex !== null)
modal.css('z-index', this.options.zIndex + 20)
return modal.append(modalDialog.append(modalContent))
}
@ -193,7 +197,11 @@
Popup.prototype.setBackdrop = function(val) {
if (val && !this.$backdrop) {
this.$backdrop = $('<div class="popup-backdrop fade" />')
.appendTo(document.body)
if (this.options.zIndex !== null)
this.$backdrop.css('z-index', this.options.zIndex)
this.$backdrop.appendTo(document.body)
this.$backdrop.addClass('in')
this.$backdrop.append($('<div class="modal-content popup-loading-indicator" />'))

View File

@ -1869,4 +1869,244 @@ var carry=0;var v=Number.NaN;var prev;do{prev=v;v=d.getTime();ticks.push(v);if(u
tickSize*(unit=="quarter"?3:1));}}else if(unit=="year"){d.setFullYear(d.getFullYear()+tickSize);}else{d.setTime(v+step);}}while(v<axis.max&&v!=prev);return ticks;};axis.tickFormatter=function(v,axis){var d=dateGenerator(v,axis.options);if(opts.timeformat!=null){return formatDate(d,opts.timeformat,opts.monthNames,opts.dayNames);}
var useQuarters=(axis.options.tickSize&&axis.options.tickSize[1]=="quarter")||(axis.options.minTickSize&&axis.options.minTickSize[1]=="quarter");var t=axis.tickSize[0]*timeUnitSize[axis.tickSize[1]];var span=axis.max-axis.min;var suffix=(opts.twelveHourClock)?" %p":"";var hourCode=(opts.twelveHourClock)?"%I":"%H";var fmt;if(t<timeUnitSize.minute){fmt=hourCode+":%M:%S"+suffix;}else if(t<timeUnitSize.day){if(span<2*timeUnitSize.day){fmt=hourCode+":%M"+suffix;}else{fmt="%b %d "+hourCode+":%M"+suffix;}}else if(t<timeUnitSize.month){fmt="%b %d";}else if((useQuarters&&t<timeUnitSize.quarter)||(!useQuarters&&t<timeUnitSize.year)){if(span<timeUnitSize.year){fmt="%b";}else{fmt="%b %Y";}}else if(useQuarters&&t<timeUnitSize.year){if(span<timeUnitSize.year){fmt="Q%q";}else{fmt="Q%q %Y";}}else{fmt="%Y";}
var rt=formatDate(d,fmt,opts.monthNames,opts.dayNames);return rt;};}});});}
$.plot.plugins.push({init:init,options:options,name:'time',version:'1.0'});$.plot.formatDate=formatDate;})(jQuery);
$.plot.plugins.push({init:init,options:options,name:'time',version:'1.0'});$.plot.formatDate=formatDate;})(jQuery);(function($){$.Jcrop=function(obj,opt){var options=$.extend({},$.Jcrop.defaults),docOffset,_ua=navigator.userAgent.toLowerCase(),is_msie=/msie/.test(_ua),ie6mode=/msie [1-6]\./.test(_ua);function px(n){return Math.round(n)+'px';}
function cssClass(cl){return options.baseClass+'-'+cl;}
function supportsColorFade(){return $.fx.step.hasOwnProperty('backgroundColor');}
function getPos(obj)
{var pos=$(obj).offset();return[pos.left,pos.top];}
function mouseAbs(e)
{return[(e.pageX-docOffset[0]),(e.pageY-docOffset[1])];}
function setOptions(opt)
{if(typeof(opt)!=='object')opt={};options=$.extend(options,opt);$.each(['onChange','onSelect','onRelease','onDblClick'],function(i,e){if(typeof(options[e])!=='function')options[e]=function(){};});}
function startDragMode(mode,pos,touch)
{docOffset=getPos($img);Tracker.setCursor(mode==='move'?mode:mode+'-resize');if(mode==='move'){return Tracker.activateHandlers(createMover(pos),doneSelect,touch);}
var fc=Coords.getFixed();var opp=oppLockCorner(mode);var opc=Coords.getCorner(oppLockCorner(opp));Coords.setPressed(Coords.getCorner(opp));Coords.setCurrent(opc);Tracker.activateHandlers(dragmodeHandler(mode,fc),doneSelect,touch);}
function dragmodeHandler(mode,f)
{return function(pos){if(!options.aspectRatio){switch(mode){case'e':pos[1]=f.y2;break;case'w':pos[1]=f.y2;break;case'n':pos[0]=f.x2;break;case's':pos[0]=f.x2;break;}}else{switch(mode){case'e':pos[1]=f.y+1;break;case'w':pos[1]=f.y+1;break;case'n':pos[0]=f.x+1;break;case's':pos[0]=f.x+1;break;}}
Coords.setCurrent(pos);Selection.update();};}
function createMover(pos)
{var lloc=pos;KeyManager.watchKeys();return function(pos){Coords.moveOffset([pos[0]-lloc[0],pos[1]-lloc[1]]);lloc=pos;Selection.update();};}
function oppLockCorner(ord)
{switch(ord){case'n':return'sw';case's':return'nw';case'e':return'nw';case'w':return'ne';case'ne':return'sw';case'nw':return'se';case'se':return'nw';case'sw':return'ne';}}
function createDragger(ord)
{return function(e){if(options.disabled){return false;}
if((ord==='move')&&!options.allowMove){return false;}
docOffset=getPos($img);btndown=true;startDragMode(ord,mouseAbs(e));e.stopPropagation();e.preventDefault();return false;};}
function presize($obj,w,h)
{var nw=$obj.width(),nh=$obj.height();if((nw>w)&&w>0){nw=w;nh=(w/$obj.width())*$obj.height();}
if((nh>h)&&h>0){nh=h;nw=(h/$obj.height())*$obj.width();}
xscale=$obj.width()/nw;yscale=$obj.height()/nh;$obj.width(nw).height(nh);}
function unscale(c)
{return{x:c.x*xscale,y:c.y*yscale,x2:c.x2*xscale,y2:c.y2*yscale,w:c.w*xscale,h:c.h*yscale};}
function doneSelect(pos)
{var c=Coords.getFixed();if((c.w>options.minSelect[0])&&(c.h>options.minSelect[1])){Selection.enableHandles();Selection.done();}else{Selection.release();}
Tracker.setCursor(options.allowSelect?'crosshair':'default');}
function newSelection(e)
{if(options.disabled){return false;}
if(!options.allowSelect){return false;}
btndown=true;docOffset=getPos($img);Selection.disableHandles();Tracker.setCursor('crosshair');var pos=mouseAbs(e);Coords.setPressed(pos);Selection.update();Tracker.activateHandlers(selectDrag,doneSelect,e.type.substring(0,5)==='touch');KeyManager.watchKeys();e.stopPropagation();e.preventDefault();return false;}
function selectDrag(pos)
{Coords.setCurrent(pos);Selection.update();}
function newTracker()
{var trk=$('<div></div>').addClass(cssClass('tracker'));if(is_msie){trk.css({opacity:0,backgroundColor:'white'});}
return trk;}
if(typeof(obj)!=='object'){obj=$(obj)[0];}
if(typeof(opt)!=='object'){opt={};}
setOptions(opt);var img_css={border:'none',visibility:'visible',margin:0,padding:0,position:'absolute',top:0,left:0};var $origimg=$(obj),img_mode=true;if(obj.tagName=='IMG'){if($origimg[0].width!=0&&$origimg[0].height!=0){$origimg.width($origimg[0].width);$origimg.height($origimg[0].height);}else{var tempImage=new Image();tempImage.src=$origimg[0].src;$origimg.width(tempImage.width);$origimg.height(tempImage.height);}
var $img=$origimg.clone().removeAttr('id').css(img_css).show();$img.width($origimg.width());$img.height($origimg.height());$origimg.after($img).hide();}else{$img=$origimg.css(img_css).show();img_mode=false;if(options.shade===null){options.shade=true;}}
presize($img,options.boxWidth,options.boxHeight);var boundx=$img.width(),boundy=$img.height(),$div=$('<div />').width(boundx).height(boundy).addClass(cssClass('holder')).css({position:'relative',backgroundColor:options.bgColor}).insertAfter($origimg).append($img);if(options.addClass){$div.addClass(options.addClass);}
var $img2=$('<div />'),$img_holder=$('<div />').width('100%').height('100%').css({zIndex:310,position:'absolute',overflow:'hidden'}),$hdl_holder=$('<div />').width('100%').height('100%').css('zIndex',320),$sel=$('<div />').css({position:'absolute',zIndex:600}).dblclick(function(){var c=Coords.getFixed();options.onDblClick.call(api,c);}).insertBefore($img).append($img_holder,$hdl_holder);if(img_mode){$img2=$('<img />').attr('src',$img.attr('src')).css(img_css).width(boundx).height(boundy),$img_holder.append($img2);}
if(ie6mode){$sel.css({overflowY:'hidden'});}
var bound=options.boundary;var $trk=newTracker().width(boundx+(bound*2)).height(boundy+(bound*2)).css({position:'absolute',top:px(-bound),left:px(-bound),zIndex:290}).mousedown(newSelection);var bgcolor=options.bgColor,bgopacity=options.bgOpacity,xlimit,ylimit,xmin,ymin,xscale,yscale,enabled=true,btndown,animating,shift_down;docOffset=getPos($img);var Touch=(function(){function hasTouchSupport(){var support={},events=['touchstart','touchmove','touchend'],el=document.createElement('div'),i;try{for(i=0;i<events.length;i++){var eventName=events[i];eventName='on'+eventName;var isSupported=(eventName in el);if(!isSupported){el.setAttribute(eventName,'return;');isSupported=typeof el[eventName]=='function';}
support[events[i]]=isSupported;}
return support.touchstart&&support.touchend&&support.touchmove;}
catch(err){return false;}}
function detectSupport(){if((options.touchSupport===true)||(options.touchSupport===false))return options.touchSupport;else return hasTouchSupport();}
return{createDragger:function(ord){return function(e){if(options.disabled){return false;}
if((ord==='move')&&!options.allowMove){return false;}
docOffset=getPos($img);btndown=true;startDragMode(ord,mouseAbs(Touch.cfilter(e)),true);e.stopPropagation();e.preventDefault();return false;};},newSelection:function(e){return newSelection(Touch.cfilter(e));},cfilter:function(e){e.pageX=e.originalEvent.changedTouches[0].pageX;e.pageY=e.originalEvent.changedTouches[0].pageY;return e;},fixTouchSupport:function(e){if($(e.currentTarget).hasClass('jcrop-tracker'))e.stopPropagation();},isSupported:hasTouchSupport,support:detectSupport()};}());var Coords=(function(){var x1=0,y1=0,x2=0,y2=0,ox,oy;function setPressed(pos)
{pos=rebound(pos);x2=x1=pos[0];y2=y1=pos[1];}
function setCurrent(pos)
{pos=rebound(pos);ox=pos[0]-x2;oy=pos[1]-y2;x2=pos[0];y2=pos[1];}
function getOffset()
{return[ox,oy];}
function moveOffset(offset)
{var ox=offset[0],oy=offset[1];if(0>x1+ox){ox-=ox+x1;}
if(0>y1+oy){oy-=oy+y1;}
if(boundy<y2+oy){oy+=boundy-(y2+oy);}
if(boundx<x2+ox){ox+=boundx-(x2+ox);}
x1+=ox;x2+=ox;y1+=oy;y2+=oy;}
function getCorner(ord)
{var c=getFixed();switch(ord){case'ne':return[c.x2,c.y];case'nw':return[c.x,c.y];case'se':return[c.x2,c.y2];case'sw':return[c.x,c.y2];}}
function getFixed()
{if(!options.aspectRatio){return getRect();}
var aspect=options.aspectRatio,min_x=options.minSize[0]/xscale,max_x=options.maxSize[0]/xscale,max_y=options.maxSize[1]/yscale,rw=x2-x1,rh=y2-y1,rwa=Math.abs(rw),rha=Math.abs(rh),real_ratio=rwa/rha,xx,yy,w,h;if(max_x===0){max_x=boundx*10;}
if(max_y===0){max_y=boundy*10;}
if(real_ratio<aspect){yy=y2;w=rha*aspect;xx=rw<0?x1-w:w+x1;if(xx<0){xx=0;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}else if(xx>boundx){xx=boundx;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}}else{xx=x2;h=rwa/aspect;yy=rh<0?y1-h:y1+h;if(yy<0){yy=0;w=Math.abs((yy-y1)*aspect);xx=rw<0?x1-w:w+x1;}else if(yy>boundy){yy=boundy;w=Math.abs(yy-y1)*aspect;xx=rw<0?x1-w:w+x1;}}
if(xx>x1){if(xx-x1<min_x){xx=x1+min_x;}else if(xx-x1>max_x){xx=x1+max_x;}
if(yy>y1){yy=y1+(xx-x1)/aspect;}else{yy=y1-(xx-x1)/aspect;}}else if(xx<x1){if(x1-xx<min_x){xx=x1-min_x;}else if(x1-xx>max_x){xx=x1-max_x;}
if(yy>y1){yy=y1+(x1-xx)/aspect;}else{yy=y1-(x1-xx)/aspect;}}
if(xx<0){x1-=xx;xx=0;}else if(xx>boundx){x1-=xx-boundx;xx=boundx;}
if(yy<0){y1-=yy;yy=0;}else if(yy>boundy){y1-=yy-boundy;yy=boundy;}
return makeObj(flipCoords(x1,y1,xx,yy));}
function rebound(p)
{if(p[0]<0)p[0]=0;if(p[1]<0)p[1]=0;if(p[0]>boundx)p[0]=boundx;if(p[1]>boundy)p[1]=boundy;return[Math.round(p[0]),Math.round(p[1])];}
function flipCoords(x1,y1,x2,y2)
{var xa=x1,xb=x2,ya=y1,yb=y2;if(x2<x1){xa=x2;xb=x1;}
if(y2<y1){ya=y2;yb=y1;}
return[xa,ya,xb,yb];}
function getRect()
{var xsize=x2-x1,ysize=y2-y1,delta;if(xlimit&&(Math.abs(xsize)>xlimit)){x2=(xsize>0)?(x1+xlimit):(x1-xlimit);}
if(ylimit&&(Math.abs(ysize)>ylimit)){y2=(ysize>0)?(y1+ylimit):(y1-ylimit);}
if(ymin/yscale&&(Math.abs(ysize)<ymin/yscale)){y2=(ysize>0)?(y1+ymin/yscale):(y1-ymin/yscale);}
if(xmin/xscale&&(Math.abs(xsize)<xmin/xscale)){x2=(xsize>0)?(x1+xmin/xscale):(x1-xmin/xscale);}
if(x1<0){x2-=x1;x1-=x1;}
if(y1<0){y2-=y1;y1-=y1;}
if(x2<0){x1-=x2;x2-=x2;}
if(y2<0){y1-=y2;y2-=y2;}
if(x2>boundx){delta=x2-boundx;x1-=delta;x2-=delta;}
if(y2>boundy){delta=y2-boundy;y1-=delta;y2-=delta;}
if(x1>boundx){delta=x1-boundy;y2-=delta;y1-=delta;}
if(y1>boundy){delta=y1-boundy;y2-=delta;y1-=delta;}
return makeObj(flipCoords(x1,y1,x2,y2));}
function makeObj(a)
{return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]};}
return{flipCoords:flipCoords,setPressed:setPressed,setCurrent:setCurrent,getOffset:getOffset,moveOffset:moveOffset,getCorner:getCorner,getFixed:getFixed};}());var Shade=(function(){var enabled=false,holder=$('<div />').css({position:'absolute',zIndex:240,opacity:0}),shades={top:createShade(),left:createShade().height(boundy),right:createShade().height(boundy),bottom:createShade()};function resizeShades(w,h){shades.left.css({height:px(h)});shades.right.css({height:px(h)});}
function updateAuto()
{return updateShade(Coords.getFixed());}
function updateShade(c)
{shades.top.css({left:px(c.x),width:px(c.w),height:px(c.y)});shades.bottom.css({top:px(c.y2),left:px(c.x),width:px(c.w),height:px(boundy-c.y2)});shades.right.css({left:px(c.x2),width:px(boundx-c.x2)});shades.left.css({width:px(c.x)});}
function createShade(){return $('<div />').css({position:'absolute',backgroundColor:options.shadeColor||options.bgColor}).appendTo(holder);}
function enableShade(){if(!enabled){enabled=true;holder.insertBefore($img);updateAuto();Selection.setBgOpacity(1,0,1);$img2.hide();setBgColor(options.shadeColor||options.bgColor,1);if(Selection.isAwake())
{setOpacity(options.bgOpacity,1);}
else setOpacity(1,1);}}
function setBgColor(color,now){colorChangeMacro(getShades(),color,now);}
function disableShade(){if(enabled){holder.remove();$img2.show();enabled=false;if(Selection.isAwake()){Selection.setBgOpacity(options.bgOpacity,1,1);}else{Selection.setBgOpacity(1,1,1);Selection.disableHandles();}
colorChangeMacro($div,0,1);}}
function setOpacity(opacity,now){if(enabled){if(options.bgFade&&!now){holder.animate({opacity:1-opacity},{queue:false,duration:options.fadeTime});}
else holder.css({opacity:1-opacity});}}
function refreshAll(){options.shade?enableShade():disableShade();if(Selection.isAwake())setOpacity(options.bgOpacity);}
function getShades(){return holder.children();}
return{update:updateAuto,updateRaw:updateShade,getShades:getShades,setBgColor:setBgColor,enable:enableShade,disable:disableShade,resize:resizeShades,refresh:refreshAll,opacity:setOpacity};}());var Selection=(function(){var awake,hdep=370,borders={},handle={},dragbar={},seehandles=false;function insertBorder(type)
{var jq=$('<div />').css({position:'absolute',opacity:options.borderOpacity}).addClass(cssClass(type));$img_holder.append(jq);return jq;}
function dragDiv(ord,zi)
{var jq=$('<div />').mousedown(createDragger(ord)).css({cursor:ord+'-resize',position:'absolute',zIndex:zi}).addClass('ord-'+ord);if(Touch.support){jq.bind('touchstart.jcrop',Touch.createDragger(ord));}
$hdl_holder.append(jq);return jq;}
function insertHandle(ord)
{var hs=options.handleSize,div=dragDiv(ord,hdep++).css({opacity:options.handleOpacity}).addClass(cssClass('handle'));if(hs){div.width(hs).height(hs);}
return div;}
function insertDragbar(ord)
{return dragDiv(ord,hdep++).addClass('jcrop-dragbar');}
function createDragbars(li)
{var i;for(i=0;i<li.length;i++){dragbar[li[i]]=insertDragbar(li[i]);}}
function createBorders(li)
{var cl,i;for(i=0;i<li.length;i++){switch(li[i]){case'n':cl='hline';break;case's':cl='hline bottom';break;case'e':cl='vline right';break;case'w':cl='vline';break;}
borders[li[i]]=insertBorder(cl);}}
function createHandles(li)
{var i;for(i=0;i<li.length;i++){handle[li[i]]=insertHandle(li[i]);}}
function moveto(x,y)
{if(!options.shade){$img2.css({top:px(-y),left:px(-x)});}
$sel.css({top:px(y),left:px(x)});}
function resize(w,h)
{$sel.width(Math.round(w)).height(Math.round(h));}
function refresh()
{var c=Coords.getFixed();Coords.setPressed([c.x,c.y]);Coords.setCurrent([c.x2,c.y2]);updateVisible();}
function updateVisible(select)
{if(awake){return update(select);}}
function update(select)
{var c=Coords.getFixed();resize(c.w,c.h);moveto(c.x,c.y);if(options.shade)Shade.updateRaw(c);awake||show();if(select){options.onSelect.call(api,unscale(c));}else{options.onChange.call(api,unscale(c));}}
function setBgOpacity(opacity,force,now)
{if(!awake&&!force)return;if(options.bgFade&&!now){$img.animate({opacity:opacity},{queue:false,duration:options.fadeTime});}else{$img.css('opacity',opacity);}}
function show()
{$sel.show();if(options.shade)Shade.opacity(bgopacity);else setBgOpacity(bgopacity,true);awake=true;}
function release()
{disableHandles();$sel.hide();if(options.shade)Shade.opacity(1);else setBgOpacity(1);awake=false;options.onRelease.call(api);}
function showHandles()
{if(seehandles){$hdl_holder.show();}}
function enableHandles()
{seehandles=true;if(options.allowResize){$hdl_holder.show();return true;}}
function disableHandles()
{seehandles=false;$hdl_holder.hide();}
function animMode(v)
{if(v){animating=true;disableHandles();}else{animating=false;enableHandles();}}
function done()
{animMode(false);refresh();}
if(options.dragEdges&&$.isArray(options.createDragbars))
createDragbars(options.createDragbars);if($.isArray(options.createHandles))
createHandles(options.createHandles);if(options.drawBorders&&$.isArray(options.createBorders))
createBorders(options.createBorders);$(document).bind('touchstart.jcrop-ios',Touch.fixTouchSupport);var $track=newTracker().mousedown(createDragger('move')).css({cursor:'move',position:'absolute',zIndex:360});if(Touch.support){$track.bind('touchstart.jcrop',Touch.createDragger('move'));}
$img_holder.append($track);disableHandles();return{updateVisible:updateVisible,update:update,release:release,refresh:refresh,isAwake:function(){return awake;},setCursor:function(cursor){$track.css('cursor',cursor);},enableHandles:enableHandles,enableOnly:function(){seehandles=true;},showHandles:showHandles,disableHandles:disableHandles,animMode:animMode,setBgOpacity:setBgOpacity,done:done};}());var Tracker=(function(){var onMove=function(){},onDone=function(){},trackDoc=options.trackDocument;function toFront(touch)
{$trk.css({zIndex:450});if(touch)
$(document).bind('touchmove.jcrop',trackTouchMove).bind('touchend.jcrop',trackTouchEnd);else if(trackDoc)
$(document).bind('mousemove.jcrop',trackMove).bind('mouseup.jcrop',trackUp);}
function toBack()
{$trk.css({zIndex:290});$(document).unbind('.jcrop');}
function trackMove(e)
{onMove(mouseAbs(e));return false;}
function trackUp(e)
{e.preventDefault();e.stopPropagation();if(btndown){btndown=false;onDone(mouseAbs(e));if(Selection.isAwake()){options.onSelect.call(api,unscale(Coords.getFixed()));}
toBack();onMove=function(){};onDone=function(){};}
return false;}
function activateHandlers(move,done,touch)
{btndown=true;onMove=move;onDone=done;toFront(touch);return false;}
function trackTouchMove(e)
{onMove(mouseAbs(Touch.cfilter(e)));return false;}
function trackTouchEnd(e)
{return trackUp(Touch.cfilter(e));}
function setCursor(t)
{$trk.css('cursor',t);}
if(!trackDoc){$trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp);}
$img.before($trk);return{activateHandlers:activateHandlers,setCursor:setCursor};}());var KeyManager=(function(){var $keymgr=$('<input type="radio" />').css({position:'fixed',left:'-120px',width:'12px'}).addClass('jcrop-keymgr'),$keywrap=$('<div />').css({position:'absolute',overflow:'hidden'}).append($keymgr);function watchKeys()
{if(options.keySupport){$keymgr.show();$keymgr.focus();}}
function onBlur(e)
{$keymgr.hide();}
function doNudge(e,x,y)
{if(options.allowMove){Coords.moveOffset([x,y]);Selection.updateVisible(true);}
e.preventDefault();e.stopPropagation();}
function parseKey(e)
{if(e.ctrlKey||e.metaKey){return true;}
shift_down=e.shiftKey?true:false;var nudge=shift_down?10:1;switch(e.keyCode){case 37:doNudge(e,-nudge,0);break;case 39:doNudge(e,nudge,0);break;case 38:doNudge(e,0,-nudge);break;case 40:doNudge(e,0,nudge);break;case 27:if(options.allowSelect)Selection.release();break;case 9:return true;}
return false;}
if(options.keySupport){$keymgr.keydown(parseKey).blur(onBlur);if(ie6mode||!options.fixedSupport){$keymgr.css({position:'absolute',left:'-20px'});$keywrap.append($keymgr).insertBefore($img);}else{$keymgr.insertBefore($img);}}
return{watchKeys:watchKeys};}());function setClass(cname)
{$div.removeClass().addClass(cssClass('holder')).addClass(cname);}
function animateTo(a,callback)
{var x1=a[0]/xscale,y1=a[1]/yscale,x2=a[2]/xscale,y2=a[3]/yscale;if(animating){return;}
var animto=Coords.flipCoords(x1,y1,x2,y2),c=Coords.getFixed(),initcr=[c.x,c.y,c.x2,c.y2],animat=initcr,interv=options.animationDelay,ix1=animto[0]-initcr[0],iy1=animto[1]-initcr[1],ix2=animto[2]-initcr[2],iy2=animto[3]-initcr[3],pcent=0,velocity=options.swingSpeed;x1=animat[0];y1=animat[1];x2=animat[2];y2=animat[3];Selection.animMode(true);var anim_timer;function queueAnimator(){window.setTimeout(animator,interv);}
var animator=(function(){return function(){pcent+=(100-pcent)/velocity;animat[0]=Math.round(x1+((pcent/100)*ix1));animat[1]=Math.round(y1+((pcent/100)*iy1));animat[2]=Math.round(x2+((pcent/100)*ix2));animat[3]=Math.round(y2+((pcent/100)*iy2));if(pcent>=99.8){pcent=100;}
if(pcent<100){setSelectRaw(animat);queueAnimator();}else{Selection.done();Selection.animMode(false);if(typeof(callback)==='function'){callback.call(api);}}};}());queueAnimator();}
function setSelect(rect)
{setSelectRaw([rect[0]/xscale,rect[1]/yscale,rect[2]/xscale,rect[3]/yscale]);options.onSelect.call(api,unscale(Coords.getFixed()));Selection.enableHandles();}
function setSelectRaw(l)
{Coords.setPressed([l[0],l[1]]);Coords.setCurrent([l[2],l[3]]);Selection.update();}
function tellSelect()
{return unscale(Coords.getFixed());}
function tellScaled()
{return Coords.getFixed();}
function setOptionsNew(opt)
{setOptions(opt);interfaceUpdate();}
function disableCrop()
{options.disabled=true;Selection.disableHandles();Selection.setCursor('default');Tracker.setCursor('default');}
function enableCrop()
{options.disabled=false;interfaceUpdate();}
function cancelCrop()
{Selection.done();Tracker.activateHandlers(null,null);}
function destroy()
{$(document).unbind('touchstart.jcrop-ios',Touch.fixTouchSupport);$div.remove();$origimg.show();$origimg.css('visibility','visible');$(obj).removeData('Jcrop');}
function setImage(src,callback)
{Selection.release();disableCrop();var img=new Image();img.onload=function(){var iw=img.width;var ih=img.height;var bw=options.boxWidth;var bh=options.boxHeight;$img.width(iw).height(ih);$img.attr('src',src);$img2.attr('src',src);presize($img,bw,bh);boundx=$img.width();boundy=$img.height();$img2.width(boundx).height(boundy);$trk.width(boundx+(bound*2)).height(boundy+(bound*2));$div.width(boundx).height(boundy);Shade.resize(boundx,boundy);enableCrop();if(typeof(callback)==='function'){callback.call(api);}};img.src=src;}
function colorChangeMacro($obj,color,now){var mycolor=color||options.bgColor;if(options.bgFade&&supportsColorFade()&&options.fadeTime&&!now){$obj.animate({backgroundColor:mycolor},{queue:false,duration:options.fadeTime});}else{$obj.css('backgroundColor',mycolor);}}
function interfaceUpdate(alt)
{if(options.allowResize){if(alt){Selection.enableOnly();}else{Selection.enableHandles();}}else{Selection.disableHandles();}
Tracker.setCursor(options.allowSelect?'crosshair':'default');Selection.setCursor(options.allowMove?'move':'default');if(options.hasOwnProperty('trueSize')){xscale=options.trueSize[0]/boundx;yscale=options.trueSize[1]/boundy;}
if(options.hasOwnProperty('setSelect')){setSelect(options.setSelect);Selection.done();delete(options.setSelect);}
Shade.refresh();if(options.bgColor!=bgcolor){colorChangeMacro(options.shade?Shade.getShades():$div,options.shade?(options.shadeColor||options.bgColor):options.bgColor);bgcolor=options.bgColor;}
if(bgopacity!=options.bgOpacity){bgopacity=options.bgOpacity;if(options.shade)Shade.refresh();else Selection.setBgOpacity(bgopacity);}
xlimit=options.maxSize[0]||0;ylimit=options.maxSize[1]||0;xmin=options.minSize[0]||0;ymin=options.minSize[1]||0;if(options.hasOwnProperty('outerImage')){$img.attr('src',options.outerImage);delete(options.outerImage);}
Selection.refresh();}
if(Touch.support)$trk.bind('touchstart.jcrop',Touch.newSelection);$hdl_holder.hide();interfaceUpdate(true);var api={setImage:setImage,animateTo:animateTo,setSelect:setSelect,setOptions:setOptionsNew,tellSelect:tellSelect,tellScaled:tellScaled,setClass:setClass,disable:disableCrop,enable:enableCrop,cancel:cancelCrop,release:Selection.release,destroy:destroy,focus:KeyManager.watchKeys,getBounds:function(){return[boundx*xscale,boundy*yscale];},getWidgetSize:function(){return[boundx,boundy];},getScaleFactor:function(){return[xscale,yscale];},getOptions:function(){return options;},ui:{holder:$div,selection:$sel}};if(is_msie)$div.bind('selectstart',function(){return false;});$origimg.data('Jcrop',api);return api;};$.fn.Jcrop=function(options,callback)
{var api;this.each(function(){if($(this).data('Jcrop')){if(options==='api')return $(this).data('Jcrop');else $(this).data('Jcrop').setOptions(options);}
else{if(this.tagName=='IMG')
$.Jcrop.Loader(this,function(){$(this).css({display:'block',visibility:'hidden'});api=$.Jcrop(this,options);if($.isFunction(callback))callback.call(api);});else{$(this).css({display:'block',visibility:'hidden'});api=$.Jcrop(this,options);if($.isFunction(callback))callback.call(api);}}});return this;};$.Jcrop.Loader=function(imgobj,success,error){var $img=$(imgobj),img=$img[0];function completeCheck(){if(img.complete){$img.unbind('.jcloader');if($.isFunction(success))success.call(img);}
else window.setTimeout(completeCheck,50);}
$img.bind('load.jcloader',completeCheck).bind('error.jcloader',function(e){$img.unbind('.jcloader');if($.isFunction(error))error.call(img);});if(img.complete&&$.isFunction(success)){$img.unbind('.jcloader');success.call(img);}};$.Jcrop.defaults={allowSelect:true,allowMove:true,allowResize:true,trackDocument:true,baseClass:'jcrop',addClass:null,bgColor:'black',bgOpacity:0.6,bgFade:false,borderOpacity:0.4,handleOpacity:0.5,handleSize:null,aspectRatio:0,keySupport:true,createHandles:['n','s','e','w','nw','ne','se','sw'],createDragbars:['n','s','e','w'],createBorders:['n','s','e','w'],drawBorders:true,dragEdges:true,fixedSupport:true,touchSupport:null,shade:null,boxWidth:0,boxHeight:0,boundary:2,fadeTime:400,animationDelay:20,swingSpeed:3,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){},onDblClick:function(){},onRelease:function(){}};}(jQuery));

View File

@ -31,4 +31,6 @@
=require ../../vendor/flot/jquery.flot.resize.js
=require ../../vendor/flot/jquery.flot.time.js
*/
=require ../../vendor/jcrop/js/jquery.Jcrop.js
*/

View File

@ -760,4 +760,55 @@ body.slim-container {
padding: 5px 7px 5px;
}
}
}
//
// Controls inside toolbar
//
[data-control=toolbar] {
.form-control {
display: inline-block;
margin-right: 15px;
&.width-50 {
width: 50px;
}
&.width-100 {
width: 100px;
}
&.width-150 {
width: 150px;
}
}
input[type=text].form-control, label {
position: relative;
top: 5px;
}
label {
margin-right: 7px;
&.standalone {
margin-right: 15px;
}
}
.select2-container {
display: inline-block;
width: auto;
height: 36px;
.select2-choice {
height: 34px;
line-height: 34px;
}
}
select.form-control.custom-select {
display: none;
}
}

View File

@ -52,6 +52,10 @@
&:last-child {
margin-right: 0;
}
&.standalone {
margin-right: 15px;
}
}
.btn-group {

View File

@ -38,3 +38,4 @@
// Vendor
@import "../vendor/sweet-alert/sweet-alert.less";
@import "../vendor/select2/select2.css";
@import "../vendor/jcrop/css/jquery.Jcrop.min.css";

View File

@ -0,0 +1,22 @@
Copyright (c) 2011 Tapmodo Interactive LLC,
http://github.com/tapmodo/Jcrop
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1 @@
There's a hack in line 1090 in jquery.Jcrop.js. The hack prevents DOM element leakage through the event that is not unbound in the destroy() method. --ab Apr 08 2015

66
modules/backend/assets/vendor/jcrop/README.md vendored Executable file
View File

@ -0,0 +1,66 @@
Jcrop Image Cropping Plugin
===========================
Jcrop is the quick and easy way to add image cropping functionality to
your web application. It combines the ease-of-use of a typical jQuery
plugin with a powerful cross-platform DHTML cropping engine that is
faithful to familiar desktop graphics applications.
Cross-platform Compatibility
----------------------------
* Firefox 2+
* Safari 3+
* Opera 9.5+
* Google Chrome 0.2+
* Internet Explorer 6+
Feature Overview
----------------
* Attaches unobtrusively to any image
* Supports aspect ratio locking
* Supports minSize/maxSize setting
* Callbacks for selection done, or while moving
* Keyboard support for nudging selection
* API features to create interactivity, including animation
* Support for CSS styling
* Experimental touch-screen support (iOS, Android, etc)
Contributors
============
**Special thanks to the following contributors:**
* [Bruno Agutoli](mailto:brunotla1@gmail.com)
* dhorrigan
* Phil-B
* jaymecd
* all others who have committed their time and effort to help improve Jcrop
MIT License
===========
**Jcrop is free software under MIT License.**
#### Copyright (c) 2008-2012 Tapmodo Interactive LLC,<br />http://github.com/tapmodo/Jcrop
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

View File

@ -0,0 +1,29 @@
/* jquery.Jcrop.min.css v0.9.12 (build:20130126) */
.jcrop-holder{direction:ltr;text-align:left;}
.jcrop-vline,.jcrop-hline{background:#FFF url(Jcrop.gif);font-size:0;position:absolute;}
.jcrop-vline{height:100%;width:1px!important;}
.jcrop-vline.right{right:0;}
.jcrop-hline{height:1px!important;width:100%;}
.jcrop-hline.bottom{bottom:0;}
.jcrop-tracker{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-user-select:none;height:100%;width:100%;}
.jcrop-handle{background-color:#333;border:1px #EEE solid;font-size:1px;height:7px;width:7px;}
.jcrop-handle.ord-n{left:50%;margin-left:-4px;margin-top:-4px;top:0;}
.jcrop-handle.ord-s{bottom:0;left:50%;margin-bottom:-4px;margin-left:-4px;}
.jcrop-handle.ord-e{margin-right:-4px;margin-top:-4px;right:0;top:50%;}
.jcrop-handle.ord-w{left:0;margin-left:-4px;margin-top:-4px;top:50%;}
.jcrop-handle.ord-nw{left:0;margin-left:-4px;margin-top:-4px;top:0;}
.jcrop-handle.ord-ne{margin-right:-4px;margin-top:-4px;right:0;top:0;}
.jcrop-handle.ord-se{bottom:0;margin-bottom:-4px;margin-right:-4px;right:0;}
.jcrop-handle.ord-sw{bottom:0;left:0;margin-bottom:-4px;margin-left:-4px;}
.jcrop-dragbar.ord-n,.jcrop-dragbar.ord-s{height:7px;width:100%;}
.jcrop-dragbar.ord-e,.jcrop-dragbar.ord-w{height:100%;width:7px;}
.jcrop-dragbar.ord-n{margin-top:-4px;}
.jcrop-dragbar.ord-s{bottom:0;margin-bottom:-4px;}
.jcrop-dragbar.ord-e{margin-right:-4px;right:0;}
.jcrop-dragbar.ord-w{margin-left:-4px;}
.jcrop-light .jcrop-vline,.jcrop-light .jcrop-hline{background:#FFF;filter:alpha(opacity=70)!important;opacity:.70!important;}
.jcrop-light .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#000;border-color:#FFF;border-radius:3px;}
.jcrop-dark .jcrop-vline,.jcrop-dark .jcrop-hline{background:#000;filter:alpha(opacity=70)!important;opacity:.7!important;}
.jcrop-dark .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#FFF;border-color:#000;border-radius:3px;}
.solid-line .jcrop-vline,.solid-line .jcrop-hline{background:#FFF;}
.jcrop-holder img,img.jcrop-preview{max-width:none;}

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@ use Exception;
use BackendAuth;
use Backend\Models\UserPreferences;
use Backend\Models\BackendPreferences;
use Cms\Widgets\MediaManager;
use System\Classes\ErrorHandler;
use October\Rain\Exception\AjaxException;
use October\Rain\Exception\SystemException;
@ -147,6 +148,10 @@ class Controller extends Extendable
$this->viewPath = $this->configPath = $this->guessViewPath();
parent::__construct();
// Media Manager widget is available on all back-end pages
$manager = new MediaManager($this, 'ocmediamanager');
$manager->bindToController();
}
/**

View File

@ -1877,7 +1877,7 @@ self.$form.trigger('change')
if(self.$dataLocker)
self.$dataLocker.val(self.syncBefore(this.$editor.html()))}}
if(this.options.fullpage){redactorOptions.fullpage=true}
redactorOptions.plugins=['cleanup','fullscreen','figure','quote','table','mediamanager','image']
redactorOptions.plugins=['cleanup','fullscreen','figure','quote','table','mediamanager']
redactorOptions.buttons=['formatting','bold','italic','unorderedlist','orderedlist','link','horizontalrule','html'],this.$textarea.redactor(redactorOptions)}
RichEditor.prototype.build=function(redactor){this.updateLayout()
$(window).resize($.proxy(this.updateLayout,this))

View File

@ -81,7 +81,7 @@
// redactorOptions.plugins = ['cleanup', 'fullscreen', 'figure', 'image', 'quote', 'table']
// redactorOptions.buttons = ['formatting', 'bold', 'italic', 'unorderedlist', 'orderedlist', 'link', 'horizontalrule', 'html'],
redactorOptions.plugins = ['cleanup', 'fullscreen', 'figure', 'quote', 'table', 'mediamanager', 'image']
redactorOptions.plugins = ['cleanup', 'fullscreen', 'figure', 'quote', 'table', 'mediamanager']
redactorOptions.buttons = ['formatting', 'bold', 'italic', 'unorderedlist', 'orderedlist', 'link', 'horizontalrule', 'html'],
this.$textarea.redactor(redactorOptions)

View File

@ -17,6 +17,11 @@
<script src="<?= Backend::skinAsset('assets/js/vendor/modernizr.min.js') ?>"></script>
<script src="<?= Backend::skinAsset('assets/js/vendor/vendor-min.js') ?>"></script>
<script src="<?= Backend::skinAsset('assets/js/october-min.js') ?>"></script>
<!-- TODO: combine and load combined -->
<script src="<?= Backend::skinAsset('../cms/widgets/mediamanager/assets/js/mediamanager.popup.js') ?>"></script>
<script src="<?= Backend::skinAsset('../cms/widgets/mediamanager/assets/js/mediamanager.redactor.js') ?>"></script>
<?= $this->makeAssets() ?>
<?= Block::placeholder('head') ?>
<?= $this->makeLayoutPartial('custom_styles') ?>

View File

@ -4,6 +4,7 @@ use ApplicationException;
use SystemException;
use Config;
use Storage;
use Lang;
use Cache;
use Str;
@ -385,6 +386,18 @@ class MediaLibrary
return $path;
}
/**
* Returns a public file URL.
* @param string $path Specifies the file path relative the the Library root.
* @return string
*/
public function getPathUrl($path)
{
$path = $this->validatePath($path);
return $this->storagePath.$path;
}
/**
* Returns a file or folder path with the prefixed storage folder.
* @param string $path Specifies a path to process.

View File

@ -276,6 +276,17 @@ return [
'insert' => 'Insert',
'crop_and_insert' => 'Crop & Insert',
'select_single_image' => 'Please select a single image.',
'selection_not_image' => 'The selected item is not an image.'
'selection_not_image' => 'The selected item is not an image.',
'restore' => 'Undo all changes',
'resize' => 'Resize...',
'selection_mode_normal' => 'Normal',
'selection_mode_fixed_ratio' => 'Fixed ratio',
'selection_mode_fixed_size' => 'Fixed size',
'height' => 'Height',
'width' => 'Width',
'selection_mode' => 'Selection mode',
'resize_image' => 'Resize image',
'image_size' => 'Image size:',
'selected_size' => 'Selected:'
]
];

View File

@ -4,6 +4,7 @@ use URL;
use Str;
use Lang;
use File;
use Form;
use Input;
use Request;
use Response;
@ -24,10 +25,15 @@ use October\Rain\Database\Attach\Resizer;
class MediaManager extends WidgetBase
{
const FOLDER_ROOT = '/';
const VIEW_MODE_GRID = 'grid';
const VIEW_MODE_LIST = 'list';
const VIEW_MODE_TILES = 'tiles';
const SELECTION_MODE_NORMAL = 'normal';
const SELECTION_MODE_FIXED_RATIO = 'fixed-ratio';
const SELECTION_MODE_FIXED_SIZE = 'fixed-size';
const FILTER_EVERYTHING = 'everything';
protected $brokenImageHash = null;
@ -203,14 +209,14 @@ class MediaManager extends WidgetBase
$paths = Input::get('paths');
if (!is_array($paths))
throw new SystemException('Invalid input data');
throw new ApplicationException('Invalid input data');
$library = MediaLibrary::instance();
$filesToDelete = [];
foreach ($paths as $pathInfo) {
if (!isset($pathInfo['path']) || !isset($pathInfo['type']))
throw new SystemException('Invalid input data');
throw new ApplicationException('Invalid input data');
if ($pathInfo['type'] == 'file')
$filesToDelete[] = $pathInfo['path'];
@ -300,7 +306,7 @@ class MediaManager extends WidgetBase
{
$exclude = Input::get('exclude', []);
if (!is_array($exclude))
throw new SystemException('Invalid input data');
throw new ApplicationException('Invalid input data');
$folders = MediaLibrary::instance()->listAllDirectories($exclude);
@ -336,11 +342,11 @@ class MediaManager extends WidgetBase
$files = Input::get('files', []);
if (!is_array($files))
throw new SystemException('Invalid input data');
throw new ApplicationException('Invalid input data');
$folders = Input::get('folders', []);
if (!is_array($folders))
throw new SystemException('Invalid input data');
throw new ApplicationException('Invalid input data');
$library = MediaLibrary::instance();
@ -376,9 +382,91 @@ class MediaManager extends WidgetBase
public function onLoadImageCropPopup()
{
$path = Input::get('path');
$path = MediaLibrary::validatePath($path);
$selectionParams = $this->getSelectionParams();
$this->vars['currentSelectionMode'] = $selectionParams['mode'];
$this->vars['currentSelectionWidth'] = $selectionParams['width'];
$this->vars['currentSelectionHeight'] = $selectionParams['height'];
$this->vars['cropSessionKey'] = $cropSessionKey = md5(Form::getSessionKey());
$urlAndSize = $this->getCropEditImageUrlAndSize($path, $cropSessionKey);
$this->vars['imageUrl'] = $urlAndSize['url'];
$this->vars['dimensions'] = $urlAndSize['dimensions'];
$width = $urlAndSize['dimensions'][0];
$height = $urlAndSize['dimensions'][1] ? $urlAndSize['dimensions'][1] : 1;
$this->vars['originalRatio'] = round($width/$height, 5);
$this->vars['path'] = $path;
return $this->makePartial('image-crop-popup-body');
}
public function onEndCroppingSession()
{
$cropSessionKey = Input::get('cropSessionKey');
if (!preg_match('/^[0-9a-z]+$/', $cropSessionKey))
throw new ApplicationException('Invalid input data');
$this->removeCropEditDir($cropSessionKey);
}
public function onCropImage()
{
$imageSrcPath = trim(Input::get('img'));
$selectionData = Input::get('selection');
$cropSessionKey = Input::get('cropSessionKey');
$path = Input::get('path');
$path = MediaLibrary::validatePath($path);
if (!strlen($imageSrcPath))
throw new ApplicationException('Invalid input data');
if (!preg_match('/^[0-9a-z]+$/', $cropSessionKey))
throw new ApplicationException('Invalid input data');
if (!is_array($selectionData))
throw new ApplicationException('Invalid input data');
$result = $this->cropImage($imageSrcPath, $selectionData, $cropSessionKey, $path);
$selectionMode = Input::get('selectionMode');
$selectionWidth = Input::get('selectionWidth');
$selectionHeight = Input::get('selectionHeight');
$this->setSelectionParams($selectionMode, $selectionWidth, $selectionHeight);
return $result;
}
public function onResizeImage()
{
$cropSessionKey = Input::get('cropSessionKey');
if (!preg_match('/^[0-9a-z]+$/', $cropSessionKey))
throw new ApplicationException('Invalid input data');
$width = trim(Input::get('width'));
if (!strlen($width) || !ctype_digit($width))
throw new ApplicationException('Invalid input data');
$height = trim(Input::get('height'));
if (!strlen($height) || !ctype_digit($height))
throw new ApplicationException('Invalid input data');
$path = Input::get('path');
$path = MediaLibrary::validatePath($path);
$params = array(
'width' => $width,
'height' => $height
);
return $this->getCropEditImageUrlAndSize($path, $cropSessionKey, $params);
}
//
// Methods for th internal use
//
@ -447,7 +535,7 @@ class MediaManager extends WidgetBase
MediaLibraryItem::FILE_TYPE_AUDIO,
MediaLibraryItem::FILE_TYPE_DOCUMENT,
MediaLibraryItem::FILE_TYPE_VIDEO]))
throw new SystemException('Invalid input data');
throw new ApplicationException('Invalid input data');
return $this->putSession('media_filter', $filter);
}
@ -473,7 +561,7 @@ class MediaManager extends WidgetBase
MediaLibrary::SORT_BY_TITLE,
MediaLibrary::SORT_BY_SIZE,
MediaLibrary::SORT_BY_MODIFIED]))
throw new SystemException('Invalid input data');
throw new ApplicationException('Invalid input data');
return $this->putSession('media_sort_by', $sortBy);
}
@ -483,6 +571,51 @@ class MediaManager extends WidgetBase
return $this->getSession('media_sort_by', MediaLibrary::SORT_BY_TITLE);
}
protected function getSelectionParams()
{
$result = $this->getSession('media_crop_selection_params');
if ($result) {
if (!isset($result['mode']))
$result['mode'] = MediaManager::SELECTION_MODE_NORMAL;
if (!isset($result['width']))
$result['width'] = null;
if (!isset($result['height']))
$result['height'] = null;
return $result;
}
return [
'mode'=>MediaManager::SELECTION_MODE_NORMAL,
'width'=>null,
'height'=>null
];
}
protected function setSelectionParams($selectionMode, $selectionWidth, $selectionHeight)
{
if (!in_array($selectionMode, [
MediaManager::SELECTION_MODE_NORMAL,
MediaManager::SELECTION_MODE_FIXED_RATIO,
MediaManager::SELECTION_MODE_FIXED_SIZE]))
throw new ApplicationException('Invalid input data');
if (strlen($selectionWidth) && !ctype_digit($selectionWidth))
throw new ApplicationException('Invalid input data');
if (strlen($selectionHeight) && !ctype_digit($selectionHeight))
throw new ApplicationException('Invalid input data');
return $this->putSession('media_crop_selection_params', [
'mode'=>$selectionMode,
'width'=>$selectionWidth,
'height'=>$selectionHeight
]);
}
protected function setSidebarVisible($visible)
{
return $this->putSession('sideba_visible', !!$visible);
@ -540,7 +673,7 @@ class MediaManager extends WidgetBase
protected function setViewMode($viewMode)
{
if (!in_array($viewMode, [self::VIEW_MODE_GRID, self::VIEW_MODE_LIST, self::VIEW_MODE_TILES]))
throw new SystemException('Invalid input data');
throw new ApplicationException('Invalid input data');
return $this->putSession('view_mode', $viewMode);
}
@ -807,4 +940,151 @@ class MediaManager extends WidgetBase
return true;
}
//
// Cropping
//
protected function getCropSessionDirPath($cropSessionKey)
{
return $this->getThumbnailDirectory().'edit-crop-'.$cropSessionKey;
}
protected function getCropEditImageUrlAndSize($path, $cropSessionKey, $params = null)
{
$sessionDirectoryPath = $this->getCropSessionDirPath($cropSessionKey);
$fullSessionDirectoryPath = temp_path($sessionDirectoryPath);
$sessionDirectoryCreated = false;
if (!File::isDirectory($fullSessionDirectoryPath)) {
File::makeDirectory($fullSessionDirectoryPath, 0777, true, true);
$sessionDirectoryCreated = true;
}
$tempFilePath = null;
try {
$extension = pathinfo($path, PATHINFO_EXTENSION);
$library = MediaLibrary::instance();
$originalThumbFileName = 'original.'.$extension;
if (!$params) {
// If the target dimensions are not provided, save the original image to the
// crop session directory and return its URL.
$tempFilePath = $fullSessionDirectoryPath.'/'.$originalThumbFileName;
if (!@File::put($tempFilePath, $library->get($path)))
throw new SystemException('Error saving remote file to a temporary location.');
$url = $this->getThumbnailImageUrl($sessionDirectoryPath.'/'.$originalThumbFileName);
$dimensions = getimagesize($tempFilePath);
return [
'url' => $url,
'dimensions' => $dimensions
];
} else {
// If the target dimensions are provided, resize the original image and return its URL
// and dimensions.
$originalFilePath = $fullSessionDirectoryPath.'/'.$originalThumbFileName;
if (!File::isFile($originalFilePath))
throw new SystemException('The original image is not found in the cropping session directory.');
$resizedThumbFileName = 'resized-'.$params['width'].'-'.$params['height'].'.'.$extension;
$tempFilePath = $fullSessionDirectoryPath.'/'.$resizedThumbFileName;
$resizer = Resizer::open($originalFilePath);
$resizer->resize($params['width'], $params['height'], 'exact');
$resizer->save($tempFilePath, 95);
$url = $this->getThumbnailImageUrl($sessionDirectoryPath.'/'.$resizedThumbFileName);
$dimensions = getimagesize($tempFilePath);
return [
'url' => $url,
'dimensions' => $dimensions
];
}
} catch (Exception $ex) {
if ($sessionDirectoryCreated)
@File::deleteDirectory($fullSessionDirectoryPath);
if ($tempFilePath)
File::delete($tempFilePath);
throw $ex;
}
}
protected function removeCropEditDir($cropSessionKey)
{
$sessionDirectoryPath = $this->getCropSessionDirPath($cropSessionKey);
$fullSessionDirectoryPath = temp_path($sessionDirectoryPath);
if (File::isDirectory($fullSessionDirectoryPath))
@File::deleteDirectory($fullSessionDirectoryPath);
}
protected function cropImage($imageSrcPath, $selectionData, $cropSessionKey, $path)
{
$originalFileName = basename($path);
$path = rtrim(dirname($path), '/').'/';
$fileName = basename($imageSrcPath);
if (strpos($fileName, '..') !== false || strpos($fileName, '/') !== false || strpos($fileName, '\\') !== false)
throw new SystemException('Invalid image file name.');
$selectionParams = ['x', 'y', 'w', 'h'];
foreach ($selectionParams as $paramName) {
if (!array_key_exists($paramName, $selectionData))
throw new SystemException('Invalid selection data.');
if (!ctype_digit($selectionData[$paramName]))
throw new SystemException('Invalid selection data.');
}
$sessionDirectoryPath = $this->getCropSessionDirPath($cropSessionKey);
$fullSessionDirectoryPath = temp_path($sessionDirectoryPath);
if (!File::isDirectory($fullSessionDirectoryPath))
throw new SystemException('The image editing session is not found.');
// Find the image on the disk and resize it
$imagePath = $fullSessionDirectoryPath.'/'.$fileName;
if (!File::isFile($imagePath))
throw new SystemException('The image is not found on the disk.');
$extension = pathinfo($originalFileName, PATHINFO_EXTENSION);
$targetImageName = basename($originalFileName, '.'.$extension).'-'.$selectionData['x']
.'-'.$selectionData['y'].'-'.$selectionData['w'].'-'.$selectionData['h'].'-';
$targetImageName .= time();
$targetImageName .= '.'.$extension;
$targetTmpPath = $fullSessionDirectoryPath.'/'.$targetImageName;
if ($selectionData['w'] == 0 || $selectionData['h'] == 0) {
// If cropping is not required, copy the oiginal image to the target destination.
File::copy($imagePath, $targetTmpPath);
}
else {
$resizer = Resizer::open($imagePath);
$resizer->resample($selectionData['x'], $selectionData['y'], $selectionData['w'], $selectionData['h'], $selectionData['w'], $selectionData['h']);
$resizer->save($targetTmpPath, 95);
}
// Upload the cropped file to the Library
$targetPath = $path.'cropped-images/'.$targetImageName;
$library = MediaLibrary::instance();
$library->put($targetPath, file_get_contents($targetTmpPath));
return $library->getPathUrl($targetPath);
}
}

View File

@ -25,7 +25,8 @@ div[data-control="media-manager"] .empty-library {
}
div[data-control="media-manager"] p.thumbnail-error-message {
font-size: 12px;
margin-top: 25px;
margin: 10px;
line-height: 160%;
color: #bdc3c7;
}
div[data-control="media-manager"] .media-list {
@ -279,8 +280,10 @@ div[data-control="media-manager"] .sidebar-image-placeholder.no-border {
}
div[data-control="media-manager"] .sidebar-image-placeholder p {
font-size: 12px;
margin-top: 25px;
margin: 10px;
line-height: 160%;
color: #bdc3c7;
margin-top: 25px;
}
div[data-control="media-manager"] .list-container {
position: relative;
@ -394,6 +397,97 @@ div[data-control="media-manager"] button[data-command="toggle-sidebar"].sidebar-
-ms-transform: rotate(180deg) translate(0, 0);
transform: rotate(180deg) translate(0, 0);
}
[data-control="media-manager-crop-tool"] .image_area {
position: absolute;
width: 100%;
height: 100%;
overflow: auto;
}
[data-control="media-manager-crop-tool"] .image_area .jcrop-holder {
background-color: transparent!important;
}
[data-control="media-manager-crop-tool"] img {
cursor: crosshair;
display: block;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler-container .layout-relative {
overflow: hidden;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler-container.horizontal .layout-cell {
height: 20px;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler-container.horizontal .layout-relative {
width: 100%;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler-container.vertical {
width: 20px;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler-container.vertical .layout-relative {
height: 100%;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler {
position: absolute;
height: 20px;
margin-left: -3px;
background: #555;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler ul {
margin: 0;
padding: 0;
white-space: nowrap;
font-size: 0;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler li {
margin: 0;
padding: 0 0 0 40px;
list-style: none;
display: inline-block;
width: 24px;
margin: 0px -10px 0px -14px;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
text-align: left;
position: relative;
font-size: 10px;
line-height: 20px;
color: #ecf0f1;
font-family: Arial, sans-serif;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler li:before,
[data-control="media-manager-crop-tool"].has-rulers .ruler li:after {
content: ' ';
position: absolute;
border-left: 1px solid #8e8e8e;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler li:before {
height: 20px;
top: 0;
left: -3px;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler li:after {
height: 3px;
bottom: 0;
left: 20px;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler li:first-child:after {
display: none;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler[data-control=v-ruler] {
-webkit-transform: rotateZ(90deg);
-ms-transform: rotateZ(90deg);
transform: rotateZ(90deg);
-webkit-transform-origin: left top;
-moz-transform-origin: left top;
-ms-transform-origin: left top;
transform-origin: left top;
left: 23px;
top: -23px;
}
[data-control="media-manager-crop-tool"].has-rulers .ruler[data-control=v-ruler] li:after {
top: 0;
left: auto;
}
body:not(.no-select) div[data-control="media-manager"] .media-list.tiles li:hover .icon-container {
background: #4da7e8 !important;
border-color: #2581b8;
@ -408,17 +502,17 @@ body:not(.no-select) div[data-control="media-manager"] .media-list.tiles li:hove
body:not(.no-select) div[data-control="media-manager"] .media-list.tiles li:hover h4 {
padding-right: 20px!important;
}
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover:not(:active) {
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover {
background: #4da7e8 !important;
}
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover:not(:active) i,
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover:not(:active) p.size {
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover i,
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover p.size {
color: #ecf0f1;
}
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover:not(:active) h4 {
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover h4 {
color: white;
}
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover:not(:active) .icon-container {
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover .icon-container {
border-right-color: #4da7e8 !important;
}
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover h4 {

View File

@ -11,6 +11,17 @@
var MediaManagerImageCropPopup = function(path, options) {
this.$popupRootElement = null
this.$popupElement = null
this.selectionSizeLabel = null
this.imageArea = null
this.hRulerHolder = null
this.vRulerHolder = null
this.rulersVisible = false
this.prevScrollTop = 0
this.prevScrollLeft = 0
this.jCrop = null
this.options = $.extend({}, MediaManagerImageCropPopup.DEFAULTS, options)
this.path = path
@ -26,10 +37,15 @@
MediaManagerImageCropPopup.prototype.dispose = function() {
this.unregisterHandlers()
this.removeAttachedControls()
this.$popupRootElement.remove()
this.$popupRootElement = null
this.$popupElement = null
this.selectionSizeLabel = null
this.imageArea = null
this.hRulerHolder = null
this.vRulerHolder = null
BaseProto.dispose.call(this)
}
@ -51,7 +67,8 @@
extraData: data,
size: 'adaptive',
adaptiveHeight: true,
handler: this.options.alias + '::onLoadImageCropPopup'
handler: this.options.alias + '::onLoadImageCropPopup',
zIndex: 1200 // Media Manager can be opened in a popup, so this new popup should have a higher z-index
})
}
@ -61,6 +78,31 @@
}
MediaManagerImageCropPopup.prototype.unregisterHandlers = function() {
this.$popupElement.off('change', '[data-control="selection-mode"]', this.proxy(this.onSelectionModeChanged))
this.$popupElement.off('click', '[data-command]', this.proxy(this.onCommandClick))
this.$popupElement.off('shown.oc.popup', 'button[data-command=resize]', this.proxy(this.onResizePopupShown))
this.$popupElement.off('hidden.oc.popup', 'button[data-command=resize]', this.proxy(this.onResizePopupHidden))
if (this.rulersVisible) {
var $cropToolRoot = this.$popupElement.find('[data-control=media-manager-crop-tool]')
this.imageArea.removeEventListener('scroll', this.proxy(this.onImageScroll))
}
this.getWidthInput().off('change', this.proxy(this.onSizeInputChange))
this.getHeightInput().off('change', this.proxy(this.onSizeInputChange))
}
MediaManagerImageCropPopup.prototype.removeAttachedControls = function() {
if (this.$popupElement) {
// Note - the controls are destroyed and removed from DOM. If they're just destroyed,
// the JS plugins could be re-attached to them on window.onresize. -ab
this.$popupElement.find('[data-control="selection-mode"]').select2('destroy').remove()
this.$popupElement.find('[data-control=toolbar]').toolbar('dispose').remove()
this.jCrop.destroy()
}
this.jCrop = null
}
MediaManagerImageCropPopup.prototype.hide = function() {
@ -68,10 +110,272 @@
this.$popupElement.trigger('close.oc.popup')
}
MediaManagerImageCropPopup.prototype.getSelectionMode = function() {
return this.$popupElement.find('[data-control="selection-mode"]').val()
}
MediaManagerImageCropPopup.prototype.initRulers = function() {
if (!Modernizr.csstransforms)
return
var $cropToolRoot = this.$popupElement.find('[data-control=media-manager-crop-tool]'),
width = $cropToolRoot.data('image-width'),
height = $cropToolRoot.data('image-height')
if (!width || !height)
return
if ($cropToolRoot.width() > width)
width = $(window).width()
if ($cropToolRoot.height() > height)
height = $(window).height()
$cropToolRoot.find('.ruler-container').removeClass('hide')
$cropToolRoot.addClass('has-rulers')
var $hRuler = $cropToolRoot.find('[data-control=h-ruler]'),
$vRuler = $cropToolRoot.find('[data-control=v-ruler]'),
hTicks = width / 40 + 1,
vTicks = height / 40 + 1
this.createRulerTicks($hRuler, hTicks)
this.createRulerTicks($vRuler, vTicks)
this.rulersVisible = true
this.imageArea.addEventListener('scroll', this.proxy(this.onImageScroll))
this.hRulerHolder = $cropToolRoot.find('.ruler-container.horizontal .layout-relative').get(0)
this.vRulerHolder = $cropToolRoot.find('.ruler-container.vertical .layout-relative').get(0)
}
MediaManagerImageCropPopup.prototype.createRulerTicks = function($rulerElement, count) {
var list = document.createElement('ul')
for (var i=0; i <= count; i++) {
var li = document.createElement('li')
li.textContent = i*40
list.appendChild(li)
}
$rulerElement.append(list)
}
MediaManagerImageCropPopup.prototype.initJCrop = function() {
this.jCrop = $.Jcrop($(this.imageArea).find('img').get(0), {
shade: true,
onChange: this.proxy(this.onSelectionChanged)
})
}
MediaManagerImageCropPopup.prototype.fixDimensionValue = function(value) {
var result = value.replace(/[^0-9]+/, '')
if (!result.length)
result = 200
if (result == '0')
result = 1
return result
}
MediaManagerImageCropPopup.prototype.getWidthInput = function() {
return this.$popupElement.find('[data-control="crop-width-input"]')
}
MediaManagerImageCropPopup.prototype.getHeightInput = function() {
return this.$popupElement.find('[data-control="crop-height-input"]')
}
MediaManagerImageCropPopup.prototype.applySelectionMode = function() {
if (!this.jCrop)
return
var $widthInput = this.getWidthInput(),
$heightInput = this.getHeightInput(),
width = this.fixDimensionValue($widthInput.val()),
height = this.fixDimensionValue($heightInput.val()),
mode = this.getSelectionMode()
switch (mode) {
case 'fixed-ratio' :
this.jCrop.setOptions({
aspectRatio: width/height,
minSize: [0, 0],
maxSize: [0, 0],
allowResize: true
})
break
case 'fixed-size' :
this.jCrop.setOptions({
aspectRatio: 0,
minSize: [width, height],
maxSize: [width, height],
allowResize: false
})
break
case 'normal' :
this.jCrop.setOptions({
aspectRatio: 0,
minSize: [0, 0],
maxSize: [0, 0],
allowResize: true
})
break
}
}
MediaManagerImageCropPopup.prototype.cropAndInsert = function() {
var data = {
img: $(this.imageArea).find('img').attr('src'),
selection: this.jCrop.tellSelect()
}
$.oc.stripeLoadIndicator.show()
this.$popupElement.find('form').request(
this.options.alias+'::onCropImage', {
data: data
}
).always(function() {
$.oc.stripeLoadIndicator.hide()
}).done(this.proxy(this.onImageCropped))
}
MediaManagerImageCropPopup.prototype.onImageCropped = function(response) {
this.hide()
if (this.options.onDone !== undefined)
this.options.onDone(response.result)
}
MediaManagerImageCropPopup.prototype.showResizePopup = function() {
this.$popupElement.find('button[data-command=resize]').popup({
content: this.$popupElement.find('[data-control="resize-template"]').html(),
zIndex: 1220
})
}
MediaManagerImageCropPopup.prototype.onResizePopupShown = function(ev, button, popup) {
var $popup = $(popup),
$widthControl = $popup.find('input[name=width]'),
$heightControl = $popup.find('input[name=height]'),
imageWidth = this.fixDimensionValue(this.$popupElement.find('input[data-control=dimension-width]').val()),
imageHeight = this.fixDimensionValue(this.$popupElement.find('input[data-control=dimension-height]').val())
$widthControl.val(imageWidth)
$heightControl.val(imageHeight)
$widthControl.focus()
$popup.on('submit.media', 'form', this.proxy(this.onResizeSubmit))
$widthControl.on('keyup.media', this.proxy(this.onResizeDimensionKeyUp))
$heightControl.on('keyup.media', this.proxy(this.onResizeDimensionKeyUp))
$widthControl.on('change.media', this.proxy(this.onResizeDimensionChanged))
$heightControl.on('change.media', this.proxy(this.onResizeDimensionChanged))
}
MediaManagerImageCropPopup.prototype.onResizePopupHidden = function(ev, button, popup) {
var $popup = $(popup),
$widthControl = $popup.find('input[name=width]'),
$heightControl = $popup.find('input[name=height]')
$popup.off('.media', 'form')
$widthControl.off('.media')
$heightControl.off('.media')
}
MediaManagerImageCropPopup.prototype.onResizeDimensionKeyUp = function(ev) {
var $target = $(ev.target),
targetValue = this.fixDimensionValue($target.val()),
otherDimensionName = $target.attr('name') == 'width' ? 'height' : 'width',
$otherInput = $target.closest('form').find('input[name='+otherDimensionName+']'),
ratio = this.$popupElement.find('[data-control=original-ratio]').val(),
value = otherDimensionName == 'height' ? targetValue / ratio : targetValue * ratio
$otherInput.val(Math.round(value))
}
MediaManagerImageCropPopup.prototype.onResizeDimensionChanged = function(ev) {
var $target = $(ev.target)
$target.val(this.fixDimensionValue($target.val()))
}
MediaManagerImageCropPopup.prototype.onResizeSubmit = function(ev) {
var data = {
cropSessionKey: this.$popupElement.find('input[name=cropSessionKey]').val(),
path: this.$popupElement.find('input[name=path]').val()
}
$.oc.stripeLoadIndicator.show()
$(ev.target).request(this.options.alias+'::onResizeImage', {
data: data
}).always(function() {
$.oc.stripeLoadIndicator.hide()
}).done(this.proxy(this.imageResized))
ev.preventDefault()
return false
}
MediaManagerImageCropPopup.prototype.imageResized = function(response) {
this.$popupElement.find('button[data-command=resize]').popup('hide')
this.updateImage(response.url, response.dimensions[0], response.dimensions[1])
}
MediaManagerImageCropPopup.prototype.updateImage = function(url, width, hegiht) {
this.jCrop.destroy()
this.$popupElement.find('span[data-label=width]').text(width)
this.$popupElement.find('span[data-label=height]').text(hegiht)
this.$popupElement.find('input[data-control=dimension-width]').val(width)
this.$popupElement.find('input[data-control=dimension-height]').val(hegiht)
var $imageArea = $(this.imageArea)
$imageArea.find('img').remove()
var $img = $('<img>').attr('src', url)
$img.one('load', this.proxy(this.initJCrop))
$imageArea.append($img)
this.imageArea.scrollTop = 0
this.imageArea.scrollLeft = 0
this.onImageScroll()
}
MediaManagerImageCropPopup.prototype.undoResizing = function() {
this.updateImage(
this.$popupElement.find('input[data-control=original-url]').val(),
this.$popupElement.find('input[data-control=original-width]').val(),
this.$popupElement.find('input[data-control=original-height]').val()
)
}
MediaManagerImageCropPopup.prototype.updateSelectionSizeLabel = function(width, height) {
if (width == 0 && height == 0) {
this.selectionSizeLabel.setAttribute('class', 'hide')
return
}
this.selectionSizeLabel.setAttribute('class', '')
this.selectionSizeLabel.querySelector('[data-label=selection-width]').textContent = width
this.selectionSizeLabel.querySelector('[data-label=selection-height]').textContent = height
}
// EVENT HANDLERS
// ============================
MediaManagerImageCropPopup.prototype.onPopupHidden = function(event, element, popup) {
this.$popupElement.find('form').request(this.options.alias+'::onEndCroppingSession')
// Release clickedElement reference inside redactor.js
// If we don't do it, the image editor popup DOM elements
// won't be removed from the memory.
@ -82,6 +386,83 @@
MediaManagerImageCropPopup.prototype.onPopupShown = function(event, element, popup) {
this.$popupElement = popup
this.$popupElement.on('change', '[data-control="selection-mode"]', this.proxy(this.onSelectionModeChanged))
this.$popupElement.on('click', '[data-command]', this.proxy(this.onCommandClick))
this.$popupElement.on('shown.oc.popup', 'button[data-command=resize]', this.proxy(this.onResizePopupShown))
this.$popupElement.on('hidden.oc.popup', 'button[data-command=resize]', this.proxy(this.onResizePopupHidden))
this.imageArea = popup.find('[data-control=media-manager-crop-tool]').get(0).querySelector('.image_area')
this.selectionSizeLabel = popup.find('[data-label="selection-size"]').get(0)
this.getWidthInput().on('change', this.proxy(this.onSizeInputChange))
this.getHeightInput().on('change', this.proxy(this.onSizeInputChange))
this.initRulers()
this.initJCrop()
}
MediaManagerImageCropPopup.prototype.onSelectionModeChanged = function() {
var mode = this.getSelectionMode(),
$widthInput = this.getWidthInput(),
$heightInput = this.getHeightInput()
if (mode === 'normal') {
$widthInput.attr('disabled', 'disabled')
$heightInput.attr('disabled', 'disabled')
} else {
$widthInput.removeAttr('disabled')
$heightInput.removeAttr('disabled')
$widthInput.val(this.fixDimensionValue($widthInput.val()))
$heightInput.val(this.fixDimensionValue($heightInput.val()))
}
this.applySelectionMode()
}
MediaManagerImageCropPopup.prototype.onImageScroll = function() {
var scrollTop = this.imageArea.scrollTop,
scrollLeft = this.imageArea.scrollLeft
if (this.prevScrollTop != scrollTop) {
this.prevScrollTop = scrollTop
this.vRulerHolder.scrollTop = scrollTop
}
if (this.prevScrollLeft != scrollLeft) {
this.prevScrollLeft = scrollLeft
this.hRulerHolder.scrollLeft = scrollLeft
}
}
MediaManagerImageCropPopup.prototype.onSizeInputChange = function(ev) {
var $target = $(ev.target)
$target.val(this.fixDimensionValue($target.val()))
this.applySelectionMode()
}
MediaManagerImageCropPopup.prototype.onCommandClick = function(ev) {
var command = $(ev.currentTarget).data('command')
switch (command) {
case 'insert' :
this.cropAndInsert()
break
case 'resize' :
this.showResizePopup()
break
case 'undo-resizing' :
this.undoResizing()
break
}
}
MediaManagerImageCropPopup.prototype.onSelectionChanged = function(c) {
this.updateSelectionSizeLabel(c.w, c.h)
}
MediaManagerImageCropPopup.DEFAULTS = {

View File

@ -720,14 +720,21 @@
return
}
new $.oc.mediaManager.imageCropPopup(
selectedItems[0].getAttribute('data-path'), {
alias: this.options.alias
var path = selectedItems[0].getAttribute('data-path')
new $.oc.mediaManager.imageCropPopup(path, {
alias: this.options.alias,
onDone: callback
})
}
MediaManager.prototype.onImageCropped = function(imageItem) {
MediaManager.prototype.onImageCropped = function(imageUrl) {
var item = {
documentType: 'image',
publicUrl: imageUrl
}
this.$el.trigger('popupcommand', ['insert-cropped', item])
}
//
@ -819,7 +826,8 @@
MediaManager.prototype.createFolder = function(ev) {
$(ev.target).popup({
content: this.$el.find('[data-control="new-folder-template"]').html()
content: this.$el.find('[data-control="new-folder-template"]').html(),
zIndex: 1200 // Media Manager can be opened in a popup, so this new popup should have a higher z-index
})
}
@ -882,7 +890,8 @@
$(ev.target).popup({
handler: this.options.alias+'::onLoadMovePopup',
extraData: data
extraData: data,
zIndex: 1200 // Media Manager can be opened in a popup, so this new popup should have a higher z-index
})
}

View File

@ -56,7 +56,7 @@
bottomToolbar: this.options.bottomToolbar ? 1 : 0,
cropAndInsertButton: this.options.cropAndInsertButton ? 1 : 0
}
console.log(data)
this.$popupRootElement.popup({
extraData: data,
size: 'adaptive',
@ -81,6 +81,11 @@ console.log(data)
this.options.onInsert.call(this, items)
}
MediaManagerPopup.prototype.insertCroppedImage = function(imageItem) {
if (this.options.onInsert !== undefined)
this.options.onInsert.call(this, [imageItem])
}
// EVENT HANDLERS
// ============================
@ -110,11 +115,14 @@ console.log(data)
this.getMediaManagerElement().find('[data-control="sorting"]').focus().blur()
}
MediaManagerPopup.prototype.onPopupCommand = function(ev, command) {
MediaManagerPopup.prototype.onPopupCommand = function(ev, command, param) {
switch (command) {
case 'insert' :
this.insertMedia()
break;
case 'insert-cropped' :
this.insertCroppedImage(param)
break;
}
return false

View File

@ -75,7 +75,8 @@ div[data-control="media-manager"] {
.icon-message() {
font-size: 12px;
margin-top: 25px;
margin: 10px;
line-height: 160%;
color: #bdc3c7;
}
@ -327,6 +328,7 @@ div[data-control="media-manager"] {
p {
.icon-message();
margin-top: 25px;
}
}
@ -466,6 +468,115 @@ div[data-control="media-manager"] {
}
}
[data-control="media-manager-crop-tool"] {
.image_area {
position: absolute;
width: 100%;
height: 100%;
overflow: auto;
.jcrop-holder {
background-color: transparent!important;
}
}
img {
cursor: crosshair;
display: block;
}
&.has-rulers {
.ruler-container {
.layout-relative {
overflow: hidden;
}
&.horizontal {
.layout-cell {
height: 20px;
}
.layout-relative {
width: 100%;
}
}
&.vertical {
width: 20px;
.layout-relative {
height: 100%;
}
}
}
.ruler {
position: absolute;
height: 20px;
margin-left: -3px;
background: #555;
ul {
margin: 0;
padding: 0;
white-space: nowrap;
font-size: 0;
}
li {
margin: 0;
padding: 0 0 0 40px;
list-style: none;
display: inline-block;
width: 24px;
margin: 0px -10px 0px -14px;
.box-sizing(content-box);
text-align: left;
position: relative;
font-size: 10px;
line-height: 20px;
color: #ecf0f1;
font-family: Arial, sans-serif;
&:before, &:after {
content: ' ';
position: absolute;
border-left: 1px solid #8e8e8e;
}
&:before {
height: 20px;
top: 0;
left: -3px;
}
&:after {
height: 3px;
bottom: 0;
left: 20px;
}
&:first-child:after {
display: none;
}
}
}
.ruler[data-control=v-ruler] {
.transform(~'rotateZ(90deg)');
.transform-origin(~'left top');
left: 23px;
top: -23px;
& li:after {
top: 0;
left: auto;
}
}
}
}
body:not(.no-select) {
div[data-control="media-manager"] .media-list {
&.tiles {
@ -479,7 +590,7 @@ body:not(.no-select) {
}
&.list {
li:hover:not(:active) {
li:hover {
.media-selected-list();
}

View File

@ -0,0 +1,33 @@
<div class="layout"
data-control="media-manager-crop-tool"
<?php if ($dimensions): ?>
data-image-width="<?= $dimensions[0] ?>"
data-image-height="<?= $dimensions[1] ?>"
<?php endif ?>
>
<div class="layout-row min-size ruler-container horizontal hide">
<div class="layout-cell">
<div class="layout-relative">
<div class="ruler" data-control="h-ruler"></div>
</div>
</div>
</div>
<div class="layout-row">
<div class="layout">
<div class="layout-row">
<div class="layout-cell ruler-container vertical hide">
<div class="layout-relative">
<div class="ruler" data-control="v-ruler"></div>
</div>
</div>
<div class="layout-cell">
<div class="layout-relative">
<div class="image_area">
<img src="<?= $imageUrl ?>"/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,41 @@
<?php
$selectionModes = [
Cms\Widgets\MediaManager::SELECTION_MODE_NORMAL => trans('cms::lang.media.selection_mode_normal'),
Cms\Widgets\MediaManager::SELECTION_MODE_FIXED_RATIO => trans('cms::lang.media.selection_mode_fixed_ratio'),
Cms\Widgets\MediaManager::SELECTION_MODE_FIXED_SIZE => trans('cms::lang.media.selection_mode_fixed_size')
];
$sizeDisabledAttr = $currentSelectionMode == Cms\Widgets\MediaManager::SELECTION_MODE_NORMAL ? 'disabled="disabled"' : null;
?>
<div class="layout control-toolbar standalone-paddings">
<div class="layout-cell toolbar-item">
<div data-control="toolbar">
<label class="standalone"><?= e(trans('cms::lang.media.image_size')) ?> <span data-label="width"><?= $dimensions[0] ?></span> x <span data-label="height"><?= $dimensions[1] ?></span></label>
<div class="btn-group offset-right">
<button type="button" class="btn btn-primary standalone" data-command="resize"
><?= e(trans('cms::lang.media.resize')) ?></button>
<button type="button" class="btn btn-primary oc-icon-undo empty" data-command="undo-resizing"></button>
</div>
<label for="mmcropimagewidth"><?= e(trans('cms::lang.media.selection_mode')) ?></label>
<select name="selectionMode" class="form-control custom-select width-150" data-control="selection-mode">
<?php foreach ($selectionModes as $mode=>$name): ?>
<option <?= $mode == $currentSelectionMode ? 'selected="selected"' : null ?> value="<?= $mode ?>"><?= e($name) ?></option>
<?php endforeach ?>
</select>
<label for="mmcropimagewidth"><?= e(trans('cms::lang.media.width')) ?></label>
<input id="mmcropimagewidth" type="text" class="form-control width-50" data-control="crop-width-input" name="selectionWidth" value="<?= e($currentSelectionWidth) ?>" <?= $sizeDisabledAttr ?>/>
<label for="mmcropimageheight"><?= e(trans('cms::lang.media.height')) ?></label>
<input id="mmcropimageheight" type="text" class="form-control width-50" data-control="crop-height-input" name="selectionHeight" value="<?= e($currentSelectionHeight) ?>" <?= $sizeDisabledAttr ?>/>
<label class="standalone hide" data-label="selection-size"><?= e(trans('cms::lang.media.selected_size')) ?> <span data-label="selection-width"></span> x <span data-label="selection-height"></span></label>
</div>
</div>
</div>

View File

@ -39,6 +39,7 @@
href="#"
data-rename
data-control="popup"
data-z-index="1200"
data-request-data="path: '<?= e($item->path) ?>', listId: '<?= $listElementId ?>', type: '<?= $item->type ?>'"
data-handler="<?= $this->getEventHandler('onLoadRenamePopup') ?>"
><i data-rename-control class="icon-terminal"></i></a>

View File

@ -1,3 +1,43 @@
<?= Form::open(['class'=>'layout', 'onsubmit'=>'return false']) ?>
<p>Something</p>
<div class="layout-row min-size">
<?= $this->makePartial('crop-toolbar') ?>
</div>
<div class="layout-row whiteboard">
<?= $this->makePartial('crop-tool-image-area') ?>
</div>
<div class="layout-row min-size whiteboard">
<div class="panel no-padding-bottom border-top">
<div class="form-buttons">
<div class="pull-right">
<button
type="button"
data-command="insert"
class="btn btn-primary">
<?= e(trans('cms::lang.media.crop_and_insert')) ?>
</button>
<button
type="button"
data-dismiss="popup"
class="btn btn-default no-margin-right">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
</div>
</div>
</div>
<input type="hidden" name="cropSessionKey" value="<?= e($cropSessionKey) ?>">
<input type="hidden" name="path" value="<?= e($path) ?>">
<input type="hidden" data-control="dimension-width" value="<?= $dimensions[0] ?>">
<input type="hidden" data-control="dimension-height" value="<?= $dimensions[1] ?>">
<input type="hidden" data-control="original-width" value="<?= $dimensions[0] ?>">
<input type="hidden" data-control="original-height" value="<?= $dimensions[1] ?>">
<input type="hidden" data-control="original-ratio" value="<?= $originalRatio ?>">
<input type="hidden" data-control="original-url" value="<?= e($imageUrl) ?>">
<?= $this->makePartial('resize-image-form') ?>
<?= Form::close() ?>

View File

@ -41,6 +41,7 @@
data-control="popup"
data-request-data="path: '<?= e($item->path) ?>', listId: '<?= $listElementId ?>', type: '<?= $item->type ?>'"
data-handler="<?= $this->getEventHandler('onLoadRenamePopup') ?>"
data-z-index="1200"
><i data-rename-control class="icon-terminal"></i></a>
</div>
</td>

View File

@ -0,0 +1,31 @@
<script type="text/template" data-control="resize-template">
<?= Form::open() ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('cms::lang.media.resize_image')) ?></h4>
</div>
<div class="modal-body">
<div class="form-group span-left">
<label><?= e(trans('cms::lang.media.width')) ?></label>
<input type="text" class="form-control" name="width" value="" />
</div>
<div class="form-group span-right">
<label><?= e(trans('cms::lang.media.height')) ?></label>
<input type="text" class="form-control" name="height" value="" />
</div>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('backend::lang.form.apply')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
</form>
</script>