46 Commits

Author SHA1 Message Date
c2f4de822f Removed tag release-1.1.0 2017-10-15 22:03:52 +13:00
ffd4c03d9c Backed out changeset: fa32e83c5a38 2017-10-15 22:03:47 +13:00
2e08ac06ca rebuild staticResources.go 2017-10-15 22:02:41 +13:00
6d739972de fix border radius display for progress bar 2017-10-15 22:02:31 +13:00
e36fd43f9b fix missing green part of progress bar 2017-10-15 22:02:19 +13:00
35bbc6c61b fix loading drawingboard css from relative URL 2017-10-15 22:02:08 +13:00
987c704730 load dependent scripts sequentially 2017-10-15 21:58:22 +13:00
9669f2aa0b bump all versions to 1.1.1 2017-10-15 20:54:41 +13:00
99139e360d Added tag release-1.1.0 for changeset cfb1e028fd06 2017-10-15 20:54:29 +13:00
696a92096d Backed out changeset: 77530eea6f02 2017-10-15 20:53:59 +13:00
f125c23fb9 Removed tag release-1.1.0 2017-10-15 20:53:35 +13:00
c50d6c4bfe rebuild staticResources.go for previous 2017-10-15 20:53:06 +13:00
5edea74333 fix missing baseURLs on remote script loads 2017-10-15 20:49:07 +13:00
1882a94e65 Added tag release-1.1.0 for changeset c7b699105bd1 2017-10-15 19:52:15 +13:00
1309293705 bump all versions to 1.1.1 2017-10-15 19:52:09 +13:00
d24c3d4895 doc: changelog 2017-10-15 19:51:36 +13:00
5e9fcc09c8 bump versions to 1.1.0 2017-10-15 19:51:17 +13:00
fb7f1dd3a5 doc: update changelog 2017-10-15 19:50:48 +13:00
2f4fe0a55f enable ctrl-V support 2017-10-15 19:49:46 +13:00
a3fc9092e3 doc: no need to point out the jquery dependency 2017-10-15 19:47:35 +13:00
6eb48bf72f doc: update changelog 2017-10-15 19:16:26 +13:00
40aa6c8917 option to disable the homepage 2017-10-15 19:16:22 +13:00
24febefba4 doc: update changelog 2017-10-15 19:12:25 +13:00
57d9b4d324 display xff IPs in log output 2017-10-15 19:11:13 +13:00
3ca73e3221 option for trustXForwardedFor 2017-10-15 19:09:14 +13:00
27cf4cf0c0 fix content selection in chrome prior to 54, et al 2017-10-15 19:00:49 +13:00
ef6b680b7e doc: update changelog 2017-10-15 18:59:00 +13:00
188ca7e679 homepage: add "again" button 2017-10-15 18:54:14 +13:00
b8f1b26aba sdk auto provide libraries, handle preInit calls, add get*URL methods, drawing canvas integration 2017-10-15 18:50:55 +13:00
ac768524ee homepage: display widget in full size 2017-10-15 18:49:46 +13:00
a5008ab455 vendor: track drawingboard 0.4.6 (MIT license) 2017-10-15 18:49:09 +13:00
9436dcd43e tweak progress captions in error cases 2017-10-08 19:14:11 +13:00
f543347b1c remove a console.log call 2017-10-08 19:07:06 +13:00
582854878b doc: mention client body size for nginx 2017-10-08 19:06:42 +13:00
642ee5a485 doc: todo (2) 2017-10-08 18:42:47 +13:00
19b20daded doc: todo 2017-10-08 18:40:43 +13:00
270e6a9397 bump all versions to 1.0.2 2017-10-08 17:16:37 +13:00
ef5351a075 Added tag release-1.0.1 for changeset b8975b9e7564 2017-10-08 17:16:27 +13:00
01ac021c7e doc: todo 2017-10-08 17:16:21 +13:00
47aeaff66e doc: changelog 2017-10-08 17:12:56 +13:00
d18a5fd272 bump all versions to 1.0.1 2017-10-08 17:12:49 +13:00
3a0566ca0a doc: tweak SDK information 2017-10-08 17:12:26 +13:00
36f62dd502 doc: add image 2017-10-08 17:12:17 +13:00
5b1a94c735 fix CORS 2017-10-08 17:06:24 +13:00
75c3a98f33 fix no default index page 2017-10-08 16:54:26 +13:00
e9fbfd0277 Added tag release-1.0.0 for changeset e2250a7fd290 2017-10-08 16:43:50 +13:00
15 changed files with 615 additions and 250 deletions

9
.hgtags Normal file
View File

