Embed resources into templates
@@ -4,8 +4,7 @@ type PixelWebConfig struct {
|
||||
APIURLExternal string `toml:"api_url_external"`
|
||||
APIURLInternal string `toml:"api_url_internal"`
|
||||
SessionCookieDomain string `toml:"session_cookie_domain"`
|
||||
StaticResourceDir string `toml:"static_resource_dir"`
|
||||
TemplateDir string `toml:"template_dir"`
|
||||
ResourceDir string `toml:"resource_dir"`
|
||||
DebugMode bool `toml:"debug_mode"`
|
||||
MaintenanceMode bool `toml:"maintenance_mode"`
|
||||
}
|
||||
@@ -15,8 +14,7 @@ const DefaultConfig = `# Pixeldrain Web UI server configuration
|
||||
api_url_external = "/api" # Used in the web browser
|
||||
api_url_internal = "http://127.0.0.1:8080" # Used for internal API requests to the pixeldrain server, not visible to users
|
||||
session_cookie_domain = ".pixeldrain.com"
|
||||
static_resource_dir = "res/static"
|
||||
template_dir = "res/template"
|
||||
resource_dir = "res"
|
||||
debug_mode = false
|
||||
maintenance_mode = false
|
||||
`
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
res/include/img/icons/bat_logo_color.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
res/include/img/icons/brave_lion.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 295 B |
Before Width: | Height: | Size: 252 B After Width: | Height: | Size: 252 B |
Before Width: | Height: | Size: 751 B After Width: | Height: | Size: 751 B |
BIN
res/include/img/icons/pixeldrain_icon.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 770 B After Width: | Height: | Size: 770 B |
Before Width: | Height: | Size: 523 B After Width: | Height: | Size: 523 B |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 556 B After Width: | Height: | Size: 556 B |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 912 B After Width: | Height: | Size: 912 B |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 966 B After Width: | Height: | Size: 966 B |
Before Width: | Height: | Size: 885 B After Width: | Height: | Size: 885 B |
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 220 KiB |
33
res/include/img/spinner.svg
Normal file
@@ -0,0 +1,33 @@
|
||||
<svg version="1.1"
|
||||
class="svg-loader"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 80 80"
|
||||
xml:space="preserve">
|
||||
<path
|
||||
fill="#D43B11"
|
||||
d="M10,40c0,0,0-0.4,0-1.1c0-0.3,0-0.8,0-1.3c0-0.3,0-0.5,0-0.8c0-0.3,0.1-0.6,0.1-0.9c0.1-0.6,0.1-1.4,0.2-2.1
|
||||
c0.2-0.8,0.3-1.6,0.5-2.5c0.2-0.9,0.6-1.8,0.8-2.8c0.3-1,0.8-1.9,1.2-3c0.5-1,1.1-2,1.7-3.1c0.7-1,1.4-2.1,2.2-3.1
|
||||
c1.6-2.1,3.7-3.9,6-5.6c2.3-1.7,5-3,7.9-4.1c0.7-0.2,1.5-0.4,2.2-0.7c0.7-0.3,1.5-0.3,2.3-0.5c0.8-0.2,1.5-0.3,2.3-0.4l1.2-0.1
|
||||
l0.6-0.1l0.3,0l0.1,0l0.1,0l0,0c0.1,0-0.1,0,0.1,0c1.5,0,2.9-0.1,4.5,0.2c0.8,0.1,1.6,0.1,2.4,0.3c0.8,0.2,1.5,0.3,2.3,0.5
|
||||
c3,0.8,5.9,2,8.5,3.6c2.6,1.6,4.9,3.4,6.8,5.4c1,1,1.8,2.1,2.7,3.1c0.8,1.1,1.5,2.1,2.1,3.2c0.6,1.1,1.2,2.1,1.6,3.1
|
||||
c0.4,1,0.9,2,1.2,3c0.3,1,0.6,1.9,0.8,2.7c0.2,0.9,0.3,1.6,0.5,2.4c0.1,0.4,0.1,0.7,0.2,1c0,0.3,0.1,0.6,0.1,0.9
|
||||
c0.1,0.6,0.1,1,0.1,1.4C74,39.6,74,40,74,40c0.2,2.2-1.5,4.1-3.7,4.3s-4.1-1.5-4.3-3.7c0-0.1,0-0.2,0-0.3l0-0.4c0,0,0-0.3,0-0.9
|
||||
c0-0.3,0-0.7,0-1.1c0-0.2,0-0.5,0-0.7c0-0.2-0.1-0.5-0.1-0.8c-0.1-0.6-0.1-1.2-0.2-1.9c-0.1-0.7-0.3-1.4-0.4-2.2
|
||||
c-0.2-0.8-0.5-1.6-0.7-2.4c-0.3-0.8-0.7-1.7-1.1-2.6c-0.5-0.9-0.9-1.8-1.5-2.7c-0.6-0.9-1.2-1.8-1.9-2.7c-1.4-1.8-3.2-3.4-5.2-4.9
|
||||
c-2-1.5-4.4-2.7-6.9-3.6c-0.6-0.2-1.3-0.4-1.9-0.6c-0.7-0.2-1.3-0.3-1.9-0.4c-1.2-0.3-2.8-0.4-4.2-0.5l-2,0c-0.7,0-1.4,0.1-2.1,0.1
|
||||
c-0.7,0.1-1.4,0.1-2,0.3c-0.7,0.1-1.3,0.3-2,0.4c-2.6,0.7-5.2,1.7-7.5,3.1c-2.2,1.4-4.3,2.9-6,4.7c-0.9,0.8-1.6,1.8-2.4,2.7
|
||||
c-0.7,0.9-1.3,1.9-1.9,2.8c-0.5,1-1,1.9-1.4,2.8c-0.4,0.9-0.8,1.8-1,2.6c-0.3,0.9-0.5,1.6-0.7,2.4c-0.2,0.7-0.3,1.4-0.4,2.1
|
||||
c-0.1,0.3-0.1,0.6-0.2,0.9c0,0.3-0.1,0.6-0.1,0.8c0,0.5-0.1,0.9-0.1,1.3C10,39.6,10,40,10,40z">
|
||||
<animateTransform
|
||||
attributeType="xml"
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 40 40"
|
||||
to="360 40 40"
|
||||
dur="0.6s"
|
||||
repeatCount="indefinite"/>
|
||||
</path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
@@ -182,7 +182,7 @@ var ListNavigator = {
|
||||
btnDownloadList.setAttribute("onClick", "Toolbar.downloadList();");
|
||||
|
||||
var btnDownloadListImg = document.createElement("img");
|
||||
btnDownloadListImg.setAttribute("src", "/res/img/floppy_small.png");
|
||||
btnDownloadListImg.setAttribute("src", "{{template `floppy_small.png`}}");
|
||||
btnDownloadListImg.setAttribute("alt", "Download List");
|
||||
|
||||
var btnDownloadListText = document.createElement("span");
|
||||
@@ -199,7 +199,7 @@ var ListNavigator = {
|
||||
btnShuffle.setAttribute("onClick", "ListNavigator.toggleShuffle();");
|
||||
|
||||
var btnShuffleImg = document.createElement("img");
|
||||
btnShuffleImg.setAttribute("src", "/res/img/shuffle_small.png");
|
||||
btnShuffleImg.setAttribute("src", "{{template `shuffle_small.png`}}");
|
||||
btnShuffleImg.setAttribute("alt", "Shuffle playback order");
|
||||
|
||||
var btnShuffleText = document.createElement("span");
|
385
res/include/script/compiled/home.js
Normal file
@@ -0,0 +1,385 @@
|
||||
var FinishedUpload = /** @class */ (function () {
|
||||
function FinishedUpload() {
|
||||
}
|
||||
return FinishedUpload;
|
||||
}());
|
||||
var uploader = null;
|
||||
var finishedUploads = new Array();
|
||||
var totalUploads = 0;
|
||||
var queueDiv = document.getElementById("uploads_queue");
|
||||
var UploadProgressBar = /** @class */ (function () {
|
||||
function UploadProgressBar(file) {
|
||||
this.file = file;
|
||||
this.name = file.name;
|
||||
this.queueNum = totalUploads;
|
||||
this.uploadDiv = document.createElement("a");
|
||||
totalUploads++;
|
||||
this.uploadDiv.classList.add("file_button");
|
||||
this.uploadDiv.style.opacity = "0";
|
||||
this.uploadDiv.innerText = "Queued\n" + this.file.name;
|
||||
queueDiv.appendChild(this.uploadDiv);
|
||||
// Browsers don't render the transition if the opacity is set and
|
||||
// updated in the same frame. So we have to wait a frame (or more)
|
||||
// before changing the opacity to make sure the transition triggers
|
||||
var d = this.uploadDiv; // `this` stops working after constructor ends
|
||||
window.setTimeout(function () { d.style.opacity = "1"; }, 100);
|
||||
}
|
||||
UploadProgressBar.prototype.onProgress = function (progress) {
|
||||
this.uploadDiv.innerText = "Uploading... " + Math.round(progress * 1000) / 10 + "%\n" + this.file.name;
|
||||
this.uploadDiv.style.background = 'linear-gradient('
|
||||
+ 'to right, '
|
||||
+ 'var(--file_background_color) 0%, '
|
||||
+ 'var(--highlight_color) ' + ((progress * 100)) + '%, '
|
||||
+ 'var(--file_background_color) ' + ((progress * 100) + 1) + '%)';
|
||||
};
|
||||
UploadProgressBar.prototype.onFinished = function (id) {
|
||||
finishedUploads[this.queueNum] = {
|
||||
id: id,
|
||||
name: this.file.name
|
||||
};
|
||||
this.uploadDiv.style.background = 'var(--file_background_color)';
|
||||
this.uploadDiv.href = '/u/' + id;
|
||||
this.uploadDiv.target = "_blank";
|
||||
var fileImg = document.createElement("img");
|
||||
fileImg.src = apiEndpoint + '/file/' + id + '/thumbnail';
|
||||
fileImg.alt = this.file.name;
|
||||
var linkSpan = document.createElement("span");
|
||||
linkSpan.style.color = "var(--highlight_color)";
|
||||
linkSpan.innerText = window.location.hostname + "/u/" + id;
|
||||
this.uploadDiv.innerHTML = ""; // Remove uploading progress
|
||||
this.uploadDiv.appendChild(fileImg);
|
||||
this.uploadDiv.appendChild(document.createTextNode(this.file.name));
|
||||
this.uploadDiv.appendChild(document.createElement("br"));
|
||||
this.uploadDiv.appendChild(linkSpan);
|
||||
};
|
||||
UploadProgressBar.prototype.onFailure = function (error) {
|
||||
this.uploadDiv.innerHTML = ""; // Remove uploading progress
|
||||
this.uploadDiv.style.background = 'var(--danger_color)';
|
||||
this.uploadDiv.appendChild(document.createTextNode(this.file.name));
|
||||
this.uploadDiv.appendChild(document.createElement("br"));
|
||||
this.uploadDiv.appendChild(document.createTextNode("Upload failed after three tries:"));
|
||||
this.uploadDiv.appendChild(document.createElement("br"));
|
||||
this.uploadDiv.appendChild(document.createTextNode(error));
|
||||
};
|
||||
return UploadProgressBar;
|
||||
}());
|
||||
function handleUploads(files) {
|
||||
if (uploader === null) {
|
||||
uploader = new UploadManager();
|
||||
}
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
uploader.uploadFile(new UploadProgressBar(files.item(i)));
|
||||
}
|
||||
}
|
||||
// List creation
|
||||
function createList(title, anonymous) {
|
||||
if (uploader.uploading()) {
|
||||
var cont = confirm("Some files have not finished uploading yet. Creating a list now " +
|
||||
"will exclude those files.\n\nContinue?");
|
||||
if (!cont) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
var postData = {
|
||||
"title": title,
|
||||
"anonymous": anonymous,
|
||||
"files": new Array()
|
||||
};
|
||||
for (var i = 0; i < finishedUploads.length; i++) {
|
||||
postData.files.push({
|
||||
"id": finishedUploads[i].id
|
||||
});
|
||||
}
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", apiEndpoint + "/list");
|
||||
xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) {
|
||||
return;
|
||||
}
|
||||
var resp = JSON.parse(xhr.response);
|
||||
if (xhr.status < 400) {
|
||||
// Request is a success
|
||||
var div = document.createElement("div");
|
||||
div.className = "file_button";
|
||||
div.innerHTML = '<img src="' + apiEndpoint + '/list/' + resp.id + '/thumbnail"/>'
|
||||
+ "List creation finished!<br/>"
|
||||
+ title + "<br/>"
|
||||
+ '<a href="/l/' + resp.id + '" target="_blank">' + window.location.hostname + '/l/' + resp.id + '</a>';
|
||||
document.getElementById("uploads_queue").appendChild(div);
|
||||
window.open('/l/' + resp.id, '_blank');
|
||||
}
|
||||
else {
|
||||
console.log("status: " + xhr.status + " response: " + xhr.response);
|
||||
var div = document.createElement("div");
|
||||
div.className = "file_button";
|
||||
div.innerHTML = "List creation failed<br/>"
|
||||
+ "The server responded with:<br/>"
|
||||
+ resp.message;
|
||||
document.getElementById("uploads_queue").append(div);
|
||||
}
|
||||
};
|
||||
xhr.send(JSON.stringify(postData));
|
||||
}
|
||||
// Form upload handlers
|
||||
// Relay click event to hidden file field
|
||||
document.getElementById("select_file_button").onclick = function () {
|
||||
document.getElementById("file_input_field").click();
|
||||
};
|
||||
document.getElementById("file_input_field").onchange = function (evt) {
|
||||
handleUploads(evt.target.files);
|
||||
// This resets the file input field
|
||||
document.getElementById("file_input_field").nodeValue = "";
|
||||
};
|
||||
/*
|
||||
* Drag 'n Drop upload handlers
|
||||
*/
|
||||
document.ondragover = function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
document.ondragenter = function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
document.addEventListener('drop', function (e) {
|
||||
if (e.dataTransfer && e.dataTransfer.files.length > 0) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleUploads(e.dataTransfer.files);
|
||||
}
|
||||
});
|
||||
function copyText(text) {
|
||||
// Create a textarea to copy the text from
|
||||
var ta = document.createElement("textarea");
|
||||
ta.setAttribute("readonly", "readonly");
|
||||
ta.style.position = "absolute";
|
||||
ta.style.left = "-9999px";
|
||||
ta.value = text; // Put the text in the textarea
|
||||
// Add the textarea to the DOM so it can be seleted by the user
|
||||
document.body.appendChild(ta);
|
||||
ta.select(); // Select the contents of the textarea
|
||||
var success = document.execCommand("copy"); // Copy the selected text
|
||||
document.body.removeChild(ta); // Remove the textarea
|
||||
return success;
|
||||
}
|
||||
// Create list button
|
||||
document.getElementById("btn_create_list").addEventListener("click", function (evt) {
|
||||
var title = prompt("You are creating a list containing " + finishedUploads.length + " files.\n"
|
||||
+ "What do you want to call it?", "My New Album");
|
||||
if (title === null) {
|
||||
return;
|
||||
}
|
||||
createList(title, false);
|
||||
});
|
||||
var btnCopyLinks = document.getElementById("btn_copy_links");
|
||||
btnCopyLinks.addEventListener("click", function () {
|
||||
var text = "";
|
||||
// Add the text to the textarea
|
||||
for (var i = 0; i < finishedUploads.length; i++) {
|
||||
// Example: https://pixeldrain.com/u/abcd1234: Some_file.png
|
||||
text += window.location.protocol + "//" + window.location.hostname + "/u/" + finishedUploads[i].id +
|
||||
" " + finishedUploads[i].name + "\n";
|
||||
}
|
||||
var defaultButtonText = btnCopyLinks.innerHTML;
|
||||
// Copy the selected text
|
||||
if (copyText(text)) {
|
||||
btnCopyLinks.classList.add("button_highlight");
|
||||
btnCopyLinks.innerHTML = "Links copied to clipboard!";
|
||||
// Return to normal
|
||||
setTimeout(function () {
|
||||
btnCopyLinks.innerHTML = defaultButtonText;
|
||||
btnCopyLinks.classList.remove("button_highlight");
|
||||
}, 60000);
|
||||
}
|
||||
else {
|
||||
btnCopyLinks.classList.add("button_red");
|
||||
btnCopyLinks.innerHTML = "Copying links failed";
|
||||
setTimeout(function () {
|
||||
btnCopyLinks.innerHTML = defaultButtonText;
|
||||
btnCopyLinks.classList.remove("button_red");
|
||||
}, 60000);
|
||||
}
|
||||
});
|
||||
var btnCopyBBCode = document.getElementById("btn_copy_bbcode");
|
||||
btnCopyBBCode.addEventListener("click", function () {
|
||||
var text = "";
|
||||
// Add the text to the textarea
|
||||
for (var i = 0; i < finishedUploads.length; i++) {
|
||||
// Example: [url=https://pixeldrain.com/u/abcd1234]Some_file.png[/url]
|
||||
text += "[url=" + window.location.protocol + "//" + window.location.hostname +
|
||||
"/u/" + finishedUploads[i].id + "]" +
|
||||
finishedUploads[i].name + "[/url]\n";
|
||||
}
|
||||
var defaultButtonText = btnCopyBBCode.innerHTML;
|
||||
// Copy the selected text
|
||||
if (copyText(text)) {
|
||||
btnCopyBBCode.classList.add("button_highlight");
|
||||
btnCopyBBCode.innerHTML = "BBCode copied to clipboard!";
|
||||
// Return to normal
|
||||
setTimeout(function () {
|
||||
btnCopyBBCode.innerHTML = defaultButtonText;
|
||||
btnCopyBBCode.classList.remove("button_highlight");
|
||||
}, 60000);
|
||||
}
|
||||
else {
|
||||
btnCopyBBCode.classList.add("button_red");
|
||||
btnCopyBBCode.innerHTML = "Copying links failed";
|
||||
setTimeout(function () {
|
||||
btnCopyBBCode.innerHTML = defaultButtonText;
|
||||
btnCopyBBCode.classList.remove("button_red");
|
||||
}, 60000);
|
||||
}
|
||||
});
|
||||
var Cookie;
|
||||
(function (Cookie) {
|
||||
function read(name) {
|
||||
var result = new RegExp('(?:^|; )' + encodeURIComponent(name) + '=([^;]*)').exec(document.cookie);
|
||||
return result ? result[1] : null;
|
||||
}
|
||||
Cookie.read = read;
|
||||
function write(name, value, days) {
|
||||
if (!days) {
|
||||
days = 365 * 20;
|
||||
}
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
var expires = "; expires=" + date.toUTCString();
|
||||
document.cookie = name + "=" + value + expires + "; path=/";
|
||||
}
|
||||
Cookie.write = write;
|
||||
function remove(name) {
|
||||
write(name, "", -1);
|
||||
}
|
||||
Cookie.remove = remove;
|
||||
})(Cookie || (Cookie = {}));
|
||||
var UploadManager = /** @class */ (function () {
|
||||
function UploadManager() {
|
||||
this.uploadQueue = new Array();
|
||||
this.uploadThreads = new Array();
|
||||
this.maxThreads = 3;
|
||||
}
|
||||
UploadManager.prototype.uploadFile = function (file) {
|
||||
console.debug("Adding upload to queue");
|
||||
this.uploadQueue.push(file);
|
||||
if (this.uploadThreads.length < this.maxThreads) {
|
||||
console.debug("Starting upload thread");
|
||||
var thread_1 = new UploadWorker(this);
|
||||
this.uploadThreads.push(thread_1);
|
||||
setTimeout(function () { thread_1.start(); }, 0); // Start a new upload thread
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < this.uploadThreads.length; i++) {
|
||||
this.uploadThreads[i].start();
|
||||
}
|
||||
}
|
||||
};
|
||||
UploadManager.prototype.uploading = function () {
|
||||
for (var i = 0; i < this.uploadThreads.length; i++) {
|
||||
if (this.uploadThreads[i].isUploading()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
UploadManager.prototype.grabFile = function () {
|
||||
if (this.uploadQueue.length > 0) {
|
||||
return this.uploadQueue.shift();
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
return UploadManager;
|
||||
}());
|
||||
var UploadWorker = /** @class */ (function () {
|
||||
function UploadWorker(manager) {
|
||||
this.tries = 0;
|
||||
this.uploading = false;
|
||||
this.manager = manager;
|
||||
}
|
||||
UploadWorker.prototype.isUploading = function () { return this.uploading; };
|
||||
UploadWorker.prototype.start = function () {
|
||||
if (!this.uploading) {
|
||||
this.newFile();
|
||||
}
|
||||
};
|
||||
UploadWorker.prototype.newFile = function () {
|
||||
var file = this.manager.grabFile();
|
||||
if (file === undefined) { // No more files in the queue. We're finished
|
||||
this.uploading = false;
|
||||
console.debug("No files left in queue");
|
||||
return; // Stop the thread
|
||||
}
|
||||
this.uploading = true;
|
||||
this.tries = 0;
|
||||
this.upload(file);
|
||||
};
|
||||
UploadWorker.prototype.upload = function (file) {
|
||||
console.debug("Starting upload of " + file.name);
|
||||
var that = this; // jquery changes the definiton of "this"
|
||||
var formData = new FormData();
|
||||
formData.append("name", file.name);
|
||||
formData.append('file', file.file);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", apiEndpoint + "/file");
|
||||
xhr.timeout = 21600000; // 6 hours, to account for slow connections
|
||||
// Update progess bar on progress
|
||||
xhr.upload.addEventListener("progress", function (evt) {
|
||||
if (evt.lengthComputable) {
|
||||
file.onProgress(evt.loaded / evt.total);
|
||||
}
|
||||
});
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) {
|
||||
return;
|
||||
}
|
||||
console.log("status: " + xhr.status);
|
||||
if (xhr.status >= 100 && xhr.status < 400) {
|
||||
var resp = JSON.parse(xhr.response);
|
||||
// Request is a success
|
||||
file.onFinished(resp.id);
|
||||
that.setHistoryCookie(resp.id);
|
||||
that.newFile(); // Continue uploading on this thread
|
||||
}
|
||||
else {
|
||||
var value, message;
|
||||
if (xhr.status >= 400) {
|
||||
var resp = JSON.parse(xhr.response);
|
||||
value = resp.value;
|
||||
message = resp.message;
|
||||
}
|
||||
console.log("Upload error. status: " + xhr.status + " response: " + xhr.response);
|
||||
if (that.tries === 3) {
|
||||
file.onFailure(value, message);
|
||||
setTimeout(function () { that.newFile(); }, 2000); // Try to continue
|
||||
return; // Upload failed
|
||||
}
|
||||
// Try again
|
||||
that.tries++;
|
||||
setTimeout(function () { that.upload(file); }, that.tries * 5000);
|
||||
}
|
||||
};
|
||||
xhr.send(formData);
|
||||
};
|
||||
UploadWorker.prototype.setHistoryCookie = function (id) {
|
||||
// Make sure the user is not logged in, for privacy. This keeps the
|
||||
// files uploaded while logged in and anonymously uploaded files
|
||||
// separated
|
||||
if (Cookie.read("pd_auth_key") !== null) {
|
||||
return;
|
||||
}
|
||||
var uc = Cookie.read("pduploads");
|
||||
// First upload in this browser
|
||||
if (uc === null) {
|
||||
Cookie.write("pduploads", id + ".", undefined);
|
||||
return;
|
||||
}
|
||||
if (uc.length > 2000) {
|
||||
// Cookie is becoming too long, drop the oldest two files
|
||||
uc = uc.substring(uc.indexOf(".") + 1).substring(uc.indexOf(".") + 1);
|
||||
}
|
||||
Cookie.write("pduploads", uc + id + ".", undefined);
|
||||
};
|
||||
return UploadWorker;
|
||||
}());
|
@@ -10,44 +10,29 @@
|
||||
/* Fonts */
|
||||
|
||||
@font-face {
|
||||
font-family: 'Ubuntu';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: fallback;
|
||||
text-rendering: optimizeLegibility;
|
||||
src:
|
||||
local('Ubuntu'),
|
||||
local('Ubuntu Regular'),
|
||||
local('Ubuntu, Regular'),
|
||||
local('Ubuntu-Regular'),
|
||||
url(/res/misc/Ubuntu-R.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Lato Thin';
|
||||
font-family: 'default';
|
||||
font-display: fallback;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
src:
|
||||
local('Lato Thin'),
|
||||
local('Lato, Thin'),
|
||||
local('Lato-Thin'),
|
||||
local('Lato Hairline'),
|
||||
local('Lato, Hairline'),
|
||||
local('Lato-Hairline'),
|
||||
url(/res/misc/LatoLatin-Thin.ttf) format('truetype');
|
||||
local('Cantarell'),
|
||||
local('Cantarell Regular'),
|
||||
local('Cantarell, Regular'),
|
||||
local('Cantarell-Regular'),
|
||||
url("/res/misc/Cantarell-Regular.otf") format("opentype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
font-family: 'light';
|
||||
font-display: fallback;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
src:
|
||||
local('Lato Regular'),
|
||||
local('Lato, Regular'),
|
||||
local('Lato-Regular'),
|
||||
url(/res/misc/Lato-Regular.ttf) format('truetype');
|
||||
local('Cantarell Light'),
|
||||
local('Cantarell, Light'),
|
||||
local('Cantarell-Light'),
|
||||
url("/res/misc/Cantarell-Light.otf") format("opentype");
|
||||
}
|
||||
|
||||
/* Page rendering configuration */
|
||||
@@ -58,7 +43,7 @@ body{
|
||||
background-color: #0d0d0d; /* Fallback */
|
||||
background-color: var(--body_color);
|
||||
background-repeat: repeat;
|
||||
font-family: 'Ubuntu';
|
||||
font-family: "default";
|
||||
margin: 0;
|
||||
line-height: 1.5em;
|
||||
color: #bfbfbf; /* Fallback */
|
||||
@@ -174,11 +159,10 @@ body{
|
||||
color: #bfbfbf; /* Fallback */
|
||||
color: var(--text_color);
|
||||
text-align: center;
|
||||
padding: 4px 6px;
|
||||
padding: 6px 6px;
|
||||
margin: 0.3em 15px 0.3em 15px;
|
||||
font-family: "Lato Thin", sans-serif;
|
||||
font-weight: bold;
|
||||
font-size: 1.5em;
|
||||
font-family: "light";
|
||||
font-size: 1.6em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: background-color 0.5s;
|
||||
@@ -197,6 +181,9 @@ body{
|
||||
text-shadow: 0 0 20px #000000;
|
||||
padding: 30px 10px 30px 10px;
|
||||
}
|
||||
body, .checkers {
|
||||
background-image: url("{{bgPattern}}");
|
||||
}
|
||||
|
||||
.highlight_dark,
|
||||
.highlight_middle,
|
||||
@@ -255,12 +242,12 @@ h1, h2, h3, h4, h5, h6 {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
h1{font-size: 2em; font-family: "Lato Thin";}
|
||||
h2{font-size: 1.75em; font-family: "Lato Thin";}
|
||||
h3{font-size: 1.5em; font-family: "Lato Thin";}
|
||||
h4{font-size: 1.25em; font-family: "Lato";}
|
||||
h5{font-size: 1em; font-family: "Lato";}
|
||||
h6{font-size: .75em; font-family: "Lato";}
|
||||
h1{font-size: 2em; font-family: "light"; font-weight: normal;}
|
||||
h2{font-size: 1.75em; font-family: "light"; font-weight: normal;}
|
||||
h3{font-size: 1.5em; font-family: "light"; font-weight: normal;}
|
||||
h4{font-size: 1.25em; font-family: "default"; font-weight: normal;}
|
||||
h5{font-size: 1em; font-family: "default"; font-weight: normal;}
|
||||
h6{font-size: .75em; font-family: "default"; font-weight: normal;}
|
||||
h3, h2{border-bottom: 1px var(--layer_3_color_border) solid;} /* Differentiate it a bit, else it just looks like bold text */
|
||||
|
||||
p, .indent {
|
||||
@@ -325,7 +312,6 @@ pre{
|
||||
margin: 10px !important;
|
||||
border-radius: 5px;
|
||||
font-size: 1.8em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.progress_bar{
|
||||
@@ -416,7 +402,6 @@ select{
|
||||
line-height: 1em;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
font-family: inherit;
|
||||
color: #bfbfbf; /* Fallback */
|
||||
color: var(--input_text_color);
|
||||
outline: 0;
|
||||
@@ -479,7 +464,6 @@ input[type="number"]{
|
||||
padding: 3px 5px;
|
||||
color: var(--input_text_color);
|
||||
height: 26px;
|
||||
font-family: 'Ubuntu', sans-serif;
|
||||
font-size: 1em;
|
||||
vertical-align: middle;
|
||||
outline: 0;
|
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 15 KiB |
BIN
res/static/misc/Cantarell-Light.otf
Normal file
BIN
res/static/misc/Cantarell-Regular.otf
Normal file
BIN
res/static/misc/Cantarell-Regular.ttf
Normal file
@@ -1,4 +0,0 @@
|
||||
All files in this directory are compiled typescript files. You can find the
|
||||
sources in /res/typescript.
|
||||
|
||||
Have fun typing!
|
@@ -89,39 +89,68 @@ function createList(title, anonymous) {
|
||||
"id": finishedUploads[i].id
|
||||
});
|
||||
}
|
||||
$.ajax({
|
||||
url: "/api/list",
|
||||
contentType: "application/json",
|
||||
method: "POST",
|
||||
data: JSON.stringify(postData),
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", apiEndpoint + "/list");
|
||||
xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) {
|
||||
return;
|
||||
}
|
||||
if (xhr.status == 200 || xhr.status == 0) {
|
||||
// Request is a success
|
||||
var resultString = "<div class=\"file_button\">"
|
||||
+ '<img src="' + apiEndpoint + '/list/' + response.id + '/thumbnail"/>'
|
||||
+ '<img src="' + apiEndpoint + '/list/' + xhr.response.id + '/thumbnail"/>'
|
||||
+ "List creation finished!<br/>"
|
||||
+ title + "<br/>"
|
||||
+ "<a href=\"/l/" + response.id + "\" target=\"_blank\">" + window.location.hostname + "/l/" + response.id + "</a>"
|
||||
+ "<a href=\"/l/" + xhr.response.id + "\" target=\"_blank\">" + window.location.hostname + "/l/" + xhr.response.id + "</a>"
|
||||
+ "</div>";
|
||||
$('#uploads_queue').append($(resultString).hide().fadeIn('slow').css("display", ""));
|
||||
$("#uploads_queue").animate({
|
||||
scrollTop: $("#uploads_queue").prop("scrollHeight")
|
||||
}, 1000);
|
||||
window.open('/l/' + response.id, '_blank');
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.log("xhr:");
|
||||
console.log(xhr);
|
||||
console.log("status:");
|
||||
console.log(status);
|
||||
console.log("error:");
|
||||
console.log(error);
|
||||
document.getElementById("uploads_queue").append(resultString);
|
||||
window.open('/l/' + xhr.response.id, '_blank');
|
||||
}
|
||||
else {
|
||||
console.log("status: " + xhr.status + " response: " + xhr.response);
|
||||
var resultString = "<div class=\"file_button\">List creation failed<br/>"
|
||||
+ "The server responded with this: <br/>"
|
||||
+ xhr.responseJSON.message
|
||||
+ xhr.response.message
|
||||
+ "</div>";
|
||||
$('#uploads_queue').append($(resultString).hide().fadeIn('slow').css("display", ""));
|
||||
document.getElementById("uploads_queue").append(resultString);
|
||||
}
|
||||
});
|
||||
};
|
||||
xhr.send(JSON.stringify(postData));
|
||||
// $.ajax({
|
||||
// url: "/api/list",
|
||||
// contentType: "application/json",
|
||||
// method: "POST",
|
||||
// data: JSON.stringify(postData),
|
||||
// dataType: "json",
|
||||
// success: function(response) {
|
||||
// var resultString = "<div class=\"file_button\">"
|
||||
// + '<img src="'+apiEndpoint+'/list/'+response.id+'/thumbnail"/>'
|
||||
// + "List creation finished!<br/>"
|
||||
// + title + "<br/>"
|
||||
// + "<a href=\"/l/" + response.id + "\" target=\"_blank\">"+window.location.hostname+"/l/" + response.id + "</a>"
|
||||
// + "</div>";
|
||||
// $('#uploads_queue').append(
|
||||
// $(resultString).hide().fadeIn('slow').css("display", "")
|
||||
// );
|
||||
// window.open('/l/'+response.id, '_blank');
|
||||
// },
|
||||
// error: function(xhr, status, error) {
|
||||
// console.log("xhr:");
|
||||
// console.log(xhr);
|
||||
// console.log("status:");
|
||||
// console.log(status);
|
||||
// console.log("error:");
|
||||
// console.log(error);
|
||||
// var resultString = "<div class=\"file_button\">List creation failed<br/>"
|
||||
// + "The server responded with this: <br/>"
|
||||
// + xhr.responseJSON.message
|
||||
// + "</div>";
|
||||
// $('#uploads_queue').append(
|
||||
// $(resultString).hide().fadeIn('slow').css("display", "")
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
}
|
||||
// Form upload handlers
|
||||
// Relay click event to hidden file field
|
||||
@@ -319,39 +348,35 @@ var UploadWorker = /** @class */ (function () {
|
||||
};
|
||||
UploadWorker.prototype.upload = function (file) {
|
||||
console.debug("Starting upload of " + file.name);
|
||||
var that = this; // jquery changes the definiton of "this"
|
||||
var formData = new FormData();
|
||||
formData.append("name", file.name);
|
||||
formData.append('file', file.file);
|
||||
var that = this; // jquery changes the definiton of "this"
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: apiEndpoint + "/file",
|
||||
data: formData,
|
||||
timeout: 21600000,
|
||||
cache: false,
|
||||
async: true,
|
||||
crossDomain: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
xhr: function () {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.upload.addEventListener("progress", function (evt) {
|
||||
xhr.open("POST", apiEndpoint + "/file");
|
||||
xhr.timeout = 21600000; // 6 hours, to account for slow connections
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
// Update progess bar on progress
|
||||
xhr.onprogress = function (evt) {
|
||||
if (evt.lengthComputable) {
|
||||
file.onProgress(evt.loaded / evt.total);
|
||||
}
|
||||
}, false);
|
||||
return xhr;
|
||||
},
|
||||
success: function (data) {
|
||||
file.onFinished(data.id);
|
||||
that.setHistoryCookie(data.id);
|
||||
console.log("Done: " + data.id);
|
||||
};
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) {
|
||||
return;
|
||||
}
|
||||
if (xhr.status == 200 || xhr.status == 0) {
|
||||
// Request is a success
|
||||
file.onFinished(xhr.response.id);
|
||||
that.setHistoryCookie(xhr.response.id);
|
||||
console.log("Done: " + xhr.response.id);
|
||||
that.newFile(); // Continue uploading on this thread
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.log("status: " + status + " error: " + error);
|
||||
}
|
||||
else {
|
||||
console.log("status: " + xhr.status + " response: " + xhr.response);
|
||||
if (that.tries === 3) {
|
||||
file.onFailure(status, error);
|
||||
file.onFailure(xhr.response.value, xhr.response.message);
|
||||
setTimeout(function () { that.newFile(); }, 2000); // Try to continue
|
||||
return; // Upload failed
|
||||
}
|
||||
@@ -359,7 +384,45 @@ var UploadWorker = /** @class */ (function () {
|
||||
that.tries++;
|
||||
setTimeout(function () { that.upload(file); }, that.tries * 3000);
|
||||
}
|
||||
});
|
||||
};
|
||||
xhr.send(formData);
|
||||
// $.ajax({
|
||||
// type: 'POST',
|
||||
// url: apiEndpoint+"/file",
|
||||
// data: formData,
|
||||
// timeout: 21600000, // 6 hours, to account for slow connections
|
||||
// cache: false,
|
||||
// async: true,
|
||||
// crossDomain: false,
|
||||
// contentType: false,
|
||||
// processData: false,
|
||||
// xhr: function () {
|
||||
// var xhr = new XMLHttpRequest();
|
||||
// xhr.upload.addEventListener("progress", function (evt) {
|
||||
// if (evt.lengthComputable) {
|
||||
// file.onProgress(evt.loaded / evt.total)
|
||||
// }
|
||||
// }, false);
|
||||
// return xhr;
|
||||
// },
|
||||
// success: function (data) {
|
||||
// file.onFinished(data.id)
|
||||
// that.setHistoryCookie(data.id)
|
||||
// console.log("Done: " + data.id)
|
||||
// that.newFile() // Continue uploading on this thread
|
||||
// },
|
||||
// error: function (xhr, status, error){
|
||||
// console.log("status: "+status+" error: "+error)
|
||||
// if (that.tries === 3) {
|
||||
// file.onFailure(status, error)
|
||||
// setTimeout(function(){that.newFile()}, 2000) // Try to continue
|
||||
// return; // Upload failed
|
||||
// }
|
||||
// // Try again
|
||||
// that.tries++
|
||||
// setTimeout(function(){that.upload(file)}, that.tries*3000)
|
||||
// }
|
||||
// });
|
||||
};
|
||||
UploadWorker.prototype.setHistoryCookie = function (id) {
|
||||
// Make sure the user is not logged in, for privacy. This keeps the
|
||||
|
@@ -68,6 +68,7 @@ class UploadProgressBar implements FileUpload {
|
||||
this.uploadDiv.appendChild(linkSpan)
|
||||
}
|
||||
public onFailure(error: string) {
|
||||
this.uploadDiv.innerHTML = "" // Remove uploading progress
|
||||
this.uploadDiv.style.background = 'var(--danger_color)'
|
||||
this.uploadDiv.appendChild(document.createTextNode(this.file.name))
|
||||
this.uploadDiv.appendChild(document.createElement("br"))
|
||||
@@ -109,43 +110,35 @@ function createList(title: string, anonymous: boolean){
|
||||
});
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/api/list",
|
||||
contentType: "application/json",
|
||||
method: "POST",
|
||||
data: JSON.stringify(postData),
|
||||
dataType: "json",
|
||||
success: function(response) {
|
||||
var resultString = "<div class=\"file_button\">"
|
||||
+ '<img src="'+apiEndpoint+'/list/'+response.id+'/thumbnail"/>'
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open("POST", apiEndpoint+"/list")
|
||||
xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8")
|
||||
xhr.onreadystatechange = function(){
|
||||
if (xhr.readyState !== 4) {return;}
|
||||
var resp = JSON.parse(xhr.response);
|
||||
|
||||
if (xhr.status < 400) {
|
||||
// Request is a success
|
||||
var div = document.createElement("div")
|
||||
div.className = "file_button";
|
||||
div.innerHTML = '<img src="'+apiEndpoint+'/list/'+resp.id+'/thumbnail"/>'
|
||||
+ "List creation finished!<br/>"
|
||||
+ title + "<br/>"
|
||||
+ "<a href=\"/l/" + response.id + "\" target=\"_blank\">"+window.location.hostname+"/l/" + response.id + "</a>"
|
||||
+ "</div>";
|
||||
$('#uploads_queue').append(
|
||||
$(resultString).hide().fadeIn('slow').css("display", "")
|
||||
);
|
||||
$("#uploads_queue").animate({
|
||||
scrollTop: $("#uploads_queue").prop("scrollHeight")
|
||||
}, 1000);
|
||||
window.open('/l/'+response.id, '_blank');
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.log("xhr:");
|
||||
console.log(xhr);
|
||||
console.log("status:");
|
||||
console.log(status);
|
||||
console.log("error:");
|
||||
console.log(error);
|
||||
var resultString = "<div class=\"file_button\">List creation failed<br/>"
|
||||
+ "The server responded with this: <br/>"
|
||||
+ xhr.responseJSON.message
|
||||
+ "</div>";
|
||||
$('#uploads_queue').append(
|
||||
$(resultString).hide().fadeIn('slow').css("display", "")
|
||||
);
|
||||
+ '<a href="/l/'+resp.id+'" target="_blank">'+window.location.hostname+'/l/'+resp.id+'</a>';
|
||||
document.getElementById("uploads_queue").appendChild(div);
|
||||
window.open('/l/'+resp.id, '_blank');
|
||||
} else {
|
||||
console.log("status: "+xhr.status+" response: "+xhr.response)
|
||||
var div = document.createElement("div")
|
||||
div.className = "file_button";
|
||||
div.innerHTML = "List creation failed<br/>"
|
||||
+ "The server responded with:<br/>"
|
||||
+ resp.message;
|
||||
document.getElementById("uploads_queue").append(div);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
xhr.send(JSON.stringify(postData));
|
||||
}
|
||||
|
||||
// Form upload handlers
|
||||
|
@@ -1,11 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outFile": "../../script/compiled/home.js"
|
||||
"outFile": "../../../include/script/compiled/home.js"
|
||||
},
|
||||
"files": [
|
||||
"home.ts",
|
||||
"../lib/cookie.ts",
|
||||
"../lib/jquery.d.ts",
|
||||
"../lib/uploader.ts"
|
||||
]
|
||||
}
|
||||
|
18063
res/static/typescript/lib/jquery.d.ts
vendored
@@ -5,7 +5,7 @@ interface FileUpload {
|
||||
name: string
|
||||
onProgress(progress: number)
|
||||
onFinished(id: string)
|
||||
onFailure(response: JQuery.Ajax.ErrorTextStatus, error: string)
|
||||
onFailure(errorID: string, errorMessage: string)
|
||||
}
|
||||
|
||||
class UploadManager {
|
||||
@@ -84,37 +84,38 @@ class UploadWorker {
|
||||
formData.append("name", file.name)
|
||||
formData.append('file', file.file)
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: apiEndpoint+"/file",
|
||||
data: formData,
|
||||
timeout: 21600000, // 6 hours, to account for slow connections
|
||||
cache: false,
|
||||
async: true,
|
||||
crossDomain: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
xhr: function () {
|
||||
var xhr = new XMLHttpRequest();
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open("POST", apiEndpoint+"/file")
|
||||
xhr.timeout = 21600000 // 6 hours, to account for slow connections
|
||||
|
||||
// Update progess bar on progress
|
||||
xhr.upload.addEventListener("progress", function (evt) {
|
||||
if (evt.lengthComputable) {
|
||||
file.onProgress(evt.loaded / evt.total)
|
||||
}
|
||||
}, false);
|
||||
return xhr;
|
||||
},
|
||||
success: function (data) {
|
||||
file.onFinished(data.id)
|
||||
that.setHistoryCookie(data.id)
|
||||
console.log("Done: " + data.id)
|
||||
});
|
||||
|
||||
xhr.onreadystatechange = function(){
|
||||
if (xhr.readyState !== 4) {return;}
|
||||
console.log("status: "+xhr.status)
|
||||
|
||||
if (xhr.status >= 100 && xhr.status < 400) {
|
||||
var resp = JSON.parse(xhr.response);
|
||||
// Request is a success
|
||||
file.onFinished(resp.id)
|
||||
that.setHistoryCookie(resp.id)
|
||||
that.newFile() // Continue uploading on this thread
|
||||
},
|
||||
error: function (xhr, status, error){
|
||||
console.log("status: "+status+" error: "+error)
|
||||
} else {
|
||||
var value, message
|
||||
if (xhr.status >= 400) {
|
||||
var resp = JSON.parse(xhr.response);
|
||||
value = resp.value
|
||||
message = resp.message
|
||||
}
|
||||
console.log("Upload error. status: "+xhr.status+" response: "+xhr.response)
|
||||
|
||||
if (that.tries === 3) {
|
||||
file.onFailure(status, error)
|
||||
file.onFailure(value, message)
|
||||
|
||||
setTimeout(function(){that.newFile()}, 2000) // Try to continue
|
||||
return; // Upload failed
|
||||
@@ -122,9 +123,11 @@ class UploadWorker {
|
||||
|
||||
// Try again
|
||||
that.tries++
|
||||
setTimeout(function(){that.upload(file)}, that.tries*3000)
|
||||
setTimeout(function(){that.upload(file)}, that.tries*5000)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
xhr.send(formData)
|
||||
}
|
||||
|
||||
private setHistoryCookie(id: string){
|
||||
|
@@ -1 +0,0 @@
|
||||
yo
|
@@ -4,7 +4,6 @@
|
||||
<head>
|
||||
{{template "meta_tags" "Administrator panel"}}
|
||||
{{template "user_style" .}}
|
||||
<script type="text/javascript">var apiEndpoint = '{{.APIEndpoint}}';</script>
|
||||
</head>
|
||||
<body>
|
||||
{{$isAdmin := .PixelAPI.UserIsAdmin}}
|
||||
@@ -24,9 +23,11 @@
|
||||
<canvas id="bandwidth_chart"></canvas>
|
||||
</div>
|
||||
|
||||
<script src="/res/script/Chart.min.js"></script>
|
||||
<script src="/res/script/jquery.js"></script>
|
||||
<script src="/res/misc/chartjs/Chart.min.js"></script>
|
||||
<script>
|
||||
var apiEndpoint = '{{.APIEndpoint}}';
|
||||
|
||||
Chart.defaults.global.defaultFontColor = "#b3b3b3";
|
||||
Chart.defaults.global.defaultFontSize = 15;
|
||||
Chart.defaults.global.defaultFontFamily = "Ubuntu";
|
||||
|
@@ -6,19 +6,17 @@
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
{{template "user_style" .}}
|
||||
<link rel="stylesheet" href="/res/style/viewer.css?v5"/>
|
||||
<link rel="stylesheet" href="/res/style/layout.css?v5"/>
|
||||
<link rel="shortcut icon" href="/res/img/tray32.png"/>
|
||||
<link rel="icon" sizes="180x180" href="/res/img/pixeldrain.png"/>
|
||||
<link rel="icon" sizes="256x256" href="/res/img/pixeldrain_big.png"/>
|
||||
<meta name="theme-color" content="#75AD38"/>
|
||||
|
||||
{{.OGData}}
|
||||
<style>
|
||||
{{template `viewer.css`}}
|
||||
{{template `layout.css`}}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
var apiEndpoint = '{{.APIEndpoint}}';
|
||||
var captchaKey = '{{.Other.CaptchaKey}}';
|
||||
</script>
|
||||
{{.OGData}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -26,7 +24,7 @@
|
||||
<div id="file_viewer_headerbar" class="highlight_1 file_viewer_headerbar">
|
||||
<button id="button_toggle_toolbar" class="button_toggle_toolbar" onClick="Toolbar.toggle();">☰</button>
|
||||
<a href="/" id="button_home" class="button button_home">
|
||||
<img src="/res/img/pixeldrain_transparent.png"
|
||||
<img src="{{template `pixeldrain_icon.png`}}"
|
||||
alt="Back to the Home page"
|
||||
style="height: 1.5em; margin: -0.2em; margin-right: 0.2em;"/>
|
||||
</a>
|
||||
@@ -49,19 +47,19 @@
|
||||
<div id="stat_downloads" style="text-align: center;">N/A</div>
|
||||
|
||||
<button id="btnDownload" class="toolbar_button button_full_width" onClick="Toolbar.download();">
|
||||
<img src="/res/img/floppy_small.png" alt="Download this file"/>
|
||||
<img src="{{template `floppy_small.png`}}" alt="Download this file"/>
|
||||
<span>Download</span>
|
||||
</button>
|
||||
<button id="btnCopy" class="toolbar_button button_full_width" onClick="Toolbar.copyUrl();">
|
||||
<img src="/res/img/clipboard_small.png" alt="Copy file URL to clipboard"/>
|
||||
<img src="{{template `clipboard_small.png`}}" alt="Copy file URL to clipboard"/>
|
||||
<span><u>C</u>opy Link</span>
|
||||
</button>
|
||||
<button id="btnShare" class="toolbar_button button_full_width" onClick="Sharebar.toggle();">
|
||||
<img src="/res/img/share_small.png" alt="Share this file on social media"/>
|
||||
<img src="{{template `share_small.png`}}" alt="Share this file on social media"/>
|
||||
<span>Share</span>
|
||||
</button>
|
||||
<button id="btnDetails" class="toolbar_button button_full_width" onClick="DetailsWindow.toggle();">
|
||||
<img src="/res/img/info_small.png" alt="Help"/>
|
||||
<img src="{{template `info_small.png`}}" alt="Help"/>
|
||||
<span>Deta<u>i</u>ls</span>
|
||||
</button>
|
||||
{{template "advertisement" .}}
|
||||
@@ -75,33 +73,34 @@
|
||||
<div id="sharebar" class="file_viewer_sharebar">
|
||||
Share on:<br/>
|
||||
<button class="sharebar-button button_full_width" onClick="window.open('mailto:please@set.address?subject=File%20on%20PixelDrain&body=' + window.location.href);">
|
||||
<img src="/res/img/social_email.png" alt="Share on E-Mail" style="width:40px; height: 40px;"/>
|
||||
<img src="{{template `social_email.png`}}" alt="Share on E-Mail" style="width:40px; height: 40px;"/>
|
||||
<br/>E-Mail
|
||||
</button>
|
||||
<button class="sharebar-button button_full_width" onclick="window.open('https://www.reddit.com/submit?url=' + window.location.href);">
|
||||
<img src="/res/img/social_reddit.png" alt="Share on Reddit" style="width:40px; height: 40px;"/>
|
||||
<img src="{{template `social_reddit.png`}}" alt="Share on Reddit" style="width:40px; height: 40px;"/>
|
||||
<br/>Reddit
|
||||
</button>
|
||||
<button class="sharebar-button button_full_width" onClick="window.open('https://twitter.com/share?url=' + window.location.href);">
|
||||
<img src="/res/img/social_twitter.png" alt="Share on Twitter" style="width:40px; height: 40px;"/>
|
||||
<img src="{{template `social_twitter.png`}}" alt="Share on Twitter" style="width:40px; height: 40px;"/>
|
||||
<br/>Twitter
|
||||
</button>
|
||||
<button class="sharebar-button button_full_width" onClick="window.open('http://www.facebook.com/sharer.php?u=' + window.location.href);">
|
||||
<img src="/res/img/social_facebook.png" alt="Share on Facebook" style="width:40px; height: 40px;"/>
|
||||
<img src="{{template `social_facebook.png`}}" alt="Share on Facebook" style="width:40px; height: 40px;"/>
|
||||
<br/>Facebook
|
||||
</button>
|
||||
<button class="sharebar-button button_full_width" onClick="window.open('http://www.tumblr.com/share/link?url=' + window.location.href);">
|
||||
<img src="/res/img/social_tumblr.png" alt="Share on Tumblr" style="width:40px; height: 40px;"/>
|
||||
<img src="{{template `social_tumblr.png`}}" alt="Share on Tumblr" style="width:40px; height: 40px;"/>
|
||||
<br/>Tumblr
|
||||
</button>
|
||||
<button class="sharebar-button button_full_width" onClick="window.open('https://voat.co/submit?linkpost=true&url=' + window.location.href);">
|
||||
<img src="/res/img/social_voat.png" alt="Share on Voat" style="width:40px; height: 40px;"/>
|
||||
<img src="{{template `social_voat.png`}}" alt="Share on Voat" style="width:40px; height: 40px;"/>
|
||||
<br/>Voat
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="filepreview" class="file_viewer_file_preview">
|
||||
<img src="/res/img/misc/loadthink.gif" style="margin-top: 20%; width: 200px; height: 200px;" />
|
||||
<!-- <img src="{{template `loadthink.gif`}}" style="margin-top: 20%; width: 200px; height: 200px;" /> -->
|
||||
<div class="image" style="margin-top: 20%; width: 100px; height: 100px;">{{template "spinner.svg"}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -162,12 +161,16 @@
|
||||
<div id="captcha_popup_captcha" style="text-align: center;"></div>
|
||||
</div>
|
||||
|
||||
<script src="/res/misc/chartjs/Chart.min.js"></script>
|
||||
<script src="/res/script/Chart.min.js"></script>
|
||||
<script src="/res/script/jquery.js"></script>
|
||||
<script src="/res/script/Toolbar.js?v7"></script>
|
||||
<script src="/res/script/Viewer.js?v7"></script>
|
||||
<script src="/res/script/ListNavigator.js?v7"></script>
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
var apiEndpoint = '{{.APIEndpoint}}';
|
||||
var captchaKey = '{{.Other.CaptchaKey}}';
|
||||
|
||||
{{template `Toolbar.js`}}
|
||||
{{template `Viewer.js`}}
|
||||
{{template `ListNavigator.js`}}
|
||||
|
||||
// This info gets filled in on the server side to prevent having to make an API call right after the page loads.
|
||||
// Just to slice another few milliseconds from the load time :)
|
||||
window.addEventListener("load", function(){
|
||||
|
@@ -4,14 +4,14 @@
|
||||
{{if ne (isBrave .UserAgent) true}}
|
||||
Use the Brave web browser for a faster and safer web!<br/>
|
||||
<a href="https://brave.com/pix009" id="btnBrave" class="button toolbar_button button_full_width button_highlight">
|
||||
<img src="/res/img/brave_lion.png" alt="Brave lion"/>
|
||||
<img src="{{template `brave_lion.png`}}" alt="Brave lion"/>
|
||||
<span>Get Brave</span>
|
||||
</a>
|
||||
<br/>
|
||||
or <a href="https://medium.com/pixeldrain/advertising-on-pixeldrain-a-more-honest-approach-d5e00e3f0c29">learn why pixeldrain supports Brave</a>
|
||||
{{else}}
|
||||
Thank you for using Brave! Please consider supporting pixeldrain with a tip
|
||||
<img src="/res/img/bat_logo_color.png" style="height: 1em; width: 1em;" />
|
||||
<img src="{{template `bat_logo_color.png`}}" style="height: 1em; width: 1em;" />
|
||||
{{end}}
|
||||
</div>
|
||||
<br/>
|
||||
|
@@ -29,7 +29,7 @@
|
||||
{{if eq $field.Type "textarea"}}
|
||||
<td colspan="2">
|
||||
{{$field.Label}}<br/>
|
||||
<textarea id="input_{{$field.Name}}" name="{{$field.Name}}" class="form_input" style="width: 100%; height: 5em; resize: vertical;">{{$field.DefaultValue}}</textarea>
|
||||
<textarea id="input_{{$field.Name}}" name="{{$field.Name}}" class="form_input" style="width: 100%; height: 10em; resize: vertical;">{{$field.DefaultValue}}</textarea>
|
||||
</td>
|
||||
{{else}}
|
||||
<td>{{$field.Label}}</td>
|
||||
|
@@ -2,7 +2,6 @@
|
||||
<title>{{.}} ~ PixelDrain</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
|
||||
<link rel="stylesheet" href="/res/style/layout.css?v4" />
|
||||
<link rel="shortcut icon" href="/res/img/tray32.png" />
|
||||
<meta name="theme-color" content="#75AD38" />
|
||||
<link rel="icon" sizes="180x180" href="/res/img/pixeldrain.png" />
|
||||
|
@@ -1,10 +1,6 @@
|
||||
{{define "user_style"}}
|
||||
<style>
|
||||
{{.UserStyle}}
|
||||
|
||||
body,
|
||||
.checkers {
|
||||
background-image: url("/res/img/{{bgPattern}}");
|
||||
}
|
||||
{{template "layout.css"}}
|
||||
</style>
|
||||
{{end}}
|
||||
|
@@ -5,8 +5,6 @@
|
||||
{{template "user_style" .}}
|
||||
<script src="res/script/jquery.js"></script>
|
||||
<script src="res/script/jquery-cookie.js"></script>
|
||||
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css"/>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
|
||||
<script type="text/javascript">var apiEndpoint = '{{.APIEndpoint}}';</script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -23,7 +21,7 @@
|
||||
<div id="uploadedFiles" class="highlight_dark"></div>
|
||||
</div></div>
|
||||
{{template "page_bottom" .}}
|
||||
<script src="/res/script/history.js"></script>
|
||||
<script>{{template `history.js`}}</script>
|
||||
{{template "analytics"}}
|
||||
</body>
|
||||
</html>{{end}}
|
||||
|
@@ -4,7 +4,6 @@
|
||||
<head>
|
||||
{{template "meta_tags" "Free file sharing service"}}
|
||||
{{template "user_style" .}}
|
||||
<script type="text/javascript">var apiEndpoint = '{{.APIEndpoint}}';</script>
|
||||
</head>
|
||||
<body>
|
||||
{{template "page_top" .}}
|
||||
@@ -65,9 +64,12 @@
|
||||
|
||||
{{template "page_bottom"}}
|
||||
|
||||
<script src="/res/script/jquery-2.1.4.min.js"></script>
|
||||
<script type="text/javascript">var API_URL = "/api";</script>
|
||||
<script src="/res/script/compiled/home.js"></script>
|
||||
<script type="text/javascript">
|
||||
var apiEndpoint = '{{.APIEndpoint}}';
|
||||
var API_URL = "/api";
|
||||
{{template "home.js"}}
|
||||
</script>
|
||||
|
||||
{{template "analytics"}}
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -4,7 +4,6 @@
|
||||
<head>
|
||||
{{template "meta_tags" "Text Upload"}}
|
||||
{{template "user_style" .}}
|
||||
<script src="/res/script/jquery-2.1.4.min.js"></script>
|
||||
|
||||
<style>
|
||||
#toolbar {
|
||||
@@ -46,11 +45,11 @@
|
||||
<body>
|
||||
<div id="toolbar">
|
||||
<button class="toolbar_button button_full_width" onClick="uploadText();">
|
||||
<img src="/res/img/upload_small.png" alt="Start Upload"/>
|
||||
<img src="{{template `upload_small.png`}}" alt="Start Upload"/>
|
||||
<span>Upload</span>
|
||||
</button>
|
||||
<a href="/" class="button toolbar_button button_full_width">
|
||||
<img src="/res/img/pixeldrain_transparent.png" alt="Back to the Home page"/>
|
||||
<img src="{{template `pixeldrain_icon.png`}}" alt="Back to the Home page"/>
|
||||
<span>Home</span>
|
||||
</a>
|
||||
<br/><br/>
|
||||
@@ -60,7 +59,10 @@
|
||||
<div class="textarea_container">
|
||||
<textarea id="textarea" class="textarea" placeholder="Your text here..." autofocus="autofocus"></textarea>
|
||||
</div>
|
||||
<script src="/res/script/compiled/textupload.js"></script>
|
||||
|
||||
<script src="/res/script/jquery.js"></script>
|
||||
<script>{{template "textupload.js"}}</script>
|
||||
|
||||
{{template "analytics"}}
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -2,16 +2,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{{template "meta_tags" "Widget showcase"}}
|
||||
{{template "meta_tags" "Free file sharing service"}}
|
||||
{{template "user_style" .}}
|
||||
<script type="text/javascript">var apiEndpoint = '{{.APIEndpoint}}';</script>
|
||||
</head>
|
||||
<body>
|
||||
{{template "page_top" .}}
|
||||
<img id="header_image" class="header_image" src="/res/img/header_neuropol.png" alt="Header image"/>
|
||||
<br/>
|
||||
<div id="body" class="body">
|
||||
{{template "menu" .}}
|
||||
|
||||
<div class="page_content"><div class="limit_width">
|
||||
<h1>Widget showcase</h1>
|
||||
<h2>Size 2 header</h2>
|
||||
<h3>Size 3 header</h3>
|
||||
@@ -23,10 +21,6 @@
|
||||
<div class="highlight_middle">Middle highlight</div>
|
||||
<div class="highlight_dark">Dark highlight</div>
|
||||
<br/>
|
||||
<div class="highlight_light">Light highlight with borders</div>
|
||||
<div class="highlight_middle">Middle highlight with borders</div>
|
||||
<div class="highlight_dark ">Dark highlight with borders</div>
|
||||
<br/>
|
||||
Link <a href="#">A link to someplace</a>.
|
||||
<hr/>
|
||||
Buttons <button>Regular ol' button!</button>
|
||||
@@ -67,8 +61,8 @@
|
||||
Color <input type="color" name="favcolor" value="#ff0000">
|
||||
|
||||
<br/>
|
||||
{{template "footer"}}
|
||||
</div>
|
||||
</div></div>
|
||||
{{template "page_bottom"}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
|
@@ -6,21 +6,20 @@ import (
|
||||
"net/http"
|
||||
|
||||
"fornaxian.com/pixeldrain-web/pixelapi"
|
||||
"fornaxian.com/pixeldrain-web/webcontroller/forms"
|
||||
"github.com/Fornaxian/log"
|
||||
)
|
||||
|
||||
func (wc *WebController) adminGlobalsForm(td *TemplateData, r *http.Request) (f forms.Form) {
|
||||
func (wc *WebController) adminGlobalsForm(td *TemplateData, r *http.Request) (f Form) {
|
||||
if isAdmin, err := td.PixelAPI.UserIsAdmin(); err != nil {
|
||||
td.Title = err.Error()
|
||||
return forms.Form{Title: td.Title}
|
||||
return Form{Title: td.Title}
|
||||
} else if !isAdmin.IsAdmin {
|
||||
td.Title = ";)"
|
||||
return forms.Form{Title: td.Title}
|
||||
return Form{Title: td.Title}
|
||||
}
|
||||
|
||||
td.Title = "Pixeldrain global configuration"
|
||||
f = forms.Form{
|
||||
f = Form{
|
||||
Name: "admin_globals",
|
||||
Title: td.Title,
|
||||
PreFormHTML: template.HTML("<p>Careful! The slightest typing error could bring the whole website down</p>"),
|
||||
@@ -35,16 +34,17 @@ func (wc *WebController) adminGlobalsForm(td *TemplateData, r *http.Request) (f
|
||||
}
|
||||
var globalsMap = make(map[string]string)
|
||||
for _, v := range globals.Globals {
|
||||
f.Fields = append(f.Fields, forms.Field{
|
||||
f.Fields = append(f.Fields, Field{
|
||||
Name: v.Key,
|
||||
DefaultValue: v.Value,
|
||||
Label: v.Key,
|
||||
Type: func() forms.FieldType {
|
||||
Type: func() FieldType {
|
||||
switch v.Key {
|
||||
case
|
||||
"email_address_change_body",
|
||||
"email_password_reset_body":
|
||||
return forms.FieldTypeTextarea
|
||||
"email_password_reset_body",
|
||||
"email_register_user_body":
|
||||
return FieldTypeTextarea
|
||||
case
|
||||
"api_ratelimit_limit",
|
||||
"api_ratelimit_rate",
|
||||
@@ -52,9 +52,9 @@ func (wc *WebController) adminGlobalsForm(td *TemplateData, r *http.Request) (f
|
||||
"file_inactive_expiry_days",
|
||||
"max_file_size",
|
||||
"pixelstore_min_redundancy":
|
||||
return forms.FieldTypeNumber
|
||||
return FieldTypeNumber
|
||||
default:
|
||||
return forms.FieldTypeText
|
||||
return FieldTypeText
|
||||
}
|
||||
}(),
|
||||
})
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package forms
|
||||
package webcontroller
|
||||
|
||||
import (
|
||||
"fmt"
|
@@ -1,77 +0,0 @@
|
||||
package webcontroller
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"fornaxian.com/pixeldrain-web/pixelapi"
|
||||
"fornaxian.com/pixeldrain-web/webcontroller/forms"
|
||||
"github.com/Fornaxian/log"
|
||||
)
|
||||
|
||||
// TemplateData is a struct that every template expects when being rendered. In
|
||||
// the field Other you can pass your own template-specific variables.
|
||||
type TemplateData struct {
|
||||
Authenticated bool
|
||||
Username string
|
||||
Email string
|
||||
UserAgent string
|
||||
UserStyle template.CSS
|
||||
APIEndpoint template.URL
|
||||
PixelAPI *pixelapi.PixelAPI
|
||||
|
||||
// Only used on file viewer page
|
||||
Title string
|
||||
OGData template.HTML
|
||||
|
||||
Other interface{}
|
||||
URLQuery url.Values
|
||||
|
||||
// Only used for pages containing forms
|
||||
Form forms.Form
|
||||
}
|
||||
|
||||
func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request) *TemplateData {
|
||||
var t = &TemplateData{
|
||||
Authenticated: false,
|
||||
Username: "",
|
||||
UserAgent: r.UserAgent(),
|
||||
UserStyle: userStyle(r),
|
||||
APIEndpoint: template.URL(wc.conf.APIURLExternal),
|
||||
URLQuery: r.URL.Query(),
|
||||
}
|
||||
|
||||
if key, err := wc.getAPIKey(r); err == nil {
|
||||
t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, key)
|
||||
uinf, err := t.PixelAPI.UserInfo()
|
||||
if err != nil {
|
||||
// This session key doesn't work, or the backend is down, user
|
||||
// cannot be authenticated
|
||||
log.Debug("Session check for key '%s' failed: %s", key, err)
|
||||
|
||||
if err.Error() == "authentication_required" || err.Error() == "authentication_failed" {
|
||||
// This key is invalid, delete it
|
||||
log.Debug("Deleting invalid API key")
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "pd_auth_key",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
Expires: time.Unix(0, 0),
|
||||
Domain: wc.conf.SessionCookieDomain,
|
||||
})
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Authentication succeeded
|
||||
t.Authenticated = true
|
||||
t.Username = uinf.Username
|
||||
t.Email = uinf.Email
|
||||
} else {
|
||||
t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, "")
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
@@ -1,147 +0,0 @@
|
||||
package webcontroller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fornaxian.com/pixeldrain-api/util"
|
||||
"github.com/Fornaxian/log"
|
||||
)
|
||||
|
||||
// TemplateManager parses templates and provides utility functions to the
|
||||
// templates' scripting language
|
||||
type TemplateManager struct {
|
||||
templates *template.Template
|
||||
|
||||
// Config
|
||||
templateDir string
|
||||
externalAPIEndpoint string
|
||||
debugModeEnabled bool
|
||||
}
|
||||
|
||||
// NewTemplateManager creates a new template manager
|
||||
func NewTemplateManager(templateDir, externalAPIEndpoint string, debugMode bool) *TemplateManager {
|
||||
return &TemplateManager{
|
||||
templateDir: templateDir,
|
||||
externalAPIEndpoint: externalAPIEndpoint,
|
||||
debugModeEnabled: debugMode,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseTemplates parses the templates in the template directory which is
|
||||
// defined in the config file.
|
||||
// If silent is false it will print an info log message for every template found
|
||||
func (tm *TemplateManager) ParseTemplates(silent bool) {
|
||||
var templatePaths []string
|
||||
|
||||
filepath.Walk(tm.templateDir, func(path string, f os.FileInfo, err error) error {
|
||||
if f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
templatePaths = append(templatePaths, path)
|
||||
if !silent {
|
||||
log.Info("Template found: %s", path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
tpl := template.New("")
|
||||
|
||||
// Import template functions from funcs.go
|
||||
tpl = tpl.Funcs(tm.funcMap())
|
||||
|
||||
var err error
|
||||
tpl, err = tpl.ParseFiles(templatePaths...)
|
||||
if err != nil {
|
||||
log.Error("Template parsing failed: %v", err)
|
||||
}
|
||||
|
||||
// Swap out the old templates with the new templates, to minimize
|
||||
// modifications to the original variable.
|
||||
tm.templates = tpl
|
||||
}
|
||||
|
||||
// Get returns the templates, so they can be used to render views
|
||||
func (tm *TemplateManager) Get() *template.Template {
|
||||
if tm.debugModeEnabled {
|
||||
tm.ParseTemplates(true)
|
||||
}
|
||||
return tm.templates
|
||||
}
|
||||
|
||||
func (tm *TemplateManager) funcMap() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"bgPattern": tm.bgPattern,
|
||||
"isBrave": tm.isBrave,
|
||||
"debugMode": tm.debugMode,
|
||||
"apiUrl": tm.apiURL,
|
||||
"pageNr": tm.pageNr,
|
||||
"add": tm.add,
|
||||
"sub": tm.sub,
|
||||
"formatData": tm.formatData,
|
||||
}
|
||||
}
|
||||
|
||||
func (tm *TemplateManager) bgPattern() string {
|
||||
var now = time.Now()
|
||||
if now.Weekday() == time.Wednesday && now.UnixNano()%10 == 0 {
|
||||
return "checker_wednesday.png"
|
||||
}
|
||||
return fmt.Sprintf("checker%d.png", now.UnixNano()%17)
|
||||
}
|
||||
func (tm *TemplateManager) isBrave(useragent string) bool {
|
||||
return strings.Contains(useragent, "Brave")
|
||||
}
|
||||
func (tm *TemplateManager) debugMode() bool {
|
||||
return tm.debugModeEnabled
|
||||
}
|
||||
func (tm *TemplateManager) apiURL() string {
|
||||
return tm.externalAPIEndpoint
|
||||
}
|
||||
func (tm *TemplateManager) pageNr(s string) (nr int) {
|
||||
// Atoi returns 0 on error, which is fine for page numbers
|
||||
if nr, _ = strconv.Atoi(s); nr < 0 {
|
||||
return 0
|
||||
}
|
||||
return nr
|
||||
}
|
||||
func (tm *TemplateManager) add(a, b interface{}) int {
|
||||
return detectInt(a) + detectInt(b)
|
||||
}
|
||||
func (tm *TemplateManager) sub(a, b interface{}) int {
|
||||
return detectInt(a) - detectInt(b)
|
||||
}
|
||||
func (tm *TemplateManager) formatData(i int) string {
|
||||
return util.FormatData(uint64(i))
|
||||
}
|
||||
|
||||
func detectInt(i interface{}) int {
|
||||
switch v := i.(type) {
|
||||
case int:
|
||||
return int(v)
|
||||
case int8:
|
||||
return int(v)
|
||||
case int16:
|
||||
return int(v)
|
||||
case int32:
|
||||
return int(v)
|
||||
case int64:
|
||||
return int(v)
|
||||
case uint:
|
||||
return int(v)
|
||||
case uint8:
|
||||
return int(v)
|
||||
case uint16:
|
||||
return int(v)
|
||||
case uint32:
|
||||
return int(v)
|
||||
case uint64:
|
||||
return int(v)
|
||||
}
|
||||
panic(fmt.Sprintf("%v is not an int", i))
|
||||
}
|
258
webcontroller/templates.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package webcontroller
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fornaxian.com/pixeldrain-api/util"
|
||||
"fornaxian.com/pixeldrain-web/pixelapi"
|
||||
"github.com/Fornaxian/log"
|
||||
)
|
||||
|
||||
// TemplateData is a struct that every template expects when being rendered. In
|
||||
// the field Other you can pass your own template-specific variables.
|
||||
type TemplateData struct {
|
||||
Authenticated bool
|
||||
Username string
|
||||
Email string
|
||||
UserAgent string
|
||||
Style pixeldrainStyleSheet
|
||||
UserStyle template.CSS
|
||||
APIEndpoint template.URL
|
||||
PixelAPI *pixelapi.PixelAPI
|
||||
|
||||
// Only used on file viewer page
|
||||
Title string
|
||||
OGData template.HTML
|
||||
|
||||
Other interface{}
|
||||
URLQuery url.Values
|
||||
|
||||
// Only used for pages containing forms
|
||||
Form Form
|
||||
}
|
||||
|
||||
func (wc *WebController) newTemplateData(w http.ResponseWriter, r *http.Request) *TemplateData {
|
||||
var t = &TemplateData{
|
||||
Authenticated: false,
|
||||
Username: "",
|
||||
UserAgent: r.UserAgent(),
|
||||
Style: userStyle(r),
|
||||
UserStyle: template.CSS(userStyle(r).String()),
|
||||
APIEndpoint: template.URL(wc.conf.APIURLExternal),
|
||||
URLQuery: r.URL.Query(),
|
||||
}
|
||||
|
||||
if key, err := wc.getAPIKey(r); err == nil {
|
||||
t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, key)
|
||||
uinf, err := t.PixelAPI.UserInfo()
|
||||
if err != nil {
|
||||
// This session key doesn't work, or the backend is down, user
|
||||
// cannot be authenticated
|
||||
log.Debug("Session check for key '%s' failed: %s", key, err)
|
||||
|
||||
if err.Error() == "authentication_required" || err.Error() == "authentication_failed" {
|
||||
// This key is invalid, delete it
|
||||
log.Debug("Deleting invalid API key")
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "pd_auth_key",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
Expires: time.Unix(0, 0),
|
||||
Domain: wc.conf.SessionCookieDomain,
|
||||
})
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Authentication succeeded
|
||||
t.Authenticated = true
|
||||
t.Username = uinf.Username
|
||||
t.Email = uinf.Email
|
||||
} else {
|
||||
t.PixelAPI = pixelapi.New(wc.conf.APIURLInternal, "")
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// TemplateManager parses templates and provides utility functions to the
|
||||
// templates' scripting language
|
||||
type TemplateManager struct {
|
||||
tpl *template.Template
|
||||
|
||||
// Config
|
||||
resourceDir string
|
||||
externalAPIEndpoint string
|
||||
debugModeEnabled bool
|
||||
}
|
||||
|
||||
// NewTemplateManager creates a new template manager
|
||||
func NewTemplateManager(resourceDir, externalAPIEndpoint string, debugMode bool) *TemplateManager {
|
||||
return &TemplateManager{
|
||||
resourceDir: resourceDir,
|
||||
externalAPIEndpoint: externalAPIEndpoint,
|
||||
debugModeEnabled: debugMode,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseTemplates parses the templates in the template directory which is
|
||||
// defined in the config file.
|
||||
// If silent is false it will print an info log message for every template found
|
||||
func (tm *TemplateManager) ParseTemplates(silent bool) {
|
||||
var err error
|
||||
var templatePaths []string
|
||||
tpl := template.New("")
|
||||
|
||||
// Import template functions
|
||||
tpl.Funcs(template.FuncMap{
|
||||
"bgPattern": tm.bgPattern,
|
||||
"isBrave": tm.isBrave,
|
||||
"debugMode": tm.debugMode,
|
||||
"apiUrl": tm.apiURL,
|
||||
"pageNr": tm.pageNr,
|
||||
"add": tm.add,
|
||||
"sub": tm.sub,
|
||||
"formatData": tm.formatData,
|
||||
})
|
||||
|
||||
// Parse dynamic templates
|
||||
if err = filepath.Walk(tm.resourceDir+"/template", func(path string, f os.FileInfo, err error) error {
|
||||
if f == nil || f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
templatePaths = append(templatePaths, path)
|
||||
if !silent {
|
||||
log.Info("Template found: %s", path)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Error("Failed to parse templates: %s", err)
|
||||
}
|
||||
if _, err = tpl.ParseFiles(templatePaths...); err != nil {
|
||||
log.Error("Template parsing failed: %v", err)
|
||||
}
|
||||
|
||||
// Parse static resources
|
||||
var file []byte
|
||||
if err = filepath.Walk(tm.resourceDir+"/include", func(path string, f os.FileInfo, err error) error {
|
||||
if f == nil || f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if file, err = ioutil.ReadFile(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.HasSuffix(path, ".png") {
|
||||
file = []byte("data:image/png;base64," + base64.StdEncoding.EncodeToString(file))
|
||||
} else if strings.HasSuffix(path, ".gif") {
|
||||
file = []byte("data:image/gif;base64," + base64.StdEncoding.EncodeToString(file))
|
||||
}
|
||||
// Wrap the resources in a template definition
|
||||
if _, err = tpl.Parse(
|
||||
`{{define "` + f.Name() + `"}}` + string(file) + `{{end}}`,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !silent {
|
||||
log.Info("Template parsed: %s", path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Error("Failed to parse templates: %s", err)
|
||||
}
|
||||
|
||||
tm.tpl = tpl
|
||||
}
|
||||
|
||||
// Get returns the templates, so they can be used to render views
|
||||
func (tm *TemplateManager) Get() *template.Template {
|
||||
if tm.debugModeEnabled {
|
||||
tm.ParseTemplates(true)
|
||||
}
|
||||
return tm.tpl
|
||||
}
|
||||
|
||||
// Templace functions. These can be called from within the template to execute
|
||||
// more specialized actions
|
||||
|
||||
func (tm *TemplateManager) bgPattern() template.URL {
|
||||
var now = time.Now()
|
||||
var file string
|
||||
if now.Weekday() == time.Wednesday && now.UnixNano()%10 == 0 {
|
||||
file = "checker_wednesday.png"
|
||||
} else {
|
||||
file = fmt.Sprintf("checker%d.png", now.UnixNano()%17)
|
||||
}
|
||||
|
||||
var buf = bytes.Buffer{}
|
||||
if err := tm.tpl.ExecuteTemplate(&buf, file, nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return template.URL(buf.String())
|
||||
}
|
||||
func (tm *TemplateManager) isBrave(useragent string) bool {
|
||||
return strings.Contains(useragent, "Brave")
|
||||
}
|
||||
func (tm *TemplateManager) debugMode() bool {
|
||||
return tm.debugModeEnabled
|
||||
}
|
||||
func (tm *TemplateManager) apiURL() string {
|
||||
return tm.externalAPIEndpoint
|
||||
}
|
||||
func (tm *TemplateManager) pageNr(s string) (nr int) {
|
||||
// Atoi returns 0 on error, which is fine for page numbers
|
||||
if nr, _ = strconv.Atoi(s); nr < 0 {
|
||||
return 0
|
||||
}
|
||||
return nr
|
||||
}
|
||||
func (tm *TemplateManager) add(a, b interface{}) int {
|
||||
return detectInt(a) + detectInt(b)
|
||||
}
|
||||
func (tm *TemplateManager) sub(a, b interface{}) int {
|
||||
return detectInt(a) - detectInt(b)
|
||||
}
|
||||
func (tm *TemplateManager) formatData(i int) string {
|
||||
return util.FormatData(uint64(i))
|
||||
}
|
||||
|
||||
func detectInt(i interface{}) int {
|
||||
switch v := i.(type) {
|
||||
case int:
|
||||
return int(v)
|
||||
case int8:
|
||||
return int(v)
|
||||
case int16:
|
||||
return int(v)
|
||||
case int32:
|
||||
return int(v)
|
||||
case int64:
|
||||
return int(v)
|
||||
case uint:
|
||||
return int(v)
|
||||
case uint8:
|
||||
return int(v)
|
||||
case uint16:
|
||||
return int(v)
|
||||
case uint32:
|
||||
return int(v)
|
||||
case uint64:
|
||||
return int(v)
|
||||
}
|
||||
panic(fmt.Sprintf("%v is not an int", i))
|
||||
}
|
@@ -6,7 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"fornaxian.com/pixeldrain-web/pixelapi"
|
||||
"fornaxian.com/pixeldrain-web/webcontroller/forms"
|
||||
"github.com/Fornaxian/log"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
@@ -26,7 +25,7 @@ func (wc *WebController) serveLogout(
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f forms.Form) {
|
||||
func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f Form) {
|
||||
// This only runs on the first request
|
||||
if wc.captchaSiteKey == "" {
|
||||
capt, err := td.PixelAPI.GetRecaptcha()
|
||||
@@ -47,16 +46,16 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f form
|
||||
|
||||
// Construct the form
|
||||
td.Title = "Register a new Pixeldrain account"
|
||||
f = forms.Form{
|
||||
f = Form{
|
||||
Name: "register",
|
||||
Title: td.Title,
|
||||
Fields: []forms.Field{
|
||||
Fields: []Field{
|
||||
{
|
||||
Name: "username",
|
||||
Label: "Username",
|
||||
Description: "used for logging into your account",
|
||||
Separator: true,
|
||||
Type: forms.FieldTypeUsername,
|
||||
Type: FieldTypeUsername,
|
||||
}, {
|
||||
Name: "e-mail",
|
||||
Label: "E-mail address",
|
||||
@@ -64,11 +63,11 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f form
|
||||
"used for password resets and important account " +
|
||||
"notifications",
|
||||
Separator: true,
|
||||
Type: forms.FieldTypeEmail,
|
||||
Type: FieldTypeEmail,
|
||||
}, {
|
||||
Name: "password1",
|
||||
Label: "Password",
|
||||
Type: forms.FieldTypeNewPassword,
|
||||
Type: FieldTypeNewPassword,
|
||||
}, {
|
||||
Name: "password2",
|
||||
Label: "Password verification",
|
||||
@@ -76,7 +75,7 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f form
|
||||
"can verify that no typing errors were made, which would " +
|
||||
"prevent you from logging into your new account",
|
||||
Separator: true,
|
||||
Type: forms.FieldTypeNewPassword,
|
||||
Type: FieldTypeNewPassword,
|
||||
}, {
|
||||
Name: "recaptcha_response",
|
||||
Label: "Turing test (click the white box)",
|
||||
@@ -84,7 +83,7 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f form
|
||||
"are not an evil robot that is trying to flood the " +
|
||||
"website with fake accounts",
|
||||
Separator: true,
|
||||
Type: forms.FieldTypeCaptcha,
|
||||
Type: FieldTypeCaptcha,
|
||||
CaptchaSiteKey: wc.captchaKey(),
|
||||
},
|
||||
},
|
||||
@@ -131,20 +130,20 @@ func (wc *WebController) registerForm(td *TemplateData, r *http.Request) (f form
|
||||
return f
|
||||
}
|
||||
|
||||
func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f forms.Form) {
|
||||
func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f Form) {
|
||||
td.Title = "Login"
|
||||
f = forms.Form{
|
||||
f = Form{
|
||||
Name: "login",
|
||||
Title: "Log in to your pixeldrain account",
|
||||
Fields: []forms.Field{
|
||||
Fields: []Field{
|
||||
{
|
||||
Name: "username",
|
||||
Label: "Username / e-mail",
|
||||
Type: forms.FieldTypeUsername,
|
||||
Type: FieldTypeUsername,
|
||||
}, {
|
||||
Name: "password",
|
||||
Label: "Password",
|
||||
Type: forms.FieldTypeCurrentPassword,
|
||||
Type: FieldTypeCurrentPassword,
|
||||
},
|
||||
},
|
||||
BackLink: "/",
|
||||
@@ -186,26 +185,26 @@ func (wc *WebController) loginForm(td *TemplateData, r *http.Request) (f forms.F
|
||||
return f
|
||||
}
|
||||
|
||||
func (wc *WebController) passwordResetForm(td *TemplateData, r *http.Request) (f forms.Form) {
|
||||
func (wc *WebController) passwordResetForm(td *TemplateData, r *http.Request) (f Form) {
|
||||
td.Title = "Recover lost password"
|
||||
f = forms.Form{
|
||||
f = Form{
|
||||
Name: "password_reset",
|
||||
Title: td.Title,
|
||||
Fields: []forms.Field{
|
||||
Fields: []Field{
|
||||
{
|
||||
Name: "email",
|
||||
Label: "E-mail address",
|
||||
Description: "we will send a password reset link to this " +
|
||||
"e-mail address",
|
||||
Separator: true,
|
||||
Type: forms.FieldTypeEmail,
|
||||
Type: FieldTypeEmail,
|
||||
}, {
|
||||
Name: "recaptcha_response",
|
||||
Label: "Turing test (click the white box)",
|
||||
Description: "the reCaptcha turing test verifies that you " +
|
||||
"are not an evil robot that is trying hijack accounts",
|
||||
Separator: true,
|
||||
Type: forms.FieldTypeCaptcha,
|
||||
Type: FieldTypeCaptcha,
|
||||
CaptchaSiteKey: wc.captchaKey(),
|
||||
},
|
||||
},
|
||||
@@ -223,7 +222,62 @@ func (wc *WebController) passwordResetForm(td *TemplateData, r *http.Request) (f
|
||||
}
|
||||
} else {
|
||||
f.SubmitSuccess = true
|
||||
f.SubmitMessages = []template.HTML{"Success! E-mail sent"}
|
||||
f.SubmitMessages = []template.HTML{
|
||||
"Success! Check your inbox for instructions to reset your password",
|
||||
}
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (wc *WebController) passwordResetConfirmForm(td *TemplateData, r *http.Request) (f Form) {
|
||||
td.Title = "Reset lost password"
|
||||
f = Form{
|
||||
Name: "password_reset_confirm",
|
||||
Title: td.Title,
|
||||
Fields: []Field{
|
||||
{
|
||||
Name: "password1",
|
||||
Label: "password",
|
||||
Type: FieldTypeNewPassword,
|
||||
}, {
|
||||
Name: "password2",
|
||||
Label: "password again",
|
||||
Description: "you need to enter your password twice so we " +
|
||||
"can verify that no typing errors were made, which would " +
|
||||
"prevent you from logging into your new account",
|
||||
Separator: true,
|
||||
Type: FieldTypeNewPassword,
|
||||
},
|
||||
},
|
||||
SubmitLabel: "Submit",
|
||||
}
|
||||
|
||||
var resetKey = r.FormValue("key")
|
||||
if resetKey == "" {
|
||||
f.SubmitSuccess = false
|
||||
f.SubmitMessages = []template.HTML{"Password reset key required"}
|
||||
return f
|
||||
}
|
||||
|
||||
if f.ReadInput(r) {
|
||||
if f.FieldVal("password1") != f.FieldVal("password2") {
|
||||
f.SubmitMessages = []template.HTML{
|
||||
"Password verification failed. Please enter the same " +
|
||||
"password in both password fields"}
|
||||
return f
|
||||
}
|
||||
|
||||
if err := td.PixelAPI.UserPasswordResetConfirm(resetKey, f.FieldVal("password1")); err != nil {
|
||||
if err.Error() == "not_found" {
|
||||
f.SubmitMessages = []template.HTML{template.HTML("Password reset key not found")}
|
||||
} else {
|
||||
log.Error("%s", err)
|
||||
f.SubmitMessages = []template.HTML{"Internal Server Error"}
|
||||
}
|
||||
} else {
|
||||
f.SubmitSuccess = true
|
||||
f.SubmitMessages = []template.HTML{"Success! You can now log in with your new password"}
|
||||
}
|
||||
}
|
||||
return f
|
||||
|
@@ -5,7 +5,6 @@ import (
|
||||
"net/http"
|
||||
|
||||
"fornaxian.com/pixeldrain-web/pixelapi"
|
||||
"fornaxian.com/pixeldrain-web/webcontroller/forms"
|
||||
"github.com/Fornaxian/log"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
@@ -24,9 +23,9 @@ func (wc *WebController) serveUserSettings(
|
||||
|
||||
td.Title = "Account settings"
|
||||
td.Other = struct {
|
||||
PasswordForm forms.Form
|
||||
EmailForm forms.Form
|
||||
UsernameForm forms.Form
|
||||
PasswordForm Form
|
||||
EmailForm Form
|
||||
UsernameForm Form
|
||||
}{
|
||||
PasswordForm: wc.passwordForm(td, r),
|
||||
EmailForm: wc.emailForm(td, r),
|
||||
@@ -35,26 +34,26 @@ func (wc *WebController) serveUserSettings(
|
||||
wc.templates.Get().ExecuteTemplate(w, "user_settings", td)
|
||||
}
|
||||
|
||||
func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f forms.Form) {
|
||||
f = forms.Form{
|
||||
func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f Form) {
|
||||
f = Form{
|
||||
Name: "password_change",
|
||||
Title: "Change password",
|
||||
Fields: []forms.Field{
|
||||
Fields: []Field{
|
||||
{
|
||||
Name: "old_password",
|
||||
Label: "Old Password",
|
||||
Type: forms.FieldTypeCurrentPassword,
|
||||
Type: FieldTypeCurrentPassword,
|
||||
}, {
|
||||
Name: "new_password1",
|
||||
Label: "New Password",
|
||||
Type: forms.FieldTypeNewPassword,
|
||||
Type: FieldTypeNewPassword,
|
||||
}, {
|
||||
Name: "new_password2",
|
||||
Label: "New Password again",
|
||||
Description: "we need you to repeat your password so you " +
|
||||
"won't be locked out of your account if you make a " +
|
||||
"typing error",
|
||||
Type: forms.FieldTypeNewPassword,
|
||||
Type: FieldTypeNewPassword,
|
||||
},
|
||||
},
|
||||
SubmitLabel: "Submit",
|
||||
@@ -89,11 +88,11 @@ func (wc *WebController) passwordForm(td *TemplateData, r *http.Request) (f form
|
||||
return f
|
||||
}
|
||||
|
||||
func (wc *WebController) emailForm(td *TemplateData, r *http.Request) (f forms.Form) {
|
||||
f = forms.Form{
|
||||
func (wc *WebController) emailForm(td *TemplateData, r *http.Request) (f Form) {
|
||||
f = Form{
|
||||
Name: "email_change",
|
||||
Title: "Change e-mail address",
|
||||
Fields: []forms.Field{
|
||||
Fields: []Field{
|
||||
{
|
||||
Name: "new_email",
|
||||
Label: "New e-mail address",
|
||||
@@ -101,7 +100,7 @@ func (wc *WebController) emailForm(td *TemplateData, r *http.Request) (f forms.F
|
||||
"verify that it's real. The address will be saved once " +
|
||||
"the link in the message is clicked. If the e-mail " +
|
||||
"doesn't arrive right away please check your spam box too",
|
||||
Type: forms.FieldTypeEmail,
|
||||
Type: FieldTypeEmail,
|
||||
},
|
||||
},
|
||||
SubmitLabel: "Submit",
|
||||
@@ -150,11 +149,11 @@ func (wc *WebController) serveEmailConfirm(
|
||||
wc.templates.Get().ExecuteTemplate(w, "email_confirm", td)
|
||||
}
|
||||
|
||||
func (wc *WebController) usernameForm(td *TemplateData, r *http.Request) (f forms.Form) {
|
||||
f = forms.Form{
|
||||
func (wc *WebController) usernameForm(td *TemplateData, r *http.Request) (f Form) {
|
||||
f = Form{
|
||||
Name: "username_change",
|
||||
Title: "Change username",
|
||||
Fields: []forms.Field{
|
||||
Fields: []Field{
|
||||
{
|
||||
Name: "new_username",
|
||||
Label: "New username",
|
||||
@@ -162,7 +161,7 @@ func (wc *WebController) usernameForm(td *TemplateData, r *http.Request) (f form
|
||||
"used to log in. If you forget your username you can " +
|
||||
"still log in using your e-mail address if you have one " +
|
||||
"configured",
|
||||
Type: forms.FieldTypeUsername,
|
||||
Type: FieldTypeUsername,
|
||||
},
|
||||
},
|
||||
SubmitLabel: "Submit",
|
||||
|
@@ -2,11 +2,10 @@ package webcontroller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func userStyle(r *http.Request) (style template.CSS) {
|
||||
func userStyle(r *http.Request) (style pixeldrainStyleSheet) {
|
||||
var selectedStyle pixeldrainStyleSheet
|
||||
|
||||
if cookie, err := r.Cookie("style"); err != nil {
|
||||
@@ -34,7 +33,40 @@ func userStyle(r *http.Request) (style template.CSS) {
|
||||
}
|
||||
}
|
||||
|
||||
return template.CSS(fmt.Sprintf(
|
||||
return selectedStyle
|
||||
}
|
||||
|
||||
type pixeldrainStyleSheet struct {
|
||||
TextColor hsl
|
||||
InputColor hsl // Buttons, text fields
|
||||
InputTextColor hsl
|
||||
HighlightColor hsl // Links, highlighted buttons, list navigation
|
||||
HighlightTextColor hsl // Text on buttons
|
||||
DangerColor hsl
|
||||
FileBackgroundColor hsl
|
||||
ScrollbarForegroundColor hsl
|
||||
ScrollbarHoverColor hsl
|
||||
ScrollbarBackgroundColor hsl
|
||||
|
||||
BackgroundColor hsl
|
||||
BodyColor hsl
|
||||
|
||||
Layer1Color hsl // Deepest and darkest layer
|
||||
Layer1Shadow int // Deep layers have little shadow
|
||||
Layer2Color hsl
|
||||
Layer2Shadow int
|
||||
Layer3Color hsl
|
||||
Layer3Shadow int
|
||||
Layer4Color hsl // Highest and brightest layer
|
||||
Layer4Shadow int // High layers have lots of shadow
|
||||
|
||||
ShadowColor hsl
|
||||
ShadowSpread int // Pixels
|
||||
ShadowIntensity int // Pixels
|
||||
}
|
||||
|
||||
func (s pixeldrainStyleSheet) String() string {
|
||||
return fmt.Sprintf(
|
||||
`:root {
|
||||
--text_color: %s;
|
||||
--input_color: %s;
|
||||
@@ -66,63 +98,35 @@ func userStyle(r *http.Request) (style template.CSS) {
|
||||
--shadow_spread: %s;
|
||||
--shadow_intensity: %s;
|
||||
}`,
|
||||
selectedStyle.TextColor.cssString(),
|
||||
selectedStyle.InputColor.cssString(),
|
||||
selectedStyle.InputColor.add(0, 0, -.03).cssString(),
|
||||
selectedStyle.InputTextColor.cssString(),
|
||||
selectedStyle.HighlightColor.cssString(),
|
||||
selectedStyle.HighlightColor.add(0, 0, -.03).cssString(),
|
||||
selectedStyle.HighlightTextColor.cssString(),
|
||||
selectedStyle.DangerColor.cssString(),
|
||||
selectedStyle.DangerColor.add(0, 0, -.03).cssString(),
|
||||
selectedStyle.FileBackgroundColor.cssString(),
|
||||
selectedStyle.ScrollbarForegroundColor.cssString(),
|
||||
selectedStyle.ScrollbarHoverColor.cssString(),
|
||||
selectedStyle.ScrollbarBackgroundColor.cssString(),
|
||||
selectedStyle.BackgroundColor.cssString(),
|
||||
selectedStyle.BodyColor.cssString(),
|
||||
selectedStyle.Layer1Color.cssString(),
|
||||
fmt.Sprintf("%dpx", selectedStyle.Layer1Shadow),
|
||||
selectedStyle.Layer2Color.cssString(),
|
||||
fmt.Sprintf("%dpx", selectedStyle.Layer2Shadow),
|
||||
selectedStyle.Layer3Color.cssString(),
|
||||
fmt.Sprintf("%dpx", selectedStyle.Layer3Shadow),
|
||||
selectedStyle.Layer4Color.cssString(),
|
||||
fmt.Sprintf("%dpx", selectedStyle.Layer4Shadow),
|
||||
selectedStyle.ShadowColor.cssString(),
|
||||
fmt.Sprintf("%dpx", selectedStyle.ShadowSpread),
|
||||
fmt.Sprintf("%dpx", selectedStyle.ShadowIntensity),
|
||||
))
|
||||
s.TextColor.cssString(),
|
||||
s.InputColor.cssString(),
|
||||
s.InputColor.add(0, 0, -.03).cssString(),
|
||||
s.InputTextColor.cssString(),
|
||||
s.HighlightColor.cssString(),
|
||||
s.HighlightColor.add(0, 0, -.03).cssString(),
|
||||
s.HighlightTextColor.cssString(),
|
||||
s.DangerColor.cssString(),
|
||||
s.DangerColor.add(0, 0, -.03).cssString(),
|
||||
s.FileBackgroundColor.cssString(),
|
||||
s.ScrollbarForegroundColor.cssString(),
|
||||
s.ScrollbarHoverColor.cssString(),
|
||||
s.ScrollbarBackgroundColor.cssString(),
|
||||
s.BackgroundColor.cssString(),
|
||||
s.BodyColor.cssString(),
|
||||
s.Layer1Color.cssString(),
|
||||
fmt.Sprintf("%dpx", s.Layer1Shadow),
|
||||
s.Layer2Color.cssString(),
|
||||
fmt.Sprintf("%dpx", s.Layer2Shadow),
|
||||
s.Layer3Color.cssString(),
|
||||
fmt.Sprintf("%dpx", s.Layer3Shadow),
|
||||
s.Layer4Color.cssString(),
|
||||
fmt.Sprintf("%dpx", s.Layer4Shadow),
|
||||
s.ShadowColor.cssString(),
|
||||
fmt.Sprintf("%dpx", s.ShadowSpread),
|
||||
fmt.Sprintf("%dpx", s.ShadowIntensity),
|
||||
)
|
||||
}
|
||||
|
||||
type pixeldrainStyleSheet struct {
|
||||
TextColor hsl
|
||||
InputColor hsl // Buttons, text fields
|
||||
InputTextColor hsl
|
||||
HighlightColor hsl // Links, highlighted buttons, list navigation
|
||||
HighlightTextColor hsl // Text on buttons
|
||||
DangerColor hsl
|
||||
FileBackgroundColor hsl
|
||||
ScrollbarForegroundColor hsl
|
||||
ScrollbarHoverColor hsl
|
||||
ScrollbarBackgroundColor hsl
|
||||
|
||||
BackgroundColor hsl
|
||||
BodyColor hsl
|
||||
|
||||
Layer1Color hsl // Deepest and darkest layer
|
||||
Layer1Shadow int // Deep layers have little shadow
|
||||
Layer2Color hsl
|
||||
Layer2Shadow int
|
||||
Layer3Color hsl
|
||||
Layer3Shadow int
|
||||
Layer4Color hsl // Highest and brightest layer
|
||||
Layer4Shadow int // High layers have lots of shadow
|
||||
|
||||
ShadowColor hsl
|
||||
ShadowSpread int // Pixels
|
||||
ShadowIntensity int // Pixels
|
||||
}
|
||||
type hsl struct {
|
||||
Hue int
|
||||
Saturation float64
|
||||
|
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"fornaxian.com/pixeldrain-web/init/conf"
|
||||
"fornaxian.com/pixeldrain-web/pixelapi"
|
||||
"fornaxian.com/pixeldrain-web/webcontroller/forms"
|
||||
"github.com/Fornaxian/log"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
@@ -19,7 +18,6 @@ import (
|
||||
type WebController struct {
|
||||
conf *conf.PixelWebConfig
|
||||
templates *TemplateManager
|
||||
staticResourceDir string
|
||||
|
||||
// page-specific variables
|
||||
captchaSiteKey string
|
||||
@@ -30,10 +28,9 @@ type WebController struct {
|
||||
func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebController {
|
||||
var wc = &WebController{
|
||||
conf: conf,
|
||||
staticResourceDir: conf.StaticResourceDir,
|
||||
}
|
||||
wc.templates = NewTemplateManager(
|
||||
conf.TemplateDir,
|
||||
conf.ResourceDir,
|
||||
conf.APIURLExternal,
|
||||
conf.DebugMode,
|
||||
)
|
||||
@@ -42,7 +39,7 @@ func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebCon
|
||||
var p = prefix
|
||||
|
||||
// Serve static files
|
||||
var fs = http.FileServer(http.Dir(wc.staticResourceDir))
|
||||
var fs = http.FileServer(http.Dir(wc.conf.ResourceDir + "/static"))
|
||||
r.GET(p+"/res/*filepath", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
w.Header().Set("Cache-Control", "public, max-age=86400") // Cache for one day
|
||||
r.URL.Path = p.ByName("filepath")
|
||||
@@ -92,6 +89,8 @@ func New(r *httprouter.Router, prefix string, conf *conf.PixelWebConfig) *WebCon
|
||||
r.GET(p+"/user/settings" /* */, wc.serveUserSettings)
|
||||
r.POST(p+"/user/settings" /* */, wc.serveUserSettings)
|
||||
r.GET(p+"/user/confirm_email" /* */, wc.serveEmailConfirm)
|
||||
r.GET(p+"/user/password_reset_confirm" /* */, wc.serveForm(wc.passwordResetConfirmForm, false))
|
||||
r.POST(p+"/user/password_reset_confirm" /**/, wc.serveForm(wc.passwordResetConfirmForm, false))
|
||||
|
||||
// Admin settings
|
||||
r.GET(p+"/admin" /* */, wc.serveTemplate("admin_panel", true))
|
||||
@@ -130,12 +129,12 @@ func (wc *WebController) serveFile(path string) httprouter.Handle {
|
||||
r *http.Request,
|
||||
p httprouter.Params,
|
||||
) {
|
||||
http.ServeFile(w, r, wc.staticResourceDir+path)
|
||||
http.ServeFile(w, r, wc.conf.ResourceDir+"/static"+path)
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *WebController) serveForm(
|
||||
handler func(*TemplateData, *http.Request) forms.Form,
|
||||
handler func(*TemplateData, *http.Request) Form,
|
||||
requireAuth bool,
|
||||
) httprouter.Handle {
|
||||
return func(
|
||||
@@ -167,7 +166,7 @@ func (wc *WebController) serveForm(
|
||||
// Remove the recaptcha field if captcha is disabled
|
||||
if wc.captchaKey() == "none" {
|
||||
for i, field := range td.Form.Fields {
|
||||
if field.Type == forms.FieldTypeCaptcha {
|
||||
if field.Type == FieldTypeCaptcha {
|
||||
td.Form.Fields = append(
|
||||
td.Form.Fields[:i],
|
||||
td.Form.Fields[i+1:]...,
|
||||
|