##// END OF EJS Templates
Reimplemented file-browser using partial-ajax...
marcink -
r2686:269c6e0b beta
parent child Browse files
Show More
@@ -0,0 +1,1 b''
1 window.JSON||(window.JSON={}),function(){function f(a){return a<10?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";return e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g,e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)d=rep[c],typeof d=="string"&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));return e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g,e}}"use strict",typeof Date.prototype.toJSON!="function"&&(Date.prototype.toJSON=function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()});var JSON=window.JSON,cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;typeof JSON.stringify!="function"&&(JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(!b||typeof b=="function"||typeof b=="object"&&typeof b.length=="number")return str("",{"":a});throw new Error("JSON.stringify")}),typeof JSON.parse!="function"&&(JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),typeof reviver=="function"?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}(),function(a,b){"use strict";var c=a.History=a.History||{};if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");c.Adapter={handlers:{},_uid:1,uid:function(a){return a._uid||(a._uid=c.Adapter._uid++)},bind:function(a,b,d){var e=c.Adapter.uid(a);c.Adapter.handlers[e]=c.Adapter.handlers[e]||{},c.Adapter.handlers[e][b]=c.Adapter.handlers[e][b]||[],c.Adapter.handlers[e][b].push(d),a["on"+b]=function(a,b){return function(d){c.Adapter.trigger(a,b,d)}}(a,b)},trigger:function(a,b,d){d=d||{};var e=c.Adapter.uid(a),f,g;c.Adapter.handlers[e]=c.Adapter.handlers[e]||{},c.Adapter.handlers[e][b]=c.Adapter.handlers[e][b]||[];for(f=0,g=c.Adapter.handlers[e][b].length;f<g;++f)c.Adapter.handlers[e][b][f].apply(this,[d])},extractEventData:function(a,c){var d=c&&c[a]||b;return d},onDomLoad:function(b){var c=a.setTimeout(function(){b()},2e3);a.onload=function(){clearTimeout(c),b()}}},typeof c.init!="undefined"&&c.init()}(window),function(a,b){"use strict";var c=a.document,d=a.setTimeout||d,e=a.clearTimeout||e,f=a.setInterval||f,g=a.History=a.History||{};if(typeof g.initHtml4!="undefined")throw new Error("History.js HTML4 Support has already been loaded...");g.initHtml4=function(){if(typeof g.initHtml4.initialized!="undefined")return!1;g.initHtml4.initialized=!0,g.enabled=!0,g.savedHashes=[],g.isLastHash=function(a){var b=g.getHashByIndex(),c;return c=a===b,c},g.saveHash=function(a){return g.isLastHash(a)?!1:(g.savedHashes.push(a),!0)},g.getHashByIndex=function(a){var b=null;return typeof a=="undefined"?b=g.savedHashes[g.savedHashes.length-1]:a<0?b=g.savedHashes[g.savedHashes.length+a]:b=g.savedHashes[a],b},g.discardedHashes={},g.discardedStates={},g.discardState=function(a,b,c){var d=g.getHashByState(a),e;return e={discardedState:a,backState:c,forwardState:b},g.discardedStates[d]=e,!0},g.discardHash=function(a,b,c){var d={discardedHash:a,backState:c,forwardState:b};return g.discardedHashes[a]=d,!0},g.discardedState=function(a){var b=g.getHashByState(a),c;return c=g.discardedStates[b]||!1,c},g.discardedHash=function(a){var b=g.discardedHashes[a]||!1;return b},g.recycleState=function(a){var b=g.getHashByState(a);return g.discardedState(a)&&delete g.discardedStates[b],!0},g.emulated.hashChange&&(g.hashChangeInit=function(){g.checkerFunction=null;var b="",d,e,h,i;return g.isInternetExplorer()?(d="historyjs-iframe",e=c.createElement("iframe"),e.setAttribute("id",d),e.style.display="none",c.body.appendChild(e),e.contentWindow.document.open(),e.contentWindow.document.close(),h="",i=!1,g.checkerFunction=function(){if(i)return!1;i=!0;var c=g.getHash()||"",d=g.unescapeHash(e.contentWindow.document.location.hash)||"";return c!==b?(b=c,d!==c&&(h=d=c,e.contentWindow.document.open(),e.contentWindow.document.close(),e.contentWindow.document.location.hash=g.escapeHash(c)),g.Adapter.trigger(a,"hashchange")):d!==h&&(h=d,g.setHash(d,!1)),i=!1,!0}):g.checkerFunction=function(){var c=g.getHash();return c!==b&&(b=c,g.Adapter.trigger(a,"hashchange")),!0},g.intervalList.push(f(g.checkerFunction,g.options.hashChangeInterval)),!0},g.Adapter.onDomLoad(g.hashChangeInit)),g.emulated.pushState&&(g.onHashChange=function(b){var d=b&&b.newURL||c.location.href,e=g.getHashByUrl(d),f=null,h=null,i=null,j;return g.isLastHash(e)?(g.busy(!1),!1):(g.doubleCheckComplete(),g.saveHash(e),e&&g.isTraditionalAnchor(e)?(g.Adapter.trigger(a,"anchorchange"),g.busy(!1),!1):(f=g.extractState(g.getFullUrl(e||c.location.href,!1),!0),g.isLastSavedState(f)?(g.busy(!1),!1):(h=g.getHashByState(f),j=g.discardedState(f),j?(g.getHashByIndex(-2)===g.getHashByState(j.forwardState)?g.back(!1):g.forward(!1),!1):(g.pushState(f.data,f.title,f.url,!1),!0))))},g.Adapter.bind(a,"hashchange",g.onHashChange),g.pushState=function(b,d,e,f){if(g.getHashByUrl(e))throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(f!==!1&&g.busy())return g.pushQueue({scope:g,callback:g.pushState,args:arguments,queue:f}),!1;g.busy(!0);var h=g.createStateObject(b,d,e),i=g.getHashByState(h),j=g.getState(!1),k=g.getHashByState(j),l=g.getHash();return g.storeState(h),g.expectedStateId=h.id,g.recycleState(h),g.setTitle(h),i===k?(g.busy(!1),!1):i!==l&&i!==g.getShortUrl(c.location.href)?(g.setHash(i,!1),!1):(g.saveState(h),g.Adapter.trigger(a,"statechange"),g.busy(!1),!0)},g.replaceState=function(a,b,c,d){if(g.getHashByUrl(c))throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(d!==!1&&g.busy())return g.pushQueue({scope:g,callback:g.replaceState,args:arguments,queue:d}),!1;g.busy(!0);var e=g.createStateObject(a,b,c),f=g.getState(!1),h=g.getStateByIndex(-2);return g.discardState(f,e,h),g.pushState(e.data,e.title,e.url,!1),!0}),g.emulated.pushState&&g.getHash()&&!g.emulated.hashChange&&g.Adapter.onDomLoad(function(){g.Adapter.trigger(a,"hashchange")})},typeof g.init!="undefined"&&g.init()}(window),function(a,b){"use strict";var c=a.console||b,d=a.document,e=a.navigator,f=a.sessionStorage||!1,g=a.setTimeout,h=a.clearTimeout,i=a.setInterval,j=a.clearInterval,k=a.JSON,l=a.alert,m=a.History=a.History||{},n=a.history;k.stringify=k.stringify||k.encode,k.parse=k.parse||k.decode;if(typeof m.init!="undefined")throw new Error("History.js Core has already been loaded...");m.init=function(){return typeof m.Adapter=="undefined"?!1:(typeof m.initCore!="undefined"&&m.initCore(),typeof m.initHtml4!="undefined"&&m.initHtml4(),!0)},m.initCore=function(){if(typeof m.initCore.initialized!="undefined")return!1;m.initCore.initialized=!0,m.options=m.options||{},m.options.hashChangeInterval=m.options.hashChangeInterval||100,m.options.safariPollInterval=m.options.safariPollInterval||500,m.options.doubleCheckInterval=m.options.doubleCheckInterval||500,m.options.storeInterval=m.options.storeInterval||1e3,m.options.busyDelay=m.options.busyDelay||250,m.options.debug=m.options.debug||!1,m.options.initialTitle=m.options.initialTitle||d.title,m.intervalList=[],m.clearAllIntervals=function(){var a,b=m.intervalList;if(typeof b!="undefined"&&b!==null){for(a=0;a<b.length;a++)j(b[a]);m.intervalList=null}},m.debug=function(){(m.options.debug||!1)&&m.log.apply(m,arguments)},m.log=function(){var a=typeof c!="undefined"&&typeof c.log!="undefined"&&typeof c.log.apply!="undefined",b=d.getElementById("log"),e,f,g,h,i;a?(h=Array.prototype.slice.call(arguments),e=h.shift(),typeof c.debug!="undefined"?c.debug.apply(c,[e,h]):c.log.apply(c,[e,h])):e="\n"+arguments[0]+"\n";for(f=1,g=arguments.length;f<g;++f){i=arguments[f];if(typeof i=="object"&&typeof k!="undefined")try{i=k.stringify(i)}catch(j){}e+="\n"+i+"\n"}return b?(b.value+=e+"\n-----\n",b.scrollTop=b.scrollHeight-b.clientHeight):a||l(e),!0},m.getInternetExplorerMajorVersion=function(){var a=m.getInternetExplorerMajorVersion.cached=typeof m.getInternetExplorerMajorVersion.cached!="undefined"?m.getInternetExplorerMajorVersion.cached:function(){var a=3,b=d.createElement("div"),c=b.getElementsByTagName("i");while((b.innerHTML="<!--[if gt IE "+ ++a+"]><i></i><![endif]-->")&&c[0]);return a>4?a:!1}();return a},m.isInternetExplorer=function(){var a=m.isInternetExplorer.cached=typeof m.isInternetExplorer.cached!="undefined"?m.isInternetExplorer.cached:Boolean(m.getInternetExplorerMajorVersion());return a},m.emulated={pushState:!Boolean(a.history&&a.history.pushState&&a.history.replaceState&&!/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i.test(e.userAgent)&&!/AppleWebKit\/5([0-2]|3[0-2])/i.test(e.userAgent)),hashChange:Boolean(!("onhashchange"in a||"onhashchange"in d)||m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<8)},m.enabled=!m.emulated.pushState,m.bugs={setHash:Boolean(!m.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),safariPoll:Boolean(!m.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),ieDoubleCheck:Boolean(m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<8),hashEscape:Boolean(m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<7)},m.isEmptyObject=function(a){for(var b in a)return!1;return!0},m.cloneObject=function(a){var b,c;return a?(b=k.stringify(a),c=k.parse(b)):c={},c},m.getRootUrl=function(){var a=d.location.protocol+"//"+(d.location.hostname||d.location.host);if(d.location.port||!1)a+=":"+d.location.port;return a+="/",a},m.getBaseHref=function(){var a=d.getElementsByTagName("base"),b=null,c="";return a.length===1&&(b=a[0],c=b.href.replace(/[^\/]+$/,"")),c=c.replace(/\/+$/,""),c&&(c+="/"),c},m.getBaseUrl=function(){var a=m.getBaseHref()||m.getBasePageUrl()||m.getRootUrl();return a},m.getPageUrl=function(){var a=m.getState(!1,!1),b=(a||{}).url||d.location.href,c;return c=b.replace(/\/+$/,"").replace(/[^\/]+$/,function(a,b,c){return/\./.test(a)?a:a+"/"}),c},m.getBasePageUrl=function(){var a=d.location.href.replace(/[#\?].*/,"").replace(/[^\/]+$/,function(a,b,c){return/[^\/]$/.test(a)?"":a}).replace(/\/+$/,"")+"/";return a},m.getFullUrl=function(a,b){var c=a,d=a.substring(0,1);return b=typeof b=="undefined"?!0:b,/[a-z]+\:\/\//.test(a)||(d==="/"?c=m.getRootUrl()+a.replace(/^\/+/,""):d==="#"?c=m.getPageUrl().replace(/#.*/,"")+a:d==="?"?c=m.getPageUrl().replace(/[\?#].*/,"")+a:b?c=m.getBaseUrl()+a.replace(/^(\.\/)+/,""):c=m.getBasePageUrl()+a.replace(/^(\.\/)+/,"")),c.replace(/\#$/,"")},m.getShortUrl=function(a){var b=a,c=m.getBaseUrl(),d=m.getRootUrl();return m.emulated.pushState&&(b=b.replace(c,"")),b=b.replace(d,"/"),m.isTraditionalAnchor(b)&&(b="./"+b),b=b.replace(/^(\.\/)+/g,"./").replace(/\#$/,""),b},m.store={},m.idToState=m.idToState||{},m.stateToId=m.stateToId||{},m.urlToId=m.urlToId||{},m.storedStates=m.storedStates||[],m.savedStates=m.savedStates||[],m.normalizeStore=function(){m.store.idToState=m.store.idToState||{},m.store.urlToId=m.store.urlToId||{},m.store.stateToId=m.store.stateToId||{}},m.getState=function(a,b){typeof a=="undefined"&&(a=!0),typeof b=="undefined"&&(b=!0);var c=m.getLastSavedState();return!c&&b&&(c=m.createStateObject()),a&&(c=m.cloneObject(c),c.url=c.cleanUrl||c.url),c},m.getIdByState=function(a){var b=m.extractId(a.url),c;if(!b){c=m.getStateString(a);if(typeof m.stateToId[c]!="undefined")b=m.stateToId[c];else if(typeof m.store.stateToId[c]!="undefined")b=m.store.stateToId[c];else{for(;;){b=(new Date).getTime()+String(Math.random()).replace(/\D/g,"");if(typeof m.idToState[b]=="undefined"&&typeof m.store.idToState[b]=="undefined")break}m.stateToId[c]=b,m.idToState[b]=a}}return b},m.normalizeState=function(a){var b,c;if(!a||typeof a!="object")a={};if(typeof a.normalized!="undefined")return a;if(!a.data||typeof a.data!="object")a.data={};b={},b.normalized=!0,b.title=a.title||"",b.url=m.getFullUrl(m.unescapeString(a.url||d.location.href)),b.hash=m.getShortUrl(b.url),b.data=m.cloneObject(a.data),b.id=m.getIdByState(b),b.cleanUrl=b.url.replace(/\??\&_suid.*/,""),b.url=b.cleanUrl,c=!m.isEmptyObject(b.data);if(b.title||c)b.hash=m.getShortUrl(b.url).replace(/\??\&_suid.*/,""),/\?/.test(b.hash)||(b.hash+="?"),b.hash+="&_suid="+b.id;return b.hashedUrl=m.getFullUrl(b.hash),(m.emulated.pushState||m.bugs.safariPoll)&&m.hasUrlDuplicate(b)&&(b.url=b.hashedUrl),b},m.createStateObject=function(a,b,c){var d={data:a,title:b,url:c};return d=m.normalizeState(d),d},m.getStateById=function(a){a=String(a);var c=m.idToState[a]||m.store.idToState[a]||b;return c},m.getStateString=function(a){var b,c,d;return b=m.normalizeState(a),c={data:b.data,title:a.title,url:a.url},d=k.stringify(c),d},m.getStateId=function(a){var b,c;return b=m.normalizeState(a),c=b.id,c},m.getHashByState=function(a){var b,c;return b=m.normalizeState(a),c=b.hash,c},m.extractId=function(a){var b,c,d;return c=/(.*)\&_suid=([0-9]+)$/.exec(a),d=c?c[1]||a:a,b=c?String(c[2]||""):"",b||!1},m.isTraditionalAnchor=function(a){var b=!/[\/\?\.]/.test(a);return b},m.extractState=function(a,b){var c=null,d,e;return b=b||!1,d=m.extractId(a),d&&(c=m.getStateById(d)),c||(e=m.getFullUrl(a),d=m.getIdByUrl(e)||!1,d&&(c=m.getStateById(d)),!c&&b&&!m.isTraditionalAnchor(a)&&(c=m.createStateObject(null,null,e))),c},m.getIdByUrl=function(a){var c=m.urlToId[a]||m.store.urlToId[a]||b;return c},m.getLastSavedState=function(){return m.savedStates[m.savedStates.length-1]||b},m.getLastStoredState=function(){return m.storedStates[m.storedStates.length-1]||b},m.hasUrlDuplicate=function(a){var b=!1,c;return c=m.extractState(a.url),b=c&&c.id!==a.id,b},m.storeState=function(a){return m.urlToId[a.url]=a.id,m.storedStates.push(m.cloneObject(a)),a},m.isLastSavedState=function(a){var b=!1,c,d,e;return m.savedStates.length&&(c=a.id,d=m.getLastSavedState(),e=d.id,b=c===e),b},m.saveState=function(a){return m.isLastSavedState(a)?!1:(m.savedStates.push(m.cloneObject(a)),!0)},m.getStateByIndex=function(a){var b=null;return typeof a=="undefined"?b=m.savedStates[m.savedStates.length-1]:a<0?b=m.savedStates[m.savedStates.length+a]:b=m.savedStates[a],b},m.getHash=function(){var a=m.unescapeHash(d.location.hash);return a},m.unescapeString=function(b){var c=b,d;for(;;){d=a.unescape(c);if(d===c)break;c=d}return c},m.unescapeHash=function(a){var b=m.normalizeHash(a);return b=m.unescapeString(b),b},m.normalizeHash=function(a){var b=a.replace(/[^#]*#/,"").replace(/#.*/,"");return b},m.setHash=function(a,b){var c,e,f;return b!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.setHash,args:arguments,queue:b}),!1):(c=m.escapeHash(a),m.busy(!0),e=m.extractState(a,!0),e&&!m.emulated.pushState?m.pushState(e.data,e.title,e.url,!1):d.location.hash!==c&&(m.bugs.setHash?(f=m.getPageUrl(),m.pushState(null,null,f+"#"+c,!1)):d.location.hash=c),m)},m.escapeHash=function(b){var c=m.normalizeHash(b);return c=a.escape(c),m.bugs.hashEscape||(c=c.replace(/\%21/g,"!").replace(/\%26/g,"&").replace(/\%3D/g,"=").replace(/\%3F/g,"?")),c},m.getHashByUrl=function(a){var b=String(a).replace(/([^#]*)#?([^#]*)#?(.*)/,"$2");return b=m.unescapeHash(b),b},m.setTitle=function(a){var b=a.title,c;b||(c=m.getStateByIndex(0),c&&c.url===a.url&&(b=c.title||m.options.initialTitle));try{d.getElementsByTagName("title")[0].innerHTML=b.replace("<","&lt;").replace(">","&gt;").replace(" & "," &amp; ")}catch(e){}return d.title=b,m},m.queues=[],m.busy=function(a){typeof a!="undefined"?m.busy.flag=a:typeof m.busy.flag=="undefined"&&(m.busy.flag=!1);if(!m.busy.flag){h(m.busy.timeout);var b=function(){var a,c,d;if(m.busy.flag)return;for(a=m.queues.length-1;a>=0;--a){c=m.queues[a];if(c.length===0)continue;d=c.shift(),m.fireQueueItem(d),m.busy.timeout=g(b,m.options.busyDelay)}};m.busy.timeout=g(b,m.options.busyDelay)}return m.busy.flag},m.busy.flag=!1,m.fireQueueItem=function(a){return a.callback.apply(a.scope||m,a.args||[])},m.pushQueue=function(a){return m.queues[a.queue||0]=m.queues[a.queue||0]||[],m.queues[a.queue||0].push(a),m},m.queue=function(a,b){return typeof a=="function"&&(a={callback:a}),typeof b!="undefined"&&(a.queue=b),m.busy()?m.pushQueue(a):m.fireQueueItem(a),m},m.clearQueue=function(){return m.busy.flag=!1,m.queues=[],m},m.stateChanged=!1,m.doubleChecker=!1,m.doubleCheckComplete=function(){return m.stateChanged=!0,m.doubleCheckClear(),m},m.doubleCheckClear=function(){return m.doubleChecker&&(h(m.doubleChecker),m.doubleChecker=!1),m},m.doubleCheck=function(a){return m.stateChanged=!1,m.doubleCheckClear(),m.bugs.ieDoubleCheck&&(m.doubleChecker=g(function(){return m.doubleCheckClear(),m.stateChanged||a(),!0},m.options.doubleCheckInterval)),m},m.safariStatePoll=function(){var b=m.extractState(d.location.href),c;if(!m.isLastSavedState(b))c=b;else return;return c||(c=m.createStateObject()),m.Adapter.trigger(a,"popstate"),m},m.back=function(a){return a!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.back,args:arguments,queue:a}),!1):(m.busy(!0),m.doubleCheck(function(){m.back(!1)}),n.go(-1),!0)},m.forward=function(a){return a!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.forward,args:arguments,queue:a}),!1):(m.busy(!0),m.doubleCheck(function(){m.forward(!1)}),n.go(1),!0)},m.go=function(a,b){var c;if(a>0)for(c=1;c<=a;++c)m.forward(b);else{if(!(a<0))throw new Error("History.go: History.go requires a positive or negative integer passed.");for(c=-1;c>=a;--c)m.back(b)}return m};if(m.emulated.pushState){var o=function(){};m.pushState=m.pushState||o,m.replaceState=m.replaceState||o}else m.onPopState=function(b,c){var e=!1,f=!1,g,h;return m.doubleCheckComplete(),g=m.getHash(),g?(h=m.extractState(g||d.location.href,!0),h?m.replaceState(h.data,h.title,h.url,!1):(m.Adapter.trigger(a,"anchorchange"),m.busy(!1)),m.expectedStateId=!1,!1):(e=m.Adapter.extractEventData("state",b,c)||!1,e?f=m.getStateById(e):m.expectedStateId?f=m.getStateById(m.expectedStateId):f=m.extractState(d.location.href),f||(f=m.createStateObject(null,null,d.location.href)),m.expectedStateId=!1,m.isLastSavedState(f)?(m.busy(!1),!1):(m.storeState(f),m.saveState(f),m.setTitle(f),m.Adapter.trigger(a,"statechange"),m.busy(!1),!0))},m.Adapter.bind(a,"popstate",m.onPopState),m.pushState=function(b,c,d,e){if(m.getHashByUrl(d)&&m.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&m.busy())return m.pushQueue({scope:m,callback:m.pushState,args:arguments,queue:e}),!1;m.busy(!0);var f=m.createStateObject(b,c,d);return m.isLastSavedState(f)?m.busy(!1):(m.storeState(f),m.expectedStateId=f.id,n.pushState(f.id,f.title,f.url),m.Adapter.trigger(a,"popstate")),!0},m.replaceState=function(b,c,d,e){if(m.getHashByUrl(d)&&m.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&m.busy())return m.pushQueue({scope:m,callback:m.replaceState,args:arguments,queue:e}),!1;m.busy(!0);var f=m.createStateObject(b,c,d);return m.isLastSavedState(f)?m.busy(!1):(m.storeState(f),m.expectedStateId=f.id,n.replaceState(f.id,f.title,f.url),m.Adapter.trigger(a,"popstate")),!0};if(f){try{m.store=k.parse(f.getItem("History.store"))||{}}catch(p){m.store={}}m.normalizeStore()}else m.store={},m.normalizeStore();m.Adapter.bind(a,"beforeunload",m.clearAllIntervals),m.Adapter.bind(a,"unload",m.clearAllIntervals),m.saveState(m.storeState(m.extractState(d.location.href,!0))),f&&(m.onUnload=function(){var a,b;try{a=k.parse(f.getItem("History.store"))||{}}catch(c){a={}}a.idToState=a.idToState||{},a.urlToId=a.urlToId||{},a.stateToId=a.stateToId||{};for(b in m.idToState){if(!m.idToState.hasOwnProperty(b))continue;a.idToState[b]=m.idToState[b]}for(b in m.urlToId){if(!m.urlToId.hasOwnProperty(b))continue;a.urlToId[b]=m.urlToId[b]}for(b in m.stateToId){if(!m.stateToId.hasOwnProperty(b))continue;a.stateToId[b]=m.stateToId[b]}m.store=a,m.normalizeStore(),f.setItem("History.store",k.stringify(a))},m.intervalList.push(i(m.onUnload,m.options.storeInterval)),m.Adapter.bind(a,"beforeunload",m.onUnload),m.Adapter.bind(a,"unload",m.onUnload));if(!m.emulated.pushState){m.bugs.safariPoll&&m.intervalList.push(i(m.safariStatePoll,m.options.safariPollInterval));if(e.vendor==="Apple Computer, Inc."||(e.appCodeName||"")==="Mozilla")m.Adapter.bind(a,"hashchange",function(){m.Adapter.trigger(a,"popstate")}),m.getHash()&&m.Adapter.onDomLoad(function(){m.Adapter.trigger(a,"hashchange")})}},m.init()}(window) No newline at end of file
@@ -1,502 +1,505 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.files
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Files controller for RhodeCode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 from __future__ import with_statement
26 26 import os
27 27 import logging
28 28 import traceback
29 29 import tempfile
30 30
31 31 from pylons import request, response, tmpl_context as c, url
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34 from pylons.decorators import jsonify
35 35
36 36 from rhodecode.lib import diffs
37 37 from rhodecode.lib import helpers as h
38 38
39 39 from rhodecode.lib.compat import OrderedDict
40 40 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str
41 41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44 44 from rhodecode.lib.vcs.conf import settings
45 45 from rhodecode.lib.vcs.exceptions import RepositoryError, \
46 46 ChangesetDoesNotExistError, EmptyRepositoryError, \
47 47 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
48 48 from rhodecode.lib.vcs.nodes import FileNode
49 49
50 50 from rhodecode.model.repo import RepoModel
51 51 from rhodecode.model.scm import ScmModel
52 52 from rhodecode.model.db import Repository
53 53
54 54 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
55 55 _context_url, get_line_ctx, get_ignore_ws
56 56
57 57
58 58 log = logging.getLogger(__name__)
59 59
60 60
61 61 class FilesController(BaseRepoController):
62 62
63 63 def __before__(self):
64 64 super(FilesController, self).__before__()
65 65 c.cut_off_limit = self.cut_off_limit
66 66
67 67 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
68 68 """
69 69 Safe way to get changeset if error occur it redirects to tip with
70 70 proper message
71 71
72 72 :param rev: revision to fetch
73 73 :param repo_name: repo name to redirect after
74 74 """
75 75
76 76 try:
77 77 return c.rhodecode_repo.get_changeset(rev)
78 78 except EmptyRepositoryError, e:
79 79 if not redirect_after:
80 80 return None
81 81 url_ = url('files_add_home',
82 82 repo_name=c.repo_name,
83 83 revision=0, f_path='')
84 84 add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
85 85 h.flash(h.literal(_('There are no files yet %s') % add_new),
86 86 category='warning')
87 87 redirect(h.url('summary_home', repo_name=repo_name))
88 88
89 89 except RepositoryError, e:
90 90 h.flash(str(e), category='warning')
91 91 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
92 92
93 93 def __get_filenode_or_redirect(self, repo_name, cs, path):
94 94 """
95 95 Returns file_node, if error occurs or given path is directory,
96 96 it'll redirect to top level path
97 97
98 98 :param repo_name: repo_name
99 99 :param cs: given changeset
100 100 :param path: path to lookup
101 101 """
102 102
103 103 try:
104 104 file_node = cs.get_node(path)
105 105 if file_node.is_dir():
106 106 raise RepositoryError('given path is a directory')
107 107 except RepositoryError, e:
108 108 h.flash(str(e), category='warning')
109 109 redirect(h.url('files_home', repo_name=repo_name,
110 110 revision=cs.raw_id))
111 111
112 112 return file_node
113 113
114 114 @LoginRequired()
115 115 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
116 116 'repository.admin')
117 117 def index(self, repo_name, revision, f_path, annotate=False):
118 118 # redirect to given revision from form if given
119 119 post_revision = request.POST.get('at_rev', None)
120 120 if post_revision:
121 121 cs = self.__get_cs_or_redirect(post_revision, repo_name)
122 122 redirect(url('files_home', repo_name=c.repo_name,
123 123 revision=cs.raw_id, f_path=f_path))
124 124
125 125 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
126 126 c.branch = request.GET.get('branch', None)
127 127 c.f_path = f_path
128 128 c.annotate = annotate
129 129 cur_rev = c.changeset.revision
130 130
131 131 # prev link
132 132 try:
133 133 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
134 134 c.url_prev = url('files_home', repo_name=c.repo_name,
135 135 revision=prev_rev.raw_id, f_path=f_path)
136 136 if c.branch:
137 137 c.url_prev += '?branch=%s' % c.branch
138 138 except (ChangesetDoesNotExistError, VCSError):
139 139 c.url_prev = '#'
140 140
141 141 # next link
142 142 try:
143 143 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
144 144 c.url_next = url('files_home', repo_name=c.repo_name,
145 145 revision=next_rev.raw_id, f_path=f_path)
146 146 if c.branch:
147 147 c.url_next += '?branch=%s' % c.branch
148 148 except (ChangesetDoesNotExistError, VCSError):
149 149 c.url_next = '#'
150 150
151 151 # files or dirs
152 152 try:
153 153 c.file = c.changeset.get_node(f_path)
154 154
155 155 if c.file.is_file():
156 156 _hist = c.changeset.get_file_history(f_path)
157 157 c.file_history = self._get_node_history(c.changeset, f_path,
158 158 _hist)
159 159 c.authors = []
160 160 for a in set([x.author for x in _hist]):
161 161 c.authors.append((h.email(a), h.person(a)))
162 162 else:
163 163 c.authors = c.file_history = []
164 164 except RepositoryError, e:
165 165 h.flash(str(e), category='warning')
166 166 redirect(h.url('files_home', repo_name=repo_name,
167 167 revision='tip'))
168 168
169 if request.environ.get('HTTP_X_PARTIAL_XHR'):
170 return render('files/files_ypjax.html')
171
169 172 return render('files/files.html')
170 173
171 174 @LoginRequired()
172 175 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
173 176 'repository.admin')
174 177 def rawfile(self, repo_name, revision, f_path):
175 178 cs = self.__get_cs_or_redirect(revision, repo_name)
176 179 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
177 180
178 181 response.content_disposition = 'attachment; filename=%s' % \
179 182 safe_str(f_path.split(Repository.url_sep())[-1])
180 183
181 184 response.content_type = file_node.mimetype
182 185 return file_node.content
183 186
184 187 @LoginRequired()
185 188 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
186 189 'repository.admin')
187 190 def raw(self, repo_name, revision, f_path):
188 191 cs = self.__get_cs_or_redirect(revision, repo_name)
189 192 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
190 193
191 194 raw_mimetype_mapping = {
192 195 # map original mimetype to a mimetype used for "show as raw"
193 196 # you can also provide a content-disposition to override the
194 197 # default "attachment" disposition.
195 198 # orig_type: (new_type, new_dispo)
196 199
197 200 # show images inline:
198 201 'image/x-icon': ('image/x-icon', 'inline'),
199 202 'image/png': ('image/png', 'inline'),
200 203 'image/gif': ('image/gif', 'inline'),
201 204 'image/jpeg': ('image/jpeg', 'inline'),
202 205 'image/svg+xml': ('image/svg+xml', 'inline'),
203 206 }
204 207
205 208 mimetype = file_node.mimetype
206 209 try:
207 210 mimetype, dispo = raw_mimetype_mapping[mimetype]
208 211 except KeyError:
209 212 # we don't know anything special about this, handle it safely
210 213 if file_node.is_binary:
211 214 # do same as download raw for binary files
212 215 mimetype, dispo = 'application/octet-stream', 'attachment'
213 216 else:
214 217 # do not just use the original mimetype, but force text/plain,
215 218 # otherwise it would serve text/html and that might be unsafe.
216 219 # Note: underlying vcs library fakes text/plain mimetype if the
217 220 # mimetype can not be determined and it thinks it is not
218 221 # binary.This might lead to erroneous text display in some
219 222 # cases, but helps in other cases, like with text files
220 223 # without extension.
221 224 mimetype, dispo = 'text/plain', 'inline'
222 225
223 226 if dispo == 'attachment':
224 227 dispo = 'attachment; filename=%s' % \
225 228 safe_str(f_path.split(os.sep)[-1])
226 229
227 230 response.content_disposition = dispo
228 231 response.content_type = mimetype
229 232 return file_node.content
230 233
231 234 @LoginRequired()
232 235 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
233 236 def edit(self, repo_name, revision, f_path):
234 237 r_post = request.POST
235 238
236 239 c.cs = self.__get_cs_or_redirect(revision, repo_name)
237 240 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
238 241
239 242 if c.file.is_binary:
240 243 return redirect(url('files_home', repo_name=c.repo_name,
241 244 revision=c.cs.raw_id, f_path=f_path))
242 245
243 246 c.f_path = f_path
244 247
245 248 if r_post:
246 249
247 250 old_content = c.file.content
248 251 sl = old_content.splitlines(1)
249 252 first_line = sl[0] if sl else ''
250 253 # modes: 0 - Unix, 1 - Mac, 2 - DOS
251 254 mode = detect_mode(first_line, 0)
252 255 content = convert_line_endings(r_post.get('content'), mode)
253 256
254 257 message = r_post.get('message') or (_('Edited %s via RhodeCode')
255 258 % (f_path))
256 259 author = self.rhodecode_user.full_contact
257 260
258 261 if content == old_content:
259 262 h.flash(_('No changes'),
260 263 category='warning')
261 264 return redirect(url('changeset_home', repo_name=c.repo_name,
262 265 revision='tip'))
263 266
264 267 try:
265 268 self.scm_model.commit_change(repo=c.rhodecode_repo,
266 269 repo_name=repo_name, cs=c.cs,
267 270 user=self.rhodecode_user,
268 271 author=author, message=message,
269 272 content=content, f_path=f_path)
270 273 h.flash(_('Successfully committed to %s') % f_path,
271 274 category='success')
272 275
273 276 except Exception:
274 277 log.error(traceback.format_exc())
275 278 h.flash(_('Error occurred during commit'), category='error')
276 279 return redirect(url('changeset_home',
277 280 repo_name=c.repo_name, revision='tip'))
278 281
279 282 return render('files/files_edit.html')
280 283
281 284 @LoginRequired()
282 285 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
283 286 def add(self, repo_name, revision, f_path):
284 287 r_post = request.POST
285 288 c.cs = self.__get_cs_or_redirect(revision, repo_name,
286 289 redirect_after=False)
287 290 if c.cs is None:
288 291 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
289 292
290 293 c.f_path = f_path
291 294
292 295 if r_post:
293 296 unix_mode = 0
294 297 content = convert_line_endings(r_post.get('content'), unix_mode)
295 298
296 299 message = r_post.get('message') or (_('Added %s via RhodeCode')
297 300 % (f_path))
298 301 location = r_post.get('location')
299 302 filename = r_post.get('filename')
300 303 file_obj = r_post.get('upload_file', None)
301 304
302 305 if file_obj is not None and hasattr(file_obj, 'filename'):
303 306 filename = file_obj.filename
304 307 content = file_obj.file
305 308
306 309 node_path = os.path.join(location, filename)
307 310 author = self.rhodecode_user.full_contact
308 311
309 312 if not content:
310 313 h.flash(_('No content'), category='warning')
311 314 return redirect(url('changeset_home', repo_name=c.repo_name,
312 315 revision='tip'))
313 316 if not filename:
314 317 h.flash(_('No filename'), category='warning')
315 318 return redirect(url('changeset_home', repo_name=c.repo_name,
316 319 revision='tip'))
317 320
318 321 try:
319 322 self.scm_model.create_node(repo=c.rhodecode_repo,
320 323 repo_name=repo_name, cs=c.cs,
321 324 user=self.rhodecode_user,
322 325 author=author, message=message,
323 326 content=content, f_path=node_path)
324 327 h.flash(_('Successfully committed to %s') % node_path,
325 328 category='success')
326 329 except NodeAlreadyExistsError, e:
327 330 h.flash(_(e), category='error')
328 331 except Exception:
329 332 log.error(traceback.format_exc())
330 333 h.flash(_('Error occurred during commit'), category='error')
331 334 return redirect(url('changeset_home',
332 335 repo_name=c.repo_name, revision='tip'))
333 336
334 337 return render('files/files_add.html')
335 338
336 339 @LoginRequired()
337 340 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
338 341 'repository.admin')
339 342 def archivefile(self, repo_name, fname):
340 343
341 344 fileformat = None
342 345 revision = None
343 346 ext = None
344 347 subrepos = request.GET.get('subrepos') == 'true'
345 348
346 349 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
347 350 archive_spec = fname.split(ext_data[1])
348 351 if len(archive_spec) == 2 and archive_spec[1] == '':
349 352 fileformat = a_type or ext_data[1]
350 353 revision = archive_spec[0]
351 354 ext = ext_data[1]
352 355
353 356 try:
354 357 dbrepo = RepoModel().get_by_repo_name(repo_name)
355 358 if dbrepo.enable_downloads is False:
356 359 return _('downloads disabled')
357 360
358 361 if c.rhodecode_repo.alias == 'hg':
359 362 # patch and reset hooks section of UI config to not run any
360 363 # hooks on fetching archives with subrepos
361 364 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
362 365 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
363 366
364 367 cs = c.rhodecode_repo.get_changeset(revision)
365 368 content_type = settings.ARCHIVE_SPECS[fileformat][0]
366 369 except ChangesetDoesNotExistError:
367 370 return _('Unknown revision %s') % revision
368 371 except EmptyRepositoryError:
369 372 return _('Empty repository')
370 373 except (ImproperArchiveTypeError, KeyError):
371 374 return _('Unknown archive type')
372 375
373 376 fd, archive = tempfile.mkstemp()
374 377 t = open(archive, 'wb')
375 378 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
376 379 t.close()
377 380
378 381 def get_chunked_archive(archive):
379 382 stream = open(archive, 'rb')
380 383 while True:
381 384 data = stream.read(16 * 1024)
382 385 if not data:
383 386 stream.close()
384 387 os.close(fd)
385 388 os.remove(archive)
386 389 break
387 390 yield data
388 391
389 392 response.content_disposition = str('attachment; filename=%s-%s%s' \
390 393 % (repo_name, revision[:12], ext))
391 394 response.content_type = str(content_type)
392 395 return get_chunked_archive(archive)
393 396
394 397 @LoginRequired()
395 398 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
396 399 'repository.admin')
397 400 def diff(self, repo_name, f_path):
398 401 ignore_whitespace = request.GET.get('ignorews') == '1'
399 402 line_context = request.GET.get('context', 3)
400 403 diff1 = request.GET.get('diff1', '')
401 404 diff2 = request.GET.get('diff2', '')
402 405 c.action = request.GET.get('diff')
403 406 c.no_changes = diff1 == diff2
404 407 c.f_path = f_path
405 408 c.big_diff = False
406 409 c.anchor_url = anchor_url
407 410 c.ignorews_url = _ignorews_url
408 411 c.context_url = _context_url
409 412 c.changes = OrderedDict()
410 413 c.changes[diff2] = []
411 414 try:
412 415 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
413 416 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
414 417 node1 = c.changeset_1.get_node(f_path)
415 418 else:
416 419 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
417 420 node1 = FileNode('.', '', changeset=c.changeset_1)
418 421
419 422 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
420 423 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
421 424 node2 = c.changeset_2.get_node(f_path)
422 425 else:
423 426 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
424 427 node2 = FileNode('.', '', changeset=c.changeset_2)
425 428 except RepositoryError:
426 429 return redirect(url('files_home', repo_name=c.repo_name,
427 430 f_path=f_path))
428 431
429 432 if c.action == 'download':
430 433 _diff = diffs.get_gitdiff(node1, node2,
431 434 ignore_whitespace=ignore_whitespace,
432 435 context=line_context)
433 436 diff = diffs.DiffProcessor(_diff, format='gitdiff')
434 437
435 438 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
436 439 response.content_type = 'text/plain'
437 440 response.content_disposition = (
438 441 'attachment; filename=%s' % diff_name
439 442 )
440 443 return diff.raw_diff()
441 444
442 445 elif c.action == 'raw':
443 446 _diff = diffs.get_gitdiff(node1, node2,
444 447 ignore_whitespace=ignore_whitespace,
445 448 context=line_context)
446 449 diff = diffs.DiffProcessor(_diff, format='gitdiff')
447 450 response.content_type = 'text/plain'
448 451 return diff.raw_diff()
449 452
450 453 else:
451 454 fid = h.FID(diff2, node2.path)
452 455 line_context_lcl = get_line_ctx(fid, request.GET)
453 456 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
454 457
455 458 lim = request.GET.get('fulldiff') or self.cut_off_limit
456 459 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
457 460 filenode_new=node2,
458 461 cut_off_limit=lim,
459 462 ignore_whitespace=ign_whitespace_lcl,
460 463 line_context=line_context_lcl,
461 464 enable_comments=False)
462 465
463 466 c.changes = [('', node2, diff, cs1, cs2, st,)]
464 467
465 468 return render('files/file_diff.html')
466 469
467 470 def _get_node_history(self, cs, f_path, changesets=None):
468 471 if changesets is None:
469 472 changesets = cs.get_file_history(f_path)
470 473 hist_l = []
471 474
472 475 changesets_group = ([], _("Changesets"))
473 476 branches_group = ([], _("Branches"))
474 477 tags_group = ([], _("Tags"))
475 478 _hg = cs.repository.alias == 'hg'
476 479 for chs in changesets:
477 480 _branch = '(%s)' % chs.branch if _hg else ''
478 481 n_desc = 'r%s:%s %s' % (chs.revision, chs.short_id, _branch)
479 482 changesets_group[0].append((chs.raw_id, n_desc,))
480 483
481 484 hist_l.append(changesets_group)
482 485
483 486 for name, chs in c.rhodecode_repo.branches.items():
484 487 branches_group[0].append((chs, name),)
485 488 hist_l.append(branches_group)
486 489
487 490 for name, chs in c.rhodecode_repo.tags.items():
488 491 tags_group[0].append((chs, name),)
489 492 hist_l.append(tags_group)
490 493
491 494 return hist_l
492 495
493 496 @LoginRequired()
494 497 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
495 498 'repository.admin')
496 499 @jsonify
497 500 def nodelist(self, repo_name, revision, f_path):
498 501 if request.environ.get('HTTP_X_PARTIAL_XHR'):
499 502 cs = self.__get_cs_or_redirect(revision, repo_name)
500 503 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
501 504 flat=False)
502 505 return {'nodes': _d + _f}
@@ -1,1043 +1,1045 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 import StringIO
9 9 import urllib
10 10 import math
11 11 import logging
12 12 import re
13 13
14 14 from datetime import datetime
15 15 from pygments.formatters.html import HtmlFormatter
16 16 from pygments import highlight as code_highlight
17 17 from pylons import url, request, config
18 18 from pylons.i18n.translation import _, ungettext
19 19 from hashlib import md5
20 20
21 21 from webhelpers.html import literal, HTML, escape
22 22 from webhelpers.html.tools import *
23 23 from webhelpers.html.builder import make_tag
24 24 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
25 25 end_form, file, form, hidden, image, javascript_link, link_to, \
26 26 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
27 27 submit, text, password, textarea, title, ul, xml_declaration, radio
28 28 from webhelpers.html.tools import auto_link, button_to, highlight, \
29 29 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
30 30 from webhelpers.number import format_byte_size, format_bit_size
31 31 from webhelpers.pylonslib import Flash as _Flash
32 32 from webhelpers.pylonslib.secure_form import secure_form
33 33 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
34 34 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
35 35 replace_whitespace, urlify, truncate, wrap_paragraphs
36 36 from webhelpers.date import time_ago_in_words
37 37 from webhelpers.paginate import Page
38 38 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
39 39 convert_boolean_attrs, NotGiven, _make_safe_id_component
40 40
41 41 from rhodecode.lib.annotate import annotate_highlight
42 42 from rhodecode.lib.utils import repo_name_slug
43 43 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
44 44 get_changeset_safe
45 45 from rhodecode.lib.markup_renderer import MarkupRenderer
46 46 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
47 47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 48 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
49 49 from rhodecode.model.changeset_status import ChangesetStatusModel
50 50 from rhodecode.model.db import URL_SEP, Permission
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 html_escape_table = {
56 56 "&": "&amp;",
57 57 '"': "&quot;",
58 58 "'": "&apos;",
59 59 ">": "&gt;",
60 60 "<": "&lt;",
61 61 }
62 62
63 63
64 64 def html_escape(text):
65 65 """Produce entities within text."""
66 66 return "".join(html_escape_table.get(c,c) for c in text)
67 67
68 68
69 69 def shorter(text, size=20):
70 70 postfix = '...'
71 71 if len(text) > size:
72 72 return text[:size - len(postfix)] + postfix
73 73 return text
74 74
75 75
76 76 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
77 77 """
78 78 Reset button
79 79 """
80 80 _set_input_attrs(attrs, type, name, value)
81 81 _set_id_attr(attrs, id, name)
82 82 convert_boolean_attrs(attrs, ["disabled"])
83 83 return HTML.input(**attrs)
84 84
85 85 reset = _reset
86 86 safeid = _make_safe_id_component
87 87
88 88
89 89 def FID(raw_id, path):
90 90 """
91 91 Creates a uniqe ID for filenode based on it's hash of path and revision
92 92 it's safe to use in urls
93 93
94 94 :param raw_id:
95 95 :param path:
96 96 """
97 97
98 98 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
99 99
100 100
101 101 def get_token():
102 102 """Return the current authentication token, creating one if one doesn't
103 103 already exist.
104 104 """
105 105 token_key = "_authentication_token"
106 106 from pylons import session
107 107 if not token_key in session:
108 108 try:
109 109 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
110 110 except AttributeError: # Python < 2.4
111 111 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
112 112 session[token_key] = token
113 113 if hasattr(session, 'save'):
114 114 session.save()
115 115 return session[token_key]
116 116
117 117
118 118 class _GetError(object):
119 119 """Get error from form_errors, and represent it as span wrapped error
120 120 message
121 121
122 122 :param field_name: field to fetch errors for
123 123 :param form_errors: form errors dict
124 124 """
125 125
126 126 def __call__(self, field_name, form_errors):
127 127 tmpl = """<span class="error_msg">%s</span>"""
128 128 if form_errors and field_name in form_errors:
129 129 return literal(tmpl % form_errors.get(field_name))
130 130
131 131 get_error = _GetError()
132 132
133 133
134 134 class _ToolTip(object):
135 135
136 136 def __call__(self, tooltip_title, trim_at=50):
137 137 """
138 138 Special function just to wrap our text into nice formatted
139 139 autowrapped text
140 140
141 141 :param tooltip_title:
142 142 """
143 143 tooltip_title = escape(tooltip_title)
144 144 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
145 145 return tooltip_title
146 146 tooltip = _ToolTip()
147 147
148 148
149 149 class _FilesBreadCrumbs(object):
150 150
151 151 def __call__(self, repo_name, rev, paths):
152 152 if isinstance(paths, str):
153 153 paths = safe_unicode(paths)
154 154 url_l = [link_to(repo_name, url('files_home',
155 155 repo_name=repo_name,
156 revision=rev, f_path=''))]
156 revision=rev, f_path=''),
157 class_='ypjax-link')]
157 158 paths_l = paths.split('/')
158 159 for cnt, p in enumerate(paths_l):
159 160 if p != '':
160 161 url_l.append(link_to(p,
161 162 url('files_home',
162 163 repo_name=repo_name,
163 164 revision=rev,
164 165 f_path='/'.join(paths_l[:cnt + 1])
165 )
166 ),
167 class_='ypjax-link'
166 168 )
167 169 )
168 170
169 171 return literal('/'.join(url_l))
170 172
171 173 files_breadcrumbs = _FilesBreadCrumbs()
172 174
173 175
174 176 class CodeHtmlFormatter(HtmlFormatter):
175 177 """
176 178 My code Html Formatter for source codes
177 179 """
178 180
179 181 def wrap(self, source, outfile):
180 182 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
181 183
182 184 def _wrap_code(self, source):
183 185 for cnt, it in enumerate(source):
184 186 i, t = it
185 187 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
186 188 yield i, t
187 189
188 190 def _wrap_tablelinenos(self, inner):
189 191 dummyoutfile = StringIO.StringIO()
190 192 lncount = 0
191 193 for t, line in inner:
192 194 if t:
193 195 lncount += 1
194 196 dummyoutfile.write(line)
195 197
196 198 fl = self.linenostart
197 199 mw = len(str(lncount + fl - 1))
198 200 sp = self.linenospecial
199 201 st = self.linenostep
200 202 la = self.lineanchors
201 203 aln = self.anchorlinenos
202 204 nocls = self.noclasses
203 205 if sp:
204 206 lines = []
205 207
206 208 for i in range(fl, fl + lncount):
207 209 if i % st == 0:
208 210 if i % sp == 0:
209 211 if aln:
210 212 lines.append('<a href="#%s%d" class="special">%*d</a>' %
211 213 (la, i, mw, i))
212 214 else:
213 215 lines.append('<span class="special">%*d</span>' % (mw, i))
214 216 else:
215 217 if aln:
216 218 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
217 219 else:
218 220 lines.append('%*d' % (mw, i))
219 221 else:
220 222 lines.append('')
221 223 ls = '\n'.join(lines)
222 224 else:
223 225 lines = []
224 226 for i in range(fl, fl + lncount):
225 227 if i % st == 0:
226 228 if aln:
227 229 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
228 230 else:
229 231 lines.append('%*d' % (mw, i))
230 232 else:
231 233 lines.append('')
232 234 ls = '\n'.join(lines)
233 235
234 236 # in case you wonder about the seemingly redundant <div> here: since the
235 237 # content in the other cell also is wrapped in a div, some browsers in
236 238 # some configurations seem to mess up the formatting...
237 239 if nocls:
238 240 yield 0, ('<table class="%stable">' % self.cssclass +
239 241 '<tr><td><div class="linenodiv" '
240 242 'style="background-color: #f0f0f0; padding-right: 10px">'
241 243 '<pre style="line-height: 125%">' +
242 244 ls + '</pre></div></td><td id="hlcode" class="code">')
243 245 else:
244 246 yield 0, ('<table class="%stable">' % self.cssclass +
245 247 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
246 248 ls + '</pre></div></td><td id="hlcode" class="code">')
247 249 yield 0, dummyoutfile.getvalue()
248 250 yield 0, '</td></tr></table>'
249 251
250 252
251 253 def pygmentize(filenode, **kwargs):
252 254 """pygmentize function using pygments
253 255
254 256 :param filenode:
255 257 """
256 258
257 259 return literal(code_highlight(filenode.content,
258 260 filenode.lexer, CodeHtmlFormatter(**kwargs)))
259 261
260 262
261 263 def pygmentize_annotation(repo_name, filenode, **kwargs):
262 264 """
263 265 pygmentize function for annotation
264 266
265 267 :param filenode:
266 268 """
267 269
268 270 color_dict = {}
269 271
270 272 def gen_color(n=10000):
271 273 """generator for getting n of evenly distributed colors using
272 274 hsv color and golden ratio. It always return same order of colors
273 275
274 276 :returns: RGB tuple
275 277 """
276 278
277 279 def hsv_to_rgb(h, s, v):
278 280 if s == 0.0:
279 281 return v, v, v
280 282 i = int(h * 6.0) # XXX assume int() truncates!
281 283 f = (h * 6.0) - i
282 284 p = v * (1.0 - s)
283 285 q = v * (1.0 - s * f)
284 286 t = v * (1.0 - s * (1.0 - f))
285 287 i = i % 6
286 288 if i == 0:
287 289 return v, t, p
288 290 if i == 1:
289 291 return q, v, p
290 292 if i == 2:
291 293 return p, v, t
292 294 if i == 3:
293 295 return p, q, v
294 296 if i == 4:
295 297 return t, p, v
296 298 if i == 5:
297 299 return v, p, q
298 300
299 301 golden_ratio = 0.618033988749895
300 302 h = 0.22717784590367374
301 303
302 304 for _ in xrange(n):
303 305 h += golden_ratio
304 306 h %= 1
305 307 HSV_tuple = [h, 0.95, 0.95]
306 308 RGB_tuple = hsv_to_rgb(*HSV_tuple)
307 309 yield map(lambda x: str(int(x * 256)), RGB_tuple)
308 310
309 311 cgenerator = gen_color()
310 312
311 313 def get_color_string(cs):
312 314 if cs in color_dict:
313 315 col = color_dict[cs]
314 316 else:
315 317 col = color_dict[cs] = cgenerator.next()
316 318 return "color: rgb(%s)! important;" % (', '.join(col))
317 319
318 320 def url_func(repo_name):
319 321
320 322 def _url_func(changeset):
321 323 author = changeset.author
322 324 date = changeset.date
323 325 message = tooltip(changeset.message)
324 326
325 327 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
326 328 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
327 329 "</b> %s<br/></div>")
328 330
329 331 tooltip_html = tooltip_html % (author, date, message)
330 332 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
331 333 short_id(changeset.raw_id))
332 334 uri = link_to(
333 335 lnk_format,
334 336 url('changeset_home', repo_name=repo_name,
335 337 revision=changeset.raw_id),
336 338 style=get_color_string(changeset.raw_id),
337 339 class_='tooltip',
338 340 title=tooltip_html
339 341 )
340 342
341 343 uri += '\n'
342 344 return uri
343 345 return _url_func
344 346
345 347 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
346 348
347 349
348 350 def is_following_repo(repo_name, user_id):
349 351 from rhodecode.model.scm import ScmModel
350 352 return ScmModel().is_following_repo(repo_name, user_id)
351 353
352 354 flash = _Flash()
353 355
354 356 #==============================================================================
355 357 # SCM FILTERS available via h.
356 358 #==============================================================================
357 359 from rhodecode.lib.vcs.utils import author_name, author_email
358 360 from rhodecode.lib.utils2 import credentials_filter, age as _age
359 361 from rhodecode.model.db import User, ChangesetStatus
360 362
361 363 age = lambda x: _age(x)
362 364 capitalize = lambda x: x.capitalize()
363 365 email = author_email
364 366 short_id = lambda x: x[:12]
365 367 hide_credentials = lambda x: ''.join(credentials_filter(x))
366 368
367 369
368 370 def fmt_date(date):
369 371 if date:
370 372 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
371 373 return date.strftime(_fmt).decode('utf8')
372 374
373 375 return ""
374 376
375 377
376 378 def is_git(repository):
377 379 if hasattr(repository, 'alias'):
378 380 _type = repository.alias
379 381 elif hasattr(repository, 'repo_type'):
380 382 _type = repository.repo_type
381 383 else:
382 384 _type = repository
383 385 return _type == 'git'
384 386
385 387
386 388 def is_hg(repository):
387 389 if hasattr(repository, 'alias'):
388 390 _type = repository.alias
389 391 elif hasattr(repository, 'repo_type'):
390 392 _type = repository.repo_type
391 393 else:
392 394 _type = repository
393 395 return _type == 'hg'
394 396
395 397
396 398 def email_or_none(author):
397 399 _email = email(author)
398 400 if _email != '':
399 401 return _email
400 402
401 403 # See if it contains a username we can get an email from
402 404 user = User.get_by_username(author_name(author), case_insensitive=True,
403 405 cache=True)
404 406 if user is not None:
405 407 return user.email
406 408
407 409 # No valid email, not a valid user in the system, none!
408 410 return None
409 411
410 412
411 413 def person(author):
412 414 # attr to return from fetched user
413 415 person_getter = lambda usr: usr.username
414 416
415 417 # Valid email in the attribute passed, see if they're in the system
416 418 _email = email(author)
417 419 if _email != '':
418 420 user = User.get_by_email(_email, case_insensitive=True, cache=True)
419 421 if user is not None:
420 422 return person_getter(user)
421 423 return _email
422 424
423 425 # Maybe it's a username?
424 426 _author = author_name(author)
425 427 user = User.get_by_username(_author, case_insensitive=True,
426 428 cache=True)
427 429 if user is not None:
428 430 return person_getter(user)
429 431
430 432 # Still nothing? Just pass back the author name then
431 433 return _author
432 434
433 435
434 436 def desc_stylize(value):
435 437 """
436 438 converts tags from value into html equivalent
437 439
438 440 :param value:
439 441 """
440 442 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
441 443 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
442 444 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
443 445 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
444 446 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
445 447 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
446 448 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
447 449 '<div class="metatag" tag="lang">\\2</div>', value)
448 450 value = re.sub(r'\[([a-z]+)\]',
449 451 '<div class="metatag" tag="\\1">\\1</div>', value)
450 452
451 453 return value
452 454
453 455
454 456 def bool2icon(value):
455 457 """Returns True/False values represented as small html image of true/false
456 458 icons
457 459
458 460 :param value: bool value
459 461 """
460 462
461 463 if value is True:
462 464 return HTML.tag('img', src=url("/images/icons/accept.png"),
463 465 alt=_('True'))
464 466
465 467 if value is False:
466 468 return HTML.tag('img', src=url("/images/icons/cancel.png"),
467 469 alt=_('False'))
468 470
469 471 return value
470 472
471 473
472 474 def action_parser(user_log, feed=False):
473 475 """
474 476 This helper will action_map the specified string action into translated
475 477 fancy names with icons and links
476 478
477 479 :param user_log: user log instance
478 480 :param feed: use output for feeds (no html and fancy icons)
479 481 """
480 482
481 483 action = user_log.action
482 484 action_params = ' '
483 485
484 486 x = action.split(':')
485 487
486 488 if len(x) > 1:
487 489 action, action_params = x
488 490
489 491 def get_cs_links():
490 492 revs_limit = 3 # display this amount always
491 493 revs_top_limit = 50 # show upto this amount of changesets hidden
492 494 revs_ids = action_params.split(',')
493 495 deleted = user_log.repository is None
494 496 if deleted:
495 497 return ','.join(revs_ids)
496 498
497 499 repo_name = user_log.repository.repo_name
498 500
499 501 repo = user_log.repository.scm_instance
500 502
501 503 def lnk(rev, repo_name):
502 504
503 505 if isinstance(rev, BaseChangeset):
504 506 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
505 507 _url = url('changeset_home', repo_name=repo_name,
506 508 revision=rev.raw_id)
507 509 title = tooltip(rev.message)
508 510 else:
509 511 lbl = '%s' % rev
510 512 _url = '#'
511 513 title = _('Changeset not found')
512 514
513 515 return link_to(lbl, _url, title=title, class_='tooltip',)
514 516
515 517 revs = []
516 518 if len(filter(lambda v: v != '', revs_ids)) > 0:
517 519 for rev in revs_ids[:revs_top_limit]:
518 520 try:
519 521 rev = repo.get_changeset(rev)
520 522 revs.append(rev)
521 523 except ChangesetDoesNotExistError:
522 524 log.error('cannot find revision %s in this repo' % rev)
523 525 revs.append(rev)
524 526 continue
525 527 cs_links = []
526 528 cs_links.append(" " + ', '.join(
527 529 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
528 530 )
529 531 )
530 532
531 533 compare_view = (
532 534 ' <div class="compare_view tooltip" title="%s">'
533 535 '<a href="%s">%s</a> </div>' % (
534 536 _('Show all combined changesets %s->%s') % (
535 537 revs_ids[0], revs_ids[-1]
536 538 ),
537 539 url('changeset_home', repo_name=repo_name,
538 540 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
539 541 ),
540 542 _('compare view')
541 543 )
542 544 )
543 545
544 546 # if we have exactly one more than normally displayed
545 547 # just display it, takes less space than displaying
546 548 # "and 1 more revisions"
547 549 if len(revs_ids) == revs_limit + 1:
548 550 rev = revs[revs_limit]
549 551 cs_links.append(", " + lnk(rev, repo_name))
550 552
551 553 # hidden-by-default ones
552 554 if len(revs_ids) > revs_limit + 1:
553 555 uniq_id = revs_ids[0]
554 556 html_tmpl = (
555 557 '<span> %s <a class="show_more" id="_%s" '
556 558 'href="#more">%s</a> %s</span>'
557 559 )
558 560 if not feed:
559 561 cs_links.append(html_tmpl % (
560 562 _('and'),
561 563 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
562 564 _('revisions')
563 565 )
564 566 )
565 567
566 568 if not feed:
567 569 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
568 570 else:
569 571 html_tmpl = '<span id="%s"> %s </span>'
570 572
571 573 morelinks = ', '.join(
572 574 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
573 575 )
574 576
575 577 if len(revs_ids) > revs_top_limit:
576 578 morelinks += ', ...'
577 579
578 580 cs_links.append(html_tmpl % (uniq_id, morelinks))
579 581 if len(revs) > 1:
580 582 cs_links.append(compare_view)
581 583 return ''.join(cs_links)
582 584
583 585 def get_fork_name():
584 586 repo_name = action_params
585 587 return _('fork name ') + str(link_to(action_params, url('summary_home',
586 588 repo_name=repo_name,)))
587 589
588 590 def get_user_name():
589 591 user_name = action_params
590 592 return user_name
591 593
592 594 def get_users_group():
593 595 group_name = action_params
594 596 return group_name
595 597
596 598 def get_pull_request():
597 599 pull_request_id = action_params
598 600 repo_name = user_log.repository.repo_name
599 601 return link_to(_('Pull request #%s') % pull_request_id,
600 602 url('pullrequest_show', repo_name=repo_name,
601 603 pull_request_id=pull_request_id))
602 604
603 605 # action : translated str, callback(extractor), icon
604 606 action_map = {
605 607 'user_deleted_repo': (_('[deleted] repository'),
606 608 None, 'database_delete.png'),
607 609 'user_created_repo': (_('[created] repository'),
608 610 None, 'database_add.png'),
609 611 'user_created_fork': (_('[created] repository as fork'),
610 612 None, 'arrow_divide.png'),
611 613 'user_forked_repo': (_('[forked] repository'),
612 614 get_fork_name, 'arrow_divide.png'),
613 615 'user_updated_repo': (_('[updated] repository'),
614 616 None, 'database_edit.png'),
615 617 'admin_deleted_repo': (_('[delete] repository'),
616 618 None, 'database_delete.png'),
617 619 'admin_created_repo': (_('[created] repository'),
618 620 None, 'database_add.png'),
619 621 'admin_forked_repo': (_('[forked] repository'),
620 622 None, 'arrow_divide.png'),
621 623 'admin_updated_repo': (_('[updated] repository'),
622 624 None, 'database_edit.png'),
623 625 'admin_created_user': (_('[created] user'),
624 626 get_user_name, 'user_add.png'),
625 627 'admin_updated_user': (_('[updated] user'),
626 628 get_user_name, 'user_edit.png'),
627 629 'admin_created_users_group': (_('[created] users group'),
628 630 get_users_group, 'group_add.png'),
629 631 'admin_updated_users_group': (_('[updated] users group'),
630 632 get_users_group, 'group_edit.png'),
631 633 'user_commented_revision': (_('[commented] on revision in repository'),
632 634 get_cs_links, 'comment_add.png'),
633 635 'user_commented_pull_request': (_('[commented] on pull request for'),
634 636 get_pull_request, 'comment_add.png'),
635 637 'user_closed_pull_request': (_('[closed] pull request for'),
636 638 get_pull_request, 'tick.png'),
637 639 'push': (_('[pushed] into'),
638 640 get_cs_links, 'script_add.png'),
639 641 'push_local': (_('[committed via RhodeCode] into repository'),
640 642 get_cs_links, 'script_edit.png'),
641 643 'push_remote': (_('[pulled from remote] into repository'),
642 644 get_cs_links, 'connect.png'),
643 645 'pull': (_('[pulled] from'),
644 646 None, 'down_16.png'),
645 647 'started_following_repo': (_('[started following] repository'),
646 648 None, 'heart_add.png'),
647 649 'stopped_following_repo': (_('[stopped following] repository'),
648 650 None, 'heart_delete.png'),
649 651 }
650 652
651 653 action_str = action_map.get(action, action)
652 654 if feed:
653 655 action = action_str[0].replace('[', '').replace(']', '')
654 656 else:
655 657 action = action_str[0]\
656 658 .replace('[', '<span class="journal_highlight">')\
657 659 .replace(']', '</span>')
658 660
659 661 action_params_func = lambda: ""
660 662
661 663 if callable(action_str[1]):
662 664 action_params_func = action_str[1]
663 665
664 666 def action_parser_icon():
665 667 action = user_log.action
666 668 action_params = None
667 669 x = action.split(':')
668 670
669 671 if len(x) > 1:
670 672 action, action_params = x
671 673
672 674 tmpl = """<img src="%s%s" alt="%s"/>"""
673 675 ico = action_map.get(action, ['', '', ''])[2]
674 676 return literal(tmpl % ((url('/images/icons/')), ico, action))
675 677
676 678 # returned callbacks we need to call to get
677 679 return [lambda: literal(action), action_params_func, action_parser_icon]
678 680
679 681
680 682
681 683 #==============================================================================
682 684 # PERMS
683 685 #==============================================================================
684 686 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
685 687 HasRepoPermissionAny, HasRepoPermissionAll
686 688
687 689
688 690 #==============================================================================
689 691 # GRAVATAR URL
690 692 #==============================================================================
691 693
692 694 def gravatar_url(email_address, size=30):
693 695 if (not str2bool(config['app_conf'].get('use_gravatar')) or
694 696 not email_address or email_address == 'anonymous@rhodecode.org'):
695 697 f = lambda a, l: min(l, key=lambda x: abs(x - a))
696 698 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
697 699
698 700 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
699 701 default = 'identicon'
700 702 baseurl_nossl = "http://www.gravatar.com/avatar/"
701 703 baseurl_ssl = "https://secure.gravatar.com/avatar/"
702 704 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
703 705
704 706 if isinstance(email_address, unicode):
705 707 #hashlib crashes on unicode items
706 708 email_address = safe_str(email_address)
707 709 # construct the url
708 710 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
709 711 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
710 712
711 713 return gravatar_url
712 714
713 715
714 716 #==============================================================================
715 717 # REPO PAGER, PAGER FOR REPOSITORY
716 718 #==============================================================================
717 719 class RepoPage(Page):
718 720
719 721 def __init__(self, collection, page=1, items_per_page=20,
720 722 item_count=None, url=None, **kwargs):
721 723
722 724 """Create a "RepoPage" instance. special pager for paging
723 725 repository
724 726 """
725 727 self._url_generator = url
726 728
727 729 # Safe the kwargs class-wide so they can be used in the pager() method
728 730 self.kwargs = kwargs
729 731
730 732 # Save a reference to the collection
731 733 self.original_collection = collection
732 734
733 735 self.collection = collection
734 736
735 737 # The self.page is the number of the current page.
736 738 # The first page has the number 1!
737 739 try:
738 740 self.page = int(page) # make it int() if we get it as a string
739 741 except (ValueError, TypeError):
740 742 self.page = 1
741 743
742 744 self.items_per_page = items_per_page
743 745
744 746 # Unless the user tells us how many items the collections has
745 747 # we calculate that ourselves.
746 748 if item_count is not None:
747 749 self.item_count = item_count
748 750 else:
749 751 self.item_count = len(self.collection)
750 752
751 753 # Compute the number of the first and last available page
752 754 if self.item_count > 0:
753 755 self.first_page = 1
754 756 self.page_count = int(math.ceil(float(self.item_count) /
755 757 self.items_per_page))
756 758 self.last_page = self.first_page + self.page_count - 1
757 759
758 760 # Make sure that the requested page number is the range of
759 761 # valid pages
760 762 if self.page > self.last_page:
761 763 self.page = self.last_page
762 764 elif self.page < self.first_page:
763 765 self.page = self.first_page
764 766
765 767 # Note: the number of items on this page can be less than
766 768 # items_per_page if the last page is not full
767 769 self.first_item = max(0, (self.item_count) - (self.page *
768 770 items_per_page))
769 771 self.last_item = ((self.item_count - 1) - items_per_page *
770 772 (self.page - 1))
771 773
772 774 self.items = list(self.collection[self.first_item:self.last_item + 1])
773 775
774 776 # Links to previous and next page
775 777 if self.page > self.first_page:
776 778 self.previous_page = self.page - 1
777 779 else:
778 780 self.previous_page = None
779 781
780 782 if self.page < self.last_page:
781 783 self.next_page = self.page + 1
782 784 else:
783 785 self.next_page = None
784 786
785 787 # No items available
786 788 else:
787 789 self.first_page = None
788 790 self.page_count = 0
789 791 self.last_page = None
790 792 self.first_item = None
791 793 self.last_item = None
792 794 self.previous_page = None
793 795 self.next_page = None
794 796 self.items = []
795 797
796 798 # This is a subclass of the 'list' type. Initialise the list now.
797 799 list.__init__(self, reversed(self.items))
798 800
799 801
800 802 def changed_tooltip(nodes):
801 803 """
802 804 Generates a html string for changed nodes in changeset page.
803 805 It limits the output to 30 entries
804 806
805 807 :param nodes: LazyNodesGenerator
806 808 """
807 809 if nodes:
808 810 pref = ': <br/> '
809 811 suf = ''
810 812 if len(nodes) > 30:
811 813 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
812 814 return literal(pref + '<br/> '.join([safe_unicode(x.path)
813 815 for x in nodes[:30]]) + suf)
814 816 else:
815 817 return ': ' + _('No Files')
816 818
817 819
818 820 def repo_link(groups_and_repos):
819 821 """
820 822 Makes a breadcrumbs link to repo within a group
821 823 joins &raquo; on each group to create a fancy link
822 824
823 825 ex::
824 826 group >> subgroup >> repo
825 827
826 828 :param groups_and_repos:
827 829 """
828 830 groups, repo_name = groups_and_repos
829 831
830 832 if not groups:
831 833 return repo_name
832 834 else:
833 835 def make_link(group):
834 836 return link_to(group.name, url('repos_group_home',
835 837 group_name=group.group_name))
836 838 return literal(' &raquo; '.join(map(make_link, groups)) + \
837 839 " &raquo; " + repo_name)
838 840
839 841
840 842 def fancy_file_stats(stats):
841 843 """
842 844 Displays a fancy two colored bar for number of added/deleted
843 845 lines of code on file
844 846
845 847 :param stats: two element list of added/deleted lines of code
846 848 """
847 849
848 850 a, d, t = stats[0], stats[1], stats[0] + stats[1]
849 851 width = 100
850 852 unit = float(width) / (t or 1)
851 853
852 854 # needs > 9% of width to be visible or 0 to be hidden
853 855 a_p = max(9, unit * a) if a > 0 else 0
854 856 d_p = max(9, unit * d) if d > 0 else 0
855 857 p_sum = a_p + d_p
856 858
857 859 if p_sum > width:
858 860 #adjust the percentage to be == 100% since we adjusted to 9
859 861 if a_p > d_p:
860 862 a_p = a_p - (p_sum - width)
861 863 else:
862 864 d_p = d_p - (p_sum - width)
863 865
864 866 a_v = a if a > 0 else ''
865 867 d_v = d if d > 0 else ''
866 868
867 869 def cgen(l_type):
868 870 mapping = {'tr': 'top-right-rounded-corner-mid',
869 871 'tl': 'top-left-rounded-corner-mid',
870 872 'br': 'bottom-right-rounded-corner-mid',
871 873 'bl': 'bottom-left-rounded-corner-mid'}
872 874 map_getter = lambda x: mapping[x]
873 875
874 876 if l_type == 'a' and d_v:
875 877 #case when added and deleted are present
876 878 return ' '.join(map(map_getter, ['tl', 'bl']))
877 879
878 880 if l_type == 'a' and not d_v:
879 881 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
880 882
881 883 if l_type == 'd' and a_v:
882 884 return ' '.join(map(map_getter, ['tr', 'br']))
883 885
884 886 if l_type == 'd' and not a_v:
885 887 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
886 888
887 889 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
888 890 cgen('a'), a_p, a_v
889 891 )
890 892 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
891 893 cgen('d'), d_p, d_v
892 894 )
893 895 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
894 896
895 897
896 898 def urlify_text(text_):
897 899 import re
898 900
899 901 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
900 902 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
901 903
902 904 def url_func(match_obj):
903 905 url_full = match_obj.groups()[0]
904 906 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
905 907
906 908 return literal(url_pat.sub(url_func, text_))
907 909
908 910
909 911 def urlify_changesets(text_, repository):
910 912 """
911 913 Extract revision ids from changeset and make link from them
912 914
913 915 :param text_:
914 916 :param repository:
915 917 """
916 918 import re
917 919 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
918 920
919 921 def url_func(match_obj):
920 922 rev = match_obj.groups()[0]
921 923 pref = ''
922 924 if match_obj.group().startswith(' '):
923 925 pref = ' '
924 926 tmpl = (
925 927 '%(pref)s<a class="%(cls)s" href="%(url)s">'
926 928 '%(rev)s'
927 929 '</a>'
928 930 )
929 931 return tmpl % {
930 932 'pref': pref,
931 933 'cls': 'revision-link',
932 934 'url': url('changeset_home', repo_name=repository, revision=rev),
933 935 'rev': rev,
934 936 }
935 937
936 938 newtext = URL_PAT.sub(url_func, text_)
937 939
938 940 return newtext
939 941
940 942
941 943 def urlify_commit(text_, repository=None, link_=None):
942 944 """
943 945 Parses given text message and makes proper links.
944 946 issues are linked to given issue-server, and rest is a changeset link
945 947 if link_ is given, in other case it's a plain text
946 948
947 949 :param text_:
948 950 :param repository:
949 951 :param link_: changeset link
950 952 """
951 953 import re
952 954 import traceback
953 955
954 956 def escaper(string):
955 957 return string.replace('<', '&lt;').replace('>', '&gt;')
956 958
957 959 def linkify_others(t, l):
958 960 urls = re.compile(r'(\<a.*?\<\/a\>)',)
959 961 links = []
960 962 for e in urls.split(t):
961 963 if not urls.match(e):
962 964 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
963 965 else:
964 966 links.append(e)
965 967
966 968 return ''.join(links)
967 969
968 970 # urlify changesets - extrac revisions and make link out of them
969 971 text_ = urlify_changesets(escaper(text_), repository)
970 972
971 973 try:
972 974 conf = config['app_conf']
973 975
974 976 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
975 977
976 978 if URL_PAT:
977 979 ISSUE_SERVER_LNK = conf.get('issue_server_link')
978 980 ISSUE_PREFIX = conf.get('issue_prefix')
979 981
980 982 def url_func(match_obj):
981 983 pref = ''
982 984 if match_obj.group().startswith(' '):
983 985 pref = ' '
984 986
985 987 issue_id = ''.join(match_obj.groups())
986 988 tmpl = (
987 989 '%(pref)s<a class="%(cls)s" href="%(url)s">'
988 990 '%(issue-prefix)s%(id-repr)s'
989 991 '</a>'
990 992 )
991 993 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
992 994 if repository:
993 995 url = url.replace('{repo}', repository)
994 996 repo_name = repository.split(URL_SEP)[-1]
995 997 url = url.replace('{repo_name}', repo_name)
996 998 return tmpl % {
997 999 'pref': pref,
998 1000 'cls': 'issue-tracker-link',
999 1001 'url': url,
1000 1002 'id-repr': issue_id,
1001 1003 'issue-prefix': ISSUE_PREFIX,
1002 1004 'serv': ISSUE_SERVER_LNK,
1003 1005 }
1004 1006
1005 1007 newtext = URL_PAT.sub(url_func, text_)
1006 1008
1007 1009 if link_:
1008 1010 # wrap not links into final link => link_
1009 1011 newtext = linkify_others(newtext, link_)
1010 1012
1011 1013 return literal(newtext)
1012 1014 except:
1013 1015 log.error(traceback.format_exc())
1014 1016 pass
1015 1017
1016 1018 return text_
1017 1019
1018 1020
1019 1021 def rst(source):
1020 1022 return literal('<div class="rst-block">%s</div>' %
1021 1023 MarkupRenderer.rst(source))
1022 1024
1023 1025
1024 1026 def rst_w_mentions(source):
1025 1027 """
1026 1028 Wrapped rst renderer with @mention highlighting
1027 1029
1028 1030 :param source:
1029 1031 """
1030 1032 return literal('<div class="rst-block">%s</div>' %
1031 1033 MarkupRenderer.rst_with_mentions(source))
1032 1034
1033 1035
1034 1036 def changeset_status(repo, revision):
1035 1037 return ChangesetStatusModel().get_status(repo, revision)
1036 1038
1037 1039
1038 1040 def changeset_status_lbl(changeset_status):
1039 1041 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1040 1042
1041 1043
1042 1044 def get_permission_name(key):
1043 1045 return dict(Permission.PERMS).get(key)
@@ -1,1712 +1,1713 b''
1 1 /**
2 2 RhodeCode JS Files
3 3 **/
4 4
5 5 if (typeof console == "undefined" || typeof console.log == "undefined"){
6 6 console = { log: function() {} }
7 7 }
8 8
9 9
10 10 var str_repeat = function(i, m) {
11 11 for (var o = []; m > 0; o[--m] = i);
12 12 return o.join('');
13 13 };
14 14
15 15 /**
16 16 * INJECT .format function into String
17 17 * Usage: "My name is {0} {1}".format("Johny","Bravo")
18 18 * Return "My name is Johny Bravo"
19 19 * Inspired by https://gist.github.com/1049426
20 20 */
21 21 String.prototype.format = function() {
22 22
23 23 function format() {
24 24 var str = this;
25 25 var len = arguments.length+1;
26 26 var safe = undefined;
27 27 var arg = undefined;
28 28
29 29 // For each {0} {1} {n...} replace with the argument in that position. If
30 30 // the argument is an object or an array it will be stringified to JSON.
31 31 for (var i=0; i < len; arg = arguments[i++]) {
32 32 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
33 33 str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
34 34 }
35 35 return str;
36 36 }
37 37
38 38 // Save a reference of what may already exist under the property native.
39 39 // Allows for doing something like: if("".format.native) { /* use native */ }
40 40 format.native = String.prototype.format;
41 41
42 42 // Replace the prototype property
43 43 return format;
44 44
45 45 }();
46 46
47 47 String.prototype.strip = function(char) {
48 48 if(char === undefined){
49 49 char = '\\s';
50 50 }
51 51 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
52 52 }
53 53 String.prototype.lstrip = function(char) {
54 54 if(char === undefined){
55 55 char = '\\s';
56 56 }
57 57 return this.replace(new RegExp('^'+char+'+'),'');
58 58 }
59 59 String.prototype.rstrip = function(char) {
60 60 if(char === undefined){
61 61 char = '\\s';
62 62 }
63 63 return this.replace(new RegExp(''+char+'+$'),'');
64 64 }
65 65
66 66
67 67 if(!Array.prototype.indexOf) {
68 68 Array.prototype.indexOf = function(needle) {
69 69 for(var i = 0; i < this.length; i++) {
70 70 if(this[i] === needle) {
71 71 return i;
72 72 }
73 73 }
74 74 return -1;
75 75 };
76 76 }
77 77
78 78 /**
79 79 * SmartColorGenerator
80 80 *
81 81 *usage::
82 82 * var CG = new ColorGenerator();
83 83 * var col = CG.getColor(key); //returns array of RGB
84 84 * 'rgb({0})'.format(col.join(',')
85 85 *
86 86 * @returns {ColorGenerator}
87 87 */
88 88 var ColorGenerator = function(){
89 89 this.GOLDEN_RATIO = 0.618033988749895;
90 90 this.CURRENT_RATIO = 0.22717784590367374 // this can be random
91 91 this.HSV_1 = 0.75;//saturation
92 92 this.HSV_2 = 0.95;
93 93 this.color;
94 94 this.cacheColorMap = {};
95 95 };
96 96
97 97 ColorGenerator.prototype = {
98 98 getColor:function(key){
99 99 if(this.cacheColorMap[key] !== undefined){
100 100 return this.cacheColorMap[key];
101 101 }
102 102 else{
103 103 this.cacheColorMap[key] = this.generateColor();
104 104 return this.cacheColorMap[key];
105 105 }
106 106 },
107 107 _hsvToRgb:function(h,s,v){
108 108 if (s == 0.0)
109 109 return [v, v, v];
110 110 i = parseInt(h * 6.0)
111 111 f = (h * 6.0) - i
112 112 p = v * (1.0 - s)
113 113 q = v * (1.0 - s * f)
114 114 t = v * (1.0 - s * (1.0 - f))
115 115 i = i % 6
116 116 if (i == 0)
117 117 return [v, t, p]
118 118 if (i == 1)
119 119 return [q, v, p]
120 120 if (i == 2)
121 121 return [p, v, t]
122 122 if (i == 3)
123 123 return [p, q, v]
124 124 if (i == 4)
125 125 return [t, p, v]
126 126 if (i == 5)
127 127 return [v, p, q]
128 128 },
129 129 generateColor:function(){
130 130 this.CURRENT_RATIO = this.CURRENT_RATIO+this.GOLDEN_RATIO;
131 131 this.CURRENT_RATIO = this.CURRENT_RATIO %= 1;
132 132 HSV_tuple = [this.CURRENT_RATIO, this.HSV_1, this.HSV_2]
133 133 RGB_tuple = this._hsvToRgb(HSV_tuple[0],HSV_tuple[1],HSV_tuple[2]);
134 134 function toRgb(v){
135 135 return ""+parseInt(v*256)
136 136 }
137 137 return [toRgb(RGB_tuple[0]),toRgb(RGB_tuple[1]),toRgb(RGB_tuple[2])];
138 138
139 139 }
140 140 }
141 141
142 142
143 143
144 144
145 145
146 146 /**
147 147 * GLOBAL YUI Shortcuts
148 148 */
149 149 var YUC = YAHOO.util.Connect;
150 150 var YUD = YAHOO.util.Dom;
151 151 var YUE = YAHOO.util.Event;
152 152 var YUQ = YAHOO.util.Selector.query;
153 153
154 154 // defines if push state is enabled for this browser ?
155 155 var push_state_enabled = Boolean(
156 156 window.history && window.history.pushState && window.history.replaceState
157 157 && !( /* disable for versions of iOS before version 4.3 (8F190) */
158 158 (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent)
159 159 /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
160 160 || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent)
161 161 )
162 162 );
163 163
164 164 var _run_callbacks = function(callbacks){
165 165 if (callbacks !== undefined){
166 166 var _l = callbacks.length;
167 167 for (var i=0;i<_l;i++){
168 168 var func = callbacks[i];
169 169 if(typeof(func)=='function'){
170 170 try{
171 171 func();
172 172 }catch (err){};
173 173 }
174 174 }
175 175 }
176 176 }
177 177
178 178 /**
179 179 * Partial Ajax Implementation
180 180 *
181 181 * @param url: defines url to make partial request
182 182 * @param container: defines id of container to input partial result
183 183 * @param s_call: success callback function that takes o as arg
184 184 * o.tId
185 185 * o.status
186 186 * o.statusText
187 187 * o.getResponseHeader[ ]
188 188 * o.getAllResponseHeaders
189 189 * o.responseText
190 190 * o.responseXML
191 191 * o.argument
192 192 * @param f_call: failure callback
193 193 * @param args arguments
194 194 */
195 195 function ypjax(url,container,s_call,f_call,args){
196 196 var method='GET';
197 197 if(args===undefined){
198 198 args=null;
199 199 }
200 200
201 201 // Set special header for partial ajax == HTTP_X_PARTIAL_XHR
202 202 YUC.initHeader('X-PARTIAL-XHR',true);
203 203
204 204 // wrapper of passed callback
205 205 var s_wrapper = (function(o){
206 206 return function(o){
207 207 YUD.get(container).innerHTML=o.responseText;
208 208 YUD.setStyle(container,'opacity','1.0');
209 209 //execute the given original callback
210 210 if (s_call !== undefined){
211 211 s_call(o);
212 212 }
213 213 }
214 214 })()
215 215 YUD.setStyle(container,'opacity','0.3');
216 216 YUC.asyncRequest(method,url,{
217 217 success:s_wrapper,
218 218 failure:function(o){
219 219 console.log(o);
220 220 YUD.get(container).innerHTML='ERROR '+o.status;
221 221 YUD.setStyle(container,'opacity','1.0');
222 222 YUD.setStyle(container,'color','red');
223 223 }
224 224 },args);
225 225
226 226 };
227 227
228 228 var ajaxPOST = function(url,postData,success) {
229 229 // Set special header for ajax == HTTP_X_PARTIAL_XHR
230 230 YUC.initHeader('X-PARTIAL-XHR',true);
231 231
232 232 var toQueryString = function(o) {
233 233 if(typeof o !== 'object') {
234 234 return false;
235 235 }
236 236 var _p, _qs = [];
237 237 for(_p in o) {
238 238 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
239 239 }
240 240 return _qs.join('&');
241 241 };
242 242
243 243 var sUrl = url;
244 244 var callback = {
245 245 success: success,
246 246 failure: function (o) {
247 247 alert("error");
248 248 },
249 249 };
250 250 var postData = toQueryString(postData);
251 251 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
252 252 return request;
253 253 };
254 254
255 255
256 256 /**
257 257 * tooltip activate
258 258 */
259 259 var tooltip_activate = function(){
260 260 function toolTipsId(){
261 261 var ids = [];
262 262 var tts = YUQ('.tooltip');
263 263 for (var i = 0; i < tts.length; i++) {
264 264 // if element doesn't not have and id
265 265 // autogenerate one for tooltip
266 266 if (!tts[i].id){
267 267 tts[i].id='tt'+((i*100)+tts.length);
268 268 }
269 269 ids.push(tts[i].id);
270 270 }
271 271 return ids
272 272 };
273 273 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
274 274 context: [[toolTipsId()],"tl","bl",null,[0,5]],
275 275 monitorresize:false,
276 276 xyoffset :[0,0],
277 277 autodismissdelay:300000,
278 278 hidedelay:5,
279 279 showdelay:20,
280 280 });
281 281 };
282 282
283 283 /**
284 284 * show more
285 285 */
286 286 var show_more_event = function(){
287 287 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
288 288 var el = e.target;
289 289 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
290 290 YUD.setStyle(el.parentNode,'display','none');
291 291 });
292 292 };
293 293
294 294
295 295 /**
296 296 * Quick filter widget
297 297 *
298 298 * @param target: filter input target
299 299 * @param nodes: list of nodes in html we want to filter.
300 300 * @param display_element function that takes current node from nodes and
301 301 * does hide or show based on the node
302 302 *
303 303 */
304 304 var q_filter = function(target,nodes,display_element){
305 305
306 306 var nodes = nodes;
307 307 var q_filter_field = YUD.get(target);
308 308 var F = YAHOO.namespace(target);
309 309
310 310 YUE.on(q_filter_field,'click',function(){
311 311 q_filter_field.value = '';
312 312 });
313 313
314 314 YUE.on(q_filter_field,'keyup',function(e){
315 315 clearTimeout(F.filterTimeout);
316 316 F.filterTimeout = setTimeout(F.updateFilter,600);
317 317 });
318 318
319 319 F.filterTimeout = null;
320 320
321 321 var show_node = function(node){
322 322 YUD.setStyle(node,'display','')
323 323 }
324 324 var hide_node = function(node){
325 325 YUD.setStyle(node,'display','none');
326 326 }
327 327
328 328 F.updateFilter = function() {
329 329 // Reset timeout
330 330 F.filterTimeout = null;
331 331
332 332 var obsolete = [];
333 333
334 334 var req = q_filter_field.value.toLowerCase();
335 335
336 336 var l = nodes.length;
337 337 var i;
338 338 var showing = 0;
339 339
340 340 for (i=0;i<l;i++ ){
341 341 var n = nodes[i];
342 342 var target_element = display_element(n)
343 343 if(req && n.innerHTML.toLowerCase().indexOf(req) == -1){
344 344 hide_node(target_element);
345 345 }
346 346 else{
347 347 show_node(target_element);
348 348 showing+=1;
349 349 }
350 350 }
351 351
352 352 // if repo_count is set update the number
353 353 var cnt = YUD.get('repo_count');
354 354 if(cnt){
355 355 YUD.get('repo_count').innerHTML = showing;
356 356 }
357 357
358 358 }
359 359 };
360 360
361 361 var tableTr = function(cls,body){
362 362 var tr = document.createElement('tr');
363 363 YUD.addClass(tr, cls);
364 364
365 365
366 366 var cont = new YAHOO.util.Element(body);
367 367 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
368 368 tr.id = 'comment-tr-{0}'.format(comment_id);
369 369 tr.innerHTML = '<td class="lineno-inline new-inline"></td>'+
370 370 '<td class="lineno-inline old-inline"></td>'+
371 371 '<td>{0}</td>'.format(body);
372 372 return tr;
373 373 };
374 374
375 375 /** comments **/
376 376 var removeInlineForm = function(form) {
377 377 form.parentNode.removeChild(form);
378 378 };
379 379
380 380 var createInlineForm = function(parent_tr, f_path, line) {
381 381 var tmpl = YUD.get('comment-inline-form-template').innerHTML;
382 382 tmpl = tmpl.format(f_path, line);
383 383 var form = tableTr('comment-form-inline',tmpl)
384 384
385 385 // create event for hide button
386 386 form = new YAHOO.util.Element(form);
387 387 var form_hide_button = new YAHOO.util.Element(form.getElementsByClassName('hide-inline-form')[0]);
388 388 form_hide_button.on('click', function(e) {
389 389 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
390 390 if(YUD.hasClass(newtr.nextElementSibling,'inline-comments-button')){
391 391 YUD.setStyle(newtr.nextElementSibling,'display','');
392 392 }
393 393 removeInlineForm(newtr);
394 394 YUD.removeClass(parent_tr, 'form-open');
395 395
396 396 });
397 397
398 398 return form
399 399 };
400 400
401 401 /**
402 402 * Inject inline comment for on given TR this tr should be always an .line
403 403 * tr containing the line. Code will detect comment, and always put the comment
404 404 * block at the very bottom
405 405 */
406 406 var injectInlineForm = function(tr){
407 407 if(!YUD.hasClass(tr, 'line')){
408 408 return
409 409 }
410 410 var submit_url = AJAX_COMMENT_URL;
411 411 var _td = tr.getElementsByClassName('code')[0];
412 412 if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(_td,'no-comment')){
413 413 return
414 414 }
415 415 YUD.addClass(tr,'form-open');
416 416 var node = tr.parentNode.parentNode.parentNode.getElementsByClassName('full_f_path')[0];
417 417 var f_path = YUD.getAttribute(node,'path');
418 418 var lineno = getLineNo(tr);
419 419 var form = createInlineForm(tr, f_path, lineno, submit_url);
420 420
421 421 var parent = tr;
422 422 while (1){
423 423 var n = parent.nextElementSibling;
424 424 // next element are comments !
425 425 if(YUD.hasClass(n,'inline-comments')){
426 426 parent = n;
427 427 }
428 428 else{
429 429 break;
430 430 }
431 431 }
432 432 YUD.insertAfter(form,parent);
433 433
434 434 var f = YUD.get(form);
435 435
436 436 var overlay = f.getElementsByClassName('overlay')[0];
437 437 var _form = f.getElementsByClassName('inline-form')[0];
438 438
439 439 form.on('submit',function(e){
440 440 YUE.preventDefault(e);
441 441
442 442 //ajax submit
443 443 var text = YUD.get('text_'+lineno).value;
444 444 var postData = {
445 445 'text':text,
446 446 'f_path':f_path,
447 447 'line':lineno
448 448 };
449 449
450 450 if(lineno === undefined){
451 451 alert('missing line !');
452 452 return
453 453 }
454 454 if(f_path === undefined){
455 455 alert('missing file path !');
456 456 return
457 457 }
458 458
459 459 if(text == ""){
460 460 return
461 461 }
462 462
463 463 var success = function(o){
464 464 YUD.removeClass(tr, 'form-open');
465 465 removeInlineForm(f);
466 466 var json_data = JSON.parse(o.responseText);
467 467 renderInlineComment(json_data);
468 468 };
469 469
470 470 if (YUD.hasClass(overlay,'overlay')){
471 471 var w = _form.offsetWidth;
472 472 var h = _form.offsetHeight;
473 473 YUD.setStyle(overlay,'width',w+'px');
474 474 YUD.setStyle(overlay,'height',h+'px');
475 475 }
476 476 YUD.addClass(overlay, 'submitting');
477 477
478 478 ajaxPOST(submit_url, postData, success);
479 479 });
480 480
481 481 setTimeout(function(){
482 482 // callbacks
483 483 tooltip_activate();
484 484 MentionsAutoComplete('text_'+lineno, 'mentions_container_'+lineno,
485 485 _USERS_AC_DATA, _GROUPS_AC_DATA);
486 486 YUD.get('text_'+lineno).focus();
487 487 },10)
488 488 };
489 489
490 490 var deleteComment = function(comment_id){
491 491 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
492 492 var postData = {'_method':'delete'};
493 493 var success = function(o){
494 494 var n = YUD.get('comment-tr-'+comment_id);
495 495 var root = n.previousElementSibling.previousElementSibling;
496 496 n.parentNode.removeChild(n);
497 497
498 498 // scann nodes, and attach add button to last one
499 499 placeAddButton(root);
500 500 }
501 501 ajaxPOST(url,postData,success);
502 502 }
503 503
504 504 var updateReviewers = function(reviewers_ids){
505 505 var url = AJAX_UPDATE_PULLREQUEST;
506 506 var postData = {'_method':'put',
507 507 'reviewers_ids': reviewers_ids};
508 508 var success = function(o){
509 509 window.location.reload();
510 510 }
511 511 ajaxPOST(url,postData,success);
512 512 }
513 513
514 514 var createInlineAddButton = function(tr){
515 515
516 516 var label = TRANSLATION_MAP['add another comment'];
517 517
518 518 var html_el = document.createElement('div');
519 519 YUD.addClass(html_el, 'add-comment');
520 520 html_el.innerHTML = '<span class="ui-btn">{0}</span>'.format(label);
521 521
522 522 var add = new YAHOO.util.Element(html_el);
523 523 add.on('click', function(e) {
524 524 injectInlineForm(tr);
525 525 });
526 526 return add;
527 527 };
528 528
529 529 var getLineNo = function(tr) {
530 530 var line;
531 531 var o = tr.children[0].id.split('_');
532 532 var n = tr.children[1].id.split('_');
533 533
534 534 if (n.length >= 2) {
535 535 line = n[n.length-1];
536 536 } else if (o.length >= 2) {
537 537 line = o[o.length-1];
538 538 }
539 539
540 540 return line
541 541 };
542 542
543 543 var placeAddButton = function(target_tr){
544 544 if(!target_tr){
545 545 return
546 546 }
547 547 var last_node = target_tr;
548 548 //scann
549 549 while (1){
550 550 var n = last_node.nextElementSibling;
551 551 // next element are comments !
552 552 if(YUD.hasClass(n,'inline-comments')){
553 553 last_node = n;
554 554 //also remove the comment button from previos
555 555 var comment_add_buttons = last_node.getElementsByClassName('add-comment');
556 556 for(var i=0;i<comment_add_buttons.length;i++){
557 557 var b = comment_add_buttons[i];
558 558 b.parentNode.removeChild(b);
559 559 }
560 560 }
561 561 else{
562 562 break;
563 563 }
564 564 }
565 565
566 566 var add = createInlineAddButton(target_tr);
567 567 // get the comment div
568 568 var comment_block = last_node.getElementsByClassName('comment')[0];
569 569 // attach add button
570 570 YUD.insertAfter(add,comment_block);
571 571 }
572 572
573 573 /**
574 574 * Places the inline comment into the changeset block in proper line position
575 575 */
576 576 var placeInline = function(target_container,lineno,html){
577 577 var lineid = "{0}_{1}".format(target_container,lineno);
578 578 var target_line = YUD.get(lineid);
579 579 var comment = new YAHOO.util.Element(tableTr('inline-comments',html))
580 580
581 581 // check if there are comments already !
582 582 var parent = target_line.parentNode;
583 583 var root_parent = parent;
584 584 while (1){
585 585 var n = parent.nextElementSibling;
586 586 // next element are comments !
587 587 if(YUD.hasClass(n,'inline-comments')){
588 588 parent = n;
589 589 }
590 590 else{
591 591 break;
592 592 }
593 593 }
594 594 // put in the comment at the bottom
595 595 YUD.insertAfter(comment,parent);
596 596
597 597 // scann nodes, and attach add button to last one
598 598 placeAddButton(root_parent);
599 599
600 600 return target_line;
601 601 }
602 602
603 603 /**
604 604 * make a single inline comment and place it inside
605 605 */
606 606 var renderInlineComment = function(json_data){
607 607 try{
608 608 var html = json_data['rendered_text'];
609 609 var lineno = json_data['line_no'];
610 610 var target_id = json_data['target_id'];
611 611 placeInline(target_id, lineno, html);
612 612
613 613 }catch(e){
614 614 console.log(e);
615 615 }
616 616 }
617 617
618 618 /**
619 619 * Iterates over all the inlines, and places them inside proper blocks of data
620 620 */
621 621 var renderInlineComments = function(file_comments){
622 622 for (f in file_comments){
623 623 // holding all comments for a FILE
624 624 var box = file_comments[f];
625 625
626 626 var target_id = YUD.getAttribute(box,'target_id');
627 627 // actually comments with line numbers
628 628 var comments = box.children;
629 629 for(var i=0; i<comments.length; i++){
630 630 var data = {
631 631 'rendered_text': comments[i].outerHTML,
632 632 'line_no': YUD.getAttribute(comments[i],'line'),
633 633 'target_id': target_id
634 634 }
635 635 renderInlineComment(data);
636 636 }
637 637 }
638 638 }
639 639
640 640 var removeReviewer = function(reviewer_id){
641 641 var el = YUD.get('reviewer_{0}'.format(reviewer_id));
642 642 if (el.parentNode !== undefined){
643 643 el.parentNode.removeChild(el);
644 644 }
645 645 }
646 646
647 647 var fileBrowserListeners = function(current_url, node_list_url, url_base){
648 648
649 649 var current_url_branch = +"?branch=__BRANCH__";
650 650 var url = url_base;
651 651 var node_url = node_list_url;
652 652
653 653 YUE.on('stay_at_branch','click',function(e){
654 654 if(e.target.checked){
655 655 var uri = current_url_branch;
656 656 uri = uri.replace('__BRANCH__',e.target.value);
657 657 window.location = uri;
658 658 }
659 659 else{
660 660 window.location = current_url;
661 661 }
662 662 })
663 663
664 664 var n_filter = YUD.get('node_filter');
665 665 var F = YAHOO.namespace('node_filter');
666 666
667 667 F.filterTimeout = null;
668 668 var nodes = null;
669 669
670 670 F.initFilter = function(){
671 671 YUD.setStyle('node_filter_box_loading','display','');
672 672 YUD.setStyle('search_activate_id','display','none');
673 673 YUD.setStyle('add_node_id','display','none');
674 674 YUC.initHeader('X-PARTIAL-XHR',true);
675 675 YUC.asyncRequest('GET',url,{
676 676 success:function(o){
677 677 nodes = JSON.parse(o.responseText).nodes;
678 678 YUD.setStyle('node_filter_box_loading','display','none');
679 679 YUD.setStyle('node_filter_box','display','');
680 680 n_filter.focus();
681 681 if(YUD.hasClass(n_filter,'init')){
682 682 n_filter.value = '';
683 683 YUD.removeClass(n_filter,'init');
684 684 }
685 685 },
686 686 failure:function(o){
687 687 console.log('failed to load');
688 688 }
689 689 },null);
690 690 }
691 691
692 692 F.updateFilter = function(e) {
693 693
694 694 return function(){
695 695 // Reset timeout
696 696 F.filterTimeout = null;
697 697 var query = e.target.value.toLowerCase();
698 698 var match = [];
699 699 var matches = 0;
700 700 var matches_max = 20;
701 701 if (query != ""){
702 702 for(var i=0;i<nodes.length;i++){
703 703
704 704 var pos = nodes[i].name.toLowerCase().indexOf(query)
705 705 if(query && pos != -1){
706 706
707 707 matches++
708 708 //show only certain amount to not kill browser
709 709 if (matches > matches_max){
710 710 break;
711 711 }
712 712
713 713 var n = nodes[i].name;
714 714 var t = nodes[i].type;
715 715 var n_hl = n.substring(0,pos)
716 716 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
717 +n.substring(pos+query.length)
718 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,node_url.replace('__FPATH__',n),n_hl));
717 +n.substring(pos+query.length)
718 node_url = node_url.replace('__FPATH__',n);
719 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,node_url,n_hl));
719 720 }
720 721 if(match.length >= matches_max){
721 722 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['search truncated']));
722 723 }
723 724 }
724 725 }
725 726 if(query != ""){
726 727 YUD.setStyle('tbody','display','none');
727 728 YUD.setStyle('tbody_filtered','display','');
728 729
729 730 if (match.length==0){
730 731 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['no matching files']));
731 732 }
732 733
733 734 YUD.get('tbody_filtered').innerHTML = match.join("");
734 735 }
735 736 else{
736 737 YUD.setStyle('tbody','display','');
737 738 YUD.setStyle('tbody_filtered','display','none');
738 739 }
739 740
740 741 }
741 742 };
742 743
743 744 YUE.on(YUD.get('filter_activate'),'click',function(){
744 745 F.initFilter();
745 746 })
746 747 YUE.on(n_filter,'click',function(){
747 748 if(YUD.hasClass(n_filter,'init')){
748 749 n_filter.value = '';
749 750 YUD.removeClass(n_filter,'init');
750 751 }
751 752 });
752 753 YUE.on(n_filter,'keyup',function(e){
753 754 clearTimeout(F.filterTimeout);
754 755 F.filterTimeout = setTimeout(F.updateFilter(e),600);
755 756 });
756 757 };
757 758
758 759
759 760 var initCodeMirror = function(textAreadId,resetUrl){
760 761 var myCodeMirror = CodeMirror.fromTextArea(YUD.get(textAreadId),{
761 762 mode: "null",
762 763 lineNumbers:true
763 764 });
764 765 YUE.on('reset','click',function(e){
765 766 window.location=resetUrl
766 767 });
767 768
768 769 YUE.on('file_enable','click',function(){
769 770 YUD.setStyle('editor_container','display','');
770 771 YUD.setStyle('upload_file_container','display','none');
771 772 YUD.setStyle('filename_container','display','');
772 773 });
773 774
774 775 YUE.on('upload_file_enable','click',function(){
775 776 YUD.setStyle('editor_container','display','none');
776 777 YUD.setStyle('upload_file_container','display','');
777 778 YUD.setStyle('filename_container','display','none');
778 779 });
779 780 };
780 781
781 782
782 783
783 784 var getIdentNode = function(n){
784 785 //iterate thru nodes untill matched interesting node !
785 786
786 787 if (typeof n == 'undefined'){
787 788 return -1
788 789 }
789 790
790 791 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
791 792 return n
792 793 }
793 794 else{
794 795 return getIdentNode(n.parentNode);
795 796 }
796 797 };
797 798
798 799 var getSelectionLink = function(selection_link_label) {
799 800 return function(){
800 801 //get selection from start/to nodes
801 802 if (typeof window.getSelection != "undefined") {
802 803 s = window.getSelection();
803 804
804 805 from = getIdentNode(s.anchorNode);
805 806 till = getIdentNode(s.focusNode);
806 807
807 808 f_int = parseInt(from.id.replace('L',''));
808 809 t_int = parseInt(till.id.replace('L',''));
809 810
810 811 if (f_int > t_int){
811 812 //highlight from bottom
812 813 offset = -35;
813 814 ranges = [t_int,f_int];
814 815
815 816 }
816 817 else{
817 818 //highligth from top
818 819 offset = 35;
819 820 ranges = [f_int,t_int];
820 821 }
821 822
822 823 if (ranges[0] != ranges[1]){
823 824 if(YUD.get('linktt') == null){
824 825 hl_div = document.createElement('div');
825 826 hl_div.id = 'linktt';
826 827 }
827 828 anchor = '#L'+ranges[0]+'-'+ranges[1];
828 829 hl_div.innerHTML = '';
829 830 l = document.createElement('a');
830 831 l.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
831 832 l.innerHTML = selection_link_label;
832 833 hl_div.appendChild(l);
833 834
834 835 YUD.get('body').appendChild(hl_div);
835 836
836 837 xy = YUD.getXY(till.id);
837 838
838 839 YUD.addClass('linktt','yui-tt');
839 840 YUD.setStyle('linktt','top',xy[1]+offset+'px');
840 841 YUD.setStyle('linktt','left',xy[0]+'px');
841 842 YUD.setStyle('linktt','visibility','visible');
842 843 }
843 844 else{
844 845 YUD.setStyle('linktt','visibility','hidden');
845 846 }
846 847 }
847 848 }
848 849 };
849 850
850 851 var deleteNotification = function(url, notification_id,callbacks){
851 852 var callback = {
852 853 success:function(o){
853 854 var obj = YUD.get(String("notification_"+notification_id));
854 855 if(obj.parentNode !== undefined){
855 856 obj.parentNode.removeChild(obj);
856 857 }
857 858 _run_callbacks(callbacks);
858 859 },
859 860 failure:function(o){
860 861 alert("error");
861 862 },
862 863 };
863 864 var postData = '_method=delete';
864 865 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
865 866 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
866 867 callback, postData);
867 868 };
868 869
869 870 var readNotification = function(url, notification_id,callbacks){
870 871 var callback = {
871 872 success:function(o){
872 873 var obj = YUD.get(String("notification_"+notification_id));
873 874 YUD.removeClass(obj, 'unread');
874 875 var r_button = obj.children[0].getElementsByClassName('read-notification')[0]
875 876
876 877 if(r_button.parentNode !== undefined){
877 878 r_button.parentNode.removeChild(r_button);
878 879 }
879 880 _run_callbacks(callbacks);
880 881 },
881 882 failure:function(o){
882 883 alert("error");
883 884 },
884 885 };
885 886 var postData = '_method=put';
886 887 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
887 888 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
888 889 callback, postData);
889 890 };
890 891
891 892 /** MEMBERS AUTOCOMPLETE WIDGET **/
892 893
893 894 var MembersAutoComplete = function (users_list, groups_list) {
894 895 var myUsers = users_list;
895 896 var myGroups = groups_list;
896 897
897 898 // Define a custom search function for the DataSource of users
898 899 var matchUsers = function (sQuery) {
899 900 // Case insensitive matching
900 901 var query = sQuery.toLowerCase();
901 902 var i = 0;
902 903 var l = myUsers.length;
903 904 var matches = [];
904 905
905 906 // Match against each name of each contact
906 907 for (; i < l; i++) {
907 908 contact = myUsers[i];
908 909 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
909 910 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
910 911 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
911 912 matches[matches.length] = contact;
912 913 }
913 914 }
914 915 return matches;
915 916 };
916 917
917 918 // Define a custom search function for the DataSource of usersGroups
918 919 var matchGroups = function (sQuery) {
919 920 // Case insensitive matching
920 921 var query = sQuery.toLowerCase();
921 922 var i = 0;
922 923 var l = myGroups.length;
923 924 var matches = [];
924 925
925 926 // Match against each name of each contact
926 927 for (; i < l; i++) {
927 928 matched_group = myGroups[i];
928 929 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
929 930 matches[matches.length] = matched_group;
930 931 }
931 932 }
932 933 return matches;
933 934 };
934 935
935 936 //match all
936 937 var matchAll = function (sQuery) {
937 938 u = matchUsers(sQuery);
938 939 g = matchGroups(sQuery);
939 940 return u.concat(g);
940 941 };
941 942
942 943 // DataScheme for members
943 944 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
944 945 memberDS.responseSchema = {
945 946 fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk"]
946 947 };
947 948
948 949 // DataScheme for owner
949 950 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
950 951 ownerDS.responseSchema = {
951 952 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
952 953 };
953 954
954 955 // Instantiate AutoComplete for perms
955 956 var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
956 957 membersAC.useShadow = false;
957 958 membersAC.resultTypeList = false;
958 959 membersAC.animVert = false;
959 960 membersAC.animHoriz = false;
960 961 membersAC.animSpeed = 0.1;
961 962
962 963 // Instantiate AutoComplete for owner
963 964 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
964 965 ownerAC.useShadow = false;
965 966 ownerAC.resultTypeList = false;
966 967 ownerAC.animVert = false;
967 968 ownerAC.animHoriz = false;
968 969 ownerAC.animSpeed = 0.1;
969 970
970 971 // Helper highlight function for the formatter
971 972 var highlightMatch = function (full, snippet, matchindex) {
972 973 return full.substring(0, matchindex)
973 974 + "<span class='match'>"
974 975 + full.substr(matchindex, snippet.length)
975 976 + "</span>" + full.substring(matchindex + snippet.length);
976 977 };
977 978
978 979 // Custom formatter to highlight the matching letters
979 980 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
980 981 var query = sQuery.toLowerCase();
981 982 var _gravatar = function(res, em, group){
982 983 if (group !== undefined){
983 984 em = '/images/icons/group.png'
984 985 }
985 986 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
986 987 return tmpl.format(em,res)
987 988 }
988 989 // group
989 990 if (oResultData.grname != undefined) {
990 991 var grname = oResultData.grname;
991 992 var grmembers = oResultData.grmembers;
992 993 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
993 994 var grprefix = "{0}: ".format(_TM['Group']);
994 995 var grsuffix = " (" + grmembers + " )";
995 996 var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
996 997
997 998 if (grnameMatchIndex > -1) {
998 999 return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,true);
999 1000 }
1000 1001 return _gravatar(grprefix + oResultData.grname + grsuffix, null,true);
1001 1002 // Users
1002 1003 } else if (oResultData.nname != undefined) {
1003 1004 var fname = oResultData.fname || "";
1004 1005 var lname = oResultData.lname || "";
1005 1006 var nname = oResultData.nname;
1006 1007
1007 1008 // Guard against null value
1008 1009 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1009 1010 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1010 1011 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1011 1012 displayfname, displaylname, displaynname;
1012 1013
1013 1014 if (fnameMatchIndex > -1) {
1014 1015 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1015 1016 } else {
1016 1017 displayfname = fname;
1017 1018 }
1018 1019
1019 1020 if (lnameMatchIndex > -1) {
1020 1021 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1021 1022 } else {
1022 1023 displaylname = lname;
1023 1024 }
1024 1025
1025 1026 if (nnameMatchIndex > -1) {
1026 1027 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1027 1028 } else {
1028 1029 displaynname = nname ? "(" + nname + ")" : "";
1029 1030 }
1030 1031
1031 1032 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1032 1033 } else {
1033 1034 return '';
1034 1035 }
1035 1036 };
1036 1037 membersAC.formatResult = custom_formatter;
1037 1038 ownerAC.formatResult = custom_formatter;
1038 1039
1039 1040 var myHandler = function (sType, aArgs) {
1040 1041
1041 1042 var myAC = aArgs[0]; // reference back to the AC instance
1042 1043 var elLI = aArgs[1]; // reference to the selected LI element
1043 1044 var oData = aArgs[2]; // object literal of selected item's result data
1044 1045 //fill the autocomplete with value
1045 1046 if (oData.nname != undefined) {
1046 1047 //users
1047 1048 myAC.getInputEl().value = oData.nname;
1048 1049 YUD.get('perm_new_member_type').value = 'user';
1049 1050 } else {
1050 1051 //groups
1051 1052 myAC.getInputEl().value = oData.grname;
1052 1053 YUD.get('perm_new_member_type').value = 'users_group';
1053 1054 }
1054 1055 };
1055 1056
1056 1057 membersAC.itemSelectEvent.subscribe(myHandler);
1057 1058 if(ownerAC.itemSelectEvent){
1058 1059 ownerAC.itemSelectEvent.subscribe(myHandler);
1059 1060 }
1060 1061
1061 1062 return {
1062 1063 memberDS: memberDS,
1063 1064 ownerDS: ownerDS,
1064 1065 membersAC: membersAC,
1065 1066 ownerAC: ownerAC,
1066 1067 };
1067 1068 }
1068 1069
1069 1070
1070 1071 var MentionsAutoComplete = function (divid, cont, users_list, groups_list) {
1071 1072 var myUsers = users_list;
1072 1073 var myGroups = groups_list;
1073 1074
1074 1075 // Define a custom search function for the DataSource of users
1075 1076 var matchUsers = function (sQuery) {
1076 1077 var org_sQuery = sQuery;
1077 1078 if(this.mentionQuery == null){
1078 1079 return []
1079 1080 }
1080 1081 sQuery = this.mentionQuery;
1081 1082 // Case insensitive matching
1082 1083 var query = sQuery.toLowerCase();
1083 1084 var i = 0;
1084 1085 var l = myUsers.length;
1085 1086 var matches = [];
1086 1087
1087 1088 // Match against each name of each contact
1088 1089 for (; i < l; i++) {
1089 1090 contact = myUsers[i];
1090 1091 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1091 1092 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1092 1093 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1093 1094 matches[matches.length] = contact;
1094 1095 }
1095 1096 }
1096 1097 return matches
1097 1098 };
1098 1099
1099 1100 //match all
1100 1101 var matchAll = function (sQuery) {
1101 1102 u = matchUsers(sQuery);
1102 1103 return u
1103 1104 };
1104 1105
1105 1106 // DataScheme for owner
1106 1107 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1107 1108
1108 1109 ownerDS.responseSchema = {
1109 1110 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1110 1111 };
1111 1112
1112 1113 // Instantiate AutoComplete for mentions
1113 1114 var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1114 1115 ownerAC.useShadow = false;
1115 1116 ownerAC.resultTypeList = false;
1116 1117 ownerAC.suppressInputUpdate = true;
1117 1118 ownerAC.animVert = false;
1118 1119 ownerAC.animHoriz = false;
1119 1120 ownerAC.animSpeed = 0.1;
1120 1121
1121 1122 // Helper highlight function for the formatter
1122 1123 var highlightMatch = function (full, snippet, matchindex) {
1123 1124 return full.substring(0, matchindex)
1124 1125 + "<span class='match'>"
1125 1126 + full.substr(matchindex, snippet.length)
1126 1127 + "</span>" + full.substring(matchindex + snippet.length);
1127 1128 };
1128 1129
1129 1130 // Custom formatter to highlight the matching letters
1130 1131 ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1131 1132 var org_sQuery = sQuery;
1132 1133 if(this.dataSource.mentionQuery != null){
1133 1134 sQuery = this.dataSource.mentionQuery;
1134 1135 }
1135 1136
1136 1137 var query = sQuery.toLowerCase();
1137 1138 var _gravatar = function(res, em, group){
1138 1139 if (group !== undefined){
1139 1140 em = '/images/icons/group.png'
1140 1141 }
1141 1142 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1142 1143 return tmpl.format(em,res)
1143 1144 }
1144 1145 if (oResultData.nname != undefined) {
1145 1146 var fname = oResultData.fname || "";
1146 1147 var lname = oResultData.lname || "";
1147 1148 var nname = oResultData.nname;
1148 1149
1149 1150 // Guard against null value
1150 1151 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1151 1152 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1152 1153 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1153 1154 displayfname, displaylname, displaynname;
1154 1155
1155 1156 if (fnameMatchIndex > -1) {
1156 1157 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1157 1158 } else {
1158 1159 displayfname = fname;
1159 1160 }
1160 1161
1161 1162 if (lnameMatchIndex > -1) {
1162 1163 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1163 1164 } else {
1164 1165 displaylname = lname;
1165 1166 }
1166 1167
1167 1168 if (nnameMatchIndex > -1) {
1168 1169 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1169 1170 } else {
1170 1171 displaynname = nname ? "(" + nname + ")" : "";
1171 1172 }
1172 1173
1173 1174 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1174 1175 } else {
1175 1176 return '';
1176 1177 }
1177 1178 };
1178 1179
1179 1180 if(ownerAC.itemSelectEvent){
1180 1181 ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1181 1182
1182 1183 var myAC = aArgs[0]; // reference back to the AC instance
1183 1184 var elLI = aArgs[1]; // reference to the selected LI element
1184 1185 var oData = aArgs[2]; // object literal of selected item's result data
1185 1186 //fill the autocomplete with value
1186 1187 if (oData.nname != undefined) {
1187 1188 //users
1188 1189 //Replace the mention name with replaced
1189 1190 var re = new RegExp();
1190 1191 var org = myAC.getInputEl().value;
1191 1192 var chunks = myAC.dataSource.chunks
1192 1193 // replace middle chunk(the search term) with actuall match
1193 1194 chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
1194 1195 '@'+oData.nname+' ');
1195 1196 myAC.getInputEl().value = chunks.join('')
1196 1197 YUD.get(myAC.getInputEl()).focus(); // Y U NO WORK !?
1197 1198 } else {
1198 1199 //groups
1199 1200 myAC.getInputEl().value = oData.grname;
1200 1201 YUD.get('perm_new_member_type').value = 'users_group';
1201 1202 }
1202 1203 });
1203 1204 }
1204 1205
1205 1206 // in this keybuffer we will gather current value of search !
1206 1207 // since we need to get this just when someone does `@` then we do the
1207 1208 // search
1208 1209 ownerAC.dataSource.chunks = [];
1209 1210 ownerAC.dataSource.mentionQuery = null;
1210 1211
1211 1212 ownerAC.get_mention = function(msg, max_pos) {
1212 1213 var org = msg;
1213 1214 var re = new RegExp('(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)$')
1214 1215 var chunks = [];
1215 1216
1216 1217
1217 1218 // cut first chunk until curret pos
1218 1219 var to_max = msg.substr(0, max_pos);
1219 1220 var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
1220 1221 var msg2 = to_max.substr(at_pos);
1221 1222
1222 1223 chunks.push(org.substr(0,at_pos))// prefix chunk
1223 1224 chunks.push(msg2) // search chunk
1224 1225 chunks.push(org.substr(max_pos)) // postfix chunk
1225 1226
1226 1227 // clean up msg2 for filtering and regex match
1227 1228 var msg2 = msg2.lstrip(' ').lstrip('\n');
1228 1229
1229 1230 if(re.test(msg2)){
1230 1231 var unam = re.exec(msg2)[1];
1231 1232 return [unam, chunks];
1232 1233 }
1233 1234 return [null, null];
1234 1235 };
1235 1236
1236 1237 ownerAC.textboxKeyUpEvent.subscribe(function(type, args){
1237 1238
1238 1239 var ac_obj = args[0];
1239 1240 var currentMessage = args[1];
1240 1241 var currentCaretPosition = args[0]._elTextbox.selectionStart;
1241 1242
1242 1243 var unam = ownerAC.get_mention(currentMessage, currentCaretPosition);
1243 1244 var curr_search = null;
1244 1245 if(unam[0]){
1245 1246 curr_search = unam[0];
1246 1247 }
1247 1248
1248 1249 ownerAC.dataSource.chunks = unam[1];
1249 1250 ownerAC.dataSource.mentionQuery = curr_search;
1250 1251
1251 1252 })
1252 1253
1253 1254 return {
1254 1255 ownerDS: ownerDS,
1255 1256 ownerAC: ownerAC,
1256 1257 };
1257 1258 }
1258 1259
1259 1260
1260 1261 var PullRequestAutoComplete = function (divid, cont, users_list, groups_list) {
1261 1262 var myUsers = users_list;
1262 1263 var myGroups = groups_list;
1263 1264
1264 1265 // Define a custom search function for the DataSource of users
1265 1266 var matchUsers = function (sQuery) {
1266 1267 // Case insensitive matching
1267 1268 var query = sQuery.toLowerCase();
1268 1269 var i = 0;
1269 1270 var l = myUsers.length;
1270 1271 var matches = [];
1271 1272
1272 1273 // Match against each name of each contact
1273 1274 for (; i < l; i++) {
1274 1275 contact = myUsers[i];
1275 1276 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1276 1277 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1277 1278 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1278 1279 matches[matches.length] = contact;
1279 1280 }
1280 1281 }
1281 1282 return matches;
1282 1283 };
1283 1284
1284 1285 // Define a custom search function for the DataSource of usersGroups
1285 1286 var matchGroups = function (sQuery) {
1286 1287 // Case insensitive matching
1287 1288 var query = sQuery.toLowerCase();
1288 1289 var i = 0;
1289 1290 var l = myGroups.length;
1290 1291 var matches = [];
1291 1292
1292 1293 // Match against each name of each contact
1293 1294 for (; i < l; i++) {
1294 1295 matched_group = myGroups[i];
1295 1296 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1296 1297 matches[matches.length] = matched_group;
1297 1298 }
1298 1299 }
1299 1300 return matches;
1300 1301 };
1301 1302
1302 1303 //match all
1303 1304 var matchAll = function (sQuery) {
1304 1305 u = matchUsers(sQuery);
1305 1306 return u
1306 1307 };
1307 1308
1308 1309 // DataScheme for owner
1309 1310 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1310 1311
1311 1312 ownerDS.responseSchema = {
1312 1313 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1313 1314 };
1314 1315
1315 1316 // Instantiate AutoComplete for mentions
1316 1317 var reviewerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1317 1318 reviewerAC.useShadow = false;
1318 1319 reviewerAC.resultTypeList = false;
1319 1320 reviewerAC.suppressInputUpdate = true;
1320 1321 reviewerAC.animVert = false;
1321 1322 reviewerAC.animHoriz = false;
1322 1323 reviewerAC.animSpeed = 0.1;
1323 1324
1324 1325 // Helper highlight function for the formatter
1325 1326 var highlightMatch = function (full, snippet, matchindex) {
1326 1327 return full.substring(0, matchindex)
1327 1328 + "<span class='match'>"
1328 1329 + full.substr(matchindex, snippet.length)
1329 1330 + "</span>" + full.substring(matchindex + snippet.length);
1330 1331 };
1331 1332
1332 1333 // Custom formatter to highlight the matching letters
1333 1334 reviewerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1334 1335 var org_sQuery = sQuery;
1335 1336 if(this.dataSource.mentionQuery != null){
1336 1337 sQuery = this.dataSource.mentionQuery;
1337 1338 }
1338 1339
1339 1340 var query = sQuery.toLowerCase();
1340 1341 var _gravatar = function(res, em, group){
1341 1342 if (group !== undefined){
1342 1343 em = '/images/icons/group.png'
1343 1344 }
1344 1345 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1345 1346 return tmpl.format(em,res)
1346 1347 }
1347 1348 if (oResultData.nname != undefined) {
1348 1349 var fname = oResultData.fname || "";
1349 1350 var lname = oResultData.lname || "";
1350 1351 var nname = oResultData.nname;
1351 1352
1352 1353 // Guard against null value
1353 1354 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1354 1355 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1355 1356 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1356 1357 displayfname, displaylname, displaynname;
1357 1358
1358 1359 if (fnameMatchIndex > -1) {
1359 1360 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1360 1361 } else {
1361 1362 displayfname = fname;
1362 1363 }
1363 1364
1364 1365 if (lnameMatchIndex > -1) {
1365 1366 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1366 1367 } else {
1367 1368 displaylname = lname;
1368 1369 }
1369 1370
1370 1371 if (nnameMatchIndex > -1) {
1371 1372 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1372 1373 } else {
1373 1374 displaynname = nname ? "(" + nname + ")" : "";
1374 1375 }
1375 1376
1376 1377 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1377 1378 } else {
1378 1379 return '';
1379 1380 }
1380 1381 };
1381 1382
1382 1383 //members cache to catch duplicates
1383 1384 reviewerAC.dataSource.cache = [];
1384 1385 // hack into select event
1385 1386 if(reviewerAC.itemSelectEvent){
1386 1387 reviewerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1387 1388
1388 1389 var myAC = aArgs[0]; // reference back to the AC instance
1389 1390 var elLI = aArgs[1]; // reference to the selected LI element
1390 1391 var oData = aArgs[2]; // object literal of selected item's result data
1391 1392 var members = YUD.get('review_members');
1392 1393 //fill the autocomplete with value
1393 1394
1394 1395 if (oData.nname != undefined) {
1395 1396 if (myAC.dataSource.cache.indexOf(oData.id) != -1){
1396 1397 return
1397 1398 }
1398 1399
1399 1400 var tmpl = '<li id="reviewer_{2}">'+
1400 1401 '<div class="reviewers_member">'+
1401 1402 '<div class="gravatar"><img alt="gravatar" src="{0}"/> </div>'+
1402 1403 '<div style="float:left">{1}</div>'+
1403 1404 '<input type="hidden" value="{2}" name="review_members" />'+
1404 1405 '<span class="delete_icon action_button" onclick="removeReviewer({2})"></span>'+
1405 1406 '</div>'+
1406 1407 '</li>'
1407 1408
1408 1409 var displayname = "{0} {1} ({2})".format(oData.fname,oData.lname,oData.nname);
1409 1410 var element = tmpl.format(oData.gravatar_lnk,displayname,oData.id);
1410 1411 members.innerHTML += element;
1411 1412 myAC.dataSource.cache.push(oData.id);
1412 1413 YUD.get('user').value = ''
1413 1414 }
1414 1415 });
1415 1416 }
1416 1417 return {
1417 1418 ownerDS: ownerDS,
1418 1419 reviewerAC: reviewerAC,
1419 1420 };
1420 1421 }
1421 1422
1422 1423
1423 1424 /**
1424 1425 * QUICK REPO MENU
1425 1426 */
1426 1427 var quick_repo_menu = function(){
1427 1428 YUE.on(YUQ('.quick_repo_menu'),'mouseenter',function(e){
1428 1429 var menu = e.currentTarget.firstElementChild.firstElementChild;
1429 1430 if(YUD.hasClass(menu,'hidden')){
1430 1431 YUD.replaceClass(e.currentTarget,'hidden', 'active');
1431 1432 YUD.replaceClass(menu, 'hidden', 'active');
1432 1433 }
1433 1434 })
1434 1435 YUE.on(YUQ('.quick_repo_menu'),'mouseleave',function(e){
1435 1436 var menu = e.currentTarget.firstElementChild.firstElementChild;
1436 1437 if(YUD.hasClass(menu,'active')){
1437 1438 YUD.replaceClass(e.currentTarget, 'active', 'hidden');
1438 1439 YUD.replaceClass(menu, 'active', 'hidden');
1439 1440 }
1440 1441 })
1441 1442 };
1442 1443
1443 1444
1444 1445 /**
1445 1446 * TABLE SORTING
1446 1447 */
1447 1448
1448 1449 // returns a node from given html;
1449 1450 var fromHTML = function(html){
1450 1451 var _html = document.createElement('element');
1451 1452 _html.innerHTML = html;
1452 1453 return _html;
1453 1454 }
1454 1455 var get_rev = function(node){
1455 1456 var n = node.firstElementChild.firstElementChild;
1456 1457
1457 1458 if (n===null){
1458 1459 return -1
1459 1460 }
1460 1461 else{
1461 1462 out = n.firstElementChild.innerHTML.split(':')[0].replace('r','');
1462 1463 return parseInt(out);
1463 1464 }
1464 1465 }
1465 1466
1466 1467 var get_name = function(node){
1467 1468 var name = node.firstElementChild.children[2].innerHTML;
1468 1469 return name
1469 1470 }
1470 1471 var get_group_name = function(node){
1471 1472 var name = node.firstElementChild.children[1].innerHTML;
1472 1473 return name
1473 1474 }
1474 1475 var get_date = function(node){
1475 1476 var date_ = YUD.getAttribute(node.firstElementChild,'date');
1476 1477 return date_
1477 1478 }
1478 1479
1479 1480 var get_age = function(node){
1480 1481 return node
1481 1482 }
1482 1483
1483 1484 var get_link = function(node){
1484 1485 return node.firstElementChild.text;
1485 1486 }
1486 1487
1487 1488 var revisionSort = function(a, b, desc, field) {
1488 1489
1489 1490 var a_ = fromHTML(a.getData(field));
1490 1491 var b_ = fromHTML(b.getData(field));
1491 1492
1492 1493 // extract revisions from string nodes
1493 1494 a_ = get_rev(a_)
1494 1495 b_ = get_rev(b_)
1495 1496
1496 1497 var comp = YAHOO.util.Sort.compare;
1497 1498 var compState = comp(a_, b_, desc);
1498 1499 return compState;
1499 1500 };
1500 1501 var ageSort = function(a, b, desc, field) {
1501 1502 var a_ = fromHTML(a.getData(field));
1502 1503 var b_ = fromHTML(b.getData(field));
1503 1504
1504 1505 // extract name from table
1505 1506 a_ = get_date(a_)
1506 1507 b_ = get_date(b_)
1507 1508
1508 1509 var comp = YAHOO.util.Sort.compare;
1509 1510 var compState = comp(a_, b_, desc);
1510 1511 return compState;
1511 1512 };
1512 1513
1513 1514 var nameSort = function(a, b, desc, field) {
1514 1515 var a_ = fromHTML(a.getData(field));
1515 1516 var b_ = fromHTML(b.getData(field));
1516 1517
1517 1518 // extract name from table
1518 1519 a_ = get_name(a_)
1519 1520 b_ = get_name(b_)
1520 1521
1521 1522 var comp = YAHOO.util.Sort.compare;
1522 1523 var compState = comp(a_, b_, desc);
1523 1524 return compState;
1524 1525 };
1525 1526
1526 1527 var permNameSort = function(a, b, desc, field) {
1527 1528 var a_ = fromHTML(a.getData(field));
1528 1529 var b_ = fromHTML(b.getData(field));
1529 1530 // extract name from table
1530 1531
1531 1532 a_ = a_.children[0].innerHTML;
1532 1533 b_ = b_.children[0].innerHTML;
1533 1534
1534 1535 var comp = YAHOO.util.Sort.compare;
1535 1536 var compState = comp(a_, b_, desc);
1536 1537 return compState;
1537 1538 };
1538 1539
1539 1540 var groupNameSort = function(a, b, desc, field) {
1540 1541 var a_ = fromHTML(a.getData(field));
1541 1542 var b_ = fromHTML(b.getData(field));
1542 1543
1543 1544 // extract name from table
1544 1545 a_ = get_group_name(a_)
1545 1546 b_ = get_group_name(b_)
1546 1547
1547 1548 var comp = YAHOO.util.Sort.compare;
1548 1549 var compState = comp(a_, b_, desc);
1549 1550 return compState;
1550 1551 };
1551 1552 var dateSort = function(a, b, desc, field) {
1552 1553 var a_ = fromHTML(a.getData(field));
1553 1554 var b_ = fromHTML(b.getData(field));
1554 1555
1555 1556 // extract name from table
1556 1557 a_ = get_date(a_)
1557 1558 b_ = get_date(b_)
1558 1559
1559 1560 var comp = YAHOO.util.Sort.compare;
1560 1561 var compState = comp(a_, b_, desc);
1561 1562 return compState;
1562 1563 };
1563 1564
1564 1565 var linkSort = function(a, b, desc, field) {
1565 1566 var a_ = fromHTML(a.getData(field));
1566 1567 var b_ = fromHTML(a.getData(field));
1567 1568
1568 1569 // extract url text from string nodes
1569 1570 a_ = get_link(a_)
1570 1571 b_ = get_link(b_)
1571 1572
1572 1573 var comp = YAHOO.util.Sort.compare;
1573 1574 var compState = comp(a_, b_, desc);
1574 1575 return compState;
1575 1576 }
1576 1577
1577 1578
1578 1579 /* Multi selectors */
1579 1580
1580 1581 var MultiSelectWidget = function(selected_id, available_id, form_id){
1581 1582
1582 1583
1583 1584 //definition of containers ID's
1584 1585 var selected_container = selected_id;
1585 1586 var available_container = available_id;
1586 1587
1587 1588 //temp container for selected storage.
1588 1589 var cache = new Array();
1589 1590 var av_cache = new Array();
1590 1591 var c = YUD.get(selected_container);
1591 1592 var ac = YUD.get(available_container);
1592 1593
1593 1594 //get only selected options for further fullfilment
1594 1595 for(var i = 0;node =c.options[i];i++){
1595 1596 if(node.selected){
1596 1597 //push selected to my temp storage left overs :)
1597 1598 cache.push(node);
1598 1599 }
1599 1600 }
1600 1601
1601 1602 //get all available options to cache
1602 1603 for(var i = 0;node =ac.options[i];i++){
1603 1604 //push selected to my temp storage left overs :)
1604 1605 av_cache.push(node);
1605 1606 }
1606 1607
1607 1608 //fill available only with those not in choosen
1608 1609 ac.options.length=0;
1609 1610 tmp_cache = new Array();
1610 1611
1611 1612 for(var i = 0;node = av_cache[i];i++){
1612 1613 var add = true;
1613 1614 for(var i2 = 0;node_2 = cache[i2];i2++){
1614 1615 if(node.value == node_2.value){
1615 1616 add=false;
1616 1617 break;
1617 1618 }
1618 1619 }
1619 1620 if(add){
1620 1621 tmp_cache.push(new Option(node.text, node.value, false, false));
1621 1622 }
1622 1623 }
1623 1624
1624 1625 for(var i = 0;node = tmp_cache[i];i++){
1625 1626 ac.options[i] = node;
1626 1627 }
1627 1628
1628 1629 function prompts_action_callback(e){
1629 1630
1630 1631 var choosen = YUD.get(selected_container);
1631 1632 var available = YUD.get(available_container);
1632 1633
1633 1634 //get checked and unchecked options from field
1634 1635 function get_checked(from_field){
1635 1636 //temp container for storage.
1636 1637 var sel_cache = new Array();
1637 1638 var oth_cache = new Array();
1638 1639
1639 1640 for(var i = 0;node = from_field.options[i];i++){
1640 1641 if(node.selected){
1641 1642 //push selected fields :)
1642 1643 sel_cache.push(node);
1643 1644 }
1644 1645 else{
1645 1646 oth_cache.push(node)
1646 1647 }
1647 1648 }
1648 1649
1649 1650 return [sel_cache,oth_cache]
1650 1651 }
1651 1652
1652 1653 //fill the field with given options
1653 1654 function fill_with(field,options){
1654 1655 //clear firtst
1655 1656 field.options.length=0;
1656 1657 for(var i = 0;node = options[i];i++){
1657 1658 field.options[i]=new Option(node.text, node.value,
1658 1659 false, false);
1659 1660 }
1660 1661
1661 1662 }
1662 1663 //adds to current field
1663 1664 function add_to(field,options){
1664 1665 for(var i = 0;node = options[i];i++){
1665 1666 field.appendChild(new Option(node.text, node.value,
1666 1667 false, false));
1667 1668 }
1668 1669 }
1669 1670
1670 1671 // add action
1671 1672 if (this.id=='add_element'){
1672 1673 var c = get_checked(available);
1673 1674 add_to(choosen,c[0]);
1674 1675 fill_with(available,c[1]);
1675 1676 }
1676 1677 // remove action
1677 1678 if (this.id=='remove_element'){
1678 1679 var c = get_checked(choosen);
1679 1680 add_to(available,c[0]);
1680 1681 fill_with(choosen,c[1]);
1681 1682 }
1682 1683 // add all elements
1683 1684 if(this.id=='add_all_elements'){
1684 1685 for(var i=0; node = available.options[i];i++){
1685 1686 choosen.appendChild(new Option(node.text,
1686 1687 node.value, false, false));
1687 1688 }
1688 1689 available.options.length = 0;
1689 1690 }
1690 1691 //remove all elements
1691 1692 if(this.id=='remove_all_elements'){
1692 1693 for(var i=0; node = choosen.options[i];i++){
1693 1694 available.appendChild(new Option(node.text,
1694 1695 node.value, false, false));
1695 1696 }
1696 1697 choosen.options.length = 0;
1697 1698 }
1698 1699
1699 1700 }
1700 1701
1701 1702 YUE.addListener(['add_element','remove_element',
1702 1703 'add_all_elements','remove_all_elements'],'click',
1703 1704 prompts_action_callback)
1704 1705 if (form_id !== undefined) {
1705 1706 YUE.addListener(form_id,'submit',function(){
1706 1707 var choosen = YUD.get(selected_container);
1707 1708 for (var i = 0; i < choosen.options.length; i++) {
1708 1709 choosen.options[i].selected = 'selected';
1709 1710 }
1710 1711 });
1711 1712 }
1712 1713 } No newline at end of file
@@ -1,159 +1,170 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <!DOCTYPE html>
3 3 <html xmlns="http://www.w3.org/1999/xhtml">
4 4 <head>
5 5 <title>${self.title()}</title>
6 6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 7 <meta name="robots" content="index, nofollow"/>
8 8 <link rel="icon" href="${h.url('/images/icons/database_gear.png')}" type="image/png" />
9 9
10 10 ## CSS ###
11 11 <%def name="css()">
12 12 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen"/>
13 13 <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css')}"/>
14 14 ## EXTRA FOR CSS
15 15 ${self.css_extra()}
16 16 </%def>
17 17 <%def name="css_extra()">
18 18 </%def>
19 19
20 20 ${self.css()}
21 21
22 22 %if c.ga_code:
23 23 <!-- Analytics -->
24 24 <script type="text/javascript">
25 25 var _gaq = _gaq || [];
26 26 _gaq.push(['_setAccount', '${c.ga_code}']);
27 27 _gaq.push(['_trackPageview']);
28 28
29 29 (function() {
30 30 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
31 31 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
32 32 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
33 33 })();
34 34 </script>
35 35 %endif
36 36
37 37 ## JAVASCRIPT ##
38 38 <%def name="js()">
39 39 <script type="text/javascript">
40 40 //JS translations map
41 41 var TRANSLATION_MAP = {
42 42 'add another comment':'${_("add another comment")}',
43 43 'Stop following this repository':"${_('Stop following this repository')}",
44 44 'Start following this repository':"${_('Start following this repository')}",
45 45 'Group':"${_('Group')}",
46 46 'members':"${_('members')}",
47 47 'search truncated': "${_('search truncated')}",
48 48 'no matching files': "${_('no matching files')}"
49 49
50 50 };
51 51 var _TM = TRANSLATION_MAP;
52 52 </script>
53 53 <script type="text/javascript" src="${h.url('/js/yui.2.9.js')}"></script>
54 54 <!--[if lt IE 9]>
55 55 <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
56 56 <![endif]-->
57 57 <script type="text/javascript" src="${h.url('/js/yui.flot.js')}"></script>
58 <script type="text/javascript" src="${h.url('/js/native.history.js')}"></script>
58 59 <script type="text/javascript" src="${h.url('/js/rhodecode.js')}"></script>
59 60 ## EXTRA FOR JS
60 61 ${self.js_extra()}
61 62
62 63 <script type="text/javascript">
64 (function(window,undefined){
65
66 // Prepare
67 var History = window.History; // Note: We are using a capital H instead of a lower h
68 if ( !History.enabled ) {
69 // History.js is disabled for this browser.
70 // This is because we can optionally choose to support HTML4 browsers or not.
71 return false;
72 }
73 })(window);
63 74 var follow_base_url = "${h.url('toggle_following')}";
64 75
65 76 var onSuccessFollow = function(target){
66 77 var f = YUD.get(target.id);
67 78 var f_cnt = YUD.get('current_followers_count');
68 79
69 80 if(f.getAttribute('class')=='follow'){
70 81 f.setAttribute('class','following');
71 82 f.setAttribute('title',_TM['Stop following this repository']);
72 83
73 84 if(f_cnt){
74 85 var cnt = Number(f_cnt.innerHTML)+1;
75 86 f_cnt.innerHTML = cnt;
76 87 }
77 88 }
78 89 else{
79 90 f.setAttribute('class','follow');
80 91 f.setAttribute('title',_TM['Start following this repository']);
81 92 if(f_cnt){
82 93 var cnt = Number(f_cnt.innerHTML)+1;
83 94 f_cnt.innerHTML = cnt;
84 95 }
85 96 }
86 97 }
87 98
88 99 var toggleFollowingUser = function(target,fallows_user_id,token,user_id){
89 100 args = 'follows_user_id='+fallows_user_id;
90 101 args+= '&amp;auth_token='+token;
91 102 if(user_id != undefined){
92 103 args+="&amp;user_id="+user_id;
93 104 }
94 105 YUC.asyncRequest('POST',follow_base_url,{
95 106 success:function(o){
96 107 onSuccessFollow(target);
97 108 }
98 109 },args);
99 110 return false;
100 111 }
101 112
102 113 var toggleFollowingRepo = function(target,fallows_repo_id,token,user_id){
103 114
104 115 args = 'follows_repo_id='+fallows_repo_id;
105 116 args+= '&amp;auth_token='+token;
106 117 if(user_id != undefined){
107 118 args+="&amp;user_id="+user_id;
108 119 }
109 120 YUC.asyncRequest('POST',follow_base_url,{
110 121 success:function(o){
111 122 onSuccessFollow(target);
112 123 }
113 124 },args);
114 125 return false;
115 126 }
116 127 YUE.onDOMReady(function(){
117 128 tooltip_activate();
118 129 show_more_event();
119 130
120 131 YUE.on('quick_login_link','click',function(e){
121 132 // make sure we don't redirect
122 133 YUE.preventDefault(e);
123 134
124 135 if(YUD.hasClass('quick_login_link','enabled')){
125 136 YUD.setStyle('quick_login','display','none');
126 137 YUD.removeClass('quick_login_link','enabled');
127 138 }
128 139 else{
129 140 YUD.setStyle('quick_login','display','');
130 141 YUD.addClass('quick_login_link','enabled');
131 142 var usr = YUD.get('username');
132 143 if(usr){
133 144 usr.focus();
134 145 }
135 146 }
136 147 });
137 148 })
138 149 </script>
139 150 </%def>
140 151 <%def name="js_extra()"></%def>
141 152 ${self.js()}
142 153 <%def name="head_extra()"></%def>
143 154 ${self.head_extra()}
144 155 </head>
145 156 <body id="body">
146 157 ## IE hacks
147 158 <!--[if IE 7]>
148 159 <script>YUD.addClass(document.body,'ie7')</script>
149 160 <![endif]-->
150 161 <!--[if IE 8]>
151 162 <script>YUD.addClass(document.body,'ie8')</script>
152 163 <![endif]-->
153 164 <!--[if IE 9]>
154 165 <script>YUD.addClass(document.body,'ie9')</script>
155 166 <![endif]-->
156 167
157 168 ${next.body()}
158 169 </body>
159 170 </html>
@@ -1,46 +1,132 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 ${_('%s Files') % c.repo_name} - ${c.rhodecode_name}
4 ${_('%s files') % c.repo_name} - ${c.rhodecode_name}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_(u'Home'),h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('files_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('files')}
13 13 %if c.file:
14 14 @ r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
15 15 %endif
16 16 </%def>
17 17
18 18 <%def name="page_nav()">
19 19 ${self.menu('files')}
20 20 </%def>
21 21
22 22 <%def name="main()">
23 23 <div class="box">
24 24 <!-- box / title -->
25 25 <div class="title">
26 26 ${self.breadcrumbs()}
27 27 <ul class="links">
28 28 <li>
29 29 <span style="text-transform: uppercase;"><a href="#">${_('branch')}: ${c.changeset.branch}</a></span>
30 30 </li>
31 31 </ul>
32 32 </div>
33 33 <div class="table">
34 34 <div id="files_data">
35 35 <%include file='files_ypjax.html'/>
36 36 </div>
37 37 </div>
38 38 </div>
39
39 40 <script type="text/javascript">
40 var YPJAX_TITLE = "${c.repo_name} ${_('Files')} - ${c.rhodecode_name}";
41 var current_url = "${h.url.current()}";
42 var node_list_url = '${h.url("files_home",repo_name=c.repo_name,revision=c.changeset.raw_id,f_path='__FPATH__')}';
43 var url_base = '${h.url("files_nodelist_home",repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.file.path)}';
44 fileBrowserListeners(current_url, node_list_url, url_base);
41 var CACHE = {};
42 var CACHE_EXPIRE = 60*1000; //cache for 60s
43
44 var ypjax_links = function(){
45 YUE.on(YUQ('.ypjax-link'), 'click',function(e){
46
47 //don't do ypjax on middle click
48 if(e.which == 2){
49 return true;
50 }
51 var el = e.currentTarget;
52 var url = el.href;
53
54 var _base_url = '${h.url("files_home",repo_name=c.repo_name,revision='',f_path='')}';
55 _base_url = _base_url.replace('//','/')
56
57 //extract rev and the f_path from url.
58 parts = url.split(_base_url)
59 if(parts.length != 2){
60 return false;
61 }
62
63 var parts2 = parts[1].split('/');
64 var rev = parts2.shift(); // pop the first element which is the revision
65 var f_path = parts2.join('/');
66
67 var title = "${_('%s files') % c.repo_name}" + " - " + f_path;
68
69 //used to construct links from the search list
70 var node_list_url = '${h.url("files_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
71 node_list_url = node_list_url.replace('__REV__',rev);
72 //send the nodelist request to this url
73 var url_base = '${h.url("files_nodelist_home",repo_name=c.repo_name,revision='__REV__',f_path='__FPATH__')}';
74 url_base = url_base.replace('__REV__',rev).replace('__FPATH__', f_path)
75
76 // Change our States and save some data for handling events
77 var data = {url:url,title:title, url_base:url_base,
78 node_list_url:node_list_url};
79 History.pushState(data, title, url);
80
81 //now we're sure that we can do ypjax things
82 YUE.preventDefault(e)
83 return false;
84 });
85 }
86
87 var callbacks = function(State){
88 ypjax_links();
89 tooltip_activate();
90 fileBrowserListeners(State.url, State.data.node_list_url, State.data.url_base);
91 // Inform Google Analytics of the change
92 if ( typeof window.pageTracker !== 'undefined' ) {
93 window.pageTracker._trackPageview(State.url);
94 }
95 }
96
97 YUE.onDOMReady(function(){
98 ypjax_links();
99 var container = 'files_data';
100 //Bind to StateChange Event
101 History.Adapter.bind(window,'statechange',function(){
102 var State = History.getState();
103 cache_key = State.url;
104 //check if we have this request in cache maybe ?
105 var _cache_obj = CACHE[cache_key];
106 var _cur_time = new Date().getTime();
107 // get from cache if it's there and not yet expired !
108 if(_cache_obj !== undefined && _cache_obj[0] > _cur_time){
109 YUD.get(container).innerHTML=_cache_obj[1];
110 YUD.setStyle(container,'opacity','1.0');
111
112 //callbacks after ypjax call
113 callbacks(State);
114 }
115 else{
116 ypjax(State.url,container,function(o){
117 //callbacks after ypjax call
118 callbacks(State);
119 if (o !== undefined){
120 //store our request in cache
121 var _expire_on = new Date().getTime()+CACHE_EXPIRE;
122 CACHE[cache_key] = [_expire_on, o.responseText];
123 }
124 });
125 }
126 });
127
128 });
129
45 130 </script>
131
46 132 </%def>
@@ -1,116 +1,116 b''
1 1 <%def name="file_class(node)">
2 2 %if node.is_file():
3 3 <%return "browser-file" %>
4 4 %else:
5 5 <%return "browser-dir"%>
6 6 %endif
7 7 </%def>
8 8 <div id="body" class="browserblock">
9 9 <div class="browser-header">
10 10 <div class="browser-nav">
11 11 ${h.form(h.url.current())}
12 12 <div class="info_box">
13 13 <span class="rev">${_('view')}@rev</span>
14 <a class="ui-btn" href="${c.url_prev}" title="${_('previous revision')}">&laquo;</a>
14 <a class="ui-btn ypjax-link" href="${c.url_prev}" title="${_('previous revision')}">&laquo;</a>
15 15 ${h.text('at_rev',value=c.changeset.revision,size=5)}
16 <a class="ui-btn" href="${c.url_next}" title="${_('next revision')}">&raquo;</a>
16 <a class="ui-btn ypjax-link" href="${c.url_next}" title="${_('next revision')}">&raquo;</a>
17 17 ## ${h.submit('view',_('view'),class_="ui-btn")}
18 18 </div>
19 19 ${h.end_form()}
20 20 </div>
21 21 <div class="browser-branch">
22 22 ${h.checkbox('stay_at_branch',c.changeset.branch,c.changeset.branch==c.branch)}
23 23 <label>${_('follow current branch')}</label>
24 24 </div>
25 25 <div class="browser-search">
26 26 <div id="search_activate_id" class="search_activate">
27 27 <a class="ui-btn" id="filter_activate" href="#">${_('search file list')}</a>
28 28 </div>
29 29 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
30 30 <div id="add_node_id" class="add_node">
31 31 <a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path)}">${_('add new file')}</a>
32 32 </div>
33 33 % endif
34 34 <div>
35 35 <div id="node_filter_box_loading" style="display:none">${_('Loading file list...')}</div>
36 36 <div id="node_filter_box" style="display:none">
37 37 ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}/<input class="init" type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
38 38 </div>
39 39 </div>
40 40 </div>
41 41 </div>
42 42
43 43 <div class="browser-body">
44 44 <table class="code-browser">
45 45 <thead>
46 46 <tr>
47 47 <th>${_('Name')}</th>
48 48 <th>${_('Size')}</th>
49 49 <th>${_('Mimetype')}</th>
50 50 <th>${_('Last Revision')}</th>
51 51 <th>${_('Last modified')}</th>
52 52 <th>${_('Last commiter')}</th>
53 53 </tr>
54 54 </thead>
55 55
56 56 <tbody id="tbody">
57 57 %if c.file.parent:
58 58 <tr class="parity0">
59 59 <td>
60 60 ${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.file.parent.path),class_="browser-dir ypjax-link")}
61 61 </td>
62 62 <td></td>
63 63 <td></td>
64 64 <td></td>
65 65 <td></td>
66 66 <td></td>
67 67 </tr>
68 68 %endif
69 69
70 70 %for cnt,node in enumerate(c.file):
71 71 <tr class="parity${cnt%2}">
72 72 <td>
73 73 %if node.is_submodule():
74 74 ${h.link_to(node.name,node.url or '#',class_="submodule-dir ypjax-link")}
75 75 %else:
76 76 ${h.link_to(node.name, h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=h.safe_unicode(node.path)),class_=file_class(node)+" ypjax-link")}
77 77 %endif:
78 78 </td>
79 79 <td>
80 80 %if node.is_file():
81 81 ${h.format_byte_size(node.size,binary=True)}
82 82 %endif
83 83 </td>
84 84 <td>
85 85 %if node.is_file():
86 86 ${node.mimetype}
87 87 %endif
88 88 </td>
89 89 <td>
90 90 %if node.is_file():
91 91 <div class="tooltip" title="${h.tooltip(node.last_changeset.message)}">
92 92 <pre>${'r%s:%s' % (node.last_changeset.revision,node.last_changeset.short_id)}</pre>
93 93 </div>
94 94 %endif
95 95 </td>
96 96 <td>
97 97 %if node.is_file():
98 98 <span class="tooltip" title="${h.tooltip(h.fmt_date(node.last_changeset.date))}">
99 99 ${h.age(node.last_changeset.date)}</span>
100 100 %endif
101 101 </td>
102 102 <td>
103 103 %if node.is_file():
104 104 <span title="${node.last_changeset.author}">
105 105 ${h.person(node.last_changeset.author)}
106 106 </span>
107 107 %endif
108 108 </td>
109 109 </tr>
110 110 %endfor
111 111 </tbody>
112 112 <tbody id="tbody_filtered" style="display:none">
113 113 </tbody>
114 114 </table>
115 115 </div>
116 116 </div>
@@ -1,18 +1,18 b''
1 1 %if c.file:
2 2 <h3 class="files_location">
3 3 ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}
4 4 %if c.annotate:
5 5 - ${_('annotation')}
6 6 %endif
7 7 </h3>
8 8 %if c.file.is_dir():
9 9 <%include file='files_browser.html'/>
10 10 %else:
11 11 <%include file='files_source.html'/>
12 %endif
12 %endif
13 13 %else:
14 14 <h2>
15 15 <a href="#" onClick="javascript:parent.history.back();" target="main">${_('Go back')}</a>
16 16 ${_('No files at given path')}: "${c.f_path or "/"}"
17 17 </h2>
18 18 %endif
@@ -1,209 +1,211 b''
1 1 <%page args="parent" />
2 2 <div class="box">
3 3 <!-- box / title -->
4 4 <div class="title">
5 5 <h5>
6 6 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
7 7 </h5>
8 8 %if c.rhodecode_user.username != 'default':
9 9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 10 <ul class="links">
11 11 <li>
12 12 %if c.group:
13 13 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository',parent_group=c.group.group_id))}</span>
14 14 %else:
15 15 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
16 16 %endif
17 17 </li>
18 18 </ul>
19 19 %endif
20 20 %endif
21 21 </div>
22 22 <!-- end box / title -->
23 23 <div class="table">
24 24 % if c.groups:
25 25 <div id='groups_list_wrap' class="yui-skin-sam">
26 26 <table id="groups_list">
27 27 <thead>
28 28 <tr>
29 29 <th class="left"><a href="#">${_('Group name')}</a></th>
30 30 <th class="left"><a href="#">${_('Description')}</a></th>
31 31 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
32 32 </tr>
33 33 </thead>
34 34
35 35 ## REPO GROUPS
36 36 % for gr in c.groups:
37 37 <tr>
38 38 <td>
39 39 <div style="white-space: nowrap">
40 40 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
41 41 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
42 42 </div>
43 43 </td>
44 44 %if c.visual.stylify_metatags:
45 45 <td>${h.desc_stylize(gr.group_description)}</td>
46 46 %else:
47 47 <td>${gr.group_description}</td>
48 48 %endif
49 49 ## this is commented out since for multi nested repos can be HEAVY!
50 50 ## in number of executed queries during traversing uncomment at will
51 51 ##<td><b>${gr.repositories_recursive_count}</b></td>
52 52 </tr>
53 53 % endfor
54 54
55 55 </table>
56 56 </div>
57 57 <div style="height: 20px"></div>
58 58 % endif
59 59 <div id="welcome" style="display:none;text-align:center">
60 60 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
61 61 </div>
62 62 <div id='repos_list_wrap' class="yui-skin-sam">
63 63 <%cnt=0%>
64 64 <%namespace name="dt" file="/data_table/_dt_elements.html"/>
65 65
66 66 <table id="repos_list">
67 67 <thead>
68 68 <tr>
69 69 <th class="left"></th>
70 70 <th class="left">${_('Name')}</th>
71 71 <th class="left">${_('Description')}</th>
72 72 <th class="left">${_('Last change')}</th>
73 73 <th class="left">${_('Tip')}</th>
74 74 <th class="left">${_('Owner')}</th>
75 75 <th class="left">${_('RSS')}</th>
76 76 <th class="left">${_('Atom')}</th>
77 77 </tr>
78 78 </thead>
79 79 <tbody>
80 80 %for cnt,repo in enumerate(c.repos_list):
81 81 <tr class="parity${(cnt+1)%2}">
82 82 ##QUICK MENU
83 83 <td class="quick_repo_menu">
84 84 ${dt.quick_menu(repo['name'])}
85 85 </td>
86 86 ##REPO NAME AND ICONS
87 87 <td class="reponame">
88 88 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'),pageargs.get('short_repo_names'))}
89 89 </td>
90 90 ##DESCRIPTION
91 91 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
92 92 %if c.visual.stylify_metatags:
93 93 ${h.urlify_text(h.desc_stylize(h.truncate(repo['description'],60)))}</span>
94 94 %else:
95 95 ${h.truncate(repo['description'],60)}</span>
96 96 %endif
97 97 </td>
98 98 ##LAST CHANGE DATE
99 99 <td>
100 100 <span class="tooltip" date="${repo['last_change']}" title="${h.tooltip(h.fmt_date(repo['last_change']))}">${h.age(repo['last_change'])}</span>
101 101 </td>
102 102 ##LAST REVISION
103 103 <td>
104 104 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
105 105 </td>
106 106 ##
107 107 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
108 108 <td>
109 109 %if c.rhodecode_user.username != 'default':
110 110 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
111 111 %else:
112 112 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
113 113 %endif:
114 114 </td>
115 115 <td>
116 116 %if c.rhodecode_user.username != 'default':
117 117 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
118 118 %else:
119 119 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
120 120 %endif:
121 121 </td>
122 122 </tr>
123 123 %endfor
124 124 </tbody>
125 125 </table>
126 126 </div>
127 127 </div>
128 128 </div>
129 129 <script>
130 130 YUD.get('repo_count').innerHTML = ${cnt+1 if cnt else 0};
131 131 var func = function(node){
132 132 return node.parentNode.parentNode.parentNode.parentNode;
133 133 }
134 134
135 var sort_by = "name";
136 var sort_dir = "asc";
135 137
136 138 // groups table sorting
137 139 var myColumnDefs = [
138 140 {key:"name",label:"${_('Group Name')}",sortable:true,
139 141 sortOptions: { sortFunction: groupNameSort }},
140 142 {key:"desc",label:"${_('Description')}",sortable:true},
141 143 ];
142 144
143 145 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
144 146
145 147 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
146 148 myDataSource.responseSchema = {
147 149 fields: [
148 150 {key:"name"},
149 151 {key:"desc"},
150 152 ]
151 153 };
152 154
153 155 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,
154 156 {
155 157 sortedBy:{key:"name",dir:"asc"},
156 158 MSG_SORTASC:"${_('Click to sort ascending')}",
157 159 MSG_SORTDESC:"${_('Click to sort descending')}"
158 160 }
159 161 );
160 162
161 163 // main table sorting
162 164 var myColumnDefs = [
163 165 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
164 166 {key:"name",label:"${_('Name')}",sortable:true,
165 167 sortOptions: { sortFunction: nameSort }},
166 168 {key:"desc",label:"${_('Description')}",sortable:true},
167 169 {key:"last_change",label:"${_('Last Change')}",sortable:true,
168 170 sortOptions: { sortFunction: ageSort }},
169 171 {key:"tip",label:"${_('Tip')}",sortable:true,
170 172 sortOptions: { sortFunction: revisionSort }},
171 173 {key:"owner",label:"${_('Owner')}",sortable:true},
172 174 {key:"rss",label:"",sortable:false},
173 175 {key:"atom",label:"",sortable:false},
174 176 ];
175 177
176 178 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
177 179
178 180 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
179 181
180 182 myDataSource.responseSchema = {
181 183 fields: [
182 184 {key:"menu"},
183 185 {key:"name"},
184 186 {key:"desc"},
185 187 {key:"last_change"},
186 188 {key:"tip"},
187 189 {key:"owner"},
188 190 {key:"rss"},
189 191 {key:"atom"},
190 192 ]
191 193 };
192 194
193 195 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
194 196 {
195 sortedBy:{key:"name",dir:"asc"},
197 sortedBy:{key:sort_by,dir:sort_dir},
196 198 MSG_SORTASC:"${_('Click to sort ascending')}",
197 199 MSG_SORTDESC:"${_('Click to sort descending')}",
198 200 MSG_EMPTY:"${_('No records found.')}",
199 201 MSG_ERROR:"${_('Data error.')}",
200 202 MSG_LOADING:"${_('Loading...')}",
201 203 }
202 204 );
203 205 myDataTable.subscribe('postRenderEvent',function(oArgs) {
204 206 tooltip_activate();
205 207 quick_repo_menu();
206 208 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
207 209 });
208 210
209 211 </script>
@@ -1,298 +1,298 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 10 pdebug = false
11 11 ################################################################################
12 12 ## Uncomment and replace with the address which should receive ##
13 13 ## any error reports after application crash ##
14 14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 15 ################################################################################
16 16 #email_to = admin@localhost
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 20 #email_prefix = [RhodeCode]
21 21
22 22 #smtp_server = mail.server.com
23 23 #smtp_username =
24 24 #smtp_password =
25 25 #smtp_port =
26 26 #smtp_use_tls = false
27 27 #smtp_use_ssl = true
28 28 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 29 #smtp_auth =
30 30
31 31 [server:main]
32 32 ##nr of threads to spawn
33 33 #threadpool_workers = 5
34 34
35 35 ##max request before thread respawn
36 36 #threadpool_max_requests = 2
37 37
38 38 ##option to use threads of process
39 39 #use_threadpool = true
40 40
41 41 #use = egg:Paste#http
42 42 use = egg:waitress#main
43 43 host = 127.0.0.1
44 44 port = 5000
45 45
46 46 [app:main]
47 47 use = egg:rhodecode
48 48 full_stack = true
49 49 static_files = true
50 50 lang=en
51 51 cache_dir = /tmp/data
52 52 index_dir = /tmp/index
53 53 app_instance_uuid = develop-test
54 54 cut_off_limit = 256000
55 55 force_https = false
56 56 commit_parse_limit = 25
57 57 use_gravatar = true
58 58 container_auth_enabled = false
59 59 proxypass_auth_enabled = false
60 60
61 61
62 62 ## overwrite schema of clone url
63 63 ## available vars:
64 64 ## scheme - http/https
65 65 ## user - current user
66 66 ## pass - password
67 67 ## netloc - network location
68 68 ## path - usually repo_name
69 69
70 70 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
71 71
72 72 ## issue tracking mapping for commits messages
73 73 ## comment out issue_pat, issue_server, issue_prefix to enable
74 74
75 75 ## pattern to get the issues from commit messages
76 76 ## default one used here is #<numbers> with a regex passive group for `#`
77 77 ## {id} will be all groups matched from this pattern
78 78
79 79 issue_pat = (?:\s*#)(\d+)
80 80
81 81 ## server url to the issue, each {id} will be replaced with match
82 82 ## fetched from the regex and {repo} is replaced with repository name
83 83
84 84 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
85 85
86 86 ## prefix to add to link to indicate it's an url
87 87 ## #314 will be replaced by <issue_prefix><id>
88 88
89 89 issue_prefix = #
90 90
91 91 ## instance-id prefix
92 92 ## a prefix key for this instance used for cache invalidation when running
93 93 ## multiple instances of rhodecode, make sure it's globally unique for
94 94 ## all running rhodecode instances. Leave empty if you don't use it
95 95 instance_id =
96 96
97 97 ## alternative return HTTP header for failed authentication. Default HTTP
98 98 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
99 99 ## handling that. Set this variable to 403 to return HTTPForbidden
100 100 auth_ret_code =
101 101
102 102 ####################################
103 103 ### CELERY CONFIG ####
104 104 ####################################
105 105 use_celery = false
106 106 broker.host = localhost
107 107 broker.vhost = rabbitmqhost
108 108 broker.port = 5672
109 109 broker.user = rabbitmq
110 110 broker.password = qweqwe
111 111
112 112 celery.imports = rhodecode.lib.celerylib.tasks
113 113
114 114 celery.result.backend = amqp
115 115 celery.result.dburi = amqp://
116 116 celery.result.serialier = json
117 117
118 118 #celery.send.task.error.emails = true
119 119 #celery.amqp.task.result.expires = 18000
120 120
121 121 celeryd.concurrency = 2
122 122 #celeryd.log.file = celeryd.log
123 123 celeryd.log.level = debug
124 124 celeryd.max.tasks.per.child = 1
125 125
126 126 #tasks will never be sent to the queue, but executed locally instead.
127 127 celery.always.eager = false
128 128
129 129 ####################################
130 130 ### BEAKER CACHE ####
131 131 ####################################
132 132 beaker.cache.data_dir=/tmp/data/cache/data
133 133 beaker.cache.lock_dir=/tmp/data/cache/lock
134 134
135 135 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
136 136
137 137 beaker.cache.super_short_term.type=memory
138 138 beaker.cache.super_short_term.expire=1
139 139 beaker.cache.super_short_term.key_length = 256
140 140
141 141 beaker.cache.short_term.type=memory
142 142 beaker.cache.short_term.expire=60
143 143 beaker.cache.short_term.key_length = 256
144 144
145 145 beaker.cache.long_term.type=memory
146 146 beaker.cache.long_term.expire=36000
147 147 beaker.cache.long_term.key_length = 256
148 148
149 149 beaker.cache.sql_cache_short.type=memory
150 150 beaker.cache.sql_cache_short.expire=1
151 151 beaker.cache.sql_cache_short.key_length = 256
152 152
153 153 beaker.cache.sql_cache_med.type=memory
154 154 beaker.cache.sql_cache_med.expire=360
155 155 beaker.cache.sql_cache_med.key_length = 256
156 156
157 157 beaker.cache.sql_cache_long.type=file
158 158 beaker.cache.sql_cache_long.expire=3600
159 159 beaker.cache.sql_cache_long.key_length = 256
160 160
161 161 ####################################
162 162 ### BEAKER SESSION ####
163 163 ####################################
164 164 ## Type of storage used for the session, current types are
165 165 ## dbm, file, memcached, database, and memory.
166 166 ## The storage uses the Container API
167 167 ## that is also used by the cache system.
168 168
169 169 ## db session example
170 170
171 171 #beaker.session.type = ext:database
172 172 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
173 173 #beaker.session.table_name = db_session
174 174
175 175 ## encrypted cookie session, good for many instances
176 176 #beaker.session.type = cookie
177 177
178 178 beaker.session.type = file
179 179 beaker.session.key = rhodecode
180 180 # secure cookie requires AES python libraries
181 181 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
182 182 #beaker.session.validate_key = 9712sds2212c--zxc123
183 183 beaker.session.timeout = 36000
184 184 beaker.session.httponly = true
185 185
186 186 ## uncomment for https secure cookie
187 187 beaker.session.secure = false
188 188
189 189 ##auto save the session to not to use .save()
190 190 beaker.session.auto = False
191 191
192 192 ##true exire at browser close
193 193 #beaker.session.cookie_expires = 3600
194 194
195 195
196 196 ################################################################################
197 197 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
198 198 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
199 199 ## execute malicious code after an exception is raised. ##
200 200 ################################################################################
201 201 #set debug = false
202 202
203 203 ##################################
204 204 ### LOGVIEW CONFIG ###
205 205 ##################################
206 206 logview.sqlalchemy = #faa
207 207 logview.pylons.templating = #bfb
208 208 logview.pylons.util = #eee
209 209
210 210 #########################################################
211 211 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
212 212 #########################################################
213 213 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.sqlite
214 214 #sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_test
215 215 #sqlalchemy.db1.url = mysql://root:qwe@localhost/rhodecode_test
216 216
217 217 sqlalchemy.db1.echo = false
218 218 sqlalchemy.db1.pool_recycle = 3600
219 219 sqlalchemy.db1.convert_unicode = true
220 220
221 221 ################################
222 222 ### LOGGING CONFIGURATION ####
223 223 ################################
224 224 [loggers]
225 225 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
226 226
227 227 [handlers]
228 228 keys = console
229 229
230 230 [formatters]
231 231 keys = generic, color_formatter
232 232
233 233 #############
234 234 ## LOGGERS ##
235 235 #############
236 236 [logger_root]
237 level = ERROR
237 level = DEBUG
238 238 handlers = console
239 239
240 240 [logger_routes]
241 level = ERROR
241 level = DEBUG
242 242 handlers =
243 243 qualname = routes.middleware
244 244 # "level = DEBUG" logs the route matched and routing variables.
245 245 propagate = 1
246 246
247 247 [logger_beaker]
248 248 level = DEBUG
249 249 handlers =
250 250 qualname = beaker.container
251 251 propagate = 1
252 252
253 253 [logger_templates]
254 254 level = INFO
255 255 handlers =
256 256 qualname = pylons.templating
257 257 propagate = 1
258 258
259 259 [logger_rhodecode]
260 level = ERROR
260 level = DEBUG
261 261 handlers =
262 262 qualname = rhodecode
263 263 propagate = 1
264 264
265 265 [logger_sqlalchemy]
266 266 level = ERROR
267 267 handlers = console
268 268 qualname = sqlalchemy.engine
269 269 propagate = 0
270 270
271 271 [logger_whoosh_indexer]
272 272 level = DEBUG
273 273 handlers =
274 274 qualname = whoosh_indexer
275 275 propagate = 1
276 276
277 277 ##############
278 278 ## HANDLERS ##
279 279 ##############
280 280
281 281 [handler_console]
282 282 class = StreamHandler
283 283 args = (sys.stderr,)
284 284 level = NOTSET
285 285 formatter = generic
286 286
287 287 ################
288 288 ## FORMATTERS ##
289 289 ################
290 290
291 291 [formatter_generic]
292 292 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
293 293 datefmt = %Y-%m-%d %H:%M:%S
294 294
295 295 [formatter_color_formatter]
296 296 class=rhodecode.lib.colored_formatter.ColorFormatter
297 297 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
298 298 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now