move to separate static content, begin restructuring into sdk-oriented design
This commit is contained in:
parent
bd1e96cf94
commit
7451d39d66
31
Server.go
31
Server.go
@ -13,13 +13,17 @@ import (
|
|||||||
|
|
||||||
var SERVER_HEADER string = `contented/0.1`
|
var SERVER_HEADER string = `contented/0.1`
|
||||||
|
|
||||||
type ServerOptions struct {
|
type ServerPublicProperties struct {
|
||||||
DataDirectory string
|
|
||||||
DBPath string
|
|
||||||
AppTitle string
|
AppTitle string
|
||||||
MaxUploadBytes int64
|
MaxUploadBytes int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServerOptions struct {
|
||||||
|
DataDirectory string
|
||||||
|
DBPath string
|
||||||
|
ServerPublicProperties
|
||||||
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
opts ServerOptions
|
opts ServerOptions
|
||||||
db *bolt.DB
|
db *bolt.DB
|
||||||
@ -90,7 +94,11 @@ func (this *Server) handleInformation(w http.ResponseWriter, fileID string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jb, err := json.Marshal(m)
|
this.serveJsonObject(w, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Server) serveJsonObject(w http.ResponseWriter, o interface{}) {
|
||||||
|
jb, err := json.Marshal(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
log.Println(err.Error())
|
||||||
http.Error(w, "Internal error", 500)
|
http.Error(w, "Internal error", 500)
|
||||||
@ -102,8 +110,12 @@ func (this *Server) handleInformation(w http.ResponseWriter, fileID string) {
|
|||||||
w.Write(jb)
|
w.Write(jb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *Server) handleAbout(w http.ResponseWriter) {
|
||||||
|
this.serveJsonObject(w, this.opts.ServerPublicProperties)
|
||||||
|
}
|
||||||
|
|
||||||
func (this *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (this *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
r.Header.Set(`Server`, SERVER_HEADER)
|
w.Header().Set(`Server`, SERVER_HEADER)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -122,6 +134,9 @@ func (this *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else if r.Method == "GET" && strings.HasPrefix(r.URL.Path, `/info/`) {
|
} else if r.Method == "GET" && strings.HasPrefix(r.URL.Path, `/info/`) {
|
||||||
this.handleInformation(w, r.URL.Path[6:])
|
this.handleInformation(w, r.URL.Path[6:])
|
||||||
|
|
||||||
|
} else if r.Method == "GET" && r.URL.Path == `/about` {
|
||||||
|
this.handleAbout(w)
|
||||||
|
|
||||||
} else if r.Method == "POST" && r.URL.Path == `/upload` {
|
} else if r.Method == "POST" && r.URL.Path == `/upload` {
|
||||||
mp, mph, err := r.FormFile("f")
|
mp, mph, err := r.FormFile("f")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -136,11 +151,9 @@ func (this *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Redirect(w, r, `/view/`+path, http.StatusFound)
|
http.Redirect(w, r, `/view/`+path, http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if r.Method == "GET" && r.URL.Path == `/` {
|
|
||||||
this.handleHomepage(w)
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
http.Error(w, "Not found", 404)
|
// TODO embed into binary
|
||||||
|
http.FileServer(http.Dir(`static`)).ServeHTTP(w, r)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,10 @@ Written in Go
|
|||||||
|
|
||||||
- Upload should redirect to a landing page
|
- Upload should redirect to a landing page
|
||||||
- Sample code to integrate with external systems (webui / ...)
|
- Sample code to integrate with external systems (webui / ...)
|
||||||
|
- Switch to SDK-oriented design
|
||||||
- Pastebin service
|
- Pastebin service
|
||||||
- View upload history / view all uploads
|
- View upload history / view all uploads
|
||||||
- Encrypted at rest (anti- provider snooping)
|
- Encrypted at rest (anti- provider snooping)
|
||||||
- Shorter URLs
|
- Shorter URLs
|
||||||
- Deploy!
|
- Deploy!
|
||||||
|
- Display 'my uploads' (id + metadata history kept in localstorage)
|
||||||
|
@ -20,10 +20,12 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
svr, err := contented.NewServer(&contented.ServerOptions{
|
svr, err := contented.NewServer(&contented.ServerOptions{
|
||||||
DataDirectory: *dataDir,
|
DataDirectory: *dataDir,
|
||||||
DBPath: *dbPath,
|
DBPath: *dbPath,
|
||||||
AppTitle: *appTitle,
|
ServerPublicProperties: contented.ServerPublicProperties{
|
||||||
MaxUploadBytes: int64(*maxUploadMb) * 1024 * 1024,
|
AppTitle: *appTitle,
|
||||||
|
MaxUploadBytes: int64(*maxUploadMb) * 1024 * 1024,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
log.Println(err.Error())
|
||||||
|
143
homepage.go
143
homepage.go
@ -1,143 +0,0 @@
|
|||||||
package contented
|
|
||||||
|
|
||||||
import (
|
|
||||||
"html"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (this *Server) handleHomepage(w http.ResponseWriter) {
|
|
||||||
extraText := ""
|
|
||||||
if this.opts.MaxUploadBytes > 0 {
|
|
||||||
extraText = " (max " + strconv.Itoa(int(this.opts.MaxUploadBytes/(1024*1024))) + " MiB)"
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set(`Content-Type`, `text/html;charset=UTF-8`)
|
|
||||||
w.WriteHeader(200)
|
|
||||||
w.Write([]byte(`<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>` + html.EscapeString(this.opts.AppTitle) + `</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<style type="text/css">
|
|
||||||
html, body {
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
#img {
|
|
||||||
width:64px;
|
|
||||||
height:64px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color:blue;
|
|
||||||
text-decoration:underline;
|
|
||||||
cursor:pointer;
|
|
||||||
}
|
|
||||||
form {
|
|
||||||
text-align: center;
|
|
||||||
border: 8px dashed lightgrey;
|
|
||||||
padding: 12px;
|
|
||||||
margin: 12px;
|
|
||||||
}
|
|
||||||
form .if-supports-drag {
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
form .if-nodrag {
|
|
||||||
display:block;
|
|
||||||
}
|
|
||||||
form.supports-drag .if-supports-drag {
|
|
||||||
display:block;
|
|
||||||
}
|
|
||||||
form.supports-drag .if-nodrag {
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
form.is-dragging {
|
|
||||||
background: lightblue;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<form id="form" method="POST" action="/upload" enctype="multipart/form-data">
|
|
||||||
<input type="hidden" name="MAX_FILE_SIZE" value="` + strconv.Itoa(int(this.opts.MaxUploadBytes)) + `" />
|
|
||||||
|
|
||||||
<svg id="img" viewBox="0 0 24 24">
|
|
||||||
<path fill="#000000" d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" />
|
|
||||||
</svg>
|
|
||||||
<div class="if-supports-drag">
|
|
||||||
<label id="form-label">Drop file(s) to upload ` + extraText + `</label>
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
<a id="classic-uploader">Classic uploader</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="if-nodrag">
|
|
||||||
<input id="form-upload" name="f" type="file" multiple>
|
|
||||||
<input type="submit" value="Upload »">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var onLoad = function() {
|
|
||||||
var form = document.getElementById("form");
|
|
||||||
var input = document.getElementById("form-upload");
|
|
||||||
var label = document.getElementById("form-label");
|
|
||||||
|
|
||||||
var makeCaptionText = function( files ) {
|
|
||||||
return files.length > 1 ? ( input.getAttribute( 'data-multiple-caption' ) || '' ).replace( '{count}', files.length ) : files[ 0 ].name;
|
|
||||||
};
|
|
||||||
|
|
||||||
var refreshCaption = function() {
|
|
||||||
label.textContent = makeCaptionText( input.files );
|
|
||||||
};
|
|
||||||
input.addEventListener('change', refreshCaption);
|
|
||||||
|
|
||||||
//
|
|
||||||
document.getElementById("classic-uploader").addEventListener("click", function() {
|
|
||||||
form.classList.remove("supports-drag");
|
|
||||||
});
|
|
||||||
|
|
||||||
var supportsDrag = ('ondrop' in window && 'FormData' in window && 'FileReader' in window);
|
|
||||||
if (supportsDrag) {
|
|
||||||
|
|
||||||
// Handle CSS
|
|
||||||
|
|
||||||
form.classList.add('supports-drag');
|
|
||||||
|
|
||||||
var handleDragState = function(event_name, classEnabled) {
|
|
||||||
form.addEventListener(event_name, function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
if (classEnabled) {
|
|
||||||
form.classList.add( 'is-dragover' );
|
|
||||||
} else {
|
|
||||||
form.classList.remove( 'is-dragover' );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDragState('dragover', true);
|
|
||||||
handleDragState('dragenter', true);
|
|
||||||
handleDragState('dragleave', false);
|
|
||||||
handleDragState('dragend', false);
|
|
||||||
handleDragState('drop', false);
|
|
||||||
|
|
||||||
// Handle uploads
|
|
||||||
|
|
||||||
form.addEventListener('drop', function(e) {
|
|
||||||
input.files = e.dataTransfer.files; // the files that were dropped
|
|
||||||
refreshCaption();
|
|
||||||
form.submit();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// Entry point
|
|
||||||
onLoad();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`))
|
|
||||||
}
|
|
34
static/index.html
Normal file
34
static/index.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Loading</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style type="text/css">
|
||||||
|
html, body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
#surrogate-area {
|
||||||
|
height:300px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="surrogate-area">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/jquery-1.12.4.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/sdk.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
"use strict";
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
contented_EnableDrop( $("#surrogate-area")[0], "", function(itm) {
|
||||||
|
console.log(itm);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
5
static/jquery-1.12.4.min.js
vendored
Normal file
5
static/jquery-1.12.4.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
124
static/sdk.js
Normal file
124
static/sdk.js
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* contented_SupportsDrop returns whether drag-and-drop is supported by this
|
||||||
|
* browser.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function contented_SupportsDrop() {
|
||||||
|
return ('ondrop' in window && 'FormData' in window && 'FileReader' in window);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* contented_EnableDrop enables drag-and-drop upload on a DOM element.
|
||||||
|
* The class "is-dragover" will be toggled on the target element.
|
||||||
|
*
|
||||||
|
* @param DOMElement element Drop target
|
||||||
|
* @param string baseURL Base URL of the contented server
|
||||||
|
* @param Function onUploaded Called with a property object for every uploaded file
|
||||||
|
*/
|
||||||
|
function contented_EnableDrop(element, baseURL, onUploaded) {
|
||||||
|
|
||||||
|
// <input type="hidden" name="MAX_FILE_SIZE" value="` + ret.MaxUploadBytes + `" />
|
||||||
|
|
||||||
|
// Create a new div for ourselves on top of the existing area
|
||||||
|
$.get(baseURL + "/about", function(ret) {
|
||||||
|
$("title").text( ret.AppTitle );
|
||||||
|
|
||||||
|
var extraText = "";
|
||||||
|
if (ret.MaxUploadBytes > 0) {
|
||||||
|
extraText = " (max " + Math.floor(ret.MaxUploadBytes / (1024*1024)) + " MiB)";
|
||||||
|
}
|
||||||
|
|
||||||
|
$.get(baseURL + "/widget.html", function(widgetHtml) {
|
||||||
|
|
||||||
|
var $f = $("<div>").html(widgetHtml);
|
||||||
|
$f.find(".contented-extratext").text(extraText);
|
||||||
|
|
||||||
|
$f.find(".contented-upload-type").click(function() {
|
||||||
|
$f.find(".contented-upload-type").removeClass("contented-upload-type-active");
|
||||||
|
$(this).addClass("contented-upload-type-active");
|
||||||
|
|
||||||
|
$f.find(".contented-upload-if").removeClass("contented-active");
|
||||||
|
$f.find(".contented-if-" + $(this).attr('data-upload-type')).addClass("contented-active");
|
||||||
|
});
|
||||||
|
|
||||||
|
var $element = $(element);
|
||||||
|
var offset = $element.offset();
|
||||||
|
|
||||||
|
$f.css({
|
||||||
|
'position': 'absolute',
|
||||||
|
'left': offset.left + "px",
|
||||||
|
'top': offset.top + "px",
|
||||||
|
'width': $element.width() + "px",
|
||||||
|
'min-width': $element.width() + "px",
|
||||||
|
'max-width': $element.width() + "px",
|
||||||
|
'height': $element.height() + "px",
|
||||||
|
'min-height': $element.height() + "px",
|
||||||
|
'max-height': $element.height() + "px"
|
||||||
|
});
|
||||||
|
|
||||||
|
$("body").append($f);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var onLoad = function() {
|
||||||
|
var form = document.getElementById("form");
|
||||||
|
var input = document.getElementById("form-upload");
|
||||||
|
var label = document.getElementById("form-label");
|
||||||
|
|
||||||
|
var makeCaptionText = function( files ) {
|
||||||
|
return files.length > 1 ? ( input.getAttribute( 'data-multiple-caption' ) || '' ).replace( '{count}', files.length ) : files[ 0 ].name;
|
||||||
|
};
|
||||||
|
|
||||||
|
var refreshCaption = function() {
|
||||||
|
onStateChange( makeCaptionText( input.files ) );
|
||||||
|
};
|
||||||
|
input.addEventListener('change', refreshCaption);
|
||||||
|
|
||||||
|
//
|
||||||
|
document.getElementById("classic-uploader").addEventListener("click", function() {
|
||||||
|
form.classList.remove("supports-drag");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (contented_SupportsDrop()) {
|
||||||
|
|
||||||
|
// Handle CSS
|
||||||
|
|
||||||
|
form.classList.add('supports-drag');
|
||||||
|
|
||||||
|
var handleDragState = function(event_name, classEnabled) {
|
||||||
|
form.addEventListener(event_name, function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (classEnabled) {
|
||||||
|
form.classList.add( 'is-dragover' );
|
||||||
|
} else {
|
||||||
|
form.classList.remove( 'is-dragover' );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragState('dragover', true);
|
||||||
|
handleDragState('dragenter', true);
|
||||||
|
handleDragState('dragleave', false);
|
||||||
|
handleDragState('dragend', false);
|
||||||
|
handleDragState('drop', false);
|
||||||
|
|
||||||
|
// Handle uploads
|
||||||
|
|
||||||
|
form.addEventListener('drop', function(e) {
|
||||||
|
input.files = e.dataTransfer.files; // the files that were dropped
|
||||||
|
refreshCaption();
|
||||||
|
form.submit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
91
static/widget.html
Normal file
91
static/widget.html
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<style type="text/css">
|
||||||
|
.contented {
|
||||||
|
box-sizing:border-box;
|
||||||
|
text-align: center;
|
||||||
|
border: 8px dashed lightgrey;
|
||||||
|
padding: 12px;
|
||||||
|
background:white; /* not transparent */
|
||||||
|
|
||||||
|
text-overflow:hidden;
|
||||||
|
overflow:auto;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
.contented .contented-upload-type-selector {
|
||||||
|
display:block;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.contented .contented-upload-type {
|
||||||
|
display:inline-block;
|
||||||
|
opacity:0.2;
|
||||||
|
transition:opacity linear 0.1s;
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
.contented .contented-upload-type:hover {
|
||||||
|
opacity:0.5;
|
||||||
|
transition:opacity linear 0s;
|
||||||
|
}
|
||||||
|
.contented .contented-upload-type svg {
|
||||||
|
width:36px;
|
||||||
|
height:36px;
|
||||||
|
}
|
||||||
|
.contented .contented-upload-type.contented-upload-type-active {
|
||||||
|
opacity:1;
|
||||||
|
}
|
||||||
|
.contented.is-dragging {
|
||||||
|
background: lightblue;
|
||||||
|
}
|
||||||
|
.contented-upload-if {
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
.contented-upload-if.contented-active {
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
.contented textarea {
|
||||||
|
position:absolute;
|
||||||
|
top: 60px;
|
||||||
|
bottom: 10px;
|
||||||
|
left: 10px;
|
||||||
|
width: calc(100% - 28px);
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="contented">
|
||||||
|
|
||||||
|
<div class="contented-upload-type-selector">
|
||||||
|
|
||||||
|
<div class="contented-upload-type contented-upload-type-active" data-upload-type="drag" title="Drag and drop">
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="#000000" d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="contented-upload-type" data-upload-type="file" title="Multiple files">
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="#000000" d="M15,7H20.5L15,1.5V7M8,0H16L22,6V18A2,2 0 0,1 20,20H8C6.89,20 6,19.1 6,18V2A2,2 0 0,1 8,0M4,4V22H20V24H4A2,2 0 0,1 2,22V4H4Z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="contented-upload-type" data-upload-type="paste" title="Paste">
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="contented-upload-if contented-if-drag contented-active">
|
||||||
|
<label>Drop files to upload <span class="contented-extratext"></span></label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="contented-upload-if contented-if-file">
|
||||||
|
<label>Select files to upload <span class="contented-extratext"></span></label><br>
|
||||||
|
<input name="f" type="file" multiple>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="contented-upload-if contented-if-paste">
|
||||||
|
<textarea name="paste-content"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
Loading…
Reference in New Issue
Block a user