소스 검색

First pass at new Web Uploader UX

Pierre-Olivier Latour 11 년 전
부모
커밋
a92da4ffae

+ 347 - 0
GCDWebUploader/GCDWebUploader.bundle/css/bootstrap-theme.css

@@ -0,0 +1,347 @@
+/*!
+ * Bootstrap v3.1.1 (http://getbootstrap.com)
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+.btn-default,
+.btn-primary,
+.btn-success,
+.btn-info,
+.btn-warning,
+.btn-danger {
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
+}
+.btn-default:active,
+.btn-primary:active,
+.btn-success:active,
+.btn-info:active,
+.btn-warning:active,
+.btn-danger:active,
+.btn-default.active,
+.btn-primary.active,
+.btn-success.active,
+.btn-info.active,
+.btn-warning.active,
+.btn-danger.active {
+  -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+          box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+}
+.btn:active,
+.btn.active {
+  background-image: none;
+}
+.btn-default {
+  text-shadow: 0 1px 0 #fff;
+  background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
+  background-image:         linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-color: #dbdbdb;
+  border-color: #ccc;
+}
+.btn-default:hover,
+.btn-default:focus {
+  background-color: #e0e0e0;
+  background-position: 0 -15px;
+}
+.btn-default:active,
+.btn-default.active {
+  background-color: #e0e0e0;
+  border-color: #dbdbdb;
+}
+.btn-primary {
+  background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
+  background-image:         linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-color: #2b669a;
+}
+.btn-primary:hover,
+.btn-primary:focus {
+  background-color: #2d6ca2;
+  background-position: 0 -15px;
+}
+.btn-primary:active,
+.btn-primary.active {
+  background-color: #2d6ca2;
+  border-color: #2b669a;
+}
+.btn-success {
+  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
+  background-image:         linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-color: #3e8f3e;
+}
+.btn-success:hover,
+.btn-success:focus {
+  background-color: #419641;
+  background-position: 0 -15px;
+}
+.btn-success:active,
+.btn-success.active {
+  background-color: #419641;
+  border-color: #3e8f3e;
+}
+.btn-info {
+  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
+  background-image:         linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-color: #28a4c9;
+}
+.btn-info:hover,
+.btn-info:focus {
+  background-color: #2aabd2;
+  background-position: 0 -15px;
+}
+.btn-info:active,
+.btn-info.active {
+  background-color: #2aabd2;
+  border-color: #28a4c9;
+}
+.btn-warning {
+  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
+  background-image:         linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-color: #e38d13;
+}
+.btn-warning:hover,
+.btn-warning:focus {
+  background-color: #eb9316;
+  background-position: 0 -15px;
+}
+.btn-warning:active,
+.btn-warning.active {
+  background-color: #eb9316;
+  border-color: #e38d13;
+}
+.btn-danger {
+  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
+  background-image:         linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-color: #b92c28;
+}
+.btn-danger:hover,
+.btn-danger:focus {
+  background-color: #c12e2a;
+  background-position: 0 -15px;
+}
+.btn-danger:active,
+.btn-danger.active {
+  background-color: #c12e2a;
+  border-color: #b92c28;
+}
+.thumbnail,
+.img-thumbnail {
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+}
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+  background-color: #e8e8e8;
+  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+  background-image:         linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
+  background-repeat: repeat-x;
+}
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+  background-color: #357ebd;
+  background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
+  background-image:         linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
+  background-repeat: repeat-x;
+}
+.navbar-default {
+  background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
+  background-image:         linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
+}
+.navbar-default .navbar-nav > .active > a {
+  background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
+  background-image:         linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);
+  background-repeat: repeat-x;
+  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
+          box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
+}
+.navbar-brand,
+.navbar-nav > li > a {
+  text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
+}
+.navbar-inverse {
+  background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
+  background-image:         linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  background-repeat: repeat-x;
+}
+.navbar-inverse .navbar-nav > .active > a {
+  background-image: -webkit-linear-gradient(top, #222 0%, #282828 100%);
+  background-image:         linear-gradient(to bottom, #222 0%, #282828 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);
+  background-repeat: repeat-x;
+  -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
+          box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
+}
+.navbar-inverse .navbar-brand,
+.navbar-inverse .navbar-nav > li > a {
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
+}
+.navbar-static-top,
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  border-radius: 0;
+}
+.alert {
+  text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
+          box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
+}
+.alert-success {
+  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
+  background-image:         linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
+  background-repeat: repeat-x;
+  border-color: #b2dba1;
+}
+.alert-info {
+  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
+  background-image:         linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
+  background-repeat: repeat-x;
+  border-color: #9acfea;
+}
+.alert-warning {
+  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
+  background-image:         linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
+  background-repeat: repeat-x;
+  border-color: #f5e79e;
+}
+.alert-danger {
+  background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
+  background-image:         linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
+  background-repeat: repeat-x;
+  border-color: #dca7a7;
+}
+.progress {
+  background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
+  background-image:         linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
+  background-repeat: repeat-x;
+}
+.progress-bar {
+  background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
+  background-image:         linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
+  background-repeat: repeat-x;
+}
+.progress-bar-success {
+  background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
+  background-image:         linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
+  background-repeat: repeat-x;
+}
+.progress-bar-info {
+  background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
+  background-image:         linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
+  background-repeat: repeat-x;
+}
+.progress-bar-warning {
+  background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
+  background-image:         linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
+  background-repeat: repeat-x;
+}
+.progress-bar-danger {
+  background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
+  background-image:         linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
+  background-repeat: repeat-x;
+}
+.list-group {
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
+}
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+  text-shadow: 0 -1px 0 #3071a9;
+  background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
+  background-image:         linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
+  background-repeat: repeat-x;
+  border-color: #3278b3;
+}
+.panel {
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
+          box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
+}
+.panel-default > .panel-heading {
+  background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+  background-image:         linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
+  background-repeat: repeat-x;
+}
+.panel-primary > .panel-heading {
+  background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
+  background-image:         linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
+  background-repeat: repeat-x;
+}
+.panel-success > .panel-heading {
+  background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
+  background-image:         linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
+  background-repeat: repeat-x;
+}
+.panel-info > .panel-heading {
+  background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
+  background-image:         linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
+  background-repeat: repeat-x;
+}
+.panel-warning > .panel-heading {
+  background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
+  background-image:         linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
+  background-repeat: repeat-x;
+}
+.panel-danger > .panel-heading {
+  background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
+  background-image:         linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
+  background-repeat: repeat-x;
+}
+.well {
+  background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
+  background-image:         linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
+  background-repeat: repeat-x;
+  border-color: #dcdcdc;
+  -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
+          box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
+}
+/*# sourceMappingURL=bootstrap-theme.css.map */

+ 69 - 42
GCDWebUploader/GCDWebUploader.bundle/css/index.css

