Support clipboard upload on home page

Add shortcuts for all the buttons
Add abuse reporter page
Fix user buckets page
Add a-ads advertisement
This commit is contained in:
2021-02-16 19:13:15 +01:00
parent 443f8c1af5
commit 346fa355c4
14 changed files with 605 additions and 376 deletions

View File

@@ -1,4 +1,4 @@
function UploadProgressBar(uploadManager, queueDiv, file){
function UploadProgressBar(uploadManager, queueDiv, file) {
this.uploadManager = uploadManager
this.file = file
this.name = file.name
@@ -14,38 +14,38 @@ function UploadProgressBar(uploadManager, queueDiv, file){
this.file,
this.name,
(progress) => { this.onProgress(progress) },
(id) => { this.onFinished(id) },
(id) => { this.onFinished(id) },
(val, msg) => { this.onFailure(val, msg) }
)
// 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
window.setTimeout(() => {this.uploadDiv.style.opacity = "1"}, 100)
window.setTimeout(() => { this.uploadDiv.style.opacity = "1" }, 100)
}
UploadProgressBar.prototype.onProgress = function(progress){
this.uploadDiv.innerText = "Uploading... " + Math.round(progress*1000)/10 + "%\n" + this.name
UploadProgressBar.prototype.onProgress = function (progress) {
this.uploadDiv.innerText = "Uploading... " + Math.round(progress * 1000) / 10 + "%\n" + this.name
this.uploadDiv.style.background = 'linear-gradient('
+'to right, '
+'var(--layer_3_color) 0%, '
+'var(--highlight_color) '+ ((progress*100)) +'%, '
+'var(--layer_3_color) '+ ((progress*100)+1) +'%)'
+ 'to right, '
+ 'var(--layer_3_color) 0%, '
+ 'var(--highlight_color) ' + ((progress * 100)) + '%, '
+ 'var(--layer_3_color) ' + ((progress * 100) + 1) + '%)'
}
UploadProgressBar.prototype.onFinished = function(id){
console.log("Upload finished: "+this.file.name+" "+id)
UploadProgressBar.prototype.onFinished = function (id) {
console.log("Upload finished: " + this.file.name + " " + id)
this.uploadDiv.style.background = 'var(--layer_3_color)'
this.uploadDiv.href = '/u/'+id
this.uploadDiv.target= "_blank"
this.uploadDiv.href = '/u/' + id
this.uploadDiv.target = "_blank"
let fileImg = document.createElement("img")
fileImg.src = apiEndpoint+'/file/'+id+'/thumbnail'
fileImg.src = apiEndpoint + '/file/' + id + '/thumbnail'
fileImg.alt = this.file.name
let linkSpan = document.createElement("span")
linkSpan.classList = "file_button_title"
linkSpan.innerText = domainURL()+"/u/"+id
linkSpan.innerText = domainURL() + "/u/" + id
this.uploadDiv.innerHTML = "" // Remove uploading progress
this.uploadDiv.appendChild(fileImg)
@@ -53,7 +53,7 @@ UploadProgressBar.prototype.onFinished = function(id){
this.uploadDiv.appendChild(document.createElement("br"))
this.uploadDiv.appendChild(linkSpan)
}
UploadProgressBar.prototype.onFailure = function(val, msg) {
UploadProgressBar.prototype.onFailure = function (val, msg) {
if (val === "") {
val = "Could not connect to server"
}
@@ -62,22 +62,18 @@ UploadProgressBar.prototype.onFailure = function(val, msg) {
this.uploadDiv.style.background = 'var(--danger_color)'
this.uploadDiv.style.color = 'var(--highlight_text_color)'
this.uploadDiv.appendChild(document.createTextNode("Upload failed: "))
this.uploadDiv.appendChild(document.createTextNode(msg+" ("+val+")"))
this.uploadDiv.appendChild(document.createTextNode(msg + " (" + val + ")"))
this.uploadDiv.appendChild(document.createElement("br"))
this.uploadDiv.appendChild(document.createTextNode(this.file.name))
console.log(msg)
}
let uploader = null
let uploader = new UploadManager(apiEndpoint + "/file", uploadsFinished)
let shareTitle = ""
let shareLink = ""
let shareLink = ""
function handleUploads(files) {
if (uploader === null){
uploader = new UploadManager(apiEndpoint+"/file", uploadsFinished)
}
if (files.length === 0) {
return
}
@@ -101,8 +97,8 @@ function uploadsFinished() {
let uploadLog = uploader.finishedUploads()
if (uploadLog.length === 1) {
shareTitle = "Download "+uploadLog[0].fileName+" here"
shareLink = domainURL()+"/u/"+uploadLog[0].fileID
shareTitle = "Download " + uploadLog[0].fileName + " here"
shareLink = domainURL() + "/u/" + uploadLog[0].fileID
showShareButtons()
} else if (uploadLog.length > 1) {
@@ -111,13 +107,13 @@ function uploadsFinished() {
createList(
title, true,
).then(resp => {
console.log("Automatic list ID "+resp.id)
shareTitle = "View "+title+" here"
shareLink = domainURL()+"/l/"+resp.id
console.log("Automatic list ID " + resp.id)
shareTitle = "View " + title + " here"
shareLink = domainURL() + "/l/" + resp.id
showShareButtons()
}).catch(err => {
alert("Failed to generate link. Please check your internet connection and try again.\nError: "+err)
alert("Failed to generate link. Please check your internet connection and try again.\nError: " + err)
})
}
}
@@ -130,10 +126,10 @@ function createList(title, anonymous) {
}
return fetch(
apiEndpoint+"/list",
apiEndpoint + "/list",
{
method: "POST",
headers: {"Content-Type": "application/json; charset=UTF-8"},
headers: { "Content-Type": "application/json; charset=UTF-8" },
body: JSON.stringify({
"title": title,
"anonymous": anonymous,
@@ -142,7 +138,7 @@ function createList(title, anonymous) {
}
).then(resp => {
if (!resp.ok) {
return Promise.reject("HTTP error: "+resp.status)
return Promise.reject("HTTP error: " + resp.status)
}
return resp.json()
})
@@ -163,7 +159,7 @@ function showShareButtons() {
}
function copyLink() {
if(copyText(shareLink)) {
if (copyText(shareLink)) {
console.log('Text copied')
document.querySelector("#btn_copy_link>span").textContent = "Copied!"
document.getElementById("btn_copy_link").classList.add("button_highlight")
@@ -177,35 +173,47 @@ function copyLink() {
* Upload Handlers
*/
// Relay click event to hidden file field
document.getElementById("upload_file_button").onclick = function() {
// Relay click event to hidden file field
document.getElementById("upload_file_button").onclick = () => {
document.getElementById("file_input_field").click()
}
document.getElementById("file_input_field").onchange = function(evt){
document.getElementById("file_input_field").onchange = e => {
// Start uploading the files async
window.setTimeout(handleUploads(evt.target.files), 1)
window.setTimeout(handleUploads(e.target.files), 1)
// This resets the file input field
document.getElementById("file_input_field").nodeValue = ""
}
document.getElementById("upload_text_button").onclick = function() {
window.location.href = '/t/'
document.getElementById("upload_text_button").onclick = () => {
window.location.href = '/t'
}
/*
* Drag 'n Drop upload handlers
*/
document.ondragover = function(e) {
document.addEventListener("dragover", e => {
document.getElementById("file_drop_highlight").style.display = ""
e.preventDefault()
e.stopPropagation()
}
document.ondragenter = function(e) {
})
document.addEventListener("dragenter", e => {
document.getElementById("file_drop_highlight").style.display = ""
e.preventDefault()
e.stopPropagation()
}
document.addEventListener('drop', function(e){
})
document.addEventListener("dragleave", e => {
document.getElementById("file_drop_highlight").style.display = "none"
e.preventDefault()
e.stopPropagation()
})
document.addEventListener("drop", e => {
document.getElementById("file_drop_highlight").style.display = "none"
if (e.dataTransfer && e.dataTransfer.files.length > 0) {
e.preventDefault()
e.stopPropagation()
@@ -214,37 +222,41 @@ document.addEventListener('drop', function(e){
window.setTimeout(handleUploads(e.dataTransfer.files), 1)
}
})
document.addEventListener("paste", e => {
if (e.clipboardData.files[0]) {
e.preventDefault();
e.stopPropagation();
window.setTimeout(handleUploads(e.clipboardData.files), 1)
}
})
/*
* Share buttons
*/
document.getElementById("btn_social_share").addEventListener("click", function() {
window.navigator.share({
title: "Pixeldrain",
text: shareTitle,
url: shareLink
})
document.getElementById("btn_social_share").addEventListener("click", () => {
window.navigator.share({ title: "Pixeldrain", text: shareTitle, url: shareLink })
})
document.getElementById("btn_copy_link").addEventListener("click", function() {
document.getElementById("btn_copy_link").addEventListener("click", () => {
copyLink()
})
document.getElementById("btn_open_link").addEventListener("click", function() {
document.getElementById("btn_open_link").addEventListener("click", () => {
window.open(shareLink, '_blank')
})
document.getElementById("btn_social_email").addEventListener("click", function() {
document.getElementById("btn_social_email").addEventListener("click", () => {
window.open('mailto:please@set.address?subject=File%20on%20pixeldrain&body=' + shareLink)
})
document.getElementById("btn_social_twitter").addEventListener("click", function() {
document.getElementById("btn_social_twitter").addEventListener("click", () => {
window.open('https://twitter.com/share?url=' + shareLink)
})
document.getElementById("btn_social_facebook").addEventListener("click", function() {
document.getElementById("btn_social_facebook").addEventListener("click", () => {
window.open('http://www.facebook.com/sharer.php?u=' + shareLink)
})
document.getElementById("btn_social_reddit").addEventListener("click", function() {
document.getElementById("btn_social_reddit").addEventListener("click", () => {
window.open('https://www.reddit.com/submit?url=' + shareLink)
})
document.getElementById("btc_social_tumblr").addEventListener("click", function() {
document.getElementById("btn_social_tumblr").addEventListener("click", () => {
window.open('http://www.tumblr.com/share/link?url=' + shareLink)
})
@@ -253,18 +265,18 @@ document.getElementById("btc_social_tumblr").addEventListener("click", function(
*/
function renderListButton(apiURL, id, title, subtitle) {
let btn = document.createElement("a")
btn.classList = "file_button"
btn.href = "/l/"+id
btn.target = "_blank"
let thumbnail = document.createElement("img")
thumbnail.src = apiURL+"/list/"+id+"/thumbnail?width=80&height=80"
thumbnail.alt = title
let titleSpan = document.createElement("span")
titleSpan.classList = "file_button_title"
titleSpan.innerText = title
let br = document.createElement("br")
let subtitleSpan = document.createElement("span")
let btn = document.createElement("a")
btn.classList = "file_button"
btn.href = "/l/" + id
btn.target = "_blank"
let thumbnail = document.createElement("img")
thumbnail.src = apiURL + "/list/" + id + "/thumbnail?width=80&height=80"
thumbnail.alt = title
let titleSpan = document.createElement("span")
titleSpan.classList = "file_button_title"
titleSpan.innerText = title
let br = document.createElement("br")
let subtitleSpan = document.createElement("span")
subtitleSpan.classList = "file_button_subtitle"
subtitleSpan.innerText = subtitle
@@ -276,12 +288,12 @@ function renderListButton(apiURL, id, title, subtitle) {
}
// Create list button
document.getElementById("btn_create_list").addEventListener("click", function(evt) {
document.getElementById("btn_create_list").addEventListener("click", function (evt) {
let title = prompt(
"You are creating a list containing " + uploader.finishedUploads().length + " files.\n"
+ "What do you want to call it?", "My New Album"
)
if(title === null){
if (title === null) {
return
}
createList(title, false).then(resp => {
@@ -289,11 +301,11 @@ document.getElementById("btn_create_list").addEventListener("click", function(ev
renderListButton(
apiEndpoint,
resp.id,
domainURL()+'/l/'+resp.id,
domainURL() + '/l/' + resp.id,
"List creation finished!",
)
)
window.open('/l/'+resp.id, '_blank')
window.open('/l/' + resp.id, '_blank')
}).catch(err => {
let div = document.createElement("div")
div.className = "file_button"
@@ -305,55 +317,55 @@ document.getElementById("btn_create_list").addEventListener("click", function(ev
})
let btnCopyLinks = document.getElementById("btn_copy_links")
btnCopyLinks.addEventListener("click", function(){
btnCopyLinks.addEventListener("click", function () {
let text = ""
let uploads = uploader.finishedUploads()
// Add the text to the textarea
for (let i = 0; i < uploads.length; i++) {
// Example: https://pixeldrain.com/u/abcd1234: Some_file.png
text += domainURL()+"/u/"+uploads[i].fileID+" "+uploads[i].fileName+"\n"
text += domainURL() + "/u/" + uploads[i].fileID + " " + uploads[i].fileName + "\n"
}
if (shareLink.includes("/l/")) {
text += "\n"+shareLink+" All "+uploads.length+" files\n"
text += "\n" + shareLink + " All " + uploads.length + " files\n"
}
// Copy the selected text
if(copyText(text)){
if (copyText(text)) {
btnCopyLinks.classList.add("button_highlight")
btnCopyLinks.innerHTML = "Links copied to clipboard!"
}else{
} else {
btnCopyLinks.classList.add("button_red")
btnCopyLinks.innerHTML = "Copying links failed"
}
})
let btnCopyBBCode = document.getElementById("btn_copy_bbcode")
btnCopyBBCode.addEventListener("click", function(){
btnCopyBBCode.addEventListener("click", function () {
let text = ""
let uploads = uploader.finishedUploads()
// Add the text to the textarea
for (let i = 0; i < uploads.length; i++) {
// Example: [url=https://pixeldrain.com/u/abcd1234]Some_file.png[/url]
text += "[url="+domainURL()+"/u/"+uploads[i].fileID+"]"+uploads[i].fileName+"[/url]\n"
text += "[url=" + domainURL() + "/u/" + uploads[i].fileID + "]" + uploads[i].fileName + "[/url]\n"
}
if (shareLink.includes("/l/")) {
text += "\n[url="+shareLink+"]All "+uploads.length+" files[/url]\n"
text += "\n[url=" + shareLink + "]All " + uploads.length + " files[/url]\n"
}
// Copy the selected text
if(copyText(text)){
if (copyText(text)) {
btnCopyBBCode.classList.add("button_highlight")
btnCopyBBCode.innerHTML = "BBCode copied to clipboard!"
}else{
} else {
btnCopyBBCode.classList.add("button_red")
btnCopyBBCode.innerHTML = "Copying links failed"
}
})
let btnCopyMarkdown = document.getElementById("btn_copy_markdown")
btnCopyMarkdown.addEventListener("click", function(){
btnCopyMarkdown.addEventListener("click", function () {
let text = ""
let uploads = uploader.finishedUploads()
@@ -362,17 +374,17 @@ btnCopyMarkdown.addEventListener("click", function(){
// Example: * [Some_file.png](https://pixeldrain.com/u/abcd1234)
if (uploads.length > 1) { text += " * " }
text += "["+uploads[i].fileName+"]("+domainURL()+"/u/"+uploads[i].fileID+")\n"
text += "[" + uploads[i].fileName + "](" + domainURL() + "/u/" + uploads[i].fileID + ")\n"
}
if (shareLink.includes("/l/")) {
text += " * [All "+uploads.length+" files]("+shareLink+")\n"
text += " * [All " + uploads.length + " files](" + shareLink + ")\n"
}
// Copy the selected text
if(copyText(text)){
if (copyText(text)) {
btnCopyMarkdown.classList.add("button_highlight")
btnCopyMarkdown.innerHTML = "Markdown copied to clipboard!"
}else{
} else {
btnCopyMarkdown.classList.add("button_red")
btnCopyMarkdown.innerHTML = "Copying links failed"
}
@@ -382,19 +394,35 @@ btnCopyMarkdown.addEventListener("click", function(){
/*
* Keyboard shortcuts
*/
document.addEventListener("keydown", function(event){
if (event.ctrlKey || event.altKey || event.metaKey) {
document.addEventListener("keydown", e => {
if (e.ctrlKey || e.altKey || e.metaKey) {
return // prevent custom shortcuts from interfering with system shortcuts
}
if (event.keyCode === 67) { // c
// Copy links to clipboard
if (e.key === "c") {
document.getElementById("btn_copy_link").click()
} else if (event.keyCode === 85) { // u
// Click the upload button
} else if (e.key === "u") {
document.getElementById("file_input_field").click()
} else if (event.keyCode === 84) { // t
// Click the text button
} else if (e.key === "t") {
document.getElementById("upload_text_button").click()
} else if (e.key === "o") {
document.getElementById("btn_open_link").click()
} else if (e.key === "l") {
document.getElementById("btn_create_list").click()
} else if (e.key === "e") {
document.getElementById("btn_social_email").click()
} else if (e.key === "w") {
document.getElementById("btn_social_twitter").click()
} else if (e.key === "f") {
document.getElementById("btn_social_facebook").click()
} else if (e.key === "r") {
document.getElementById("btn_social_reddit").click()
} else if (e.key === "m") {
document.getElementById("btn_social_tumblr").click()
} else if (e.key === "a") {
document.getElementById("btn_copy_links").click()
} else if (e.key === "d") {
document.getElementById("btn_copy_markdown").click()
} else if (e.key === "b") {
document.getElementById("btn_copy_bbcode").click()
}
console.log(event.keyCode)
})

View File

@@ -10,8 +10,23 @@
<div class="page_content">
{{if and .Authenticated .User.IsAdmin}}
<div class="limit_width">
<a class="button" href="/admin/globals">Update global settings</a>
<a class="button" href="/admin/abuse">Block files</a>
<h3>Actions</h3>
</div>
<br/>
<a class="button" href="/admin/abuse">
<i class="icon">block</i>
Block files
</a>
<a class="button" href="/admin/abuse_reporters">
<i class="icon">report</i>
Manage abuse reporters
</a>
<a class="button" href="/admin/globals">
<i class="icon">edit</i>
Update global settings
</a>
<br/>
<div class="limit_width">
<h3>Bandwidth and views</h3>
</div>
<div class="highlight_dark">

View File

@@ -0,0 +1,19 @@
{{define "admin_abuse_reporters"}}<!DOCTYPE html>
<html lang="en">
<head>
{{template "meta_tags" "Abuse reporters"}}
{{template "user_style" .}}
<script>window.api_endpoint = '{{.APIEndpoint}}';</script>
<link rel='stylesheet' href='/res/svelte/admin_abuse_reporters.css'>
<script defer src='/res/svelte/admin_abuse_reporters.js'></script>
</head>
<body>
{{template "page_top" .}}
<h1>Abuse reporters</h1>
<div id="page_content" class="page_content"></div>
{{template "page_bottom" .}}
{{template "analytics"}}
</body>
</html>
{{end}}

View File

@@ -186,7 +186,15 @@
<a href="/click/7wy9gg2J?target=%2F%23pro" class="button button_highlight">Pixeldrain Pro: Only €2 per month</a>
</div>
</div>
{{end}}
{{ else }}
<!-- scrolling="no" is not allowed by the W3C, but overflow: hidden doesn't work in chrome, so I have no choice -->
<iframe class="sponsors_banner"
data-aa="73974"
src="//ad.a-ads.com/73974?size=728x90&background_color={{.Style.Layer1Color.RGB}}&text_color={{.Style.TextColor.RGB}}&title_color={{.Style.HighlightColor.RGB}}&title_hover_color={{.Style.HighlightColor.RGB}}&link_color={{.Style.HighlightColor.RGB}}&link_hover_color={{.Style.HighlightColor.RGB}}"
style="width:728px; height:90px; border:none; padding:0; overflow:hidden;"
scrolling="no">
</iframe>
{{ end }}
{{ else if not .Other.UserAdsEnabled }}
<div style="text-align: center; line-height: 1.3em; font-size: 13px;">
Thank you for supporting pixeldrain!
@@ -295,7 +303,7 @@
{{ if eq .Other.AdType 5 }}
<!-- AdMaven -->
<script data-cfasync="false" async src="//d227cncaprzd7y.cloudfront.net/?acncd=905608"></script>
{{ else if eq .Other.AdType 7 }}
{{ else if or (eq .Other.AdType 7) (eq .Other.AdType 8) (eq .Other.AdType 9) (eq .Other.AdType 10) (eq .Other.AdType 11) }}
<!-- PropellerAds -->
<script>
// Load fires when the page is completely finished loading,

View File

@@ -125,63 +125,37 @@
<img class="header_image" src="/res/img/header_orbitron_wide.png" alt="Header image">
</picture>
<br/>
<div class="highlight_blue">
<div class="limit_width">
<p>
Hello! Due to
<a href="https://nypost.com/2021/02/14/hundreds-of-thousands-without-power-after-oregon-ice-storm/" target="_blank">an ice storm in Oregon</a>
the datacenter where pixeldrain is hosted in North America
was cut off from the internet and has lost power. This
caused an outage of a few hours last saturday (it happened
when I was out ice skating, so I didn't notice it until I
got back).
</p>
<p>
I am now redirecting all American (north and south) traffic
to the UK, the server there has more than enough capacity to
handle it all. Due to the long distance the website might
feel a bit slower.
</p>
<p>
Files which were uploaded to the server in Oregon shortly
before the outage will not be available until the server is
back up. Normally all files are backed up in Finland, but
this process takes a while. Due to the abruptness of the
outage a lot of files could not make it over. When the
server comes back online your files will be downloadable
again.
</p>
<p>
I will post updates <a href="https://twitter.com/Fornax96"
target="_blank">on Twitter</a> when the situation changes.
</p>
</div>
</div>
<div class="page_content" style="padding-top: 0; margin-bottom: 100px;">
<div id="instruction_1" class="instruction_highlight" style="margin-top: 0;"><div class="limit_width">
<span class="big_number">1</span>
<span class="instruction_text">Select files to upload</span>
<br/>
You can also drop files anywhere on this page from your file
manager
<br/> You can also drop files on this page from your file
manager or paste an image from your clipboard
</div></div>
<input id="file_input_field" type="file" name="file" multiple="multiple"/>
<button id="upload_file_button" class="big_button button_highlight">
<i class="icon small">cloud_upload</i>
<u>U</u>pload Files</button>
<u>U</u>pload Files
</button>
<button id="upload_text_button" class="big_button button_highlight">
<i class="icon small">text_fields</i>
Upload <u>T</u>ext</button>
Upload <u>T</u>ext
</button>
<br/>
<p>
By uploading files to pixeldrain you acknowledge and accept our
<a href="/about#content-policy">content policy</a>.
<p>
<div id="instruction_2" class="instruction_highlight">
<div class="limit_width">
<span class="big_number">2</span><span class="instruction_text">Wait for the files to finish uploading</span>
</div>
</div>
<div id="file_drop_highlight" class="highlight_green" style="display: none;">
Gimme gimme gimme!<br/>
Drop your files to upload them
</div>
<div id="uploads_queue"></div>
<div id="instruction_3" class="instruction_highlight">
@@ -197,30 +171,30 @@
{{template `copy.svg` .}}<br/><span><u>C</u>opy link</span>
</button>
<button id="btn_open_link" class="social_button" style="display: inline-block">
{{template `open_in_new.svg` .}}<br/><span>Open link</span>
{{template `open_in_new.svg` .}}<br/><span><u>O</u>pen link</span>
</button>
<div id="social_buttons" style="display: inline-block">
<button id="btn_social_email" class="social_button">
{{template `email.svg` .}}<br/>E-Mail
{{template `email.svg` .}}<br/><u>E</u>-Mail
</button>
<button id="btn_social_twitter" class="social_button">
{{template `twitter.svg` .}}<br/>Twitter
{{template `twitter.svg` .}}<br/>T<u>w</u>itter
</button>
<button id="btn_social_facebook" class="social_button">
{{template `facebook.svg` .}}<br/>Facebook
{{template `facebook.svg` .}}<br/><u>F</u>acebook
</button>
<button id="btn_social_reddit" class="social_button">
{{template `reddit.svg` .}}<br/>Reddit
{{template `reddit.svg` .}}<br/><u>R</u>eddit
</button>
<button id="btc_social_tumblr" class="social_button">
{{template `tumblr.svg` .}}<br/>Tumblr
<button id="btn_social_tumblr" class="social_button">
{{template `tumblr.svg` .}}<br/>Tu<u>m</u>blr
</button>
</div>
<br/><br/>
<button id="btn_create_list"><i class="icon">list</i> Create list with uploaded files</button>
<button id="btn_copy_links"><i class="icon">content_copy</i> Copy all links to clipboard</button>
<button id="btn_copy_markdown"><i class="icon">content_copy</i> Copy markdown to clipboard</button>
<button id="btn_copy_bbcode"><i class="icon">content_copy</i> Copy BBCode to clipboard</button>
<button id="btn_create_list"><i class="icon">list</i> Create <u>l</u>ist with uploaded files</button>
<button id="btn_copy_links"><i class="icon">content_copy</i> Copy <u>a</u>ll links to clipboard</button>
<button id="btn_copy_markdown"><i class="icon">content_copy</i> Copy mark<u>d</u>own to clipboard</button>
<button id="btn_copy_bbcode"><i class="icon">content_copy</i> Copy <u>B</u>BCode to clipboard</button>
<br/>
<div id="created_lists"></div>
</div>

View File

@@ -32,6 +32,7 @@ export default [
"filesystem",
"modal",
"user_buckets",
"admin_abuse_reporters",
].map((name, index) => ({
input: `src/${name}.js`,
output: {

View File

@@ -0,0 +1,8 @@
import App from './admin_abuse_reporters/AbuseReporters.svelte';
const app = new App({
target: document.getElementById("page_content"),
props: {}
});
export default app;

View File

@@ -0,0 +1,182 @@
<script>
import { onMount } from "svelte";
import { formatDate, formatDuration } from "../util/Formatting.svelte";
import Spinner from "../util/Spinner.svelte";
let loading = true
let reporters = []
let creating = false
let new_reporter_email
let new_reporter_name
let new_reporter_type = "individual"
const get_reporters = async () => {
loading = true;
try {
const resp = await fetch(window.api_endpoint+"/admin/abuse_reporter");
if(resp.status >= 400) {
throw new Error(resp.text());
}
reporters = await resp.json();
} catch (err) {
alert(err);
} finally {
loading = false;
}
};
const create_reporter = async () => {
if (!new_reporter_email.value) {
alert("Please enter an e-mail address!")
return
} else if (!new_reporter_name.value) {
alert("Please enter a name!")
return
} else if (!new_reporter_type) {
alert("Please enter a type!")
return
}
try {
const form = new FormData()
form.append("email", new_reporter_email.value)
form.append("name", new_reporter_name.value)
form.append("type", new_reporter_type)
const resp = await fetch(
window.api_endpoint+"/admin/abuse_reporter",
{ method: "POST", body: form }
);
if(resp.status >= 400) {
throw new Error(await resp.text());
}
} catch (err) {
alert("Failed to add abuse reporter! "+err)
}
creating = false
get_reporters();
}
const delete_reporter = async (email) => {
if (!confirm("Delete this reporter address?\n\n"+email)) {
return
}
try {
const resp = await fetch(
window.api_endpoint+"/admin/abuse_reporter/"+encodeURI(email),
{ method: "DELETE" }
);
if(resp.status >= 400) {
throw new Error(await resp.text());
}
} catch (err) {
alert("Failed to delete abuse reporter! "+err)
}
get_reporters();
}
onMount(get_reporters);
</script>
<div>
{#if loading}
<div class="spinner_container">
<Spinner />
</div>
{/if}
<div class="limit_width">
<div class="toolbar" style="text-align: left;">
<a class="button" href="/admin">
<i class="icon">arrow_back</i> Return to admin panel
</a>
<div class="toolbar_spacer"></div>
<button class:button_highlight={creating} on:click={() => {creating = !creating}}>
<i class="icon">create</i> Add abuse reporter
</button>
</div>
{#if creating}
<div class="highlight_light">
<form on:submit|preventDefault={create_reporter}>
<table class="form">
<tr>
<td>E-mail address</td>
<td><input type="text" bind:this={new_reporter_email}/></td>
</tr>
<tr>
<td>Name</td>
<td><input type="text" bind:this={new_reporter_name} value="Anonymous tip"/></td>
</tr>
<tr>
<td>Type</td>
<td>
<input id="reporter_type_individual" name="reporter_type" type="radio" bind:group={new_reporter_type} value="individual" />
<label for="reporter_type_individual">Individual</label>
<br/>
<input id="reporter_type_org" name="reporter_type" type="radio" bind:group={new_reporter_type} value="org" />
<label for="reporter_type_org">Organisation</label>
</td>
</tr>
<tr>
<td colspan="2">
<button class="button_highlight" type="submit" style="float: right;">
<i class="icon">save</i> Save
</button>
</td>
</tr>
</table>
</form>
</div>
{/if}
</div>
<br/>
<table style="text-align: left;">
<tr>
<td>E-mail</td>
<td>Name</td>
<td>Blocked</td>
<td>Type</td>
<td>Last used</td>
<td>Created</td>
<td></td>
</tr>
{#each reporters as reporter}
<tr>
<td>{reporter.email}</td>
<td>{reporter.name}</td>
<td>{reporter.files_blocked}</td>
<td>{reporter.type}</td>
<td>{formatDate(reporter.last_used, true, true, false)}</td>
<td>{formatDate(reporter.created, false, false, false)}</td>
<td>
<button on:click|preventDefault={() => {delete_reporter(reporter.email)}} class="button button_red">
<i class="icon">delete</i>
</button>
</td>
</tr>
{/each}
</table>
</div>
<style>
.spinner_container {
position: absolute;
top: 10px;
left: 10px;
height: 100px;
width: 100px;
}
.toolbar {
display: flex;
flex-direction: row;
width: 100%;
}
.toolbar > * { flex: 0 0 auto; }
.toolbar_spacer { flex: 1 1 auto; }
</style>

View File

@@ -1,178 +1,181 @@
<script>
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher();
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher();
export let bucket_id;
export let target_dir;
export let bucket_id;
export let target_dir;
let upload_jobs = [];
let upload_threads = 0;
let max_upload_threads = 3;
let upload_jobs = [];
let upload_threads = 0;
let max_upload_threads = 3;
// Adds files to the upload queue. The file_list parameter needs to be of type
// FileList. Upload will also create the necessary directories to place nested
// files in.
export const upload = (file_list) => {
for (let i = 0; i < file_list.length; i++) {
upload_jobs.push({
file: file_list[i],
progress: 0,
target_dir: target_dir.valueOf(),
uploading: false,
finished: false,
tries: 0,
});
// Adds files to the upload queue. The file_list parameter needs to be of type
// FileList. Upload will also create the necessary directories to place nested
// files in.
export const upload = (file_list) => {
console.log(file_list)
for (let i = 0; i < file_list.length; i++) {
upload_jobs.push({
file: file_list[i],
progress: 0,
target_dir: target_dir.valueOf(),
uploading: false,
finished: false,
tries: 0,
});
}
// This updates the UI
upload_jobs = upload_jobs;
while (upload_threads < max_upload_threads) {
upload_threads++;
setTimeout(upload_file, 1);
}
};
const uploads_finished = () => {
dispatch("finished");
};
const upload_file = () => {
let job = null;
for (let i = 0; i < upload_jobs.length; i++) {
// If a file is done we remove it from the array
if (upload_jobs[i].progress >= 1) {
upload_jobs.splice(i, 1);
continue;
}
// This updates the UI
upload_jobs = upload_jobs;
while (upload_threads < max_upload_threads) {
upload_threads++;
setTimeout(upload_file, 1);
if (
upload_jobs[i].uploading === false &&
upload_jobs[i].finished === false
) {
job = upload_jobs[i];
job.uploading = true;
upload_jobs = upload_jobs;
break;
}
};
}
if (job === null) {
upload_threads--;
const uploads_finished = () => {
dispatch("finished");
};
const upload_file = () => {
let job = null;
for (let i = 0; i < upload_jobs.length; i++) {
// If a file is done we remove it from the array
if (upload_jobs[i].progress >= 1) {
upload_jobs.splice(i, 1);
continue;
}
if (
upload_jobs[i].uploading === false &&
upload_jobs[i].finished === false
) {
job = upload_jobs[i];
job.uploading = true;
upload_jobs = upload_jobs;
break;
}
if (upload_threads === 0) {
uploads_finished();
}
if (job === null) {
upload_threads--;
return;
}
if (upload_threads === 0) {
uploads_finished();
}
console.log(job);
let form = new FormData();
form.append("type", "file");
form.append("file", job.file);
let xhr = new XMLHttpRequest();
xhr.open(
"POST",
"/api/filesystem/" +
bucket_id +
encodeURIComponent(job.target_dir + "/" + job.file.name),
true
);
xhr.timeout = 21600000; // 6 hours, to account for slow connections
// Report progress updates back to the caller
xhr.upload.addEventListener("progress", (evt) => {
if (evt.lengthComputable) {
job.progress = evt.loaded / evt.total;
upload_jobs = upload_jobs;
}
});
xhr.onreadystatechange = () => {
// readystate 4 means the upload is done
if (xhr.readyState !== 4) {
return;
}
console.log(job);
if (xhr.status >= 100 && xhr.status < 400) {
// Request is a success
let form = new FormData();
form.append("type", "file");
form.append("file", job.file);
let xhr = new XMLHttpRequest();
xhr.open(
"POST",
"/api/filesystem/" +
bucket_id +
encodeURIComponent(job.target_dir + "/" + job.file.name),
true
);
xhr.timeout = 21600000; // 6 hours, to account for slow connections
// Report progress updates back to the caller
xhr.upload.addEventListener("progress", (evt) => {
if (evt.lengthComputable) {
job.progress = evt.loaded / evt.total;
upload_jobs = upload_jobs;
}
});
xhr.onreadystatechange = () => {
// readystate 4 means the upload is done
if (xhr.readyState !== 4) {
// Finish the upload job
job.uploading = false;
job.finished = true;
upload_file();
} else if (xhr.status >= 400) {
// Request failed
console.log(
"Upload error. status: " +
xhr.status +
" response: " +
xhr.response
);
let resp = JSON.parse(xhr.response);
if (job.tries === 3) {
// Upload failed
return;
}
if (xhr.status >= 100 && xhr.status < 400) {
// Request is a success
// Finish the upload job
job.uploading = false;
job.finished = true;
upload_file();
} else if (xhr.status >= 400) {
// Request failed
console.log(
"Upload error. status: " +
xhr.status +
" response: " +
xhr.response
);
let resp = JSON.parse(xhr.response);
if (job.tries === 3) {
// Upload failed
return;
} else {
// Try again
job.tries++;
job.uploading = false;
job.finished = false;
}
// Sleep the upload thread for 5 seconds
setTimeout(upload_file, 5000);
} else {
// Request did not arrive
if (job.tries === 3) {
// Upload failed
alert("upload failed " + xhr.responseText);
job.uploading = false;
job.finished = false;
} else {
// Try again
job.tries++;
}
// Sleep the upload thread for 5 seconds
setTimeout(upload_file, 5000);
// Try again
job.tries++;
job.uploading = false;
job.finished = false;
}
upload_jobs = upload_jobs;
};
xhr.send(form);
};
// Sleep the upload thread for 5 seconds
setTimeout(upload_file, 5000);
} else {
// Request did not arrive
if (job.tries === 3) {
// Upload failed
alert("upload failed " + xhr.responseText);
job.uploading = false;
job.finished = false;
} else {
// Try again
job.tries++;
}
// File input dialog handling
let file_input;
export const picker = () => {
file_input.click();
};
const file_input_change = (e) => {
upload(e.target.files);
file_input.nodeValue = "";
};
// Drag and drop upload
let hidden = true;
const dragover = (e) => {
hidden = false;
};
const dragleave = (e) => {
hidden = true;
};
const drop = (e) => {
hidden = true;
upload(e.dataTransfer.files);
};
const paste = (e) => {
if (e.clipboardData.files[0]) {
e.preventDefault();
e.stopPropagation();
console.log(e.clipboardData.files[0].getAsFile());
// Sleep the upload thread for 5 seconds
setTimeout(upload_file, 5000);
}
upload_jobs = upload_jobs;
};
xhr.send(form);
};
// File input dialog handling
let file_input;
export const picker = () => {
file_input.click();
};
const file_input_change = (e) => {
upload(e.target.files);
file_input.nodeValue = "";
};
// Drag and drop upload
let hidden = true;
const dragover = (e) => {
hidden = false;
};
const dragleave = (e) => {
hidden = true;
};
const drop = (e) => {
hidden = true;
upload(e.dataTransfer.files);
};
const paste = (e) => {
if (e.clipboardData.files[0]) {
e.preventDefault();
e.stopPropagation();
console.log(e.clipboardData.files[0]);
upload(e.clipboardData.files)
}
};
</script>
<style>

View File

@@ -1,42 +0,0 @@
<script>
import { fs_create_bucket } from "../filesystem/FilesystemAPI.svelte";
let name
const submit = async () => {
if (!name.value) {
alert("Please enter a name!")
return
}
try {
let bucket = await fs_create_bucket(name.value)
console.log(bucket)
} catch (err) {
alert("Failed to create bucket! "+err)
}
}
</script>
<div class="highlight_light">
<form on:submit|preventDefault={submit}>
<table class="form">
<tr>
<td>
Name
</td>
<td>
<input type="text" bind:this={name}/>
</td>
</tr>
<tr>
<td colspan="2">
<button class="button_highlight" type="submit" style="float: right;">
<i class="icon">save</i> Save
</button>
</td>
</tr>
</table>
</form>
</div>

View File

@@ -1,5 +1,7 @@
<script>
import { fs_delete_bucket } from "../filesystem/FilesystemAPI.svelte";
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher()
export let bucket
let details_hidden = true
@@ -24,6 +26,8 @@ const delete_bucket = async () => {
} catch (err) {
alert("Failed to delete bucket! "+err)
}
dispatch("refresh");
}
</script>

View File

@@ -1,17 +1,17 @@
<script>
import { onMount } from "svelte";
import Bucket from "./Bucket.svelte";
import UserBucket from "./UserBucket.svelte";
import Spinner from "../util/Spinner.svelte";
import { fs_get_buckets } from "../filesystem/FilesystemAPI.svelte";
import NewBucket from "./NewBucket.svelte";
import { fs_get_buckets, fs_create_bucket } from "../filesystem/FilesystemAPI.svelte";
let loading = true;
let buckets = [];
let loading = true
let buckets = []
let new_bucket;
let creating_bucket = false;
let creating_bucket = false
let new_bucket_name
const get_buckets = async () => {
loading = true;
try {
let resp = await fs_get_buckets();
buckets = resp.buckets;
@@ -22,6 +22,23 @@ const get_buckets = async () => {
}
};
const create_bucket = async () => {
if (!new_bucket_name.value) {
alert("Please enter a name!")
return
}
try {
let bucket = await fs_create_bucket(new_bucket_name.value)
console.log(bucket)
} catch (err) {
alert("Failed to create bucket! "+err)
}
creating_bucket = false
get_buckets();
}
onMount(get_buckets);
</script>
@@ -42,29 +59,40 @@ onMount(get_buckets);
</button>
</div>
{#if creating_bucket}
<NewBucket bind:this={new_bucket}></NewBucket>
<div class="highlight_light">
<form on:submit|preventDefault={create_bucket}>
<table class="form">
<tr>
<td>
Name
</td>
<td>
<input type="text" bind:this={new_bucket_name}/>
</td>
</tr>
<tr>
<td colspan="2">
<button class="button_highlight" type="submit" style="float: right;">
<i class="icon">save</i> Save
</button>
</td>
</tr>
</table>
</form>
</div>
{/if}
<h2>Persistent buckets</h2>
<p>
These buckets don't expire, but have limited storage space and
bandwidth. Their limits can be raised by buying a subscription.
</p>
{#each buckets as bucket}
<Bucket bucket={bucket}></Bucket>
<UserBucket bucket={bucket} on:refresh={get_buckets}></UserBucket>
{/each}
<br/>
<h2>Temporary buckets</h2>
<p>
</p>
</div>
</div>
<style>
.spinner_container {
display: inline-block;
position: absolute;
top: 10px;
left: 10px;
height: 100px;
width: 100px;
}

View File

@@ -50,15 +50,15 @@ func adType() int {
switch i := rand.Intn(20); i {
case 0, 1: // 10%
return amarulaSolutions
case 2: // 5%
case 2: // 5%, also shows propellerads
return pdpro1
case 3: // 5%
case 3: // 5%, also shows propellerads
return pdpro2
case 4: // 5%
case 4: // 5%, also shows propellerads
return pdpro3
case 5: // 5%
case 5: // 5%, also shows propellerads
return pdpro4
default: // 70%
default: // 70%, also shows a-ads
return propellerAds
// default:
// panic(fmt.Errorf("random number generator returned unrecognised number: %d", i))

View File

@@ -161,11 +161,12 @@ func New(
{PST, "knoxfs_activate" /* */, wc.serveForm(wc.knoxfsLinkForm, true)},
// Admin settings
{GET, "admin" /* */, wc.serveTemplate("admin_panel", true)},
{GET, "admin/globals" /**/, wc.serveForm(wc.adminGlobalsForm, true)},
{PST, "admin/globals" /**/, wc.serveForm(wc.adminGlobalsForm, true)},
{GET, "admin/abuse" /* */, wc.serveForm(wc.adminAbuseForm, true)},
{PST, "admin/abuse" /* */, wc.serveForm(wc.adminAbuseForm, true)},
{GET, "admin" /* */, wc.serveTemplate("admin_panel", true)},
{GET, "admin/globals" /* */, wc.serveForm(wc.adminGlobalsForm, true)},
{PST, "admin/globals" /* */, wc.serveForm(wc.adminGlobalsForm, true)},
{GET, "admin/abuse" /* */, wc.serveForm(wc.adminAbuseForm, true)},
{PST, "admin/abuse" /* */, wc.serveForm(wc.adminAbuseForm, true)},
{GET, "admin/abuse_reporters" /**/, wc.serveTemplate("admin_abuse_reporters", true)},
// Advertising related
{GET, "click/:id" /* */, wc.serveAdClick},