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`
|
||||
|
||||
type ServerOptions struct {
|
||||
DataDirectory string
|
||||
DBPath string
|
||||
type ServerPublicProperties struct {
|
||||
AppTitle string
|
||||
MaxUploadBytes int64
|
||||
}
|
||||
|
||||
type ServerOptions struct {
|
||||
DataDirectory string
|
||||
DBPath string
|
||||
ServerPublicProperties
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
opts ServerOptions
|
||||
db *bolt.DB
|
||||
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -20,10 +20,12 @@ func main() {
|
||||
flag.Parse()
|
||||
|
||||
svr, err := contented.NewServer(&contented.ServerOptions{
|
||||
DataDirectory: *dataDir,
|
||||
DBPath: *dbPath,
|
||||
AppTitle: *appTitle,
|
||||
MaxUploadBytes: int64(*maxUploadMb) * 1024 * 1024,
|
||||
DataDirectory: *dataDir,
|
||||
DBPath: *dbPath,
|
||||
ServerPublicProperties: contented.ServerPublicProperties{
|
||||
AppTitle: *appTitle,
|
||||
MaxUploadBytes: int64(*maxUploadMb) * 1024 * 1024,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
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