1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-07 01:41:39 +00:00

added file op:rename, cp, mv, upload, create

This commit is contained in:
llj
2012-07-02 10:21:17 +08:00
parent 4cd0a2cc44
commit 8b40a83880
37 changed files with 5991 additions and 36 deletions

View File

@@ -5,7 +5,7 @@ a:hover { color: #ff9933; text-decoration: underline; }
img { border:none; }
h2 { font-size:18px; color:#808; }
h3 { font-size:15px; color:#808; font-weight:normal; margin:12px 0 2px; }
h4 { font-size:14px; color:#000; margin:2px 0 0; }
h4 { font-size:14px; color:#000; font-weight:normal; margin:2px 0 0; }
ol { padding-left:2em; }
/* input button */
textarea { border: 1px solid #80B0B0; }
@@ -127,7 +127,6 @@ table img {
}
#left-panel { float:right; width:220px; }
#right-panel { float:left; width:680px; }
#main-panel { width:100%; }
.side {
color:#333;
width:220px;
@@ -178,8 +177,7 @@ table img {
/* footer */
#footer a { color:#333; text-decoration:none; }
/* main */
h2.subject {}
p.path, p.access-notice { margin: 12px 0 6px 0; }
.path, .access-notice { margin: 12px 0 6px 0; }
.with-bg li {
padding-left:10px;
line-height:20px;
@@ -190,7 +188,8 @@ p.path, p.access-notice { margin: 12px 0 6px 0; }
width:321px;
margin:45px 0 0 300px;
}
.avatar-op h2 {
.avatar-op h2,
.upload-file-panel h3 {
padding:2px 5px;
background:#E9F1F4;
border-radius:3px;
@@ -289,6 +288,10 @@ p.path, p.access-notice { margin: 12px 0 6px 0; }
color:#e83;
font-weight:normal;
}
#simplemodal-container .jstree a {
color:#000;
font-weight:normal;
}
#simplemodal-container h3 {
margin:0 0 4px;
}
@@ -339,6 +342,45 @@ p.path, p.access-notice { margin: 12px 0 6px 0; }
}
/*repo page*/
.repo-top-bar {
margin-bottom:-5px;
}
.repo-op {
margin-top:12px;
}
.repo-op a {
margin-left:8px;
background:#fff scroll no-repeat left 50%;
}
.repo-op .upload-file {
padding-left:17px;
background-image:url('../img/upload.jpg');
}
#add-new-dir {
padding-left:20px;
background-image:url('../img/add-new-folder.jpg');
}
.more-op {
cursor:pointer;
}
.op-list {
position:absolute;
background:#fff;
padding:6px 1px;
border:1px solid #eee;
border-radius:5px;
-moz-border-radius:5px;
z-index:10;
}
.op-list li {
padding:0 12px;
}
.op-target {
color:#c39;
}
#rename-form .new-name {
margin-top:8px;
}
.latest-commit .more:hover {
text-decoration:none;
}
@@ -360,7 +402,7 @@ p.path, p.access-notice { margin: 12px 0 6px 0; }
color:#666;
margin-left:2px;
}
h2.repo-history {
.repo-history {
font-weight:normal;
font-size:16px;
margin: 4px 0 12px 0;
@@ -381,6 +423,10 @@ h2.repo-history {
#ls-ch ul {
padding:0 0 6px 0;
}
/*file mv*/
#dirs {
margin-top:8px;
}
/*repo-share-form*/
#email_or_group,
#share-link,
@@ -509,3 +555,24 @@ h2.repo-history {
.reply-form .text-input {
width:82%;
}
/*file upload*/
.upload-file-panel {
width:400px;
margin:70px auto 0;
}
#upload-file-form {
margin-top:10px;
}
#task-progress-bar {/*for progress container*/
width:95%;
height:1em;
}
#task-progress-bar .ui-progressbar-value {/*for progress*/
background:#e83;
margin:0;
}
#task-progress-bar,
#upload-cancel {
margin-top:8px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

BIN
media/img/more-option.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

BIN
media/img/upload-20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

BIN
media/img/upload.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

View File

@@ -73,3 +73,36 @@ function addAutocomplete(ele_id, container_id, data) {
});
}
function filesizeformat(bytes, precision)
{
var kilobyte = 1024;
var megabyte = kilobyte * 1024;
var gigabyte = megabyte * 1024;
var terabyte = gigabyte * 1024;
if (precision === undefined)
precision = 0;
if ((bytes >= 0) && (bytes < kilobyte)) {
return bytes + ' B';
} else if ((bytes >= kilobyte) && (bytes < megabyte)) {
return (bytes / kilobyte).toFixed(precision) + ' KB';
} else if ((bytes >= megabyte) && (bytes < gigabyte)) {
return (bytes / megabyte).toFixed(precision) + ' MB';
} else if ((bytes >= gigabyte) && (bytes < terabyte)) {
return (bytes / gigabyte).toFixed(precision) + ' GB';
} else if (bytes >= terabyte) {
return (bytes / terabyte).toFixed(precision) + ' TB';
} else {
return bytes + ' B';
}
}
function e(str) {
return encodeURIComponent(str);
}

View File

@@ -0,0 +1,96 @@
/**
* Cookie plugin
*
* Copyright (c) 2006 Klaus Hartl (stilbuero.de)
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
*/
/**
* Create a cookie with the given name and value and other optional parameters.
*
* @example $.cookie('the_cookie', 'the_value');
* @desc Set the value of a cookie.
* @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
* @desc Create a cookie with all available options.
* @example $.cookie('the_cookie', 'the_value');
* @desc Create a session cookie.
* @example $.cookie('the_cookie', null);
* @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
* used when the cookie was set.
*
* @param String name The name of the cookie.
* @param String value The value of the cookie.
* @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
* @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
* If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
* If set to null or omitted, the cookie will be a session cookie and will not be retained
* when the the browser exits.
* @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
* @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
* @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
* require a secure protocol (like HTTPS).
* @type undefined
*
* @name $.cookie
* @cat Plugins/Cookie
* @author Klaus Hartl/klaus.hartl@stilbuero.de
*/
/**
* Get the value of a cookie with the given name.
*
* @example $.cookie('the_cookie');
* @desc Get the value of a cookie.
*
* @param String name The name of the cookie.
* @return The value of the cookie.
* @type String
*
* @name $.cookie
* @cat Plugins/Cookie
* @author Klaus Hartl/klaus.hartl@stilbuero.de
*/
jQuery.cookie = function(name, value, options) {
if (typeof value != 'undefined') { // name and value given, set cookie
options = options || {};
if (value === null) {
value = '';
options.expires = -1;
}
var expires = '';
if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
var date;
if (typeof options.expires == 'number') {
date = new Date();
date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
} else {
date = options.expires;
}
expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
}
// CAUTION: Needed to parenthesize options.path and options.domain
// in the following expressions, otherwise they evaluate to undefined
// in the packed version for some reason...
var path = options.path ? '; path=' + (options.path) : '';
var domain = options.domain ? '; domain=' + (options.domain) : '';
var secure = options.secure ? '; secure' : '';
document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
} else { // only name given, get cookie
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
};

View File

@@ -0,0 +1,99 @@
/*
* jQuery Hotkeys Plugin
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Based upon the plugin by Tzury Bar Yochay:
* http://github.com/tzuryby/hotkeys
*
* Original idea by:
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
*/
(function(jQuery){
jQuery.hotkeys = {
version: "0.8",
specialKeys: {
8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
},
shiftNums: {
"`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
"8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
".": ">", "/": "?", "\\": "|"
}
};
function keyHandler( handleObj ) {
// Only care when a possible input has been specified
if ( typeof handleObj.data !== "string" ) {
return;
}
var origHandler = handleObj.handler,
keys = handleObj.data.toLowerCase().split(" ");
handleObj.handler = function( event ) {
// Don't fire in text-accepting inputs that we didn't directly bind to
if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
event.target.type === "text") ) {
return;
}
// Keypress represents characters, not special keys
var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
character = String.fromCharCode( event.which ).toLowerCase(),
key, modif = "", possible = {};
// check combinations (alt|ctrl|shift+anything)
if ( event.altKey && special !== "alt" ) {
modif += "alt+";
}
if ( event.ctrlKey && special !== "ctrl" ) {
modif += "ctrl+";
}
// TODO: Need to make sure this works consistently across platforms
if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
modif += "meta+";
}
if ( event.shiftKey && special !== "shift" ) {
modif += "shift+";
}
if ( special ) {
possible[ modif + special ] = true;
} else {
possible[ modif + character ] = true;
possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
if ( modif === "shift+" ) {
possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
}
}
for ( var i = 0, l = keys.length; i < l; i++ ) {
if ( possible[ keys[i] ] ) {
return origHandler.apply( this, arguments );
}
}
};
}
jQuery.each([ "keydown", "keyup", "keypress" ], function() {
jQuery.event.special[ this ] = { add: keyHandler };
});
})( jQuery );

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