@@ -25,75 +25,102 @@
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-p {
-  margin: 0px;
+.header {
+  color: #222;
 }
 
-.container {
-  margin-top: 20px;
-  margin-bottom: 10px;
+.row-file {
+  height: 40px;
 }
 
-.template-download, .template-upload {
-  height: 60px;
+.column-icon {
+  width: 40px;
+  text-align: center;
 }
 
-.table {
-  border: 2px solid #EEE;
+.column-name {
 }
 
-.table > tbody > tr > td {
-  vertical-align: middle;
+.column-size {
+  width: 100px;
+  text-align: right;
+}
+
+.column-delete {
+  width: 40px;
+  text-align: center;
 }
 
-.file-status {
-  width: 150px;
+.column-path {  
 }
 
-.file-action {
-  width: 120px;
+.column-progress {
+  width: 200px;
+}
+
+.footer {
+  color: #999;
   text-align: center;
+  font-size: 0.9em;
 }
 
-.fileupload-process {
-  float: none;
+/* Bootstrap overrides */
+
+.btn:focus {
+  outline: none;  /* FIXME: Work around for Chrome only but still draws focus ring while button pressed */
 }
 
-.fileupload-processing .fileupload-process {
-  display: inline;
-  width: 24px;
-  height: 24px;
-  margin-left: 10px;
-  margin-top: 5px;
-  vertical-align: top;
-  background: none;
+.row > div > p {
+  text-align: right;
+  margin-top: 8px;
+  margin-bottom: 0px;
 }
 
 .progress {
-  margin: 0px;
-  height: 18px;
+  margin-top: 8px;
+  margin-bottom: 0px;
 }
 
-.progress-extended {
-  color: #444;
-  font-size: 0.9em;
-  margin-top: 5px;
+.table .progress {
+  margin-top: 0px;
+  margin-bottom: 0px;
+  height: 16px;
 }
 
-.title {
-  margin-bottom: 10px;
-  font-size: 2.2em;
-  font-weight: bold;
+.row {
+  margin-top: 30px;
+  margin-bottom: 20px;
 }
 
-.header {
-  color: #222;
-  margin-bottom: 30px;
+.panel-default > .panel-heading {
+  color: #555;
 }
 
-.footer {
-  margin-top: 40px;
+.breadcrumb {
+  background-color: transparent;
+  border-radius: 0px;
+  margin-bottom: 0px;
+  padding: 0px;
+}
+
+.breadcrumb > .active {
+  color: #555;
+}
+
+.breadcrumb > li + li:before {
   color: #999;
-  text-align: center;
-  font-size: 0.9em;
+}
+
+.table > tbody > tr > td {
+  vertical-align: middle;
+}
+
+.table > tbody > tr > td > p {
+  margin: 0px;
+}
+
+/* Initial state */
+
+.uploading {
+  display: none;
 }

+ 0 - 57
GCDWebUploader/GCDWebUploader.bundle/css/jquery.fileupload-ui.css

@@ -1,57 +0,0 @@
-@charset "UTF-8";
-/*
- * jQuery File Upload UI Plugin CSS 9.0.0
- * https://github.com/blueimp/jQuery-File-Upload
- *
- * Copyright 2010, Sebastian Tschan
- * https://blueimp.net
- *
- * Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
- */
-
-.fileupload-buttonbar .btn,
-.fileupload-buttonbar .toggle {
-  margin-bottom: 5px;
-}
-.progress-animated .progress-bar,
-.progress-animated .bar {
-  background: url("../img/progressbar.gif") !important;
-  filter: none;
-}
-.fileupload-process {
-  float: right;
-  display: none;
-}
-.fileupload-processing .fileupload-process,
-.files .processing .preview {
-  display: block;
-  width: 32px;
-  height: 32px;
-  background: url("../img/loading.gif") center no-repeat;
-  background-size: contain;
-}
-.files audio,
-.files video {
-  max-width: 300px;
-}
-
-@media (max-width: 767px) {
-  .fileupload-buttonbar .toggle,
-  .files .toggle,
-  .files .btn span {
-    display: none;
-  }
-  .files .name {
-    width: 80px;
-    word-wrap: break-word;
-  }
-  .files audio,
-  .files video {
-    max-width: 80px;
-  }
-  .files img,
-  .files canvas {
-    max-width: 100%;
-  }
-}

+ 2 - 4
GCDWebUploader/GCDWebUploader.bundle/en.lproj/Localizable.strings

@@ -1,5 +1,3 @@
 "TITLE" = "%@";
-"HEADER" = "Drag & drop files on this window or use the \"Upload Files…\" button to upload new files. This file uploader requires a modern browser and JavaScript to be enabled.";
-"FOOTER" = "%@ %@ — %@";
-
-"UNSUPPORTED_FILE_EXTENSION" = "Unsupported file type";
+"HEADER" = "Drag & drop files on this window or use the \"Upload Files…\" button to upload new files.";
+"FOOTER" = "%@ %@";

BIN
GCDWebUploader/GCDWebUploader.bundle/img/loading.gif


BIN
GCDWebUploader/GCDWebUploader.bundle/img/progressbar.gif


+ 95 - 72
GCDWebUploader/GCDWebUploader.bundle/index.html

@@ -25,109 +25,132 @@
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 -->
 
-<!DOCTYPE HTML>
+<!DOCTYPE html>
 <html lang="en">
   <head>
     <meta charset="utf-8">
-    <title>%title%</title>
-    
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>My Site</title>
     <link rel="stylesheet" href="css/bootstrap.css">
+    <link rel="stylesheet" href="css/bootstrap-theme.css">
     <link rel="stylesheet" href="css/jquery.fileupload.css">
-    <link rel="stylesheet" href="css/jquery.fileupload-ui.css">
     <link rel="stylesheet" href="css/index.css">
-    
+    <!--[if lt IE 9]>
+      <script type="text/javascript" src="js/html5shiv.min.js"></script>
+      <script type="text/javascript" src="js/respond.min.js"></script>
+    <![endif]-->
     <script type="text/javascript" src="js/jquery.min.js"></script>
     <script type="text/javascript" src="js/jquery.ui.widget.js"></script>
-    <script type="text/javascript" src="js/tmpl.min.js"></script>
-    <script type="text/javascript" src="js/jquery.iframe-transport.js"></script>
+    <script type="text/javascript" src="js/jquery.jeditable.js"></script>
     <script type="text/javascript" src="js/jquery.fileupload.js"></script>
-    <script type="text/javascript" src="js/jquery.fileupload-process.js"></script>
-    <script type="text/javascript" src="js/jquery.fileupload-ui.js"></script>
+    <script type="text/javascript" src="js/jquery.iframe-transport.js"></script>
+    <script type="text/javascript" src="js/bootstrap.min.js"></script>
+    <script type="text/javascript" src="js/tmpl.min.js"></script>
+    <script type="text/javascript">
+      var _device = "%device%";
+    </script>
     <script type="text/javascript" src="js/index.js"></script>
   </head>
   <body>
     
     <div class="container">
-      <p class="title">%title%</p>
+      
+      <div class="page-header">
+        <h1>%title%</h1>
+      </div>
+      
       <p class="header">%header%</p>
-      <form id="fileupload" method="POST" enctype="multipart/form-data">
-        <div class="row fileupload-buttonbar">
-          <div class="col-sm-8">
-            <span class="btn btn-success fileinput-button">
-              <i class="glyphicon glyphicon-plus"></i>
-              <span>Upload Files&hellip;</span>
-              <input type="file" name="files[]" multiple>
-            </span>
-            <button type="reset" class="btn btn-warning cancel">
-              <i class="glyphicon glyphicon-ban-circle"></i>
-              <span>Cancel Uploads</span>
+      
+      <div id="alerts"></div>
+      
+      <div class="row">
+        <div class="col-sm-8">
+          <div class="btn-toolbar">
+            <button type="button" class="btn btn-primary fileinput-button">
+              <span class="glyphicon glyphicon-upload"></span> Upload Files&hellip;
+              <input id="fileupload" type="file" name="files[]" multiple>
+            </button>
+            <button type="button" class="btn btn-success" id="create-folder">
+              <span class="glyphicon glyphicon-folder-close"></span> Create Folder&hellip;
+            </button>
+            <button type="button" class="btn btn-default" id="reload">
+              <span class="glyphicon glyphicon-refresh"></span> Refresh
             </button>
-            <img src="img/loading.gif" class="fileupload-process"></img>
           </div>
-          <div class="col-sm-4 fileupload-progress fade">
-            <div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100">
-              <div class="progress-bar progress-bar-success" style="width:0%;"></div>
-            </div>
-            <div class="progress-extended">&nbsp;</div>
+        </div>
+        <div class="col-sm-4">
+          <div class="progress progress-striped active uploading">
+            <div class="progress-bar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" id="progress-bar"></div>
           </div>
         </div>
-        <br/>
-        <table role="presentation" class="table table-striped"><tbody class="files"></tbody></table>
-      </form>
+      </div>
+      
+      <div class="panel panel-default uploading">
+        <div class="panel-heading">File Uploads in Progress</div>
+        <table class="table table-striped"><tbody id="uploads"></tbody></table>
+      </div>
+      
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <ol class="breadcrumb" id="path"></ol>
+        </div>
+        <table class="table table-striped"><tbody id="listing"></tbody></table>
+      </div>
+      
       <p class="footer">%footer%</p>
+      
     </div>
     
-    <script id="template-upload" type="text/x-tmpl">
-    {% for (var i=0, file; file=o.files[i]; i++) { %}
-      <tr class="template-upload fade">
-        <td class="file-name">
-          <p class="name">{%=file.name%}</p>
-          <strong class="error text-danger"></strong>
+    <script type="text/x-tmpl" id="template-listing">
+      <tr class="row-file" data-path="{%=o.path%}" data-name="{%=o.name%}">
+        <td class="column-icon">
+          {% if (o.size != null) { %}
+            <button type="button" class="btn btn-default btn-xs button-download">
+              <span class="glyphicon glyphicon-download"></span>
+            </button>
+          {% } else { %}
+            <button type="button" class="btn btn-default btn-xs button-open">
+              <span class="glyphicon glyphicon-folder-open"></span>
+            </button>
+          {% } %}
         </td>
-        <td class="file-status">
-          <div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
-            <div class="progress-bar progress-bar-success" style="width:0%;"></div>
-          </div>
+        <td class="column-name"><p class="edit">{%=o.name%}</p></td>
+        <td class="column-size">
+          {% if (o.size != null) { %}
+            <p>{%=formatFileSize(o.size)%}</p>
+          {% } %}
         </td>
-        <td class="file-action">
-          <button class="btn btn-warning cancel">
-            <i class="glyphicon glyphicon-ban-circle"></i>
-            <span>Cancel</span>
+        <td class="column-delete">
+          <button type="button" class="btn btn-danger btn-xs button-delete">
+            <span class="glyphicon glyphicon-trash"></span>
           </button>
         </td>
       </tr>
-    {% } %}
     </script>
     
-    <script id="template-download" type="text/x-tmpl">
-    {% for (var i=0, file; file=o.files[i]; i++) { %}
-      <tr class="template-download fade">
-        <td class="file-name">
-          <p class="name">
-            {% if (file.url) { %}
-              <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}">{%=file.name%}</a>
-            {% } else { %}
-              <span>{%=file.name%}</span>
-            {% } %}
-          </p>
-          {% if (file.error) { %}
-            <div><span class="label label-danger">Error</span> {%=file.error%}</div>
-          {% } %}
-        </td>
-        <td class="file-status">
-          <p class="size">{%=o.formatFileSize(file.size)%}</p>
-        </td>
-        <td class="file-action">
-          {% if (file.deleteUrl) { %}
-            <button class="btn btn-danger delete" data-type="{%=file.deleteType%}" data-url="{%=file.deleteUrl%}">
-              <i class="glyphicon glyphicon-trash"></i>
-              <span>Delete</span>
-            </button>
-          {% } %}
+    <script type="text/x-tmpl" id="template-uploads">
+      <tr class="row-file">
+        <td class="column-icon">
+          <button type="button" class="btn btn-warning btn-xs button-cancel">
+            <span class="glyphicon glyphicon-ban-circle"></span>
+          </button>
         </td>
+        <td class="column-path"><p>{%=o.path%}</p></td>
+        <td class="column-progress">
+          <div class="progress">
+            <div class="progress-bar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" id="progress-bar"></div>
+          </div>
+        </ts>
       </tr>
-    {% } %}
     </script>
     
-  </body> 
+    <script type="text/x-tmpl" id="template-alert">
+      <div class="alert alert-{%=o.level%} alert-dismissable">
+        <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
+        <strong>{%=o.title%}</strong>{%=o.description%}
+      </div>
+    </script>
+    
+  </body>
 </html>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 5 - 0
GCDWebUploader/GCDWebUploader.bundle/js/bootstrap.min.js


+ 8 - 0
GCDWebUploader/GCDWebUploader.bundle/js/html5shiv.min.js

@@ -0,0 +1,8 @@
+/*
+ HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
+*/
+(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
+a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}</style>";
+c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
+"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:"3.7.0",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);
+if(g)return a.createDocumentFragment();for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);

+ 185 - 20
GCDWebUploader/GCDWebUploader.bundle/js/index.js

@@ -25,34 +25,199 @@
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-$(function () {
+var _path = null;
+
+function formatFileSize(bytes) {
+  if (bytes >= 1000000000) {
+    return (bytes / 1000000000).toFixed(2) + ' GB';
+  }
+  if (bytes >= 1000000) {
+    return (bytes / 1000000).toFixed(2) + ' MB';
+  }
+  return (bytes / 1000).toFixed(2) + ' KB';
+}
+
+function _showError(message, textStatus, errorThrown) {
+  $("#alerts").prepend(tmpl("template-alert", {
+    level: "danger",
+    title: (errorThrown != "" ? errorThrown : textStatus) + ": ",
+    description: message
+  }));
+}
+
+function _reload(path) {
+  $.ajax({
+    url: 'list',
+    type: 'GET',
+    data: {path: path},
+    dataType: 'json'
+  }).fail(function(jqXHR, textStatus, errorThrown) {
+    _showError("Failed retrieving contents of \"" + path + "\"", textStatus, errorThrown);
+  })
+  .done(function(result) {
+    
+    if (path != _path) {
+      $("#path").empty();
+      if (path == "/") {
+        $("#path").append('<li class="active">' + _device + '</li>');
+      } else {
+        $("#path").append('<li data-path="/"><a>' + _device + '</a></li>');
+        var components = path.split("/").slice(1, -1);
+        for (var i = 0; i < components.length - 1; ++i) {
+          var subpath = "/" + components.slice(0, i + 1).join("/") + "/";
+          $("#path").append('<li data-path="' + subpath + '"><a>' + components[i] + '</a></li>');
+        }
+        $("#path > li").click(function(event) {
+          _reload($(this).attr("data-path"));
+          event.preventDefault();
+        });
+        $("#path").append('<li class="active">' + components[components.length - 1] + '</li>');
+      }
+      _path = path;
+    }
+    
+    $("#listing").empty();
+    for (var i = 0, file; file = result[i]; ++i) {
+      $("#listing").append(tmpl("template-listing", file));
+    }
+    
+    $(".edit").editable(function(value, settings) { 
+      var name = $(this).parent().parent().attr("data-name");
+      if (value != name) {
+        var path = $(this).parent().parent().attr("data-path");
+        $.ajax({
+          url: 'move',
+          type: 'POST',
+          data: {oldPath: path, newPath: _path + value},
+          dataType: 'json'
+        }).fail(function(jqXHR, textStatus, errorThrown) {
+          _showError("Failed moving \"" + path + "\" to \"" + _path + value + "\"", textStatus, errorThrown);
+        }).always(function() {
+          _reload(_path);
+        });
+      }
+      return value;
+    }, {
+      width: 200,
+      tooltip: 'Click to rename...'
+    });
+    
+    $(".button-download").click(function(event) {
+      var path = $(this).parent().parent().attr("data-path");
+      setTimeout(function() {
+        window.location = "download?path=" + encodeURIComponent(path);
+      }, 0);
+    });
+    
+    $(".button-open").click(function(event) {
+      var path = $(this).parent().parent().attr("data-path");
+      _reload(path);
+    });
+    
+    $(".button-delete").click(function(event) {
+      var path = $(this).parent().parent().attr("data-path");
+      $.ajax({
+        url: 'delete',
+        type: 'POST',
+        data: {path: path},
+        dataType: 'json'
+      }).fail(function(jqXHR, textStatus, errorThrown) {
+        _showError("Failed deleting \"" + path + "\"", textStatus, errorThrown);
+      }).always(function(result) {
+        _reload(_path);
+      });
+    });
+    
+  });
+}
+
+$(document).ready(function() {
   
-  // Initialize jQuery File Upload widget
-  $('#fileupload').fileupload({
-    url: 'upload',
+  $("#fileupload").fileupload({
     dropZone: $(document),
     pasteZone: null,
     autoUpload: true,
-    sequentialUploads: true
-    // limitConcurrentUploads: 2
-    // forceIframeTransport: true
+    sequentialUploads: true,
+    // limitConcurrentUploads: 2,
+    // forceIframeTransport: true,
+    
+    url: 'upload',
+    type: 'POST',
+    dataType: 'json',
+    
+    start: function(e) {
+      $("#progress-bar").css("width", "0%");
+      $(".uploading").show();
+    },
+    
+    progressall: function(e, data) {
+      var progress = parseInt(data.loaded / data.total * 100, 10);
+      $("#progress-bar").css("width", progress + "%");  // .text(progress + "%")
+    },
+    
+    stop: function(e) {
+      $(".uploading").hide();
+    },
+    
+    add: function(e, data) {
+      
+      $(".uploading").show();
+      
+      var file = data.files[0];
+      data.formData = {
+        path: _path
+      };
+      data.context = $(tmpl("template-uploads", {
+        path: _path + file.name
+      })).appendTo("#uploads");
+      var jqXHR = data.submit();
+      data.context.find("button").click(function(event) {
+        jqXHR.abort();
+      });
+    },
+    
+    progress: function(e, data) {
+      var progress = parseInt(data.loaded / data.total * 100, 10);
+      data.context.find(".progress-bar").css("width", progress + "%");
+    },
+    
+    done: function(e, data) {
+      _reload(_path);
+    },
+    
+    fail: function(e, data) {
+      var file = data.files[0];
+      if (data.errorThrown != "abort") {
+        _showError("Failed uploading \"" + file.name + "\" to \"" + _path + "\"", data.textStatus, data.errorThrown);
+      }
+    },
+    
+    always: function(e, data) {
+      data.context.remove();
+    },
+    
   });
   
-  // Load existing files
-  $('#fileupload').addClass('fileupload-processing');
-  $.ajax({
-    url: 'list',
-    dataType: 'json',
-    context: $('#fileupload')[0]
-  }).always(function () {
-    $(this).removeClass('fileupload-processing');
-  }).done(function (result) {
-    $(this).fileupload('option', 'done').call(this, $.Event('done'), {result: result});
+  $("#create-folder").click(function(event) {
+    var name = prompt("Please enter folder name:", "Untitled folder");
+    if ((name != null) && (name != "")) {
+      $.ajax({
+        url: 'create',
+        type: 'POST',
+        data: {path: _path + name},
+        dataType: 'json'
+      }).fail(function(jqXHR, textStatus, errorThrown) {
+        _showError("Failed creating folder \"" + name + "\" in \"" + _path + "\"", textStatus, errorThrown);
+      }).always(function(result) {
+        _reload(_path);
+      });
+    }
   });
   
-  // Disable the default browser action for file drops on the document
-  $(document).bind('drop dragover', function (e) {
-    e.preventDefault();
+  $("#reload").click(function(event) {
+    _reload(_path);
   });
   
+  _reload("/");
+  
 });

+ 0 - 152
GCDWebUploader/GCDWebUploader.bundle/js/jquery.fileupload-jquery-ui.js

@@ -1,152 +0,0 @@
-/*
- * jQuery File Upload jQuery UI Plugin 8.7.1
- * https://github.com/blueimp/jQuery-File-Upload
- *
- * Copyright 2013, Sebastian Tschan
- * https://blueimp.net
- *
- * Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
- */
-
-/* jshint nomen:false */
-/* global define, window */
-
-(function (factory) {
-    'use strict';
-    if (typeof define === 'function' && define.amd) {
-        // Register as an anonymous AMD module:
-        define(['jquery', './jquery.fileupload-ui'], factory);
-    } else {
-        // Browser globals:
-        factory(window.jQuery);
-    }
-}(function ($) {
-    'use strict';
-
-    $.widget('blueimp.fileupload', $.blueimp.fileupload, {
-
-        options: {
-            processdone: function (e, data) {
-                data.context.find('.start').button('enable');
-            },
-            progress: function (e, data) {
-                if (data.context) {
-                    data.context.find('.progress').progressbar(
-                        'option',
-                        'value',
-                        parseInt(data.loaded / data.total * 100, 10)
-                    );
-                }
-            },
-            progressall: function (e, data) {
-                var $this = $(this);
-                $this.find('.fileupload-progress')
-                    .find('.progress').progressbar(
-                        'option',
-                        'value',
-                        parseInt(data.loaded / data.total * 100, 10)
-                    ).end()
-                    .find('.progress-extended').each(function () {
-                        $(this).html(
-                            ($this.data('blueimp-fileupload') ||
-                                    $this.data('fileupload'))
-                                ._renderExtendedProgress(data)
-                        );
-                    });
-            }
-        },
-
-        _renderUpload: function (func, files) {
-            var node = this._super(func, files),
-                showIconText = $(window).width() > 480;
-            node.find('.progress').empty().progressbar();
-            node.find('.start').button({
-                icons: {primary: 'ui-icon-circle-arrow-e'},
-                text: showIconText
-            });
-            node.find('.cancel').button({
-                icons: {primary: 'ui-icon-cancel'},
-                text: showIconText
-            });
-            if (node.hasClass('fade')) {
-                node.hide();
-            }
-            return node;
-        },
-
-        _renderDownload: function (func, files) {
-            var node = this._super(func, files),
-                showIconText = $(window).width() > 480;
-            node.find('.delete').button({
-                icons: {primary: 'ui-icon-trash'},
-                text: showIconText
-            });
-            if (node.hasClass('fade')) {
-                node.hide();
-            }
-            return node;
-        },
-
-        _startHandler: function (e) {
-            $(e.currentTarget).button('disable');
-            this._super(e);
-        },
-
-        _transition: function (node) {
-            var deferred = $.Deferred();
-            if (node.hasClass('fade')) {
-                node.fadeToggle(
-                    this.options.transitionDuration,
-                    this.options.transitionEasing,
-                    function () {
-                        deferred.resolveWith(node);
-                    }
-                );
-            } else {
-                deferred.resolveWith(node);
-            }
-            return deferred;
-        },
-
-        _create: function () {
-            this._super();
-            this.element
-                .find('.fileupload-buttonbar')
-                .find('.fileinput-button').each(function () {
-                    var input = $(this).find('input:file').detach();
-                    $(this)
-                        .button({icons: {primary: 'ui-icon-plusthick'}})
-                        .append(input);
-                })
-                .end().find('.start')
-                .button({icons: {primary: 'ui-icon-circle-arrow-e'}})
-                .end().find('.cancel')
-                .button({icons: {primary: 'ui-icon-cancel'}})
-                .end().find('.delete')
-                .button({icons: {primary: 'ui-icon-trash'}})
-                .end().find('.progress').progressbar();
-        },
-
-        _destroy: function () {
-            this.element
-                .find('.fileupload-buttonbar')
-                .find('.fileinput-button').each(function () {
-                    var input = $(this).find('input:file').detach();
-                    $(this)
-                        .button('destroy')
-                        .append(input);
-                })
-                .end().find('.start')
-                .button('destroy')
-                .end().find('.cancel')
-                .button('destroy')
-                .end().find('.delete')
-                .button('destroy')
-                .end().find('.progress').progressbar('destroy');
-            this._super();
-        }
-
-    });
-
-}));

+ 0 - 172
GCDWebUploader/GCDWebUploader.bundle/js/jquery.fileupload-process.js

@@ -1,172 +0,0 @@
-/*
- * jQuery File Upload Processing Plugin 1.3.0
- * https://github.com/blueimp/jQuery-File-Upload
- *
- * Copyright 2012, Sebastian Tschan
- * https://blueimp.net
- *
- * Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
- */
-
-/* jshint nomen:false */
-/* global define, window */
-
-(function (factory) {
-    'use strict';
-    if (typeof define === 'function' && define.amd) {
-        // Register as an anonymous AMD module:
-        define([
-            'jquery',
-            './jquery.fileupload'
-        ], factory);
-    } else {
-        // Browser globals:
-        factory(
-            window.jQuery
-        );
-    }
-}(function ($) {
-    'use strict';
-
-    var originalAdd = $.blueimp.fileupload.prototype.options.add;
-
-    // The File Upload Processing plugin extends the fileupload widget
-    // with file processing functionality:
-    $.widget('blueimp.fileupload', $.blueimp.fileupload, {
-
-        options: {
-            // The list of processing actions:
-            processQueue: [
-                /*
-                {
-                    action: 'log',
-                    type: 'debug'
-                }
-                */
-            ],
-            add: function (e, data) {
-                var $this = $(this);
-                data.process(function () {
-                    return $this.fileupload('process', data);
-                });
-                originalAdd.call(this, e, data);
-            }
-        },
-
-        processActions: {
-            /*
-            log: function (data, options) {
-                console[options.type](
-                    'Processing "' + data.files[data.index].name + '"'
-                );
-            }
-            */
-        },
-
-        _processFile: function (data, originalData) {
-            var that = this,
-                dfd = $.Deferred().resolveWith(that, [data]),
-                chain = dfd.promise();
-            this._trigger('process', null, data);
-            $.each(data.processQueue, function (i, settings) {
-                var func = function (data) {
-                    if (originalData.errorThrown) {
-                        return $.Deferred()
-                                .rejectWith(that, [originalData]).promise();
-                    }
-                    return that.processActions[settings.action].call(
-                        that,
-                        data,
-                        settings
-                    );
-                };
-                chain = chain.pipe(func, settings.always && func);
-            });
-            chain
-                .done(function () {
-                    that._trigger('processdone', null, data);
-                    that._trigger('processalways', null, data);
-                })
-                .fail(function () {
-                    that._trigger('processfail', null, data);
-                    that._trigger('processalways', null, data);
-                });
-            return chain;
-        },
-
-        // Replaces the settings of each processQueue item that
-        // are strings starting with an "@", using the remaining
-        // substring as key for the option map,
-        // e.g. "@autoUpload" is replaced with options.autoUpload:
-        _transformProcessQueue: function (options) {
-            var processQueue = [];
-            $.each(options.processQueue, function () {
-                var settings = {},
-                    action = this.action,
-                    prefix = this.prefix === true ? action : this.prefix;
-                $.each(this, function (key, value) {
-                    if ($.type(value) === 'string' &&
-                            value.charAt(0) === '@') {
-                        settings[key] = options[
-                            value.slice(1) || (prefix ? prefix +
-                                key.charAt(0).toUpperCase() + key.slice(1) : key)
-                        ];
-                    } else {
-                        settings[key] = value;
-                    }
-
-                });
-                processQueue.push(settings);
-            });
-            options.processQueue = processQueue;
-        },
-
-        // Returns the number of files currently in the processsing queue:
-        processing: function () {
-            return this._processing;
-        },
-
-        // Processes the files given as files property of the data parameter,
-        // returns a Promise object that allows to bind callbacks:
-        process: function (data) {
-            var that = this,
-                options = $.extend({}, this.options, data);
-            if (options.processQueue && options.processQueue.length) {
-                this._transformProcessQueue(options);
-                if (this._processing === 0) {
-                    this._trigger('processstart');
-                }
-                $.each(data.files, function (index) {
-                    var opts = index ? $.extend({}, options) : options,
-                        func = function () {
-                            if (data.errorThrown) {
-                                return $.Deferred()
-                                        .rejectWith(that, [data]).promise();
-                            }
-                            return that._processFile(opts, data);
-                        };
-                    opts.index = index;
-                    that._processing += 1;
-                    that._processingQueue = that._processingQueue.pipe(func, func)
-                        .always(function () {
-                            that._processing -= 1;
-                            if (that._processing === 0) {
-                                that._trigger('processstop');
-                            }
-                        });
-                });
-            }
-            return this._processingQueue;
-        },
-
-        _create: function () {
-            this._super();
-            this._processing = 0;
-            this._processingQueue = $.Deferred().resolveWith(this)
-                .promise();
-        }
-
-    });
-
-}));

+ 0 - 699
GCDWebUploader/GCDWebUploader.bundle/js/jquery.fileupload-ui.js

@@ -1,699 +0,0 @@
-/*
- * jQuery File Upload User Interface Plugin 9.5.2
- * https://github.com/blueimp/jQuery-File-Upload
- *
- * Copyright 2010, Sebastian Tschan
- * https://blueimp.net
- *
- * Licensed under the MIT license:
- * http://www.opensource.org/licenses/MIT
- */
-
-/* jshint nomen:false */
-/* global define, window */
-
-(function (factory) {
-    'use strict';
-    if (typeof define === 'function' && define.amd) {
-        // Register as an anonymous AMD module:
-        define([
-            'jquery',
-            'tmpl',
-            './jquery.fileupload-image',
-            './jquery.fileupload-audio',
-            './jquery.fileupload-video',
-            './jquery.fileupload-validate'
-        ], factory);
-    } else {
-        // Browser globals:
-        factory(
-            window.jQuery,
-            window.tmpl
-        );
-    }
-}(function ($, tmpl) {
-    'use strict';
-
-    $.blueimp.fileupload.prototype._specialOptions.push(
-        'filesContainer',
-        'uploadTemplateId',
-        'downloadTemplateId'
-    );
-
-    // The UI version extends the file upload widget
-    // and adds complete user interface interaction:
-    $.widget('blueimp.fileupload', $.blueimp.fileupload, {
-
-        options: {
-            // By default, files added to the widget are uploaded as soon
-            // as the user clicks on the start buttons. To enable automatic
-            // uploads, set the following option to true:
-            autoUpload: false,
-            // The ID of the upload template:
-            uploadTemplateId: 'template-upload',
-            // The ID of the download template:
-            downloadTemplateId: 'template-download',
-            // The container for the list of files. If undefined, it is set to
-            // an element with class "files" inside of the widget element:
-            filesContainer: undefined,
-            // By default, files are appended to the files container.
-            // Set the following option to true, to prepend files instead:
-            prependFiles: false,
-            // The expected data type of the upload response, sets the dataType
-            // option of the $.ajax upload requests:
-            dataType: 'json',
-
-            // Function returning the current number of files,
-            // used by the maxNumberOfFiles validation:
-            getNumberOfFiles: function () {
-                return this.filesContainer.children()
-                    .not('.processing').length;
-            },
-
-            // Callback to retrieve the list of files from the server response:
-            getFilesFromResponse: function (data) {
-                if (data.result && $.isArray(data.result.files)) {
-                    return data.result.files;
-                }
-                return [];
-            },
-
-            // The add callback is invoked as soon as files are added to the fileupload
-            // widget (via file input selection, drag & drop or add API call).
-            // See the basic file upload widget for more information:
-            add: function (e, data) {
-                if (e.isDefaultPrevented()) {
-                    return false;
-                }
-                var $this = $(this),
-                    that = $this.data('blueimp-fileupload') ||
-                        $this.data('fileupload'),
-                    options = that.options;
-                data.context = that._renderUpload(data.files)
-                    .data('data', data)
-                    .addClass('processing');
-                options.filesContainer[
-                    options.prependFiles ? 'prepend' : 'append'
-                ](data.context);
-                that._forceReflow(data.context);
-                that._transition(data.context);
-                data.process(function () {
-                    return $this.fileupload('process', data);
-                }).always(function () {
-                    data.context.each(function (index) {
-                        $(this).find('.size').text(
-                            that._formatFileSize(data.files[index].size)
-                        );
-                    }).removeClass('processing');
-                    that._renderPreviews(data);
-                }).done(function () {
-                    data.context.find('.start').prop('disabled', false);
-                    if ((that._trigger('added', e, data) !== false) &&
-                            (options.autoUpload || data.autoUpload) &&
-                            data.autoUpload !== false) {
-                        data.submit();
-                    }
-                }).fail(function () {
-                    if (data.files.error) {
-                        data.context.each(function (index) {
-                            var error = data.files[index].error;
-                            if (error) {
-                                $(this).find('.error').text(error);
-                            }
-                        });
-                    }
-                });
-            },
-            // Callback for the start of each file upload request:
-            send: function (e, data) {
-                if (e.isDefaultPrevented()) {
-                    return false;
-                }
-                var that = $(this).data('blueimp-fileupload') ||
-                        $(this).data('fileupload');
-                if (data.context && data.dataType &&
-                        data.dataType.substr(0, 6) === 'iframe') {
-                    // Iframe Transport does not support progress events.
-                    // In lack of an indeterminate progress bar, we set
-                    // the progress to 100%, showing the full animated bar:
-                    data.context
-                        .find('.progress').addClass(
-                            !$.support.transition && 'progress-animated'
-                        )
-                        .attr('aria-valuenow', 100)
-                        .children().first().css(
-                            'width',
-                            '100%'
-                        );
-                }
-                return that._trigger('sent', e, data);
-            },
-            // Callback for successful uploads:
-            done: function (e, data) {
-                if (e.isDefaultPrevented()) {
-                    return false;
-                }
-                var that = $(this).data('blueimp-fileupload') ||
-                        $(this).data('fileupload'),
-                    getFilesFromResponse = data.getFilesFromResponse ||
-                        that.options.getFilesFromResponse,
-                    files = getFilesFromResponse(data),
-                    template,
-                    deferred;
-                if (data.context) {
-                    data.context.each(function (index) {
-                        var file = files[index] ||
-                                {error: 'Empty file upload result'};
-                        deferred = that._addFinishedDeferreds();
-                        that._transition($(this)).done(
-                            function () {
-                                var node = $(this);
-                                template = that._renderDownload([file])
-                                    .replaceAll(node);
-                                that._forceReflow(template);
-                                that._transition(template).done(
-                                    function () {
-                                        data.context = $(this);
-                                        that._trigger('completed', e, data);
-                                        that._trigger('finished', e, data);
-                                        deferred.resolve();
-                                    }
-                                );
-                            }
-                        );
-                    });
-                } else {
-                    template = that._renderDownload(files)[
-                        that.options.prependFiles ? 'prependTo' : 'appendTo'
-                    ](that.options.filesContainer);
-                    that._forceReflow(template);
-                    deferred = that._addFinishedDeferreds();
-                    that._transition(template).done(
-                        function () {
-                            data.context = $(this);
-                            that._trigger('completed', e, data);
-                            that._trigger('finished', e, data);
-                            deferred.resolve();
-                        }
-                    );
-                }
-            },
-            // Callback for failed (abort or error) uploads:
-            fail: function (e, data) {
-                if (e.isDefaultPrevented()) {
-                    return false;
-                }
-                var that = $(this).data('blueimp-fileupload') ||
-                        $(this).data('fileupload'),
-                    template,
-                    deferred;
-                if (data.context) {
-                    data.context.each(function (index) {
-                        if (data.errorThrown !== 'abort') {
-                            var file = data.files[index];
-                            file.error = file.error || data.errorThrown ||
-                                true;
-                            deferred = that._addFinishedDeferreds();
-                            that._transition($(this)).done(
-                                function () {
-                                    var node = $(this);
-                                    template = that._renderDownload([file])
-                                        .replaceAll(node);
-                                    that._forceReflow(template);
-                                    that._transition(template).done(
-                                        function () {
-                                            data.context = $(this);
-                                            that._trigger('failed', e, data);
-                                            that._trigger('finished', e, data);
-                                            deferred.resolve();
-                                        }
-                                    );
-                                }
-                            );
-                        } else {
-                            deferred = that._addFinishedDeferreds();
-                            that._transition($(this)).done(
-                                function () {
-                                    $(this).remove();
-                                    that._trigger('failed', e, data);
-                                    that._trigger('finished', e, data);
-                                    deferred.resolve();
-                                }
-                            );
-                        }
-                    });
-                } else if (data.errorThrown !== 'abort') {
-                    data.context = that._renderUpload(data.files)[
-                        that.options.prependFiles ? 'prependTo' : 'appendTo'
-                    ](that.options.filesContainer)
-                        .data('data', data);
-                    that._forceReflow(data.context);
-                    deferred = that._addFinishedDeferreds();
-                    that._transition(data.context).done(
-                        function () {
-                            data.context = $(this);
-                            that._trigger('failed', e, data);
-                            that._trigger('finished', e, data);
-                            deferred.resolve();
-                        }
-                    );
-                } else {
-                    that._trigger('failed', e, data);
-                    that._trigger('finished', e, data);
-                    that._addFinishedDeferreds().resolve();
-                }
-            },
-            // Callback for upload progress events:
-            progress: function (e, data) {
-                if (e.isDefaultPrevented()) {
-                    return false;
-                }
-                var progress = Math.floor(data.loaded / data.total * 100);
-                if (data.context) {
-                    data.context.each(function () {
-                        $(this).find('.progress')
-                            .attr('aria-valuenow', progress)
-                            .children().first().css(
-                                'width',
-                                progress + '%'
-                            );
-                    });
-                }
-            },
-            // Callback for global upload progress events:
-            progressall: function (e, data) {
-                if (e.isDefaultPrevented()) {
-                    return false;
-                }
-                var $this = $(this),
-                    progress = Math.floor(data.loaded / data.total * 100),
-                    globalProgressNode = $this.find('.fileupload-progress'),
-                    extendedProgressNode = globalProgressNode
-                        .find('.progress-extended');
-                if (extendedProgressNode.length) {
-                    extendedProgressNode.html(
-                        ($this.data('blueimp-fileupload') || $this.data('fileupload'))
-                            ._renderExtendedProgress(data)
-                    );
-                }
-                globalProgressNode
-                    .find('.progress')
-                    .attr('aria-valuenow', progress)
-                    .children().first().css(
-                        'width',
-                        progress + '%'
-                    );
-            },
-            // Callback for uploads start, equivalent to the global ajaxStart event:
-            start: function (e) {
-                if (e.isDefaultPrevented()) {
-                    return false;
-                }
-                var that = $(this).data('blueimp-fileupload') ||
-                        $(this).data('fileupload');
-                that._resetFinishedDeferreds();
-                that._transition($(this).find('.fileupload-progress')).done(
-                    function () {
-                        that._trigger('started', e);
-                    }
-                );
-            },
-            // Callback for uploads stop, equivalent to the global ajaxStop event:
-            stop: function (e) {
-                if (e.isDefaultPrevented()) {
-                    return false;
-                }
-                var that = $(this).data('blueimp-fileupload') ||
-                        $(this).data('fileupload'),
-                    deferred = that._addFinishedDeferreds();
-                $.when.apply($, that._getFinishedDeferreds())
-                    .done(function () {
-                        that._trigger('stopped', e);
-                    });
-                that._transition($(this).find('.fileupload-progress')).done(
-                    function () {
-                        $(this).find('.progress')
-                            .attr('aria-valuenow', '0')
-                            .children().first().css('width', '0%');
-                        $(this).find('.progress-extended').html('&nbsp;');
-                        deferred.resolve();
-                    }
-                );
-            },
-            processstart: function (e) {
-                if (e.isDefaultPrevented()) {
-                    return false;
-                }
-                $(this).addClass('fileupload-processing');
-            },
-            processstop: function (e) {
-                if (e.isDefaultPrevented()) {
-                    return false;
-                }
-                $(this).removeClass('fileupload-processing');
-            },
-            // Callback for file deletion:
-            destroy: function (e, data) {
-                if (e.isDefaultPrevented()) {
-                    return false;
-                }
-                var that = $(this).data('blueimp-fileupload') ||
-                        $(this).data('fileupload'),
-                    removeNode = function () {
-                        that._transition(data.context).done(
-                            function () {
-                                $(this).remove();
-                                that._trigger('destroyed', e, data);
-                            }
-                        );
-                    };
-                if (data.url) {
-                    data.dataType = data.dataType || that.options.dataType;
-                    $.ajax(data).done(removeNode).fail(function () {
-                        that._trigger('destroyfailed', e, data);
-                    });
-                } else {
-                    removeNode();
-                }
-            }
-        },
-
-        _resetFinishedDeferreds: function () {
-            this._finishedUploads = [];
-        },
-
-        _addFinishedDeferreds: function (deferred) {
-            if (!deferred) {
-                deferred = $.Deferred();
-            }
-            this._finishedUploads.push(deferred);
-            return deferred;
-        },
-
-        _getFinishedDeferreds: function () {
-            return this._finishedUploads;
-        },
-
-        // Link handler, that allows to download files
-        // by drag & drop of the links to the desktop:
-        _enableDragToDesktop: function () {
-            var link = $(this),
-                url = link.prop('href'),
-                name = link.prop('download'),
-                type = 'application/octet-stream';
-            link.bind('dragstart', function (e) {
-                try {
-                    e.originalEvent.dataTransfer.setData(
-                        'DownloadURL',
-                        [type, name, url].join(':')
-                    );
-                } catch (ignore) {}
-            });
-        },
-
-        _formatFileSize: function (bytes) {
-            if (typeof bytes !== 'number') {
-                return '';
-            }
-            if (bytes >= 1000000000) {
-                return (bytes / 1000000000).toFixed(2) + ' GB';
-            }
-            if (bytes >= 1000000) {
-                return (bytes / 1000000).toFixed(2) + ' MB';
-            }
-            return (bytes / 1000).toFixed(2) + ' KB';
-        },
-
-        _formatBitrate: function (bits) {
-            if (typeof bits !== 'number') {
-                return '';
-            }
-            if (bits >= 1000000000) {
-                return (bits / 1000000000).toFixed(2) + ' Gbit/s';
-            }
-            if (bits >= 1000000) {
-                return (bits / 1000000).toFixed(2) + ' Mbit/s';
-            }
-            if (bits >= 1000) {
-                return (bits / 1000).toFixed(2) + ' kbit/s';
-            }
-            return bits.toFixed(2) + ' bit/s';
-        },
-
-        _formatTime: function (seconds) {
-            var date = new Date(seconds * 1000),
-                days = Math.floor(seconds / 86400);
-            days = days ? days + 'd ' : '';
-            return days +
-                ('0' + date.getUTCHours()).slice(-2) + ':' +
-                ('0' + date.getUTCMinutes()).slice(-2) + ':' +
-                ('0' + date.getUTCSeconds()).slice(-2);
-        },
-
-        _formatPercentage: function (floatValue) {
-            return (floatValue * 100).toFixed(2) + ' %';
-        },
-
-        _renderExtendedProgress: function (data) {
-            return this._formatBitrate(data.bitrate) + ' | ' +
-                this._formatTime(
-                    (data.total - data.loaded) * 8 / data.bitrate
-                ) + ' | ' +
-                this._formatPercentage(
-                    data.loaded / data.total
-                ) + ' | ' +
-                this._formatFileSize(data.loaded) + ' / ' +
-                this._formatFileSize(data.total);
-        },
-
-        _renderTemplate: function (func, files) {
-            if (!func) {
-                return $();
-            }
-            var result = func({
-                files: files,
-                formatFileSize: this._formatFileSize,
-                options: this.options
-            });
-            if (result instanceof $) {
-                return result;
-            }
-            return $(this.options.templatesContainer).html(result).children();
-        },
-
-        _renderPreviews: function (data) {
-            data.context.find('.preview').each(function (index, elm) {
-                $(elm).append(data.files[index].preview);
-            });
-        },
-
-        _renderUpload: function (files) {
-            return this._renderTemplate(
-                this.options.uploadTemplate,
-                files
-            );
-        },
-
-        _renderDownload: function (files) {
-            return this._renderTemplate(
-                this.options.downloadTemplate,
-                files
-            ).find('a[download]').each(this._enableDragToDesktop).end();
-        },
-
-        _startHandler: function (e) {
-            e.preventDefault();
-            var button = $(e.currentTarget),
-                template = button.closest('.template-upload'),
-                data = template.data('data');
-            button.prop('disabled', true);
-            if (data && data.submit) {
-                data.submit();
-            }
-        },
-
-        _cancelHandler: function (e) {
-            e.preventDefault();
-            var template = $(e.currentTarget)
-                    .closest('.template-upload,.template-download'),
-                data = template.data('data') || {};
-            data.context = data.context || template;
-            if (data.abort) {
-                data.abort();
-            } else {
-                data.errorThrown = 'abort';
-                this._trigger('fail', e, data);
-            }
-        },
-
-        _deleteHandler: function (e) {
-            e.preventDefault();
-            var button = $(e.currentTarget);
-            this._trigger('destroy', e, $.extend({
-                context: button.closest('.template-download'),
-                type: 'DELETE'
-            }, button.data()));
-        },
-
-        _forceReflow: function (node) {
-            return $.support.transition && node.length &&
-                node[0].offsetWidth;
-        },
-
-        _transition: function (node) {
-            var dfd = $.Deferred();
-            if ($.support.transition && node.hasClass('fade') && node.is(':visible')) {
-                node.bind(
-                    $.support.transition.end,
-                    function (e) {
-                        // Make sure we don't respond to other transitions events
-                        // in the container element, e.g. from button elements:
-                        if (e.target === node[0]) {
-                            node.unbind($.support.transition.end);
-                            dfd.resolveWith(node);
-                        }
-                    }
-                ).toggleClass('in');
-            } else {
-                node.toggleClass('in');
-                dfd.resolveWith(node);
-            }
-            return dfd;
-        },
-
-        _initButtonBarEventHandlers: function () {
-            var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'),
-                filesList = this.options.filesContainer;
-            this._on(fileUploadButtonBar.find('.start'), {
-                click: function (e) {
-                    e.preventDefault();
-                    filesList.find('.start').click();
-                }
-            });
-            this._on(fileUploadButtonBar.find('.cancel'), {
-                click: function (e) {
-                    e.preventDefault();
-                    filesList.find('.cancel').click();
-                }
-            });
-            this._on(fileUploadButtonBar.find('.delete'), {
-                click: function (e) {
-                    e.preventDefault();
-                    filesList.find('.toggle:checked')
-                        .closest('.template-download')
-                        .find('.delete').click();
-                    fileUploadButtonBar.find('.toggle')
-                        .prop('checked', false);
-                }
-            });
-            this._on(fileUploadButtonBar.find('.toggle'), {
-                change: function (e) {
-                    filesList.find('.toggle').prop(
-                        'checked',
-                        $(e.currentTarget).is(':checked')
-                    );
-                }
-            });
-        },
-
-        _destroyButtonBarEventHandlers: function () {
-            this._off(
-                this.element.find('.fileupload-buttonbar')
-                    .find('.start, .cancel, .delete'),
-                'click'
-            );
-            this._off(
-                this.element.find('.fileupload-buttonbar .toggle'),
-                'change.'
-            );
-        },
-
-        _initEventHandlers: function () {
-            this._super();
-            this._on(this.options.filesContainer, {
-                'click .start': this._startHandler,
-                'click .cancel': this._cancelHandler,
-                'click .delete': this._deleteHandler
-            });
-            this._initButtonBarEventHandlers();
-        },
-
-        _destroyEventHandlers: function () {
-            this._destroyButtonBarEventHandlers();
-            this._off(this.options.filesContainer, 'click');
-            this._super();
-        },
-
-        _enableFileInputButton: function () {
-            this.element.find('.fileinput-button input')
-                .prop('disabled', false)
-                .parent().removeClass('disabled');
-        },
-
-        _disableFileInputButton: function () {
-            this.element.find('.fileinput-button input')
-                .prop('disabled', true)
-                .parent().addClass('disabled');
-        },
-
-        _initTemplates: function () {
-            var options = this.options;
-            options.templatesContainer = this.document[0].createElement(
-                options.filesContainer.prop('nodeName')
-            );
-            if (tmpl) {
-                if (options.uploadTemplateId) {
-                    options.uploadTemplate = tmpl(options.uploadTemplateId);
-                }
-                if (options.downloadTemplateId) {
-                    options.downloadTemplate = tmpl(options.downloadTemplateId);
-                }
-            }
-        },
-
-        _initFilesContainer: function () {
-            var options = this.options;
-            if (options.filesContainer === undefined) {
-                options.filesContainer = this.element.find('.files');
-            } else if (!(options.filesContainer instanceof $)) {
-                options.filesContainer = $(options.filesContainer);
-            }
-        },
-
-        _initSpecialOptions: function () {
-            this._super();
-            this._initFilesContainer();
-            this._initTemplates();
-        },
-
-        _create: function () {
-            this._super();
-            this._resetFinishedDeferreds();
-            if (!$.support.fileInput) {
-                this._disableFileInputButton();
-            }
-        },
-
-        enable: function () {
-            var wasDisabled = false;
-            if (this.options.disabled) {
-                wasDisabled = true;
-            }
-            this._super();
-            if (wasDisabled) {
-                this.element.find('input, button').prop('disabled', false);
-                this._enableFileInputButton();
-            }
-        },
-
-        disable: function () {
-            if (!this.options.disabled) {
-                this.element.find('input, button').prop('disabled', true);
-                this._disableFileInputButton();
-            }
-            this._super();
-        }
-
-    });
-
-}));

+ 546 - 0
GCDWebUploader/GCDWebUploader.bundle/js/jquery.jeditable.js

@@ -0,0 +1,546 @@
+/*
+ * Jeditable - jQuery in place edit plugin
+ *
+ * Copyright (c) 2006-2013 Mika Tuupola, Dylan Verheul
+ *
+ * Licensed under the MIT license:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *
+ * Project home:
+ *   http://www.appelsiini.net/projects/jeditable
+ *
+ * Based on editable by Dylan Verheul <dylan_at_dyve.net>:
+ *    http://www.dyve.net/jquery/?editable
+ *
+ */
+
+/**
+  * Version 1.7.3
+  *
+  * ** means there is basic unit tests for this parameter. 
+  *
+  * @name  Jeditable
+  * @type  jQuery
+  * @param String  target             (POST) URL or function to send edited content to **
+  * @param Hash    options            additional options 
+  * @param String  options[method]    method to use to send edited content (POST or PUT) **
+  * @param Function options[callback] Function to run after submitting edited content **
+  * @param String  options[name]      POST parameter name of edited content
+  * @param String  options[id]        POST parameter name of edited div id
+  * @param Hash    options[submitdata] Extra parameters to send when submitting edited content.
+  * @param String  options[type]      text, textarea or select (or any 3rd party input type) **
+  * @param Integer options[rows]      number of rows if using textarea ** 
+  * @param Integer options[cols]      number of columns if using textarea **
+  * @param Mixed   options[height]    'auto', 'none' or height in pixels **
+  * @param Mixed   options[width]     'auto', 'none' or width in pixels **
+  * @param String  options[loadurl]   URL to fetch input content before editing **
+  * @param String  options[loadtype]  Request type for load url. Should be GET or POST.
+  * @param String  options[loadtext]  Text to display while loading external content.
+  * @param Mixed   options[loaddata]  Extra parameters to pass when fetching content before editing.
+  * @param Mixed   options[data]      Or content given as paramameter. String or function.**
+  * @param String  options[indicator] indicator html to show when saving
+  * @param String  options[tooltip]   optional tooltip text via title attribute **
+  * @param String  options[event]     jQuery event such as 'click' of 'dblclick' **
+  * @param String  options[submit]    submit button value, empty means no button **
+  * @param String  options[cancel]    cancel button value, empty means no button **
+  * @param String  options[cssclass]  CSS class to apply to input form. 'inherit' to copy from parent. **
+  * @param String  options[style]     Style to apply to input form 'inherit' to copy from parent. **
+  * @param String  options[select]    true or false, when true text is highlighted ??
+  * @param String  options[placeholder] Placeholder text or html to insert when element is empty. **
+  * @param String  options[onblur]    'cancel', 'submit', 'ignore' or function ??
+  *             
+  * @param Function options[onsubmit] function(settings, original) { ... } called before submit
+  * @param Function options[onreset]  function(settings, original) { ... } called before reset
+  * @param Function options[onerror]  function(settings, original, xhr) { ... } called on error
+  *             
+  * @param Hash    options[ajaxoptions]  jQuery Ajax options. See docs.jquery.com.
+  *             
+  */
+
+(function($) {
+
+    $.fn.editable = function(target, options) {
+            
+        if ('disable' == target) {
+            $(this).data('disabled.editable', true);
+            return;
+        }
+        if ('enable' == target) {
+            $(this).data('disabled.editable', false);
+            return;
+        }
+        if ('destroy' == target) {
+            $(this)
+                .unbind($(this).data('event.editable'))
+                .removeData('disabled.editable')
+                .removeData('event.editable');
+            return;
+        }
+        
+        var settings = $.extend({}, $.fn.editable.defaults, {target:target}, options);
+        
+        /* setup some functions */
+        var plugin   = $.editable.types[settings.type].plugin || function() { };
+        var submit   = $.editable.types[settings.type].submit || function() { };
+        var buttons  = $.editable.types[settings.type].buttons 
+                    || $.editable.types['defaults'].buttons;
+        var content  = $.editable.types[settings.type].content 
+                    || $.editable.types['defaults'].content;
+        var element  = $.editable.types[settings.type].element 
+                    || $.editable.types['defaults'].element;
+        var reset    = $.editable.types[settings.type].reset 
+                    || $.editable.types['defaults'].reset;
+        var callback = settings.callback || function() { };
+        var onedit   = settings.onedit   || function() { }; 
+        var onsubmit = settings.onsubmit || function() { };
+        var onreset  = settings.onreset  || function() { };
+        var onerror  = settings.onerror  || reset;
+          
+        /* Show tooltip. */
+        if (settings.tooltip) {
+            $(this).attr('title', settings.tooltip);
+        }
+        
+        settings.autowidth  = 'auto' == settings.width;
+        settings.autoheight = 'auto' == settings.height;
+        
+        return this.each(function() {
+                        
+            /* Save this to self because this changes when scope changes. */
+            var self = this;  
+                   
+            /* Inlined block elements lose their width and height after first edit. */
+            /* Save them for later use as workaround. */
+            var savedwidth  = $(self).width();
+            var savedheight = $(self).height();
+
+            /* Save so it can be later used by $.editable('destroy') */
+            $(this).data('event.editable', settings.event);
+            
+            /* If element is empty add something clickable (if requested) */
+            if (!$.trim($(this).html())) {
+                $(this).html(settings.placeholder);
+            }
+            
+            $(this).bind(settings.event, function(e) {
+                
+                /* Abort if element is disabled. */
+                if (true === $(this).data('disabled.editable')) {
+                    return;
+                }
+                
+                /* Prevent throwing an exeption if edit field is clicked again. */
+                if (self.editing) {
+                    return;
+                }
+                
+                /* Abort if onedit hook returns false. */
+                if (false === onedit.apply(this, [settings, self])) {
+                   return;
+                }
+                
+                /* Prevent default action and bubbling. */
+                e.preventDefault();
+                e.stopPropagation();
+                
+                /* Remove tooltip. */
+                if (settings.tooltip) {
+                    $(self).removeAttr('title');
+                }
+                
+                /* Figure out how wide and tall we are, saved width and height. */
+                /* Workaround for http://dev.jquery.com/ticket/2190 */
+                if (0 == $(self).width()) {
+                    settings.width  = savedwidth;
+                    settings.height = savedheight;
+                } else {
+                    if (settings.width != 'none') {
+                        settings.width = 
+                            settings.autowidth ? $(self).width()  : settings.width;
+                    }
+                    if (settings.height != 'none') {
+                        settings.height = 
+                            settings.autoheight ? $(self).height() : settings.height;
+                    }
+                }
+                
+                /* Remove placeholder text, replace is here because of IE. */
+                if ($(this).html().toLowerCase().replace(/(;|"|\/)/g, '') == 
+                    settings.placeholder.toLowerCase().replace(/(;|"|\/)/g, '')) {
+                        $(this).html('');
+                }
+                                
+                self.editing    = true;
+                self.revert     = $(self).html();
+                $(self).html('');
+
+                /* Create the form object. */
+                var form = $('<form />');
+                
+                /* Apply css or style or both. */
+                if (settings.cssclass) {
+                    if ('inherit' == settings.cssclass) {
+                        form.attr('class', $(self).attr('class'));
+                    } else {
+                        form.attr('class', settings.cssclass);
+                    }
+                }
+
+                if (settings.style) {
+                    if ('inherit' == settings.style) {
+                        form.attr('style', $(self).attr('style'));
+                        /* IE needs the second line or display wont be inherited. */
+                        form.css('display', $(self).css('display'));                
+                    } else {
+                        form.attr('style', settings.style);
+                    }
+                }
+
+                /* Add main input element to form and store it in input. */
+                var input = element.apply(form, [settings, self]);
+
+                /* Set input content via POST, GET, given data or existing value. */
+                var input_content;
+                
+                if (settings.loadurl) {
+                    var t = setTimeout(function() {
+                        input.disabled = true;
+                        content.apply(form, [settings.loadtext, settings, self]);
+                    }, 100);
+
+                    var loaddata = {};
+                    loaddata[settings.id] = self.id;
+                    if ($.isFunction(settings.loaddata)) {
+                        $.extend(loaddata, settings.loaddata.apply(self, [self.revert, settings]));
+                    } else {
+                        $.extend(loaddata, settings.loaddata);
+                    }
+                    $.ajax({
+                       type : settings.loadtype,
+                       url  : settings.loadurl,
+                       data : loaddata,
+                       async : false,
+                       success: function(result) {
+                          window.clearTimeout(t);
+                          input_content = result;
+                          input.disabled = false;
+                       }
+                    });
+                } else if (settings.data) {
+                    input_content = settings.data;
+                    if ($.isFunction(settings.data)) {
+                        input_content = settings.data.apply(self, [self.revert, settings]);
+                    }
+                } else {
+                    input_content = self.revert; 
+                }
+                content.apply(form, [input_content, settings, self]);
+
+                input.attr('name', settings.name);
+        
+                /* Add buttons to the form. */
+                buttons.apply(form, [settings, self]);
+         
+                /* Add created form to self. */
+                $(self).append(form);
+         
+                /* Attach 3rd party plugin if requested. */
+                plugin.apply(form, [settings, self]);
+
+                /* Focus to first visible form element. */
+                $(':input:visible:enabled:first', form).focus();
+
+                /* Highlight input contents when requested. */
+                if (settings.select) {
+                    input.select();
+                }
+        
+                /* discard changes if pressing esc */
+                input.keydown(function(e) {
+                    if (e.keyCode == 27) {
+                        e.preventDefault();
+                        reset.apply(form, [settings, self]);
+                    }
+                });
+
+                /* Discard, submit or nothing with changes when clicking outside. */
+                /* Do nothing is usable when navigating with tab. */
+                var t;
+                if ('cancel' == settings.onblur) {
+                    input.blur(function(e) {
+                        /* Prevent canceling if submit was clicked. */
+                        t = setTimeout(function() {
+                            reset.apply(form, [settings, self]);
+                        }, 500);
+                    });
+                } else if ('submit' == settings.onblur) {
+                    input.blur(function(e) {
+                        /* Prevent double submit if submit was clicked. */
+                        t = setTimeout(function() {
+                            form.submit();
+                        }, 200);
+                    });
+                } else if ($.isFunction(settings.onblur)) {
+                    input.blur(function(e) {
+                        settings.onblur.apply(self, [input.val(), settings]);
+                    });
+                } else {
+                    input.blur(function(e) {
+                      /* TODO: maybe something here */
+                    });
+                }
+
+                form.submit(function(e) {
+
+                    if (t) { 
+                        clearTimeout(t);
+                    }
+
+                    /* Do no submit. */
+                    e.preventDefault(); 
+            
+                    /* Call before submit hook. */
+                    /* If it returns false abort submitting. */                    
+                    if (false !== onsubmit.apply(form, [settings, self])) { 
+                        /* Custom inputs call before submit hook. */
+                        /* If it returns false abort submitting. */
+                        if (false !== submit.apply(form, [settings, self])) { 
+
+                          /* Check if given target is function */
+                          if ($.isFunction(settings.target)) {
+                              var str = settings.target.apply(self, [input.val(), settings]);
+                              $(self).html(str);
+                              self.editing = false;
+                              callback.apply(self, [self.innerHTML, settings]);
+                              /* TODO: this is not dry */                              
+                              if (!$.trim($(self).html())) {
+                                  $(self).html(settings.placeholder);
+                              }
+                          } else {
+                              /* Add edited content and id of edited element to POST. */
+                              var submitdata = {};
+                              submitdata[settings.name] = input.val();
+                              submitdata[settings.id] = self.id;
+                              /* Add extra data to be POST:ed. */
+                              if ($.isFunction(settings.submitdata)) {
+                                  $.extend(submitdata, settings.submitdata.apply(self, [self.revert, settings]));
+                              } else {
+                                  $.extend(submitdata, settings.submitdata);
+                              }
+
+                              /* Quick and dirty PUT support. */
+                              if ('PUT' == settings.method) {
+                                  submitdata['_method'] = 'put';
+                              }
+
+                              /* Show the saving indicator. */
+                              $(self).html(settings.indicator);
+                              
+                              /* Defaults for ajaxoptions. */
+                              var ajaxoptions = {
+                                  type    : 'POST',
+                                  data    : submitdata,
+                                  dataType: 'html',
+                                  url     : settings.target,
+                                  success : function(result, status) {
+                                      if (ajaxoptions.dataType == 'html') {
+                                        $(self).html(result);
+                                      }
+                                      self.editing = false;
+                                      callback.apply(self, [result, settings]);
+                                      if (!$.trim($(self).html())) {
+                                          $(self).html(settings.placeholder);
+                                      }
+                                  },
+                                  error   : function(xhr, status, error) {
+                                      onerror.apply(form, [settings, self, xhr]);
+                                  }
+                              };
+                              
+                              /* Override with what is given in settings.ajaxoptions. */
+                              $.extend(ajaxoptions, settings.ajaxoptions);   
+                              $.ajax(ajaxoptions);          
+                              
+                            }
+                        }
+                    }
+                    
+                    /* Show tooltip again. */
+                    $(self).attr('title', settings.tooltip);
+                    
+                    return false;
+                });
+            });
+            
+            /* Privileged methods */
+            this.reset = function(form) {
+                /* Prevent calling reset twice when blurring. */
+                if (this.editing) {
+                    /* Before reset hook, if it returns false abort reseting. */
+                    if (false !== onreset.apply(form, [settings, self])) { 
+                        $(self).html(self.revert);
+                        self.editing   = false;
+                        if (!$.trim($(self).html())) {
+                            $(self).html(settings.placeholder);
+                        }
+                        /* Show tooltip again. */
+                        if (settings.tooltip) {
+                            $(self).attr('title', settings.tooltip);                
+                        }
+                    }                    
+                }
+            };            
+        });
+
+    };
+
+
+    $.editable = {
+        types: {
+            defaults: {
+                element : function(settings, original) {
+                    var input = $('<input type="hidden"></input>');                
+                    $(this).append(input);
+                    return(input);
+                },
+                content : function(string, settings, original) {
+                    $(':input:first', this).val(string);
+                },
+                reset : function(settings, original) {
+                  original.reset(this);
+                },
+                buttons : function(settings, original) {
+                    var form = this;
+                    if (settings.submit) {
+                        /* If given html string use that. */
+                        if (settings.submit.match(/>$/)) {
+                            var submit = $(settings.submit).click(function() {
+                                if (submit.attr("type") != "submit") {
+                                    form.submit();
+                                }
+                            });
+                        /* Otherwise use button with given string as text. */
+                        } else {
+                            var submit = $('<button type="submit" />');
+                            submit.html(settings.submit);                            
+                        }
+                        $(this).append(submit);
+                    }
+                    if (settings.cancel) {
+                        /* If given html string use that. */
+                        if (settings.cancel.match(/>$/)) {
+                            var cancel = $(settings.cancel);
+                        /* otherwise use button with given string as text */
+                        } else {
+                            var cancel = $('<button type="cancel" />');
+                            cancel.html(settings.cancel);
+                        }
+                        $(this).append(cancel);
+
+                        $(cancel).click(function(event) {
+                            if ($.isFunction($.editable.types[settings.type].reset)) {
+                                var reset = $.editable.types[settings.type].reset;                                                                
+                            } else {
+                                var reset = $.editable.types['defaults'].reset;                                
+                            }
+                            reset.apply(form, [settings, original]);
+                            return false;
+                        });
+                    }
+                }
+            },
+            text: {
+                element : function(settings, original) {
+                    var input = $('<input />');
+                    if (settings.width  != 'none') { input.width(settings.width);  }
+                    if (settings.height != 'none') { input.height(settings.height); }
+                    /* https://bugzilla.mozilla.org/show_bug.cgi?id=236791 */
+                    //input[0].setAttribute('autocomplete','off');
+                    input.attr('autocomplete','off');
+                    $(this).append(input);
+                    return(input);
+                }
+            },
+            textarea: {
+                element : function(settings, original) {
+                    var textarea = $('<textarea />');
+                    if (settings.rows) {
+                        textarea.attr('rows', settings.rows);
+                    } else if (settings.height != "none") {
+                        textarea.height(settings.height);
+                    }
+                    if (settings.cols) {
+                        textarea.attr('cols', settings.cols);
+                    } else if (settings.width != "none") {
+                        textarea.width(settings.width);
+                    }
+                    $(this).append(textarea);
+                    return(textarea);
+                }
+            },
+            select: {
+               element : function(settings, original) {
+                    var select = $('<select />');
+                    $(this).append(select);
+                    return(select);
+                },
+                content : function(data, settings, original) {
+                    /* If it is string assume it is json. */
+                    if (String == data.constructor) {      
+                        eval ('var json = ' + data);
+                    } else {
+                    /* Otherwise assume it is a hash already. */
+                        var json = data;
+                    }
+                    for (var key in json) {
+                        if (!json.hasOwnProperty(key)) {
+                            continue;
+                        }
+                        if ('selected' == key) {
+                            continue;
+                        } 
+                        var option = $('<option />').val(key).append(json[key]);
+                        $('select', this).append(option);    
+                    }                    
+                    /* Loop option again to set selected. IE needed this... */ 
+                    $('select', this).children().each(function() {
+                        if ($(this).val() == json['selected'] || 
+                            $(this).text() == $.trim(original.revert)) {
+                                $(this).attr('selected', 'selected');
+                        }
+                    });
+                    /* Submit on change if no submit button defined. */
+                    if (!settings.submit) {
+                        var form = this;
+                        $('select', this).change(function() {
+                            form.submit();
+                        });
+                    }
+                }
+            }
+        },
+
+        /* Add new input type */
+        addInputType: function(name, input) {
+            $.editable.types[name] = input;
+        }
+    };
+
+    /* Publicly accessible defaults. */
+    $.fn.editable.defaults = {
+        name       : 'value',
+        id         : 'id',
+        type       : 'text',
+        width      : 'auto',
+        height     : 'auto',
+        event      : 'click.editable',
+        onblur     : 'cancel',
+        loadtype   : 'GET',
+        loadtext   : 'Loading...',
+        placeholder: 'Click to edit',
+        loaddata   : {},
+        submitdata : {},
+        ajaxoptions: {}
+    };
+
+})(jQuery);

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 4 - 0
GCDWebUploader/GCDWebUploader.bundle/js/respond.min.js


+ 4 - 2
GCDWebUploader/GCDWebUploader.h

@@ -30,8 +30,10 @@
 @class GCDWebUploader;
 
 @protocol GCDWebUploaderDelegate <NSObject>
-- (void)webUploader:(GCDWebUploader*)uploader didUploadFile:(NSString*)fileName;
-- (void)webUploader:(GCDWebUploader*)uploader didDeleteFile:(NSString*)fileName;
+- (void)webUploader:(GCDWebUploader*)uploader didUploadFileAtPath:(NSString*)path;
+- (void)webUploader:(GCDWebUploader*)uploader didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath;
+- (void)webUploader:(GCDWebUploader*)uploader didDeleteItemAtPath:(NSString*)path;
+- (void)webUploader:(GCDWebUploader*)uploader didCreateDirectoryAtPath:(NSString*)path;
 @end
 
 @interface GCDWebUploader : GCDWebServer

+ 141 - 83
GCDWebUploader/GCDWebUploader.m

@@ -46,23 +46,6 @@
 }
 @end
 
-static NSDictionary* _GetInfoForFile(NSString* path) {
-  NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:NULL];
-  if ([[attributes objectForKey:NSFileType] isEqualToString:NSFileTypeRegular]) {
-    NSString* name = [path lastPathComponent];
-    NSString* file = GCDWebServerEscapeURLString(name);
-    return @{
-             @"url": [@"/download?file=" stringByAppendingString:file],
-             @"name": name,
-             @"size": [attributes objectForKey:NSFileSize],
-             @"type": GCDWebServerGetMimeTypeForExtension([name pathExtension]),
-             @"deleteType": @"DELETE",
-             @"deleteUrl": [@"/delete?file=" stringByAppendingString:file]
-             };
-  }
-  return nil;
-}
-
 @implementation GCDWebUploader
 
 @synthesize uploadDirectory=_uploadDirectory, delegate=_delegate, allowedFileExtensions=_allowedExtensions, showHiddenFiles=_showHidden, title=_title, header=_header, footer=_footer;
@@ -74,9 +57,28 @@ static NSDictionary* _GetInfoForFile(NSString* path) {
   return YES;
 }
 
+- (NSString*) _uniquePathForPath:(NSString*)path {
+  if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
+    NSString* directory = [path stringByDeletingLastPathComponent];
+    NSString* file = [path lastPathComponent];
+    NSString* base = [file stringByDeletingPathExtension];
+    NSString* extension = [file pathExtension];
+    int retries = 0;
+    do {
+      if (extension.length) {
+        path = [directory stringByAppendingPathComponent:[[base stringByAppendingFormat:@" (%i)", ++retries] stringByAppendingPathExtension:extension]];
+      } else {
+        path = [directory stringByAppendingPathComponent:[base stringByAppendingFormat:@" (%i)", ++retries]];
+      }
+    } while ([[NSFileManager defaultManager] fileExistsAtPath:path]);
+  }
+  return path;
+}
+
 - (id)initWithUploadDirectory:(NSString*)path {
   if ((self = [super init])) {
-    NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"GCDWebUploader" ofType:@"bundle"]];
+#warning
+    NSBundle* siteBundle = [NSBundle bundleWithPath:@"/Users/pol/Source/GitHub-GCDWebServer/GCDWebUploader/GCDWebUploader.bundle"];  // [[NSBundle mainBundle] pathForResource:@"GCDWebUploader" ofType:@"bundle"]
     if (siteBundle == nil) {
 #if !__has_feature(objc_arc)
       [self release];
@@ -101,22 +103,26 @@ static NSDictionary* _GetInfoForFile(NSString* path) {
       if (header == nil) {
         header = [siteBundle localizedStringForKey:@"HEADER" value:@"" table:nil];
       }
-      NSString* footer = uploader.footer;
-      if (footer == nil) {
 #if TARGET_OS_IPHONE
-        NSString* name = [[UIDevice currentDevice] name];
+      NSString* device = [[UIDevice currentDevice] name];
 #else
-        CFStringRef name = SCDynamicStoreCopyComputerName(NULL, NULL);
+#if __has_feature(objc_arc)
+      NSString* device = CFBridgingRelease(SCDynamicStoreCopyComputerName(NULL, NULL));
+#else
+      NSString* device = [(id)SCDynamicStoreCopyComputerName(NULL, NULL) autorelease];
+#endif
 #endif
+      NSString* footer = uploader.footer;
+      if (footer == nil) {
         footer = [NSString stringWithFormat:[siteBundle localizedStringForKey:@"FOOTER" value:@"" table:nil],
                   [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"],
-                  [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
-                  name];
+                  [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]];
       }
       return [GCDWebServerDataResponse responseWithHTMLTemplate:[siteBundle pathForResource:@"index" ofType:@"html"]
                                                       variables:@{
                                                                   @"title": title,
                                                                   @"header": header,
+                                                                  @"device": device,
                                                                   @"footer": footer
                                                                   }];
       
@@ -125,24 +131,42 @@ static NSDictionary* _GetInfoForFile(NSString* path) {
     // File listing
     [self addHandlerForMethod:@"GET" path:@"/list" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
       
-      NSString* basePath = uploader.uploadDirectory;
-      BOOL showHidden = uploader.showHiddenFiles;
-      NSArray* contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:basePath error:NULL];
-      if (contents) {
-        NSMutableArray* files = [NSMutableArray array];
-        for (NSString* path in [contents sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) {
-          if (showHidden || ![path hasPrefix:@"."]) {
-            if ([uploader _checkFileExtension:path]) {
-              NSDictionary* info = _GetInfoForFile([basePath stringByAppendingPathComponent:path]);
-              if (info) {
-                [files addObject:info];
+      NSString* relativePath = [[request query] objectForKey:@"path"];
+      NSString* absolutePath = [uploader.uploadDirectory stringByAppendingPathComponent:relativePath];
+      BOOL isDirectory;
+      if ([[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
+        if (isDirectory) {
+          BOOL showHidden = uploader.showHiddenFiles;
+          NSArray* contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:absolutePath error:NULL];
+          if (contents) {
+            NSMutableArray* array = [NSMutableArray array];
+            for (NSString* item in [contents sortedArrayUsingSelector:@selector(localizedStandardCompare:)]) {
+              if (showHidden || ![item hasPrefix:@"."]) {
+                NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[absolutePath stringByAppendingPathComponent:item] error:NULL];
+                NSString* type = [attributes objectForKey:NSFileType];
+                if ([type isEqualToString:NSFileTypeRegular] && [uploader _checkFileExtension:item]) {
+                  [array addObject:@{
+                                     @"path": [relativePath stringByAppendingPathComponent:item],
+                                     @"name": item,
+                                     @"size": [attributes objectForKey:NSFileSize]
+                                     }];
+                } else if ([type isEqualToString:NSFileTypeDirectory]) {
+                  [array addObject:@{
+                                     @"path": [[relativePath stringByAppendingPathComponent:item] stringByAppendingString:@"/"],
+                                     @"name": item
+                                     }];
+                }
               }
             }
+            return [GCDWebServerDataResponse responseWithJSONObject:array];
+          } else {
+            return [GCDWebServerResponse responseWithStatusCode:500];
           }
+        } else {
+          return [GCDWebServerResponse responseWithStatusCode:400];
         }
-        return [GCDWebServerDataResponse responseWithJSONObject:@{@"files": files}];
       } else {
-        return [GCDWebServerResponse responseWithStatusCode:500];
+        return [GCDWebServerResponse responseWithStatusCode:404];
       }
       
     }];
@@ -150,11 +174,15 @@ static NSDictionary* _GetInfoForFile(NSString* path) {
     // File download
     [self addHandlerForMethod:@"GET" path:@"/download" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
       
-      NSString* file = [[request query] objectForKey:@"file"];
-      NSString* path = [uploader.uploadDirectory stringByAppendingPathComponent:file];
+      NSString* relativePath = [[request query] objectForKey:@"path"];
+      NSString* absolutePath = [uploader.uploadDirectory stringByAppendingPathComponent:relativePath];
       BOOL isDirectory;
-      if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory] && !isDirectory) {
-        return [GCDWebServerFileResponse responseWithFile:path isAttachment:YES];
+      if ([[NSFileManager defaultManager] fileExistsAtPath:absolutePath isDirectory:&isDirectory]) {
+        if (isDirectory) {
+          return [GCDWebServerResponse responseWithStatusCode:400];
+        } else {
+          return [GCDWebServerFileResponse responseWithFile:absolutePath isAttachment:YES];
+        }
       } else {
         return [GCDWebServerResponse responseWithStatusCode:404];
       }
@@ -170,70 +198,100 @@ static NSDictionary* _GetInfoForFile(NSString* path) {
       
       GCDWebServerMultiPartFile* file = [[(GCDWebServerMultiPartFormRequest*)request files] objectForKey:@"files[]"];
       NSString* fileName = file.fileName;
-      if ([uploader _checkFileExtension:fileName]) {
-        NSString* path = nil;
-        int retries = 0;
-        while (1) {
-          if (retries > 0) {
-            path = [uploader.uploadDirectory stringByAppendingPathComponent:[[[fileName stringByDeletingPathExtension] stringByAppendingFormat:@" (%i)", retries] stringByAppendingPathExtension:[fileName pathExtension]]];
-          } else {
-            path = [uploader.uploadDirectory stringByAppendingPathComponent:fileName];
-          }
-          if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
-            break;
+      if ((![fileName hasPrefix:@"."] || uploader.showHiddenFiles) && [uploader _checkFileExtension:fileName]) {
+        NSString* relativePath = [(GCDWebServerMultiPartArgument*)[[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"path"] string];
+        NSString* absolutePath = [uploader _uniquePathForPath:[[uploader.uploadDirectory stringByAppendingPathComponent:relativePath] stringByAppendingPathComponent:fileName]];
+        NSError* error = nil;
+        if ([[NSFileManager defaultManager] moveItemAtPath:file.temporaryPath toPath:absolutePath error:&error]) {
+          dispatch_async(dispatch_get_main_queue(), ^{
+            [uploader.delegate webUploader:uploader didUploadFileAtPath:absolutePath];
+          });
+          return [GCDWebServerDataResponse responseWithJSONObject:@{} contentType:contentType];
+        } else {
+          return [GCDWebServerResponse responseWithStatusCode:500];
+        }
+      } else {
+        return [GCDWebServerResponse responseWithStatusCode:400];
+      }
+      
+    }];
+    
+    // File and folder moving
+    [self addHandlerForMethod:@"POST" path:@"/move" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
+      
+      NSString* oldRelativePath = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"oldPath"];
+      NSString* oldAbsolutePath = [uploader.uploadDirectory stringByAppendingPathComponent:oldRelativePath];
+      BOOL isDirectory;
+      if ([[NSFileManager defaultManager] fileExistsAtPath:oldAbsolutePath isDirectory:&isDirectory]) {
+        NSString* newRelativePath = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"newPath"];
+        if (!uploader.showHiddenFiles) {
+          for (NSString* component in [newRelativePath pathComponents]) {
+            if ([component hasPrefix:@"."]) {
+              return [GCDWebServerResponse responseWithStatusCode:400];
+            }
           }
-          ++retries;
         }
-        NSError* error = nil;
-        if ([[NSFileManager defaultManager] moveItemAtPath:file.temporaryPath toPath:path error:&error]) {
+        if (!isDirectory && ![uploader _checkFileExtension:newRelativePath]) {
+          return [GCDWebServerResponse responseWithStatusCode:400];
+        }
+        NSString* newAbsolutePath = [uploader _uniquePathForPath:[uploader.uploadDirectory stringByAppendingPathComponent:newRelativePath]];
+        if ([[NSFileManager defaultManager] moveItemAtPath:oldAbsolutePath toPath:newAbsolutePath error:NULL]) {
           dispatch_async(dispatch_get_main_queue(), ^{
-            [uploader.delegate webUploader:uploader didUploadFile:[path lastPathComponent]];
+            [uploader.delegate webUploader:uploader didMoveItemFromPath:oldAbsolutePath toPath:newAbsolutePath];
           });
-          NSDictionary* info = _GetInfoForFile(path);
-          return [GCDWebServerDataResponse responseWithJSONObject:@{@"files": @[info]} contentType:contentType];
+          return [GCDWebServerDataResponse responseWithJSONObject:@{}];
         } else {
-          return [GCDWebServerDataResponse responseWithJSONObject:@{
-                                                                    @"files": @[@{
-                                                                                  @"name": fileName,
-                                                                                  @"size": @0,
-                                                                                  @"error": [error localizedDescription]
-                                                                                  }]
-                                                                    } contentType:contentType];
+          return [GCDWebServerResponse responseWithStatusCode:500];
         }
       } else {
-        return [GCDWebServerDataResponse responseWithJSONObject:@{
-                                                                  @"files": @[@{
-                                                                                @"name": fileName,
-                                                                                @"size": @0,
-                                                                                @"error": [siteBundle localizedStringForKey:@"UNSUPPORTED_FILE_EXTENSION" value:@"" table:nil]
-                                                                                }]
-                                                                  } contentType:contentType];
+        return [GCDWebServerResponse responseWithStatusCode:404];
       }
       
     }];
     
     // File deletion
-    [self addHandlerForMethod:@"DELETE" path:@"/delete" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
+    [self addHandlerForMethod:@"POST" path:@"/delete" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
       
-      NSString* file = [[request query] objectForKey:@"file"];
-      NSString* path = [uploader.uploadDirectory stringByAppendingPathComponent:file];
-      BOOL isDirectory;
-      if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory] && !isDirectory) {
-        BOOL success = [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
-        if (success) {
+      NSString* relativePath = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"path"];
+      NSString* absolutePath = [uploader.uploadDirectory stringByAppendingPathComponent:relativePath];
+      if ([[NSFileManager defaultManager] fileExistsAtPath:absolutePath]) {
+        if ([[NSFileManager defaultManager] removeItemAtPath:absolutePath error:NULL]) {
           dispatch_async(dispatch_get_main_queue(), ^{
-            [uploader.delegate webUploader:uploader didDeleteFile:file];
+            [uploader.delegate webUploader:uploader didDeleteItemAtPath:absolutePath];
           });
+          return [GCDWebServerDataResponse responseWithJSONObject:@{}];
+        } else {
+          return [GCDWebServerResponse responseWithStatusCode:500];
         }
-        return [GCDWebServerResponse responseWithStatusCode:(success ? 204 : 500)];
-        // TODO: Contrary to the documentation at https://github.com/blueimp/jQuery-File-Upload/wiki/Setup, jquery.fileupload-ui.js ignores the returned JSON
-        // return [GCDWebServerDataResponse responseWithJSONObject:@{@"files": @[@{file: [NSNumber numberWithBool:success]}]}];
       } else {
         return [GCDWebServerResponse responseWithStatusCode:404];
       }
       
     }];
     
+    // Directory creation
+    [self addHandlerForMethod:@"POST" path:@"/create" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
+      
+      NSString* relativePath = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"path"];
+      if (!uploader.showHiddenFiles) {
+        for (NSString* component in [relativePath pathComponents]) {
+          if ([component hasPrefix:@"."]) {
+            return [GCDWebServerResponse responseWithStatusCode:400];
+          }
+        }
+      }
+      NSString* absolutePath = [uploader _uniquePathForPath:[uploader.uploadDirectory stringByAppendingPathComponent:relativePath]];
+      if ([[NSFileManager defaultManager] createDirectoryAtPath:absolutePath withIntermediateDirectories:YES attributes:nil error:NULL]) {
+        dispatch_async(dispatch_get_main_queue(), ^{
+          [uploader.delegate webUploader:uploader didCreateDirectoryAtPath:absolutePath];
+        });
+        return [GCDWebServerDataResponse responseWithJSONObject:@{}];
+      } else {
+        return [GCDWebServerResponse responseWithStatusCode:500];
+      }
+      
+    }];
+    
   }
   return self;
 }

+ 12 - 4
iOS/AppDelegate.m

@@ -63,12 +63,20 @@
   return YES;
 }
 
-- (void)webUploader:(GCDWebUploader*)uploader didUploadFile:(NSString*)fileName {
-  NSLog(@"[UPLOAD] %@", fileName);
+- (void)webUploader:(GCDWebUploader*)uploader didUploadFileAtPath:(NSString*)path {
+  NSLog(@"[UPLOAD] %@", path);
 }
 
-- (void)webUploader:(GCDWebUploader*)uploader didDeleteFile:(NSString*)fileName {
-  NSLog(@"[DELETE] %@", fileName);
+- (void)webUploader:(GCDWebUploader*)uploader didMoveItemFromPath:(NSString*)fromPath toPath:(NSString*)toPath {
+  NSLog(@"[MOVE] %@ -> %@", fromPath, toPath);
+}
+
+- (void)webUploader:(GCDWebUploader*)uploader didDeleteItemAtPath:(NSString*)path {
+  NSLog(@"[DELETE] %@", path);
+}
+
+- (void)webUploader:(GCDWebUploader*)uploader didCreateDirectoryAtPath:(NSString*)path {
+  NSLog(@"[CREATE] %@", path);
 }
 
 @end

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.