move to separate static content, begin restructuring into sdk-oriented design

This commit is contained in:
mappu 2017-10-07 18:05:58 +13:00
parent bd1e96cf94
commit 7451d39d66
8 changed files with 284 additions and 156 deletions

View File

@ -13,11 +13,15 @@ import (
var SERVER_HEADER string = `contented/0.1`
type ServerPublicProperties struct {
AppTitle string
MaxUploadBytes int64
}
type ServerOptions struct {
DataDirectory string
DBPath string
AppTitle string
MaxUploadBytes int64
ServerPublicProperties
}
type Server struct {
@ -90,7 +94,11 @@ func (this *Server) handleInformation(w http.ResponseWriter, fileID string) {
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 {
log.Println(err.Error())
http.Error(w, "Internal error", 500)
@ -102,8 +110,12 @@ func (this *Server) handleInformation(w http.ResponseWriter, fileID string) {
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) {
r.Header.Set(`Server`, SERVER_HEADER)
w.Header().Set(`Server`, SERVER_HEADER)
if this.opts.MaxUploadBytes > 0 {
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/`) {
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` {
mp, mph, err := r.FormFile("f")
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)
}
} else if r.Method == "GET" && r.URL.Path == `/` {
this.handleHomepage(w)
} else {
http.Error(w, "Not found", 404)
// TODO embed into binary
http.FileServer(http.Dir(`static`)).ServeHTTP(w, r)
}
}

View File

@ -13,8 +13,10 @@ Written in Go
- Upload should redirect to a landing page
- Sample code to integrate with external systems (webui / ...)
- Switch to SDK-oriented design
- Pastebin service
- View upload history / view all uploads
- Encrypted at rest (anti- provider snooping)
- Shorter URLs
- Deploy!
- Display 'my uploads' (id + metadata history kept in localstorage)

View File

@ -22,8 +22,10 @@ func main() {
svr, err := contented.NewServer(&contented.ServerOptions{
DataDirectory: *dataDir,
DBPath: *dbPath,
ServerPublicProperties: contented.ServerPublicProperties{
AppTitle: *appTitle,
MaxUploadBytes: int64(*maxUploadMb) * 1024 * 1024,
},
})
if err != nil {
log.Println(err.Error())

View File

@ -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 &raquo;">
</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
View 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

File diff suppressed because one or more lines are too long

124
static/sdk.js Normal file
View 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
View 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>