@@ -0,0 +1,9 @@
e2250a7fd29052ea767f18e1459cabea4cd7efd3 release-1.0.0
b8975b9e75648a7c2a5003c67db92cf2216e01c0 release-1.0.1
c7b699105bd166e7a01aa0e678d34624680bf81e release-1.1.0
c7b699105bd166e7a01aa0e678d34624680bf81e release-1.1.0
0000000000000000000000000000000000000000 release-1.1.0
0000000000000000000000000000000000000000 release-1.1.0
cfb1e028fd0627614aa01184893f9f29f20a347e release-1.1.0
cfb1e028fd0627614aa01184893f9f29f20a347e release-1.1.0
0000000000000000000000000000000000000000 release-1.1.0

View File

@@ -2,7 +2,7 @@
# Makefile for contented # Makefile for contented
# #
VERSION:=1.0.0 VERSION:=1.1.0
SOURCES:=Makefile \ SOURCES:=Makefile \
static \ static \

View File

@@ -23,6 +23,8 @@ type ServerOptions struct {
DataDirectory string DataDirectory string
DBPath string DBPath string
BandwidthLimit int64 BandwidthLimit int64
TrustXForwardedFor bool
EnableHomepage bool
ServerPublicProperties ServerPublicProperties
} }
@@ -75,7 +77,13 @@ func (this *Server) handleAbout(w http.ResponseWriter) {
this.serveJsonObject(w, this.opts.ServerPublicProperties) this.serveJsonObject(w, this.opts.ServerPublicProperties)
} }
func remoteIP(r *http.Request) string { func (this *Server) remoteIP(r *http.Request) string {
if this.opts.TrustXForwardedFor {
if xff := r.Header.Get("X-Forwarded-For"); len(xff) > 0 {
return xff
}
}
return strings.TrimRight(strings.TrimRight(r.RemoteAddr, "0123456789"), ":") return strings.TrimRight(strings.TrimRight(r.RemoteAddr, "0123456789"), ":")
} }
@@ -86,6 +94,7 @@ const (
func (this *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (this *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set(`Server`, SERVER_HEADER) w.Header().Set(`Server`, SERVER_HEADER)
w.Header().Set(`Access-Control-Allow-Origin`, `*`) // Blanket allow CORS
if this.opts.MaxUploadBytes > 0 { if this.opts.MaxUploadBytes > 0 {
r.Body = http.MaxBytesReader(w, r.Body, this.opts.MaxUploadBytes) r.Body = http.MaxBytesReader(w, r.Body, this.opts.MaxUploadBytes)
} }
@@ -108,11 +117,13 @@ func (this *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
this.handleUpload(w, r) this.handleUpload(w, r)
} else if r.Method == "OPTIONS" { } else if r.Method == "OPTIONS" {
// Blanket allow // Blanket allow (headers already set)
w.Header().Set(`Access-Control-Allow-Origin`, `*`)
w.WriteHeader(200) w.WriteHeader(200)
} else if static, err := Asset(r.URL.Path[1:]); err == nil && r.Method == "GET" { } else if r.Method == "GET" && r.URL.Path == `/` && this.opts.EnableHomepage {
http.Redirect(w, r, `/index.html`, http.StatusFound)
} else if static, err := Asset(r.URL.Path[1:]); err == nil && r.Method == "GET" && (this.opts.EnableHomepage || r.URL.Path != `/index.html`) {
http.ServeContent(w, r, r.URL.Path[1:], this.startTime, bytes.NewReader(static)) http.ServeContent(w, r, r.URL.Path[1:], this.startTime, bytes.NewReader(static))
} else { } else {

View File

@@ -8,6 +8,6 @@ TODO
- Nicer preview page after uploading - Nicer preview page after uploading
- Option to disable manual uploading from the landing page
- Gallery preview (multiple element hashid) - Gallery preview (multiple element hashid)
- Prevent selecting around the toggle buttons (firefox for android)

View File

@@ -6,9 +6,11 @@ You can use contented as a standalone upload server, or you can use the SDK to e
=FEATURES= =FEATURES=
- Drag and drop uploader, or fallback classic uploader, or pastebin-style uploader - Drag and drop upload
- Multiple files upload - Multiple files upload
- Pastebin upload - Pastebin upload
- Custom drawing upload ([url=https://github.com/Leimi/drawingboard.js]via[/url])
- Ctrl-V upload
- SDK-oriented design for embedding, including CORS support - SDK-oriented design for embedding, including CORS support
- Mobile friendly HTML interface - Mobile friendly HTML interface
- Preserves uploaded filename and content-type metadata - Preserves uploaded filename and content-type metadata
@@ -24,6 +26,8 @@ You can use contented as a standalone upload server, or you can use the SDK to e
Directory for stored content (default "") Directory for stored content (default "")
-db string -db string
Path for metadata database (default "contented.db") Path for metadata database (default "contented.db")
-enableHomepage
Enable homepage (disable for embedded use only) (default true)
-listen string -listen string
(default "127.0.0.1:80") (default "127.0.0.1:80")
-max int -max int
@@ -32,8 +36,12 @@ You can use contented as a standalone upload server, or you can use the SDK to e
Maximum upload speed in bytes/sec (set zero for unlimited) Maximum upload speed in bytes/sec (set zero for unlimited)
-title string -title string
Title used in web interface (default "contented") Title used in web interface (default "contented")
-trustXForwardedFor
Trust X-Forwarded-For reverse proxy headers
` `
If you are hosting behind a reverse proxy, remember to set its post body size parameter appropriately (e.g. `client_max_body_size` for nginx).
=USAGE (HTTP)= =USAGE (HTTP)=
The server responds on the following URLs: The server responds on the following URLs:
@@ -44,18 +52,31 @@ The server responds on the following URLs:
=USAGE (EMBEDDING FOR WEB)= =USAGE (EMBEDDING FOR WEB)=
Your webpage should load the SDK from the contented server, then call the `contented.init` function to display the upload widget over the top of an existing DOM element. Your webpage should load the SDK from the contented server, then call the `contented.init` function to display the upload widget over the top of an existing DOM element. Your callback will be passed an array of file IDs of any uploaded items.
The SDK will run your callback, passing it the file IDs of any uploaded items. `<script type="text/javascript" src="SERVER_ADDR/sdk.js"></script>
The SDK depends on jQuery.
`
<script type="text/javascript" src="SERVER_ADDR/sdk.js"></script>
contented.init("#target", function(/* String[] */ items) {}); contented.init("#target", function(/* String[] */ items) {});
` `
=CHANGELOG= =CHANGELOG=
2017-10-15: 1.1.0
- Feature: Drawing mode
- Feature: Ctrl+V image upload
- Feature: Option to trust X-Forwarded-For headers when using a reverse proxy
- Feature: Add `getDownloadURL`, `getInfoJSONURL`, `getPreviewURL` SDK methods
- Feature: Option to disable uploading via the homepage
- Feature: Add button to repeat when uploading from homepage
- Enhancement: Automatically load library dependencies
- Enhancement: Display homepage widget using the full screen size
- Include drawingboard.js 0.4.6 (MIT license)
- Fix a cosmetic issue with javascript console output
- Fix a cosmetic issue with error messages if an upload failed
2017-10-08: 1.0.1
- Fix an issue with CORS preflight requests
- Fix an issue with index URLs
2017-10-08: 1.0.0 2017-10-08: 1.0.0
- Initial public release - Initial public release
- Include jQuery 1.12.4 (MIT license)

BIN
_dist/image1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -18,6 +18,8 @@ func main() {
appTitle := flag.String("title", "contented", "Title used in web interface") appTitle := flag.String("title", "contented", "Title used in web interface")
maxUploadMb := flag.Int("max", 8, "Maximum size of uploaded files in MiB (set zero for unlimited)") maxUploadMb := flag.Int("max", 8, "Maximum size of uploaded files in MiB (set zero for unlimited)")
maxUploadSpeed := flag.Int("speed", 0, "Maximum upload speed in bytes/sec (set zero for unlimited)") maxUploadSpeed := flag.Int("speed", 0, "Maximum upload speed in bytes/sec (set zero for unlimited)")
trustXForwardedFor := flag.Bool("trustXForwardedFor", false, "Trust X-Forwarded-For reverse proxy headers")
enableHomepage := flag.Bool("enableHomepage", true, "Enable homepage (disable for embedded use only)")
flag.Parse() flag.Parse()
@@ -25,6 +27,8 @@ func main() {
DataDirectory: *dataDir, DataDirectory: *dataDir,
DBPath: *dbPath, DBPath: *dbPath,
BandwidthLimit: int64(*maxUploadSpeed), BandwidthLimit: int64(*maxUploadSpeed),
TrustXForwardedFor: *trustXForwardedFor,
EnableHomepage: *enableHomepage,
ServerPublicProperties: contented.ServerPublicProperties{ ServerPublicProperties: contented.ServerPublicProperties{
AppTitle: *appTitle, AppTitle: *appTitle,
MaxUploadBytes: int64(*maxUploadMb) * 1024 * 1024, MaxUploadBytes: int64(*maxUploadMb) * 1024 * 1024,

View File

@@ -10,7 +10,7 @@ import (
func (this *Server) handleView(w http.ResponseWriter, r *http.Request, fileID string) { func (this *Server) handleView(w http.ResponseWriter, r *http.Request, fileID string) {
err := this.handleViewInternal(w, r, r.URL.Path[len(downloadUrlPrefix):]) err := this.handleViewInternal(w, r, r.URL.Path[len(downloadUrlPrefix):])
if err != nil { if err != nil {
log.Printf("%s View failed: %s\n", r.RemoteAddr, err.Error()) log.Printf("%s View failed: %s\n", this.remoteIP(r), err.Error())
if os.IsNotExist(err) { if os.IsNotExist(err) {
http.Error(w, "File not found", 404) http.Error(w, "File not found", 404)
} else { } else {

5
static/drawingboard-0.4.6.min.css vendored Normal file

File diff suppressed because one or more lines are too long

4
static/drawingboard-0.4.6.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -7,9 +7,23 @@
<style type="text/css"> <style type="text/css">
html, body { html, body {
font-family: sans-serif; font-family: sans-serif;
margin:0;
width:100%;
height:100%;
}
#padder {
height:100%;
position:relative;
} }
#surrogate-area { #surrogate-area {
height:300px; position:absolute;
top:10px;
bottom:10px;
left:10px;
right:10px;
}
button.again {
margin-top:2em;
} }
/* hide close button */ /* hide close button */
.contented-close { .contented-close {
@@ -18,9 +32,11 @@ html, body {
</style> </style>
</head> </head>
<body> <body>
<div id="padder">
<div id="surrogate-area"> <div id="surrogate-area">
Loading... Loading...
</div> </div>
</div>
<script type="text/javascript" src="/jquery-1.12.4.min.js"></script> <script type="text/javascript" src="/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="/sdk.js"></script> <script type="text/javascript" src="/sdk.js"></script>
@@ -45,11 +61,16 @@ contented.init("#surrogate-area", function(items) {
for (var i = 0; i < items.length; ++i) { for (var i = 0; i < items.length; ++i) {
$table.append($("<tr>").append([ $table.append($("<tr>").append([
$("<td>").text(items[i]), $("<td>").text(items[i]),
$("<td>").html("<a target='_blank' href='/get/" + items[i] + "'>get</a>"), $("<td>").html("<a target='_blank' href='" + contented.getDownloadURL(items[i]) + "'>get</a>"),
$("<td>").html("<a target='_blank' href='/info/" + items[i] + "'>info</a>") $("<td>").html("<a target='_blank' href='" + contented.getInfoJSONURL(items[i]) + "'>info</a>"),
])) ]))
} }
$("#surrogate-area").html($table); $("#surrogate-area").html([
$table,
$("<button>").addClass("again").text("Again...").click(function() {
window.location.href = window.location.href;
}),
]);
}); });
</script> </script>

View File

@@ -1,29 +1,48 @@
; ;
var contented = (function ($, currentScriptPath) {
//
var contented = (function() {
"use strict"; "use strict";
var baseURL = currentScriptPath.replace('sdk.js', ''); // @ref https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
var formatBytes = function(bytes) { var binStr = atob( this.toDataURL(type, quality).split(',')[1] ),
if (bytes < 1024) { len = binStr.length,
return bytes + " B"; arr = new Uint8Array(len);
} else if (bytes < (1024*1024)) {
return (bytes / 1024).toFixed(1) + " KiB"; for (var i = 0; i < len; i++ ) {
} else if (bytes < (1024*1024*1024)) { arr[i] = binStr.charCodeAt(i);
return (bytes / (1024*1024)).toFixed(1) + " MiB";
} else {
return (bytes / (1024*1024*1024)).toFixed(1) + " GiB";
} }
callback( new Blob( [arr], {type: type || 'image/png'} ) );
}
});
}
var getCurrentScriptPath = function () {
// Determine current script path
// @ref https://stackoverflow.com/a/26023176
var scripts = document.querySelectorAll('script[src]');
var currentScript = scripts[scripts.length - 1].src;
var currentScriptChunks = currentScript.split('/');
var currentScriptFile = currentScriptChunks[currentScriptChunks.length - 1];
return currentScript.replace(currentScriptFile, '');
}; };
/** var currentScriptPath = getCurrentScriptPath();
* supportsDrop returns whether drag-and-drop is supported by this browser. var baseURL = currentScriptPath.replace('sdk.js', '');
*
* @return bool return {
*/ "loaded": false,
var supportsDrop = function () {
return ('ondrop' in window && 'FormData' in window && 'FileReader' in window); "baseURL": baseURL,
}
"__preInit": [],
/** /**
* initArea shows the contented upload widget over the top of a target DOM element. * initArea shows the contented upload widget over the top of a target DOM element.
@@ -32,6 +51,82 @@ var contented = (function ($, currentScriptPath) {
* @param Function onUploaded Called with an array of upload IDs * @param Function onUploaded Called with an array of upload IDs
* @param Function onClose Called when the widget is being destroyed * @param Function onClose Called when the widget is being destroyed
*/ */
"init": function(elementSelector, onUploaded, onClose) {
contented.__preInit.push([elementSelector, onUploaded, onClose]);
},
/**
* supportsDrop returns whether drag-and-drop is supported by this browser.
*
* @return bool
*/
"supportsDrop": function() {
return ('ondrop' in window && 'FormData' in window && 'FileReader' in window);
},
"getPreviewURL": function(id) {
return baseURL + "get/" + id; // n.b. there's no better preview URL yet
},
"getDownloadURL": function(id) {
return baseURL + "get/" + id;
},
"getInfoJSONURL": function(id) {
return baseURL + "info/" + id;
}
};
})();
;(function() {
"use strict";
var loadScript = function(url, onLoad) {
var script = document.createElement('script');
script.onload = onLoad;
script.src = url;
document.head.appendChild(script);
};
var loadScripts = function(urls, onLoad) {
// load sequentially
var i = 0;
var loadNext = function() {
if (i === urls.length) {
onLoad();
return;
}
var url = urls[i];
i += 1;
loadScript(url, loadNext);
};
loadNext();
};
var formatBytes = function(bytes) {
var k = 1024, m = (1024*1024), g = (1024*1024*1024);
if (bytes < k) {
return bytes + " B";
} else if (bytes < m) {
return (bytes / k).toFixed(1) + " KiB";
} else if (bytes < g) {
return (bytes / m).toFixed(1) + " MiB";
} else {
return (bytes / g).toFixed(1) + " GiB";
}
};
// @ref https://stackoverflow.com/a/2117523
var guid = function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
var afterScriptsLoaded = function() {
var initArea = function (elementSelector, onUploaded, onClose) { var initArea = function (elementSelector, onUploaded, onClose) {
onUploaded = onUploaded || function () { }; onUploaded = onUploaded || function () { };
onClose = onClose || function () { }; onClose = onClose || function () { };
@@ -44,44 +139,45 @@ var contented = (function ($, currentScriptPath) {
// <input type="hidden" name="MAX_FILE_SIZE" value="` + ret.MaxUploadBytes + `" /> // <input type="hidden" name="MAX_FILE_SIZE" value="` + ret.MaxUploadBytes + `" />
// Create a new div for ourselves on top of the existing area // Create a new div for ourselves on top of the existing area
$.get(baseURL + "about", function (ret) { $.get(contented.baseURL + "about", function (ret) {
var extraText = ""; var extraText = "";
if (ret.MaxUploadBytes > 0) { if (ret.MaxUploadBytes > 0) {
extraText = " (max " + formatBytes(ret.MaxUploadBytes) + ")"; extraText = " (max " + formatBytes(ret.MaxUploadBytes) + ")";
} }
$.get(baseURL + "widget.html", function (widgetHtml) { $.get(contented.baseURL + "widget.html", function (widgetHtml) {
var $f = $("<div>").html(widgetHtml); var $f = $("<div>").html(widgetHtml);
$f.find(".contented-extratext").text(extraText); $f.find(".contented-extratext").text(extraText);
var ourClose = function () { // Tab buttons
$f.remove(); // remove from dom
onClose(); // upstream close
};
$f.find(".contented-close").click(function () {
ourClose();
})
var hasSetupDrawingBoardYet = false;
var setType = function (type) { var setType = function (type) {
$f.find(".contented-upload-type").removeClass("contented-upload-type-active"); $f.find(".contented-upload-type").removeClass("contented-upload-type-active");
$f.find(".contented-upload-type[data-upload-type=" + type + "]").addClass("contented-upload-type-active"); $f.find(".contented-upload-type[data-upload-type=" + type + "]").addClass("contented-upload-type-active");
$f.find(".contented-upload-if").removeClass("contented-active"); $f.find(".contented-upload-if").removeClass("contented-active");
$f.find(".contented-if-" + type).addClass("contented-active"); $f.find(".contented-if-" + type).addClass("contented-active");
if (type == "drag") {
enablePasteHandler();
} else {
disablePasteHandler();
}
if (type == "drawing" && !hasSetupDrawingBoardYet) {
setupDrawingBoard();
hasSetupDrawingBoardYet = true;
}
}; };
$f.find(".contented-upload-type").click(function () { $f.find(".contented-upload-type").click(function () {
setType($(this).attr('data-upload-type')); setType($(this).attr('data-upload-type'));
}); });
if (!supportsDrop()) { // Widget positioning
// switch default
setType('file');
}
//
var $element = $(element); var $element = $(element);
var offset = $element.offset(); var offset = $element.offset();
@@ -98,6 +194,8 @@ var contented = (function ($, currentScriptPath) {
'max-height': $element.height() + "px" 'max-height': $element.height() + "px"
}); });
// Drag and drop support
$f.find('.contented').on('dragover dragenter', function (e) { $f.find('.contented').on('dragover dragenter', function (e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -122,6 +220,8 @@ var contented = (function ($, currentScriptPath) {
handleUploadFrom($(".contented-file-selector")[0].files); handleUploadFrom($(".contented-file-selector")[0].files);
}); });
// Pastebin
$f.find('.contented-paste-upload').on('click', function(e) { $f.find('.contented-paste-upload').on('click', function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -129,7 +229,132 @@ var contented = (function ($, currentScriptPath) {
handleUploadFrom([blob]); handleUploadFrom([blob]);
}); });
// Ctrl+V uploads
var pasteHandler = function(e) {
e.preventDefault();
e.stopPropagation();
var items = (e.clipboardData || e.originalEvent.clipboardData).items;
var items_length = items.length;
var blobs = [];
var handled = 0;
var haveHandled = function() {
handled += 1;
if (handled == items_length) {
if (blobs.length > 0) {
handleUploadFrom( blobs );
} else {
// alert("Pasted 0 files");
}
}
};
for (var i = 0; i < items.length; ++i) {
var item = items[i];
var mimeType = item.type;
if (item.kind === 'file') {
blobs.push(item.getAsFile());
haveHandled();
} else if (item.kind === 'string') {
item.getAsString(function(s) {
blobs.push( new Blob([s], {type : mimeType}) );
haveHandled();
});
} else {
// file|string are the only supported types in
// all browsers at the time of writing
// Ignore future possibilities
haveHandled();
}
}
};
var enablePasteHandler = function() {
document.addEventListener('paste', pasteHandler);
};
var disablePasteHandler = function() {
document.removeEventListener('paste', pasteHandler);
};
// Embed in DOM, load default area
$("body").append($f); $("body").append($f);
if (!contented.supportsDrop()) {
setType('file');
} else {
setType('drag');
}
// Drawing board
var setupDrawingBoard = function() {
$("head").append(
'<link rel="stylesheet" type="text/css" href="' + contented.baseURL + 'drawingboard-0.4.6.min.css">'
);
var db_id = "contented-drawing-area-" + guid();
var $db = $("<div>").attr('id', db_id);
DrawingBoard.Control.ContentedUpload = DrawingBoard.Control.extend({
name: 'upload',
initialize: function() {
var $el = this.$el;
$el.append('<button class="contented-drawingboard-upload">Upload</button>');
$el.on('click', '.contented-drawingboard-upload', $.proxy(function(e) {
e.preventDefault();
e.stopPropagation();
$el.prop('disabled', true);
$el.text('Saving...');
$db.find("canvas")[0].toBlob(function(theBlob) {
handleUploadFrom([ theBlob ]);
});
}, this));
}
});
$db.css({
//'width': $f.find(".contented-content-area").width(),
'height': $f.find(".contented-content-area").height(),
'overflow': 'hidden'
});
$f.find(".contented-drawing-area").append($db);
var db = new DrawingBoard.Board(db_id, {
'controls': [
'Color',
'Size',
'DrawingMode',
'Navigation',
'ContentedUpload'
],
'controlsPosition': 'center',
'enlargeYourContainer': false,
'webStorage': false,
'droppable': false // don't mess with existing drop support
});
};
// Close button
var ourClose = function () {
$f.remove(); // remove from dom
disablePasteHandler();
onClose(); // upstream close
};
$f.find(".contented-close").click(function () {
ourClose();
})
// Progress bar
var setProgressCaption = function(message) { var setProgressCaption = function(message) {
$f.find(".contented-if-progress label").text(message); $f.find(".contented-if-progress label").text(message);
@@ -138,7 +363,9 @@ var contented = (function ($, currentScriptPath) {
$f.find(".contented-progress-element").css('width', (frc * 100) + "%"); $f.find(".contented-progress-element").css('width', (frc * 100) + "%");
}; };
var handleUploadFrom = function (files) { // Common upload handler
var handleUploadFrom = function(files) {
setProgressCaption("Uploading, please wait..."); setProgressCaption("Uploading, please wait...");
setProgressPercentage(0); setProgressPercentage(0);
@@ -155,7 +382,7 @@ var contented = (function ($, currentScriptPath) {
// ajax request // ajax request
$.ajax({ $.ajax({
url: baseURL + "upload", url: contented.baseURL + "upload",
type: "POST", type: "POST",
data: ajaxData, data: ajaxData,
dataType: 'json', // response type dataType: 'json', // response type
@@ -167,7 +394,6 @@ var contented = (function ($, currentScriptPath) {
xhr.upload.addEventListener( xhr.upload.addEventListener(
'progress', 'progress',
function(ev) { function(ev) {
console.log([ev.lengthComputable, ev.loaded, ev.total]);
if (ev.lengthComputable) { if (ev.lengthComputable) {
setProgressCaption("Uploading (" + formatBytes(ev.loaded) + " / " + formatBytes(ev.total) + ")..."); setProgressCaption("Uploading (" + formatBytes(ev.loaded) + " / " + formatBytes(ev.total) + ")...");
setProgressPercentage(ev.total == 0 ? 0 : ev.loaded / ev.total); setProgressPercentage(ev.total == 0 ? 0 : ev.loaded / ev.total);
@@ -178,44 +404,47 @@ var contented = (function ($, currentScriptPath) {
return xhr; return xhr;
}, },
complete: function () { complete: function () {
setProgressCaption("Upload complete.");
setProgressPercentage(1); setProgressPercentage(1);
}, },
success: function (data) { success: function (data) {
setProgressCaption("Upload completed successfully.");
onUploaded(data); onUploaded(data);
ourClose(); ourClose();
}, },
error: function () { error: function () {
setProgressCaption("Upload failed."); setProgressCaption("Upload failed!");
} }
}); });
} }
// .
}); });
}); });
} }
// // Update fields in global variable
return { contented.init = initArea;
'supportsDrop': supportsDrop, contented.loaded = true;
'init': initArea
// Call initArea for all pre-initialised elements
for (var i = 0; i < contented.__preInit.length; ++i) {
initArea(contented.__preInit[i][0], contented.__preInit[i][1], contented.__preInit[i][2]);
}
}; };
})( // Load scripts
jQuery, var needScripts = [];
(function () { if (typeof jQuery === "undefined") {
"use strict"; needScripts.push(contented.baseURL + "jquery-1.12.4.min.js");
}
if (typeof DrawingBoard === "undefined") {
needScripts.push(contented.baseURL + "drawingboard-0.4.6.min.js");
}
// Determine current script path loadScripts(needScripts, afterScriptsLoaded);
// @ref https://stackoverflow.com/a/26023176
var scripts = document.querySelectorAll('script[src]');
var currentScript = scripts[scripts.length - 1].src;
var currentScriptChunks = currentScript.split('/');
var currentScriptFile = currentScriptChunks[currentScriptChunks.length - 1];
return currentScript.replace(currentScriptFile, ''); })()
})()
);

View File

@@ -24,6 +24,7 @@
.contented .contented-upload-type-selector { .contented .contented-upload-type-selector {
display:block; display:block;
margin-bottom: 1em; margin-bottom: 1em;
-webkit-user-select: none;
user-select: none; user-select: none;
} }
.contented .contented-upload-type { .contented .contented-upload-type {
@@ -66,7 +67,7 @@
.contented-upload-if { .contented-upload-if {
display:none; display:none;
} }
.contented-if-paste { .contented-if-paste, .contented-if-drawing {
height:100%; height:100%;
} }
.contented-upload-if.contented-active { .contented-upload-if.contented-active {
@@ -87,12 +88,14 @@
border-radius:8px; border-radius:8px;
background:lightgrey; background:lightgrey;
position:relative; position:relative;
overflow:hidden;
} }
.contented-progress-element { .contented-progress-element {
position:absolute; position:absolute;
background:darkgreen; background:darkgreen;
left:0; left:0;
width:0%; width:0%;
height:100%;
} }
</style> </style>
<div class="contented"> <div class="contented">
@@ -122,11 +125,17 @@
<path fill="#000000" d="M19,20H5V4H7V7H17V4H19M12,2A1,1 0 0,1 13,3A1,1 0 0,1 12,4A1,1 0 0,1 11,3A1,1 0 0,1 12,2M19,2H14.82C14.4,0.84 13.3,0 12,0C10.7,0 9.6,0.84 9.18,2H5A2,2 0 0,0 3,4V20A2,2 0 0,0 5,22H19A2,2 0 0,0 21,20V4A2,2 0 0,0 19,2Z" /> <path fill="#000000" d="M19,20H5V4H7V7H17V4H19M12,2A1,1 0 0,1 13,3A1,1 0 0,1 12,4A1,1 0 0,1 11,3A1,1 0 0,1 12,2M19,2H14.82C14.4,0.84 13.3,0 12,0C10.7,0 9.6,0.84 9.18,2H5A2,2 0 0,0 3,4V20A2,2 0 0,0 5,22H19A2,2 0 0,0 21,20V4A2,2 0 0,0 19,2Z" />
</svg> </svg>
</div> </div>
<div class="contented-upload-type" data-upload-type="drawing" title="Drawing">
<svg viewBox="0 0 24 24">
<path fill="#000000" d="M16.84,2.73C16.45,2.73 16.07,2.88 15.77,3.17L13.65,5.29L18.95,10.6L21.07,8.5C21.67,7.89 21.67,6.94 21.07,6.36L17.9,3.17C17.6,2.88 17.22,2.73 16.84,2.73M12.94,6L4.84,14.11L7.4,14.39L7.58,16.68L9.86,16.85L10.15,19.41L18.25,11.3M4.25,15.04L2.5,21.73L9.2,19.94L8.96,17.78L6.65,17.61L6.47,15.29" />
</svg>
</div>
</div> </div>
<div class="contented-content-area"> <div class="contented-content-area">
<div class="contented-upload-if contented-if-drag contented-active"> <div class="contented-upload-if contented-if-drag contented-active">
<label>Drop files to upload <span class="contented-extratext"></span></label> <label>Drop files or Ctrl-V to upload <span class="contented-extratext"></span></label>
</div> </div>
<div class="contented-upload-if contented-if-file"> <div class="contented-upload-if contented-if-file">
@@ -140,6 +149,10 @@
<button class="contented-paste-upload">Upload &raquo;</button> <button class="contented-paste-upload">Upload &raquo;</button>
</div> </div>
<div class="contented-upload-if contented-if-drawing">
<div class="contented-drawing-area"></div>
</div>
<div class="contented-upload-if contented-if-progress"> <div class="contented-upload-if contented-if-progress">
<label>...</label> <label>...</label>
<div class="contented-progress-bar"><div class="contented-progress-element"></div></div> <div class="contented-progress-bar"><div class="contented-progress-element"></div></div>

File diff suppressed because one or more lines are too long

View File

@@ -18,15 +18,17 @@ import (
func (this *Server) handleUpload(w http.ResponseWriter, r *http.Request) { func (this *Server) handleUpload(w http.ResponseWriter, r *http.Request) {
remoteIP := this.remoteIP(r)
err := r.ParseMultipartForm(this.opts.MaxUploadBytes * 2) err := r.ParseMultipartForm(this.opts.MaxUploadBytes * 2)
if err != nil { if err != nil {
log.Printf("%s Invalid request: %s\n", r.RemoteAddr, err.Error()) log.Printf("%s Invalid request: %s\n", remoteIP, err.Error())
http.Error(w, "Invalid request", 400) http.Error(w, "Invalid request", 400)
return return
} }
if r.MultipartForm == nil || r.MultipartForm.File == nil || len(r.MultipartForm.File["f"]) < 1 { if r.MultipartForm == nil || r.MultipartForm.File == nil || len(r.MultipartForm.File["f"]) < 1 {
log.Printf("%s Invalid request: no multipart content\n", r.RemoteAddr) log.Printf("%s Invalid request: no multipart content\n", remoteIP)
http.Error(w, "Invalid request", 400) http.Error(w, "Invalid request", 400)
return return
} }
@@ -36,14 +38,14 @@ func (this *Server) handleUpload(w http.ResponseWriter, r *http.Request) {
for _, fhs := range r.MultipartForm.File["f"] { for _, fhs := range r.MultipartForm.File["f"] {
f, err := fhs.Open() f, err := fhs.Open()
if err != nil { if err != nil {
log.Printf("%s Internal error: %s\n", r.RemoteAddr, err.Error()) log.Printf("%s Internal error: %s\n", remoteIP, err.Error())
http.Error(w, "Internal error", 500) http.Error(w, "Internal error", 500)
return return
} }
path, err := this.handleUploadFile(f, fhs, remoteIP(r)) path, err := this.handleUploadFile(f, fhs, remoteIP)
if err != nil { if err != nil {
log.Printf("%s Upload failed: %s\n", r.RemoteAddr, err.Error()) log.Printf("%s Upload failed: %s\n", remoteIP, err.Error())
http.Error(w, "Upload failed", 500) http.Error(w, "Upload failed", 500)
} }
@@ -52,7 +54,7 @@ func (this *Server) handleUpload(w http.ResponseWriter, r *http.Request) {
jb, err := json.Marshal(ret) jb, err := json.Marshal(ret)
if err != nil { if err != nil {
log.Printf("%s Internal error: %s\n", r.RemoteAddr, err.Error()) log.Printf("%s Internal error: %s\n", remoteIP, err.Error())
http.Error(w, "Internal error", 500) http.Error(w, "Internal error", 500)
return return
} }