View File

@@ -0,0 +1,61 @@
/*
* jsTree apple theme 1.0
* Supported features: dots/no-dots, icons/no-icons, focused, loading
* Supported plugins: ui (hovered, clicked), checkbox, contextmenu, search
*/
.jstree-apple > ul { background:url("bg.jpg") left top repeat; }
.jstree-apple li,
.jstree-apple ins { background-image:url("d.png"); background-repeat:no-repeat; background-color:transparent; }
.jstree-apple li { background-position:-90px 0; background-repeat:repeat-y; }
.jstree-apple li.jstree-last { background:transparent; }
.jstree-apple .jstree-open > ins { background-position:-72px 0; }
.jstree-apple .jstree-closed > ins { background-position:-54px 0; }
.jstree-apple .jstree-leaf > ins { background-position:-36px 0; }
.jstree-apple a { border-radius:4px; -moz-border-radius:4px; -webkit-border-radius:4px; text-shadow:1px 1px 1px white; }
.jstree-apple .jstree-hovered { background:#e7f4f9; border:1px solid #d8f0fa; padding:0 3px 0 1px; text-shadow:1px 1px 1px silver; }
.jstree-apple .jstree-clicked { background:#beebff; border:1px solid #99defd; padding:0 3px 0 1px; }
.jstree-apple a .jstree-icon { background-position:-56px -20px; }
.jstree-apple a.jstree-loading .jstree-icon { background:url("throbber.gif") center center no-repeat !important; }
.jstree-apple.jstree-focused { background:white; }
.jstree-apple .jstree-no-dots li,
.jstree-apple .jstree-no-dots .jstree-leaf > ins { background:transparent; }
.jstree-apple .jstree-no-dots .jstree-open > ins { background-position:-18px 0; }
.jstree-apple .jstree-no-dots .jstree-closed > ins { background-position:0 0; }
.jstree-apple .jstree-no-icons a .jstree-icon { display:none; }
.jstree-apple .jstree-search { font-style:italic; }
.jstree-apple .jstree-no-icons .jstree-checkbox { display:inline-block; }
.jstree-apple .jstree-no-checkboxes .jstree-checkbox { display:none !important; }
.jstree-apple .jstree-checked > a > .jstree-checkbox { background-position:-38px -19px; }
.jstree-apple .jstree-unchecked > a > .jstree-checkbox { background-position:-2px -19px; }
.jstree-apple .jstree-undetermined > a > .jstree-checkbox { background-position:-20px -19px; }
.jstree-apple .jstree-checked > a > .checkbox:hover { background-position:-38px -37px; }
.jstree-apple .jstree-unchecked > a > .jstree-checkbox:hover { background-position:-2px -37px; }
.jstree-apple .jstree-undetermined > a > .jstree-checkbox:hover { background-position:-20px -37px; }
#vakata-dragged.jstree-apple ins { background:transparent !important; }
#vakata-dragged.jstree-apple .jstree-ok { background:url("d.png") -2px -53px no-repeat !important; }
#vakata-dragged.jstree-apple .jstree-invalid { background:url("d.png") -18px -53px no-repeat !important; }
#jstree-marker.jstree-apple { background:url("d.png") -41px -57px no-repeat !important; text-indent:-100px; }
.jstree-apple a.jstree-search { color:aqua; }
.jstree-apple .jstree-locked a { color:silver; cursor:default; }
#vakata-contextmenu.jstree-apple-context,
#vakata-contextmenu.jstree-apple-context li ul { background:#f0f0f0; border:1px solid #979797; -moz-box-shadow: 1px 1px 2px #999; -webkit-box-shadow: 1px 1px 2px #999; box-shadow: 1px 1px 2px #999; }
#vakata-contextmenu.jstree-apple-context li { }
#vakata-contextmenu.jstree-apple-context a { color:black; }
#vakata-contextmenu.jstree-apple-context a:hover,
#vakata-contextmenu.jstree-apple-context .vakata-hover > a { padding:0 5px; background:#e8eff7; border:1px solid #aecff7; color:black; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; }
#vakata-contextmenu.jstree-apple-context li.jstree-contextmenu-disabled a,
#vakata-contextmenu.jstree-apple-context li.jstree-contextmenu-disabled a:hover { color:silver; background:transparent; border:0; padding:1px 4px; }
#vakata-contextmenu.jstree-apple-context li.vakata-separator { background:white; border-top:1px solid #e0e0e0; margin:0; }
#vakata-contextmenu.jstree-apple-context li ul { margin-left:-4px; }
/* TODO: IE6 support - the `>` selectors */

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

View File

@@ -0,0 +1,77 @@
/*
* jsTree classic theme 1.0
* Supported features: dots/no-dots, icons/no-icons, focused, loading
* Supported plugins: ui (hovered, clicked), checkbox, contextmenu, search
*/
.jstree-classic li,
.jstree-classic ins { background-image:url("d.png"); background-repeat:no-repeat; background-color:transparent; }
.jstree-classic li { background-position:-90px 0; background-repeat:repeat-y; }
.jstree-classic li.jstree-last { background:transparent; }
.jstree-classic .jstree-open > ins { background-position:-72px 0; }
.jstree-classic .jstree-closed > ins { background-position:-54px 0; }
.jstree-classic .jstree-leaf > ins { background-position:-36px 0; }
.jstree-classic .jstree-hovered { background:#e7f4f9; border:1px solid #e7f4f9; padding:0 2px 0 1px; }
.jstree-classic .jstree-clicked { background:navy; border:1px solid navy; padding:0 2px 0 1px; color:white; }
.jstree-classic a .jstree-icon { background-position:-56px -19px; }
.jstree-classic .jstree-open > a .jstree-icon { background-position:-56px -36px; }
.jstree-classic a.jstree-loading .jstree-icon { background:url("throbber.gif") center center no-repeat !important; }
.jstree-classic.jstree-focused { background:white; }
.jstree-classic .jstree-no-dots li,
.jstree-classic .jstree-no-dots .jstree-leaf > ins { background:transparent; }
.jstree-classic .jstree-no-dots .jstree-open > ins { background-position:-18px 0; }
.jstree-classic .jstree-no-dots .jstree-closed > ins { background-position:0 0; }
.jstree-classic .jstree-no-icons a .jstree-icon { display:none; }
.jstree-classic .jstree-search { font-style:italic; }
.jstree-classic .jstree-no-icons .jstree-checkbox { display:inline-block; }
.jstree-classic .jstree-no-checkboxes .jstree-checkbox { display:none !important; }
.jstree-classic .jstree-checked > a > .jstree-checkbox { background-position:-38px -19px; }
.jstree-classic .jstree-unchecked > a > .jstree-checkbox { background-position:-2px -19px; }
.jstree-classic .jstree-undetermined > a > .jstree-checkbox { background-position:-20px -19px; }
.jstree-classic .jstree-checked > a > .jstree-checkbox:hover { background-position:-38px -37px; }
.jstree-classic .jstree-unchecked > a > .jstree-checkbox:hover { background-position:-2px -37px; }
.jstree-classic .jstree-undetermined > a > .jstree-checkbox:hover { background-position:-20px -37px; }
#vakata-dragged.jstree-classic ins { background:transparent !important; }
#vakata-dragged.jstree-classic .jstree-ok { background:url("d.png") -2px -53px no-repeat !important; }
#vakata-dragged.jstree-classic .jstree-invalid { background:url("d.png") -18px -53px no-repeat !important; }
#jstree-marker.jstree-classic { background:url("d.png") -41px -57px no-repeat !important; text-indent:-100px; }
.jstree-classic a.jstree-search { color:aqua; }
.jstree-classic .jstree-locked a { color:silver; cursor:default; }
#vakata-contextmenu.jstree-classic-context,
#vakata-contextmenu.jstree-classic-context li ul { background:#f0f0f0; border:1px solid #979797; -moz-box-shadow: 1px 1px 2px #999; -webkit-box-shadow: 1px 1px 2px #999; box-shadow: 1px 1px 2px #999; }
#vakata-contextmenu.jstree-classic-context li { }
#vakata-contextmenu.jstree-classic-context a { color:black; }
#vakata-contextmenu.jstree-classic-context a:hover,
#vakata-contextmenu.jstree-classic-context .vakata-hover > a { padding:0 5px; background:#e8eff7; border:1px solid #aecff7; color:black; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; }
#vakata-contextmenu.jstree-classic-context li.jstree-contextmenu-disabled a,
#vakata-contextmenu.jstree-classic-context li.jstree-contextmenu-disabled a:hover { color:silver; background:transparent; border:0; padding:1px 4px; }
#vakata-contextmenu.jstree-classic-context li.vakata-separator { background:white; border-top:1px solid #e0e0e0; margin:0; }
#vakata-contextmenu.jstree-classic-context li ul { margin-left:-4px; }
/* IE6 BEGIN */
.jstree-classic li,
.jstree-classic ins,
#vakata-dragged.jstree-classic .jstree-invalid,
#vakata-dragged.jstree-classic .jstree-ok,
#jstree-marker.jstree-classic { _background-image:url("d.gif"); }
.jstree-classic .jstree-open ins { _background-position:-72px 0; }
.jstree-classic .jstree-closed ins { _background-position:-54px 0; }
.jstree-classic .jstree-leaf ins { _background-position:-36px 0; }
.jstree-classic .jstree-open a ins.jstree-icon { _background-position:-56px -36px; }
.jstree-classic .jstree-closed a ins.jstree-icon { _background-position:-56px -19px; }
.jstree-classic .jstree-leaf a ins.jstree-icon { _background-position:-56px -19px; }
#vakata-contextmenu.jstree-classic-context ins { _display:none; }
#vakata-contextmenu.jstree-classic-context li { _zoom:1; }
.jstree-classic .jstree-undetermined a .jstree-checkbox { _background-position:-20px -19px; }
.jstree-classic .jstree-checked a .jstree-checkbox { _background-position:-38px -19px; }
.jstree-classic .jstree-unchecked a .jstree-checkbox { _background-position:-2px -19px; }
/* IE6 END */

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

View File

@@ -0,0 +1,84 @@
/*
* jsTree default-rtl theme 1.0
* Supported features: dots/no-dots, icons/no-icons, focused, loading
* Supported plugins: ui (hovered, clicked), checkbox, contextmenu, search
*/
.jstree-default-rtl li,
.jstree-default-rtl ins { background-image:url("d.png"); background-repeat:no-repeat; background-color:transparent; }
.jstree-default-rtl li { background-position:-90px 0; background-repeat:repeat-y; }
.jstree-default-rtl li.jstree-last { background:transparent; }
.jstree-default-rtl .jstree-open > ins { background-position:-72px 0; }
.jstree-default-rtl .jstree-closed > ins { background-position:-54px 0; }
.jstree-default-rtl .jstree-leaf > ins { background-position:-36px 0; }
.jstree-default-rtl .jstree-hovered { background:#e7f4f9; border:1px solid #d8f0fa; padding:0 2px 0 1px; }
.jstree-default-rtl .jstree-clicked { background:#beebff; border:1px solid #99defd; padding:0 2px 0 1px; }
.jstree-default-rtl a .jstree-icon { background-position:-56px -19px; }
.jstree-default-rtl a.jstree-loading .jstree-icon { background:url("throbber.gif") center center no-repeat !important; }
.jstree-default-rtl.jstree-focused { background:#ffffee; }
.jstree-default-rtl .jstree-no-dots li,
.jstree-default-rtl .jstree-no-dots .jstree-leaf > ins { background:transparent; }
.jstree-default-rtl .jstree-no-dots .jstree-open > ins { background-position:-18px 0; }
.jstree-default-rtl .jstree-no-dots .jstree-closed > ins { background-position:0 0; }
.jstree-default-rtl .jstree-no-icons a .jstree-icon { display:none; }
.jstree-default-rtl .jstree-search { font-style:italic; }
.jstree-default-rtl .jstree-no-icons .jstree-checkbox { display:inline-block; }
.jstree-default-rtl .jstree-no-checkboxes .jstree-checkbox { display:none !important; }
.jstree-default-rtl .jstree-checked > a > .jstree-checkbox { background-position:-38px -19px; }
.jstree-default-rtl .jstree-unchecked > a > .jstree-checkbox { background-position:-2px -19px; }
.jstree-default-rtl .jstree-undetermined > a > .jstree-checkbox { background-position:-20px -19px; }
.jstree-default-rtl .jstree-checked > a > .jstree-checkbox:hover { background-position:-38px -37px; }
.jstree-default-rtl .jstree-unchecked > a > .jstree-checkbox:hover { background-position:-2px -37px; }
.jstree-default-rtl .jstree-undetermined > a > .jstree-checkbox:hover { background-position:-20px -37px; }
#vakata-dragged.jstree-default-rtl ins { background:transparent !important; }
#vakata-dragged.jstree-default-rtl .jstree-ok { background:url("d.png") -2px -53px no-repeat !important; }
#vakata-dragged.jstree-default-rtl .jstree-invalid { background:url("d.png") -18px -53px no-repeat !important; }
#jstree-marker.jstree-default-rtl { background:url("d.png") -41px -57px no-repeat !important; text-indent:-100px; }
.jstree-default-rtl a.jstree-search { color:aqua; }
.jstree-default-rtl .jstree-locked a { color:silver; cursor:default; }
#vakata-contextmenu.jstree-default-rtl-context,
#vakata-contextmenu.jstree-default-rtl-context li ul { background:#f0f0f0; border:1px solid #979797; -moz-box-shadow: 1px 1px 2px #999; -webkit-box-shadow: 1px 1px 2px #999; box-shadow: 1px 1px 2px #999; }
#vakata-contextmenu.jstree-default-rtl-context li { }
#vakata-contextmenu.jstree-default-rtl-context a { color:black; }
#vakata-contextmenu.jstree-default-rtl-context a:hover,
#vakata-contextmenu.jstree-default-rtl-context .vakata-hover > a { padding:0 5px; background:#e8eff7; border:1px solid #aecff7; color:black; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; }
#vakata-contextmenu.jstree-default-rtl-context li.jstree-contextmenu-disabled a,
#vakata-contextmenu.jstree-default-rtl-context li.jstree-contextmenu-disabled a:hover { color:silver; background:transparent; border:0; padding:1px 4px; }
#vakata-contextmenu.jstree-default-rtl-context li.vakata-separator { background:white; border-top:1px solid #e0e0e0; margin:0; }
#vakata-contextmenu.jstree-default-rtl-context li ul { margin-left:-4px; }
/* IE6 BEGIN */
.jstree-default-rtl li,
.jstree-default-rtl ins,
#vakata-dragged.jstree-default-rtl .jstree-invalid,
#vakata-dragged.jstree-default-rtl .jstree-ok,
#jstree-marker.jstree-default-rtl { _background-image:url("d.gif"); }
.jstree-default-rtl .jstree-open ins { _background-position:-72px 0; }
.jstree-default-rtl .jstree-closed ins { _background-position:-54px 0; }
.jstree-default-rtl .jstree-leaf ins { _background-position:-36px 0; }
.jstree-default-rtl a ins.jstree-icon { _background-position:-56px -19px; }
#vakata-contextmenu.jstree-default-rtl-context ins { _display:none; }
#vakata-contextmenu.jstree-default-rtl-context li { _zoom:1; }
.jstree-default-rtl .jstree-undetermined a .jstree-checkbox { _background-position:-18px -19px; }
.jstree-default-rtl .jstree-checked a .jstree-checkbox { _background-position:-36px -19px; }
.jstree-default-rtl .jstree-unchecked a .jstree-checkbox { _background-position:0px -19px; }
/* IE6 END */
/* RTL part */
.jstree-default-rtl .jstree-hovered, .jstree-default-rtl .jstree-clicked { padding:0 1px 0 2px; }
.jstree-default-rtl li { background-image:url("dots.gif"); background-position: 100% 0px; }
.jstree-default-rtl .jstree-checked > a > .jstree-checkbox { background-position:-36px -19px; margin-left:2px; }
.jstree-default-rtl .jstree-unchecked > a > .jstree-checkbox { background-position:0px -19px; margin-left:2px; }
.jstree-default-rtl .jstree-undetermined > a > .jstree-checkbox { background-position:-18px -19px; margin-left:2px; }
.jstree-default-rtl .jstree-checked > a > .jstree-checkbox:hover { background-position:-36px -37px; }
.jstree-default-rtl .jstree-unchecked > a > .jstree-checkbox:hover { background-position:0px -37px; }
.jstree-default-rtl .jstree-undetermined > a > .jstree-checkbox:hover { background-position:-18px -37px; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -0,0 +1,74 @@
/*
* jsTree default theme 1.0
* Supported features: dots/no-dots, icons/no-icons, focused, loading
* Supported plugins: ui (hovered, clicked), checkbox, contextmenu, search
*/
.jstree-default li,
.jstree-default ins { background-image:url("d.png"); background-repeat:no-repeat; background-color:transparent; }
.jstree-default li { background-position:-90px 0; background-repeat:repeat-y; }
.jstree-default li.jstree-last { background:transparent; }
.jstree-default .jstree-open > ins { background-position:-72px 0; }
.jstree-default .jstree-closed > ins { background-position:-54px 0; }
.jstree-default .jstree-leaf > ins { background-position:-36px 0; }
.jstree-default .jstree-hovered { background:#e7f4f9; border:1px solid #d8f0fa; padding:0 2px 0 1px; }
.jstree-default .jstree-clicked { background:#beebff; border:1px solid #99defd; padding:0 2px 0 1px; }
.jstree-default a .jstree-icon { background-position:-56px -19px; }
.jstree-default a.jstree-loading .jstree-icon { background:url("throbber.gif") center center no-repeat !important; }
.jstree-default.jstree-focused { background:#ffffee; }
.jstree-default .jstree-no-dots li,
.jstree-default .jstree-no-dots .jstree-leaf > ins { background:transparent; }
.jstree-default .jstree-no-dots .jstree-open > ins { background-position:-18px 0; }
.jstree-default .jstree-no-dots .jstree-closed > ins { background-position:0 0; }
.jstree-default .jstree-no-icons a .jstree-icon { display:none; }
.jstree-default .jstree-search { font-style:italic; }
.jstree-default .jstree-no-icons .jstree-checkbox { display:inline-block; }
.jstree-default .jstree-no-checkboxes .jstree-checkbox { display:none !important; }
.jstree-default .jstree-checked > a > .jstree-checkbox { background-position:-38px -19px; }
.jstree-default .jstree-unchecked > a > .jstree-checkbox { background-position:-2px -19px; }
.jstree-default .jstree-undetermined > a > .jstree-checkbox { background-position:-20px -19px; }
.jstree-default .jstree-checked > a > .jstree-checkbox:hover { background-position:-38px -37px; }
.jstree-default .jstree-unchecked > a > .jstree-checkbox:hover { background-position:-2px -37px; }
.jstree-default .jstree-undetermined > a > .jstree-checkbox:hover { background-position:-20px -37px; }
#vakata-dragged.jstree-default ins { background:transparent !important; }
#vakata-dragged.jstree-default .jstree-ok { background:url("d.png") -2px -53px no-repeat !important; }
#vakata-dragged.jstree-default .jstree-invalid { background:url("d.png") -18px -53px no-repeat !important; }
#jstree-marker.jstree-default { background:url("d.png") -41px -57px no-repeat !important; text-indent:-100px; }
.jstree-default a.jstree-search { color:aqua; }
.jstree-default .jstree-locked a { color:silver; cursor:default; }
#vakata-contextmenu.jstree-default-context,
#vakata-contextmenu.jstree-default-context li ul { background:#f0f0f0; border:1px solid #979797; -moz-box-shadow: 1px 1px 2px #999; -webkit-box-shadow: 1px 1px 2px #999; box-shadow: 1px 1px 2px #999; }
#vakata-contextmenu.jstree-default-context li { }
#vakata-contextmenu.jstree-default-context a { color:black; }
#vakata-contextmenu.jstree-default-context a:hover,
#vakata-contextmenu.jstree-default-context .vakata-hover > a { padding:0 5px; background:#e8eff7; border:1px solid #aecff7; color:black; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; }
#vakata-contextmenu.jstree-default-context li.jstree-contextmenu-disabled a,
#vakata-contextmenu.jstree-default-context li.jstree-contextmenu-disabled a:hover { color:silver; background:transparent; border:0; padding:1px 4px; }
#vakata-contextmenu.jstree-default-context li.vakata-separator { background:white; border-top:1px solid #e0e0e0; margin:0; }
#vakata-contextmenu.jstree-default-context li ul { margin-left:-4px; }
/* IE6 BEGIN */
.jstree-default li,
.jstree-default ins,
#vakata-dragged.jstree-default .jstree-invalid,
#vakata-dragged.jstree-default .jstree-ok,
#jstree-marker.jstree-default { _background-image:url("d.gif"); }
.jstree-default .jstree-open ins { _background-position:-72px 0; }
.jstree-default .jstree-closed ins { _background-position:-54px 0; }
.jstree-default .jstree-leaf ins { _background-position:-36px 0; }
.jstree-default a ins.jstree-icon { _background-position:-56px -19px; }
#vakata-contextmenu.jstree-default-context ins { _display:none; }
#vakata-contextmenu.jstree-default-context li { _zoom:1; }
.jstree-default .jstree-undetermined a .jstree-checkbox { _background-position:-20px -19px; }
.jstree-default .jstree-checked a .jstree-checkbox { _background-position:-38px -19px; }
.jstree-default .jstree-unchecked a .jstree-checkbox { _background-position:-2px -19px; }
/* IE6 END */

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -112,6 +112,7 @@ INSTALLED_APPS = (
'seahub.group',
'seahub.share',
'seahub.subdomain',
'gunicorn',
)
AUTHENTICATION_BACKENDS = (
@@ -203,5 +204,27 @@ NOTIFICATION_CACHE_TIMEOUT = 0
LOGIN_URL = SITE_ROOT + 'accounts/login'
FILE_UPLOAD_MAX_MEMORY_SIZE = 0
FILE_UPLOAD_TEMP_DIR = "/tmp/seafile-upload"
if not os.access(FILE_UPLOAD_TEMP_DIR, os.F_OK):
os.mkdir(FILE_UPLOAD_TEMP_DIR)
FILE_UPLOAD_HANDLERS = (
"seahub.utils.UploadProgressCachedHandler",
"django.core.files.uploadhandler.MemoryFileUploadHandler",
"django.core.files.uploadhandler.TemporaryFileUploadHandler",
)
# profile
#AUTH_PROFILE_MODULE = "profile.UserProfile"
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/tmp/seahub_cache',
}
}
MAX_UPLOAD_FILE_SIZE = 1024 * 1024 * 1024 # 1GB
MAX_UPLOAD_FILE_NAME_LEN = 256

View File

@@ -77,7 +77,7 @@
<div id="right-panel">
{% block right_panel %}{% endblock %}
</div>
<div id="main-panel" class="clear ovhd">
<div id="main-panel" class="clear w100">
{% block main_panel %}{% endblock %}
</div>
<div id="dialog-confirm" class="hide">

View File

@@ -4,5 +4,5 @@
{% block main_panel %}
<p>{{ error_msg }}</p>
<p class="error">{{ error_msg }}</p>
{% endblock %}

View File

@@ -0,0 +1,27 @@
{% extends "myhome_base.html" %}
{% block extra_script %}
<script type="text/javascript">
// Update progress bar
function update_progress_info() {
$.ajax({
url: '{{ SITE_ROOT }}file_upload_progress/?X-Progress-ID=' + '{{ uuid }}',
dataType: 'json',
cache: false,
contentType: 'application/json; charset=utf-8',
success: function(data) {
if (data) {
$('#upload-progress-text', window.parent.document).html(filesizeformat(data.uploaded) + ' / ' + filesizeformat(data.length));
$('#task-progress-bar', window.parent.document).removeClass('hide').progressbar({
value: data.uploaded / data.length * 100
});
}
}
});
setTimeout(update_progress_info, 1000);
};
update_progress_info();
</script>
{% endblock %}

View File

@@ -12,9 +12,7 @@
{% endblock %}
{% block main_panel %}
<h2 class="subject">
{{repo.props.name}}
</h2>
<h2>{{repo.props.name}}</h2>
<div class="side fright">
<h3>基本信息</h3>
@@ -61,7 +59,8 @@
{% if not can_access %}
<p class="access-notice">无法在线查看该同步目录。</p>
{% else %}
<p class="path">
<div class="repo-top-bar w100 ovhd">
<p class="path fleft">
当前路径:
{% for name, link in zipped %}
{% if not forloop.last %}
@@ -71,12 +70,18 @@
{% endif %}
{% endfor %}
</p>
<div class="repo-op fright">
<a href="{{ SITE_ROOT }}repo/upload_file/{{repo.id}}/?p={{ path }}" class="upload-file">上传</a>
<a id="add-new-dir" href="#">新建目录</a>
</div>
</div>
<table>
<tr>
<th width="5%"></th>
<th width="69%">名字</th>
<th width="67%">名字</th>
<th width="13%">大小</th>
<th width="13%">操作</th>
<th width="15%">操作</th>
</tr>
{% for dirent in dir_list %}
@@ -84,18 +89,32 @@
<td class="icon-container"><img src="{{ MEDIA_URL }}img/folder-icon-24.png" alt="目录" /></td>
<td><a href="{{ SITE_ROOT }}repo/{{ repo.id }}/?p={{ path|urlencode }}{{ dirent.obj_name|urlencode }}">{{ dirent.obj_name }}</a></td>
<td></td>
<td></td>
<td>
<img src="{{ MEDIA_URL }}img/more-option.png" alt="更多操作" class="more-op hide" />
<ul class="op-list hide">
<li><a class="op" href="{{ SITE_ROOT }}repo/{{ repo.props.id }}/{{ dirent.props.obj_id }}/?p={{ path|urlencode }}&file_name={{ dirent.props.obj_name|urlencode }}&op=del">删除</a></li>
<li><a class="op dir-rename" href="#" data="{{ dirent.obj_name }}">重命名</a></li>
<li><a class="op dir-mv" href="#" data="{{ dirent.obj_name }}">移动</a></li>
<li><a class="op dir-cp" href="#" data="{{ dirent.obj_name }}">复制</a></li>
</ul>
</td>
</tr>
{% endfor %}
{% for dirent in file_list %}
<tr>
<td class="icon-container"><img src="{{ MEDIA_URL }}img/{{ dirent.obj_name|file_icon_filter }}" alt="文件" /></td>
<td>{{ dirent.props.obj_name }}</td>
<td><a class="op" href="{{ SITE_ROOT }}repo/{{ repo.props.id }}/{{ dirent.props.obj_id }}/?file_name={{ dirent.props.obj_name }}&op=view">{{ dirent.props.obj_name }}</a></td>
<td>{{ dirent.file_size|filesizeformat }}</td>
<td>
<a class="op" href="{{ SITE_ROOT }}repo/{{ repo.props.id }}/{{ dirent.props.obj_id }}/?file_name={{ dirent.props.obj_name }}&op=view">查看</a>
<a class="op" href="{{ SITE_ROOT }}repo/{{ repo.props.id }}/{{ dirent.props.obj_id }}/?file_name={{ dirent.props.obj_name }}&op=download">下载</a>
<img src="{{ MEDIA_URL }}img/more-option.png" alt="更多操作" class="more-op hide" />
<ul class="op-list hide">
<li><a class="op" href="{{ SITE_ROOT }}repo/{{ repo.props.id }}/{{ dirent.props.obj_id }}/?file_name={{ dirent.props.obj_name }}&op=download">下载</a></li>
<li><a class="op" href="{{ SITE_ROOT }}repo/{{ repo.props.id }}/{{ dirent.props.obj_id }}/?p={{ path|urlencode }}&file_name={{ dirent.props.obj_name|urlencode }}&op=del">删除</a></li>
<li><a class="op file-rename" href="#" data="{{ dirent.obj_name }}">重命名</a></li>
<li><a class="op file-mv" href="#" data="{{ dirent.obj_name }}">移动</a></li>
<li><a class="op file-cp" href="#" data="{{ dirent.obj_name }}">复制</a></li>
</ul>
</td>
</tr>
{% endfor %}
@@ -103,9 +122,206 @@
{% endif %}
{% endif %}
</div>
<form id="add-new-dir-form" action="{{ SITE_ROOT}}repo/new_dir/" method="post" class="hide">
<h4>新目录名称:</h4>
<input type="hidden" name="repo_id" value="{{ repo.id }}" />
<input type="hidden" name="parent_dir" value="{{ path }}" />
<input type="text" name="new_dir_name" value="" /><br />
<p class="error hide">输入不能为空。</p>
<input type="submit" value="提交" class="submit" />
<button class="simplemodal-close">取消</button>
</form>
<form id="mv-form" action="{{ SITE_ROOT }}file/move/" method="post" class="hide">
<p id="mv-hd"></p>
<div id="dirs"></div>
<input type="hidden" name="operation" id="operation" value="" />
<input type="hidden" name="src_repo" value="{{ repo.id }}" />
<input type="hidden" name="src_path" value="{{ path }}" />
<input type="hidden" name="obj_name" value="" />
<input type="hidden" name="obj_type" value="" />
<input type="hidden" name="dst_repo" value="" />
<input type="hidden" name="dst_path" value="" />
<p class="error hide">请点击选择目标目录。</p>
<input type="submit" value="提交" class="submit" />
<button class="simplemodal-close">取消</button>
</form>
<form id="rename-form" action="{{ SITE_ROOT}}repo/file_rename/" method="post" class="hide">
<p><span id="rename-target" class="op-target"></span> 重命名为:</p>
<input type="hidden" name="repo_id" value="{{ repo.id }}" />
<input type="hidden" name="parent_dir" value="{{ path }}" />
<input type="hidden" name="oldname" value="" />
<input type="text" name="newname" value="" class="new-name" /><br />
<p class="error hide">输入不能为空。</p>
<input type="submit" value="提交" class="submit" />
<button class="simplemodal-close">取消</button>
</form>
{% endblock %}
{% block extra_script %}
<script type="text/javascript" src="{{ MEDIA_URL }}jstree_pre1.0_stable/jquery.jstree.js"></script>
<script type="text/javascript">
$('#add-new-dir').click(function () {
$('#add-new-dir-form').modal({appendTo:'#main'});
return false;
});
$("table tr:gt(0)").hover(
function() {
$(this).find('.more-op').removeClass('hide');
},
function() {
$(this).find('.more-op').addClass('hide');
}
);
var Hide = '';
$('.more-op').hover(
function() {
$(this).parent().css('position','relative');
$('.op-list').attr('class', 'op-list hide');
if ($(this).offset().top + $(this).next().height() > $('#main').offset().top + $('#main').height()) {
$(this).next().css('bottom', 25);
}
$(this).next().removeClass('hide');
clearTimeout(Hide);
return false;
},
function() {
var op_list = $(this).next();
Hide = setTimeout(function() { op_list.addClass('hide'); }, 1000);
}
);
$('.op-list').hover(
function(){
clearTimeout(Hide);
},
function(){
$(this).addClass('hide');
}
);
$('.op-list li').hover(
function() {
$(this).css('background', '#eee');
},
function() {
$(this).css('background', '#fff');
}
);
$('.file-rename, .dir-rename').click(function () {
var type = $(this).hasClass('file-rename') ? '文件 ' : '目录 ',
name = $(this).attr('data');
$('#rename-target').html(type + name);
$('input[name="oldname"]').val(name);
$('#rename-form').modal({appendTo:'#main'});
return false;
});
var accessible_repos = [];
{% for repo in accessible_repos %}
{% if repo.props.has_subdir %}
accessible_repos.push({
'data': '{{ repo.props.name }}',
'attr': {'repo_id': '{{ repo.props.id }}'},
'state': 'closed'
});
{% else %}
accessible_repos.push({
'data': '{{ repo.props.name }}',
'attr': {'repo_id': '{{ repo.props.id }}'}
});
{% endif %}
{% endfor %}
$('.file-cp, .file-mv, .dir-cp, .dir-mv').click(function () {
var obj_name = $(this).attr('data'),
mv_type = '',
file_type = '';
if ($(this).hasClass('file-cp') || $(this).hasClass('dir-cp')) {
$('#operation').val('cp');
mv_type = '复制';
} else {
$('#operation').val('mv');
mv_type = '移动';
}
if ($(this).hasClass('file-cp') || $(this).hasClass('file-mv')) {
file_type = '文件';
$('input[name="obj_type"]').val('file');
} else {
file_type = '目录';
$('input[name="obj_type"]').val('dir');
}
$('input[name="obj_name"]').val(obj_name);
$('#mv-hd').html('将 <span class="op-target">' + file_type + ' ' + obj_name + '</span> ' + mv_type + '到:');
$('#dirs')
.bind('loaded.jstree', function(event,data) {
$('#mv-form').modal({appendTo:'#main'});
})
.bind('after_open.jstree after_close.jstree set_focus.jstree', function(event, data) {
//resize the popup when open/close/focus a dir
$('#simplemodal-container').css('height', $('#mv-form').height()).css('width', $('#mv-form').width());
})
.bind('select_node.jstree', function(event,data) {
var repo_id = data.rslt.obj.attr('repo_id') || data.inst._get_parent(data.rslt.obj).attr('repo_id');
$('input[name="dst_repo"]').attr('value', repo_id);
var path = data.inst.get_path(data.rslt.obj);
var mv_dst_path = '';
if (path.length == 1) {
mv_dst_path = '/';
} else {
path.shift();
mv_dst_path = '/' + path.join('/') + '/';
}
$('input[name="dst_path"]').attr('value', mv_dst_path);
})
.jstree({
'json_data': {
'data': accessible_repos,
'ajax': {
'url': function(data) {
var path = this.get_path(data);
var repo_id = data.attr('repo_id');
if (path.length == 1) {
path = '/';
} else {
path.shift();
path = '/' + path.join('/') + '/';
}
return '{{ SITE_ROOT }}file/move/get_subdir/?repo_id=' + encodeURIComponent(repo_id) + '&path=' + encodeURIComponent(path);
}
}
},
'core': {
'animation': 100
},
'plugins': ['themes', 'json_data', 'ui']
});
return false;
});
$('#add-new-dir-form .submit, #rename-form .submit').click(function() {
if (!$.trim($(this).prev().prev().prev().val())) {//if the input is empty
$(this).prev().removeClass('hide');//show error msg
$('#simplemodal-container').css('height', $(this).parent().height()).css('width', $(this).parent().width());
return false;
}
});
$('#mv-form .submit').click(function() {
if (!$.trim($(this).prev().prev().val())) {//if the input is empty
$(this).prev().removeClass('hide');//show error msg
$('#simplemodal-container').css('height', $(this).parent().height()).css('width', $(this).parent().width());
return false;
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,106 @@
{% extends "myhome_base.html" %}
{% load seahub_tags %}
{% block info_bar_message %}
{% if request.user.is_authenticated %}
{{ block.super }}
{% else %}
<div id="info-bar">
<span class="info">当前链接会在短期内失效,欢迎您 <a href="http://seafile.com/" target="_blank">加入Seafile </a>体验更多功能。</span>
</div>
{% endif %}
{% endblock %}
{% block main_panel %}
{% if used_space < total_space %}
<div class="upload-file-panel">
<h3>上传文件到
{% for name, link in zipped %}
<a href="{{ SITE_ROOT }}repo/{{ repo.id }}/?p={{ link|urlencode }}">{{ name }}</a> /
{% endfor %}
</h3>
<form id="upload-file-form" enctype="multipart/form-data" method="post">
<input type="hidden" name="parent_dir" id="parent_dir" value="{{ parent_dir }}" />
<input type="file" name="file" id="file" /><br />
<p id="error-msg" class="error">{{ error_msg }}</p>
<input id="upload-submit" type="submit" value="提交" />
</form>
<div id="upload-progress" class="hide">
<p>上传进度: <span id="upload-progress-text">获取中,请稍侯...</span></p>
<div id="task-progress-bar" class="hide"></div>
<button id="upload-cancel">取消</button>
</div>
<iframe id="request-progress" class="hide"><!--for chrome--></iframe>
</div>
{% else %}
<div class="text-panel">
<p class="error">您的空间已经用完。</p>
</div>
{% endif %}
{% endblock %}
{% block extra_script %}
<script type="text/javascript">
function gen_uuid() {
var uuid = "";
for (var i=0; i < 32; i++) {
uuid += Math.floor(Math.random() * 16).toString(16);
}
return uuid;
}
// NOTE: In order to real time show upload progress, we must deploy seahub in
// ngnix or gunicorn, since we need more than one seahub process to be
// running: one for receiving file, one for handling ajax request.
function submit_and_real_time_show () {
var form = $('#upload-file-form')[0],
uuid = gen_uuid(); // id for this upload so we can fetch progress info.
$('#upload-progress').removeClass('hide');
// Append X-Progress-ID uuid form action
form.action += (form.action.indexOf('?') == -1 ? '?' : '&') + 'X-Progress-ID=' + uuid;
form.submit();
$('#upload-submit').addClass('hide');
// Update progress bar: not work in chrome. for ff.
function update_progress_info() {
$.ajax({
url: '{{ SITE_ROOT }}file_upload_progress/?X-Progress-ID=' + uuid,
dataType: 'json',
cache: false,
contentType: 'application/json; charset=utf-8',
success: function(data) {
if (data) {
$('#upload-progress-text').html(filesizeformat(data.uploaded) + ' / ' + filesizeformat(data.length));
$('#task-progress-bar').removeClass('hide').progressbar({
value: data.uploaded / data.length * 100
});
}
}
});
setTimeout(update_progress_info, 1000);
};
update_progress_info();
// Update progress bar: for chrome
$('#request-progress').attr('src', '{{ SITE_ROOT }}file_upload_progress_page/?uuid=' + uuid);
return false;
};
$('#upload-submit').click(function () {
if (!$.trim($('#file').val())) {
$('#error-msg').html('请先选择一个文件');
return false;
}
$('#error-msg').addClass('hide');
submit_and_real_time_show();
return false;
});
$('#upload-cancel').click(function() {
location.reload(true);
});
</script>
{% endblock %}

13
urls.py
View File

@@ -9,7 +9,10 @@ from seahub.views import root, peers, myhome, \
ownerhome, repo_history_dir, repo_history_revert, \
user_info, repo_set_access_property, repo_access_file, \
repo_remove_share, repo_download, org_info, \
seafile_access_check, back_local, repo_history_changes
seafile_access_check, back_local, repo_history_changes, \
repo_upload_file, file_upload_progress, file_upload_progress_page, get_subdir, file_move, \
repo_new_dir, repo_rename_file, validate_filename
from seahub.notifications.views import notification_list
from seahub.share.views import share_admin
from seahub.group.views import group_list
@@ -40,6 +43,12 @@ urlpatterns = patterns('',
url(r'^shareadmin/$', share_admin, name='share_admin'),
(r'^shareadmin/removeshare/$', repo_remove_share),
(r'^file_upload_progress/$', file_upload_progress),
(r'^file_upload_progress_page/$', file_upload_progress_page),
(r'^repo/new_dir/$', repo_new_dir),
(r'^repo/upload_check/$', validate_filename),
(r'^repo/file_rename/$', repo_rename_file),
url(r'^repo/upload_file/(?P<repo_id>[^/]+)/$', repo_upload_file, name='repo_upload_file'),
url(r'^repo/(?P<repo_id>[^/]+)/$', repo, name='repo'),
(r'^repo/history/(?P<repo_id>[^/]+)/$', repo_history),
(r'^repo/history/dir/(?P<repo_id>[^/]+)/$', repo_history_dir),
@@ -51,6 +60,8 @@ urlpatterns = patterns('',
# (r'^repo/setap/(?P<repo_id>[^/]+)/$', repo_set_access_property),
(r'^repo/(?P<repo_id>[^/]+)/(?P<obj_id>[^/]+)/$', repo_access_file),
(r'^download/repo/$', repo_download),
(r'^file/move/get_subdir/$', get_subdir),
(r'^file/move/$', file_move),
(r'^seafile_access_check/$', seafile_access_check),
url(r'^org/remove/(?P<org_id>[\d]+)/$', org_remove, name="org_remove"),
(r'^org/$', org_info),

145
utils.py
View File

@@ -1,13 +1,21 @@
#!/usr/bin/env python
# encoding: utf-8
import re
import settings
import re
import time
import os
import stat
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.utils.hashcompat import sha_constructor
from seaserv import get_commits
from django.core.files.uploadhandler import FileUploadHandler, StopFutureHandlers, StopUpload
from django.core.cache import cache
from seaserv import seafserv_rpc, ccnet_threaded_rpc, seafserv_threaded_rpc, \
get_repo, get_commits, get_group_repoids
EMPTY_SHA1 = '0000000000000000000000000000000000000000'
def go_permission_error(request, msg=None):
"""
@@ -85,3 +93,136 @@ def calculate_repo_last_modify(repo_list):
repo.latest_modify = get_commits(repo.id, 0, 1)[0].ctime
except:
repo.latest_modify = None
class UploadProgressCachedHandler(FileUploadHandler):
"""Tracks progress for file uploads. The http post request must contain a
header or query parameter, 'X-Progress-ID', which should contain a unique
string to identify the upload to be tracked.
"""
def __init__(self, request=None):
super(UploadProgressCachedHandler, self).__init__(request)
self.progress_id = None
self.cache_key = None
self.chunk_size = 1024
def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
self.content_length = content_length
if 'X-Progress-ID' in self.request.GET :
self.progress_id = self.request.GET['X-Progress-ID']
elif 'X-Progress-ID' in self.request.META:
self.progress_id = self.request.META['X-Progress-ID']
print "handle_raw_input: ", self.progress_id
if self.progress_id:
self.cache_key = "%s_%s" % (self.request.user.username, self.progress_id )
# make cache expiring in 30 seconds
cache.set(self.cache_key, {
'length': self.content_length,
'uploaded' : 0
}, 30)
def new_file(self, field_name, file_name, content_type, content_length, charset=None):
pass
def receive_data_chunk(self, raw_data, start):
time.sleep(1)
if self.cache_key:
data = cache.get(self.cache_key)
data['uploaded'] += self.chunk_size
cache.set(self.cache_key, data, 30)
return raw_data
def file_complete(self, file_size):
pass
def upload_complete(self):
if self.cache_key:
cache.delete(self.cache_key)
def check_filename_with_rename(repo_id, parent_dir, filename):
latest_commit = get_commits(repo_id, 0, 1)[0]
dirents = seafserv_rpc.list_dir_by_path(latest_commit.id,
parent_dir.encode('utf-8'))
def no_duplicate(name):
for dirent in dirents:
if dirent.obj_name == name:
return False
return True
def make_new_name(filename, i):
base, ext = os.path.splitext(filename)
if ext:
new_base = "%s (%d)" % (base, i)
return new_base + ext
else:
return "%s (%d)" % (filename, i)
if no_duplicate(filename):
return filename
else:
i = 1
while True:
new_name = make_new_name (filename, i)
if no_duplicate(new_name):
return new_name
else:
i += 1
def get_accessible_repos(request, repo):
"""Get all repos the current user can access when coping/moving files
online. If the repo is encrypted, then files can only be copied/moved
within the same repo. Otherwise, files can be copied/moved between
owned/shared/group repos of the current user.
"""
def check_has_subdir(repo):
latest_commit = get_commits(repo.props.id, 0, 1)[0]
if latest_commit.root_id == EMPTY_SHA1:
return False
dirs = seafserv_rpc.list_dir_by_path(latest_commit.id, '/')
for dirent in dirs:
if stat.S_ISDIR(dirent.props.mode):
return True
if repo.encrypted:
repo.has_subdir = check_has_subdir(repo)
accessible_repos = [repo]
return accessible_repos
accessible_repos = []
email = request.user.username
owned_repos = seafserv_threaded_rpc.list_owned_repos(email)
shared_repos = seafserv_threaded_rpc.list_share_repos(email, 'to_email', -1, -1)
groups_repos = []
groups = ccnet_threaded_rpc.get_groups(email)
for group in groups:
group_repo_ids = get_group_repoids(group_id=group.id)
for repo_id in group_repo_ids:
if not repo_id:
continue
group_repo = get_repo(repo_id)
if not group_repo:
continue
group_repo.share_from = seafserv_threaded_rpc.get_group_repo_share_from(repo_id)
if email != group_repo.share_from:
groups_repos.append(group_repo)
def has_repo(repos, repo):
for r in repos:
if repo.id == r.id:
return True
return False
for repo in owned_repos + shared_repos + groups_repos:
if not has_repo(accessible_repos, repo):
if not repo.props.encrypted:
accessible_repos.append(repo)
repo.props.has_subdir = check_has_subdir(repo)
return accessible_repos

306
views.py
View File

@@ -1,5 +1,6 @@
# encoding: utf-8
import settings
import os
import stat
import simplejson as json
import sys
@@ -14,6 +15,9 @@ from django.shortcuts import render_to_response, redirect
from django.template import Context, loader, RequestContext
from django.views.decorators.csrf import csrf_protect
from django.core.cache import cache
from django.http import HttpResponse, HttpResponseServerError
from auth.decorators import login_required
from auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm, \
PasswordChangeForm
@@ -30,7 +34,8 @@ from seahub.notifications.models import UserNotification
from forms import AddUserForm
from utils import go_permission_error, go_error, list_to_string, \
get_httpserver_root, get_ccnetapplet_root, gen_token, \
calculate_repo_last_modify
calculate_repo_last_modify, \
check_filename_with_rename, get_accessible_repos, EMPTY_SHA1
from seahub.profile.models import Profile
@login_required
@@ -191,7 +196,7 @@ def render_repo(request, repo_id, error=''):
if path[-1] != '/':
path = path + '/'
if latest_commit.root_id == '0000000000000000000000000000000000000000':
if latest_commit.root_id == EMPTY_SHA1:
dirs = []
else:
try:
@@ -213,6 +218,12 @@ def render_repo(request, repo_id, error=''):
file_list.sort(lambda x, y : cmp(x.obj_name.lower(),
y.obj_name.lower()))
try:
accessible_repos = get_accessible_repos(request, repo)
except SearpcError, e:
error_msg = e.msg
return go_error(request, error_msg)
# generate path and link
zipped = gen_path_link(path, repo.name)
@@ -229,8 +240,115 @@ def render_repo(request, repo_id, error=''):
"path" : path,
"zipped" : zipped,
"error" : error,
"accessible_repos" : accessible_repos,
}, context_instance=RequestContext(request))
@login_required
def repo_upload_file(request, repo_id):
repo = get_repo(repo_id)
total_space = pow(2,30)
used_space = seafserv_threaded_rpc.get_user_quota_usage(request.user.username)
############ GET ############
if request.method == 'GET':
parent_dir = request.GET.get('p', '/')
zipped = gen_path_link (parent_dir, repo.name)
# TODO: per user quota
return render_to_response ('repo_upload_file.html', {
"repo": repo,
"parent_dir": parent_dir,
"used_space": used_space,
"total_space": total_space,
"zipped": zipped,
}, context_instance=RequestContext(request))
############ POST ############
parent_dir = request.POST.get('parent_dir', '/')
def render_upload_error(error_msg):
zipped = gen_path_link (parent_dir, repo.name)
return render_to_response ('repo_upload_file.html', {
"error_msg": error_msg,
"repo": repo,
"used_space": used_space,
"total_space": total_space,
"zipped": zipped,
"parent_dir": parent_dir,
}, context_instance=RequestContext(request))
try:
tmp_file = request.FILES['file']
except:
error_msg = u'请选择一个文件'
return render_upload_error(error_msg)
tmp_file_path = tmp_file.temporary_file_path()
if not os.access(tmp_file_path, os.F_OK):
error_msg = u'上传文件失败'
return render_upload_error(error_msg)
# rename the file if there is name conflicts
filename = check_filename_with_rename(repo_id, parent_dir, tmp_file.name)
if len(filename) > settings.MAX_UPLOAD_FILE_NAME_LEN:
error_msg = u"您上传的文件名称太长"
return go_error(request, error_msg)
if tmp_file.size > settings.MAX_UPLOAD_FILE_SIZE:
error_msg = u"您上传的文件太大"
return go_error(request, error_msg)
try:
seafserv_threaded_rpc.put_file (repo_id, tmp_file_path, parent_dir,
filename, request.user.username);
except SearpcError, e:
error_msg = e.msg
return render_upload_error(error_msg)
url = reverse('repo', args=[repo_id]) + ('?p=%s' % parent_dir)
return HttpResponseRedirect(url)
def get_subdir(request):
repo_id = request.GET.get('repo_id', '')
path = request.GET.get('path', '')
if not (repo_id and path):
return go_error(request)
latest_commit = get_commits(repo_id, 0, 1)[0]
try:
dirents = seafserv_rpc.list_dir_by_path(latest_commit.id, path.encode('utf-8'))
except SearpcError, e:
return go_error(request, e.msg)
subdirs = []
for dirent in dirents:
if not stat.S_ISDIR(dirent.props.mode):
continue
dirent.has_subdir = False
path_ = os.path.join (path, dirent.obj_name)
try:
dirs_ = seafserv_rpc.list_dir_by_path(latest_commit.id, path_.encode('utf-8'))
except SearpcError, e:
return go_error(request, e.msg)
for dirent_ in dirs_:
if stat.S_ISDIR(dirent_.props.mode):
dirent.has_subdir = True
break
if dirent.has_subdir:
subdir = {
'data': dirent.obj_name,
'attr': {'repo_id': repo_id },
'state': 'closed'
}
subdirs.append(subdir)
else:
subdirs.append(dirent.obj_name)
content_type = 'application/json; charset=utf-8'
return HttpResponse(json.dumps(subdirs),
content_type=content_type)
def repo(request, repo_id):
if request.method == 'GET':
return render_repo(request, repo_id)
@@ -544,7 +662,7 @@ def myhome(request):
owned_repos.sort(lambda x, y: cmp(y.latest_modify, x.latest_modify))
# Repos that are share to me
in_repos = seafserv_threaded_rpc.list_share_repos(request.user.username,
in_repos = seafserv_threaded_rpc.list_share_repos(email,
'to_email', -1, -1)
calculate_repo_last_modify(in_repos)
in_repos.sort(lambda x, y: cmp(y.latest_modify, x.latest_modify))
@@ -620,6 +738,19 @@ def repo_set_access_property(request, repo_id):
return HttpResponseRedirect(reverse('repo', args=[repo_id]))
@login_required
def repo_del_file(request, repo_id):
parent_dir = request.GET.get("p", "/")
file_name = request.GET.get("file_name")
user = request.user.username
try:
seafserv_threaded_rpc.del_file(repo_id, parent_dir,file_name, user)
except Exception, e:
pass
url = reverse('repo', args=[repo_id]) + ('?p=%s' % parent_dir)
return HttpResponseRedirect(url)
def repo_access_file(request, repo_id, obj_id):
if repo_id:
repo = get_repo(repo_id)
@@ -638,6 +769,12 @@ def repo_access_file(request, repo_id, obj_id):
if repo.props.encrypted and not password_set:
return HttpResponseRedirect(reverse('repo', args=[repo_id]))
op = request.GET.get('op', 'view')
file_name = request.GET.get('file_name', '')
if op == 'del':
return repo_del_file(request, repo_id)
# if a repo doesn't have access property in db, then assume it's 'own'
repo_ap = seafserv_threaded_rpc.repo_query_access_property(repo_id)
if not repo_ap:
@@ -663,8 +800,6 @@ def repo_access_file(request, repo_id, obj_id):
raise Http404
http_server_root = get_httpserver_root()
file_name = request.GET.get('file_name', '')
op = request.GET.get('op', 'view')
redirect_url = '%s/access?repo_id=%s&id=%s&filename=%s&op=%s&t=%s&u=%s' % (http_server_root,
repo_id, obj_id,
@@ -697,6 +832,53 @@ def repo_download(request):
return HttpResponseRedirect(redirect_url)
@login_required
def file_move(request):
src_repo_id = request.POST.get('src_repo')
src_path = request.POST.get('src_path')
dst_repo_id = request.POST.get('dst_repo')
dst_path = request.POST.get('dst_path')
obj_name = request.POST.get('obj_name')
obj_type = request.POST.get('obj_type') # dir or file
op = request.POST.get('operation')
if not (src_repo_id and src_path and dst_repo_id \
and dst_path and obj_name and obj_type and op):
return go_error(request)
# do nothing when dst is the same as src
if src_repo_id == dst_repo_id and src_path == dst_path:
url = reverse('repo', args=[src_repo_id]) + ('?p=%s' % src_path)
return HttpResponseRedirect(url)
# Error when moving/copying a dir to its subdir
if obj_type == 'dir':
src_dir = os.path.join(src_path, obj_name)
if dst_path.startswith(src_dir):
error_msg = u"不能把目录 %s %s到它的子目录 %s" \
% (src_dir, u"复制" if op == 'cp' else u"移动", dst_path)
return go_error(request, error_msg)
new_obj_name = check_filename_with_rename(dst_repo_id, dst_path, obj_name)
try:
if op == 'cp':
seafserv_threaded_rpc.copy_file (src_repo_id, src_path, obj_name,
dst_repo_id, dst_path, new_obj_name,
request.user.username)
elif op == 'mv':
seafserv_threaded_rpc.move_file (src_repo_id, src_path, obj_name,
dst_repo_id, dst_path, new_obj_name,
request.user.username)
except Exception, e:
return go_error(request, str(e))
url = reverse('repo', args=[src_repo_id]) + ('?p=%s' % src_path)
return HttpResponseRedirect(url)
def seafile_access_check(request):
repo_id = request.GET.get('repo_id', '')
applet_root = get_ccnetapplet_root()
@@ -1141,3 +1323,117 @@ def org_info(request):
'groups': groups,
}, context_instance=RequestContext(request))
@login_required
def file_upload_progress(request):
"""
Return JSON object with information about the progress of an upload.
"""
progress_id = None
if 'X-Progress-ID' in request.GET:
progress_id = request.GET['X-Progress-ID']
elif 'X-Progress-ID' in request.META:
progress_id = request.META['X-Progress-ID']
if progress_id:
cache_key = "%s_%s" % (request.user.username, progress_id)
data = cache.get(cache_key)
return HttpResponse(json.dumps(data))
else:
return HttpResponseServerError('Server Error: You must provide X-Progress-ID header or query param.')
@login_required
def file_upload_progress_page(request):
'''
As iframe in repo_upload_file.html, for solving problem in chrome.
'''
uuid = request.GET.get('uuid', '')
return render_to_response('file_upload_progress_page.html', {
'uuid': uuid,
}, context_instance=RequestContext(request))
@login_required
def repo_new_dir(request):
repo_id = request.POST.get("repo_id")
parent_dir = request.POST.get("parent_dir")
new_dir_name = request.POST.get("new_dir_name")
user = request.user.username
if not new_dir_name:
error_msg = u"请输入新目录名"
return go_error(request, error_msg)
if not (repo_id and parent_dir and user):
return go_error(request)
if len(new_dir_name) > settings.MAX_UPLOAD_FILE_NAME_LEN:
error_msg = u"您输入的目录名称过长"
return go_error (request, error_msg)
try:
if not seafserv_threaded_rpc.is_valid_filename(repo_id, new_dir_name):
error_msg = (u"您输入的目录名称 %s 包含非法字符" % new_dir_name)
return go_error (request, error_msg)
except SearpcError,e:
return go_error (request, e.msg)
new_dir_name = check_filename_with_rename(repo_id, parent_dir, new_dir_name)
try:
seafserv_threaded_rpc.put_dir(repo_id, parent_dir, new_dir_name, user)
except Exception, e:
return go_error(request, str(e))
url = reverse('repo', args=[repo_id]) + ('?p=%s' % parent_dir)
return HttpResponseRedirect(url)
@login_required
def repo_rename_file(request):
repo_id = request.POST.get("repo_id")
parent_dir = request.POST.get("parent_dir")
oldname = request.POST.get("oldname")
newname = request.POST.get("newname")
user = request.user.username
print repo_id, parent_dir, oldname, newname, user
if not newname:
error_msg = u"新文件名不能为空"
return go_error(request, error_msg)
if len(newname) > settings.MAX_UPLOAD_FILE_NAME_LEN:
error_msg = u"新文件名太长"
return go_error(request, error_msg)
if not (repo_id and parent_dir and oldname):
return go_error(request)
try:
seafserv_threaded_rpc.rename_file (repo_id, parent_dir,
oldname, newname, user)
except Exception, e:
return go_error(request, str(e))
url = reverse('repo', args=[repo_id]) + ('?p=%s' % parent_dir)
return HttpResponseRedirect(url)
@login_required
def validate_filename(request):
repo_id = request.GET.get('repo_id')
filename = request.GET.get('filename')
if not (repo_id and filename):
return go_error(request)
result = {'ret':'yes'}
try:
ret = seafserv_threaded_rpc.is_valid_filename (repo_id, filename);
except SearpcError:
result['ret'] = 'error'
else:
result['ret'] = 'yes' if ret == 1 else 'no'
content_type = 'application/json; charset=utf-8'
return HttpResponse(json.dumps(result), content_type=content_type)