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

@@ -69,15 +69,11 @@ UploadProgressBar.prototype.onFailure = function(val, msg) {
}
let uploader = null
let uploader = new UploadManager(apiEndpoint + "/file", uploadsFinished)
let shareTitle = ""
let shareLink = ""
function handleUploads(files) {
if (uploader === null){
uploader = new UploadManager(apiEndpoint+"/file", uploadsFinished)
}
if (files.length === 0) {
return
}
@@ -178,34 +174,46 @@ function copyLink() {
*/
// Relay click event to hidden file field
document.getElementById("upload_file_button").onclick = function() {
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)
})
@@ -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,6 +186,14 @@
<a href="/click/7wy9gg2J?target=%2F%23pro" class="button button_highlight">Pixeldrain Pro: Only €2 per month</a>
</div>
</div>
{{ 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;">
@@ -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

@@ -13,6 +13,8 @@
// 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],
@@ -170,7 +172,8 @@
if (e.clipboardData.files[0]) {
e.preventDefault();
e.stopPropagation();
console.log(e.clipboardData.files[0].getAsFile());
console.log(e.clipboardData.files[0]);
upload(e.clipboardData.files)
}
};
</script>

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

@@ -166,6 +166,7 @@ func New(
{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},