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.uploadManager = uploadManager
this.file = file this.file = file
this.name = file.name this.name = file.name
@@ -14,38 +14,38 @@ function UploadProgressBar(uploadManager, queueDiv, file){
this.file, this.file,
this.name, this.name,
(progress) => { this.onProgress(progress) }, (progress) => { this.onProgress(progress) },
(id) => { this.onFinished(id) }, (id) => { this.onFinished(id) },
(val, msg) => { this.onFailure(val, msg) } (val, msg) => { this.onFailure(val, msg) }
) )
// Browsers don't render the transition if the opacity is set and // 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) // updated in the same frame. So we have to wait a frame (or more)
// before changing the opacity to make sure the transition triggers // 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){ UploadProgressBar.prototype.onProgress = function (progress) {
this.uploadDiv.innerText = "Uploading... " + Math.round(progress*1000)/10 + "%\n" + this.name this.uploadDiv.innerText = "Uploading... " + Math.round(progress * 1000) / 10 + "%\n" + this.name
this.uploadDiv.style.background = 'linear-gradient(' this.uploadDiv.style.background = 'linear-gradient('
+'to right, ' + 'to right, '
+'var(--layer_3_color) 0%, ' + 'var(--layer_3_color) 0%, '
+'var(--highlight_color) '+ ((progress*100)) +'%, ' + 'var(--highlight_color) ' + ((progress * 100)) + '%, '
+'var(--layer_3_color) '+ ((progress*100)+1) +'%)' + 'var(--layer_3_color) ' + ((progress * 100) + 1) + '%)'
} }
UploadProgressBar.prototype.onFinished = function(id){ UploadProgressBar.prototype.onFinished = function (id) {
console.log("Upload finished: "+this.file.name+" "+id) console.log("Upload finished: " + this.file.name + " " + id)
this.uploadDiv.style.background = 'var(--layer_3_color)' this.uploadDiv.style.background = 'var(--layer_3_color)'
this.uploadDiv.href = '/u/'+id this.uploadDiv.href = '/u/' + id
this.uploadDiv.target= "_blank" this.uploadDiv.target = "_blank"
let fileImg = document.createElement("img") let fileImg = document.createElement("img")
fileImg.src = apiEndpoint+'/file/'+id+'/thumbnail' fileImg.src = apiEndpoint + '/file/' + id + '/thumbnail'
fileImg.alt = this.file.name fileImg.alt = this.file.name
let linkSpan = document.createElement("span") let linkSpan = document.createElement("span")
linkSpan.classList = "file_button_title" linkSpan.classList = "file_button_title"
linkSpan.innerText = domainURL()+"/u/"+id linkSpan.innerText = domainURL() + "/u/" + id
this.uploadDiv.innerHTML = "" // Remove uploading progress this.uploadDiv.innerHTML = "" // Remove uploading progress
this.uploadDiv.appendChild(fileImg) this.uploadDiv.appendChild(fileImg)
@@ -53,7 +53,7 @@ UploadProgressBar.prototype.onFinished = function(id){
this.uploadDiv.appendChild(document.createElement("br")) this.uploadDiv.appendChild(document.createElement("br"))
this.uploadDiv.appendChild(linkSpan) this.uploadDiv.appendChild(linkSpan)
} }
UploadProgressBar.prototype.onFailure = function(val, msg) { UploadProgressBar.prototype.onFailure = function (val, msg) {
if (val === "") { if (val === "") {
val = "Could not connect to server" 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.background = 'var(--danger_color)'
this.uploadDiv.style.color = 'var(--highlight_text_color)' this.uploadDiv.style.color = 'var(--highlight_text_color)'
this.uploadDiv.appendChild(document.createTextNode("Upload failed: ")) 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.createElement("br"))
this.uploadDiv.appendChild(document.createTextNode(this.file.name)) this.uploadDiv.appendChild(document.createTextNode(this.file.name))
console.log(msg) console.log(msg)
} }
let uploader = null let uploader = new UploadManager(apiEndpoint + "/file", uploadsFinished)
let shareTitle = "" let shareTitle = ""
let shareLink = "" let shareLink = ""
function handleUploads(files) { function handleUploads(files) {
if (uploader === null){
uploader = new UploadManager(apiEndpoint+"/file", uploadsFinished)
}
if (files.length === 0) { if (files.length === 0) {
return return
} }
@@ -101,8 +97,8 @@ function uploadsFinished() {
let uploadLog = uploader.finishedUploads() let uploadLog = uploader.finishedUploads()
if (uploadLog.length === 1) { if (uploadLog.length === 1) {
shareTitle = "Download "+uploadLog[0].fileName+" here" shareTitle = "Download " + uploadLog[0].fileName + " here"
shareLink = domainURL()+"/u/"+uploadLog[0].fileID shareLink = domainURL() + "/u/" + uploadLog[0].fileID
showShareButtons() showShareButtons()
} else if (uploadLog.length > 1) { } else if (uploadLog.length > 1) {
@@ -111,13 +107,13 @@ function uploadsFinished() {
createList( createList(
title, true, title, true,
).then(resp => { ).then(resp => {
console.log("Automatic list ID "+resp.id) console.log("Automatic list ID " + resp.id)
shareTitle = "View "+title+" here" shareTitle = "View " + title + " here"
shareLink = domainURL()+"/l/"+resp.id shareLink = domainURL() + "/l/" + resp.id
showShareButtons() showShareButtons()
}).catch(err => { }).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( return fetch(
apiEndpoint+"/list", apiEndpoint + "/list",
{ {
method: "POST", method: "POST",
headers: {"Content-Type": "application/json; charset=UTF-8"}, headers: { "Content-Type": "application/json; charset=UTF-8" },
body: JSON.stringify({ body: JSON.stringify({
"title": title, "title": title,
"anonymous": anonymous, "anonymous": anonymous,
@@ -142,7 +138,7 @@ function createList(title, anonymous) {
} }
).then(resp => { ).then(resp => {
if (!resp.ok) { if (!resp.ok) {
return Promise.reject("HTTP error: "+resp.status) return Promise.reject("HTTP error: " + resp.status)
} }
return resp.json() return resp.json()
}) })
@@ -163,7 +159,7 @@ function showShareButtons() {
} }
function copyLink() { function copyLink() {
if(copyText(shareLink)) { if (copyText(shareLink)) {
console.log('Text copied') console.log('Text copied')
document.querySelector("#btn_copy_link>span").textContent = "Copied!" document.querySelector("#btn_copy_link>span").textContent = "Copied!"
document.getElementById("btn_copy_link").classList.add("button_highlight") document.getElementById("btn_copy_link").classList.add("button_highlight")
@@ -177,35 +173,47 @@ function copyLink() {
* Upload Handlers * Upload Handlers
*/ */
// Relay click event to hidden file field // 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").click()
} }
document.getElementById("file_input_field").onchange = function(evt){ document.getElementById("file_input_field").onchange = e => {
// Start uploading the files async // 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 // This resets the file input field
document.getElementById("file_input_field").nodeValue = "" document.getElementById("file_input_field").nodeValue = ""
} }
document.getElementById("upload_text_button").onclick = function() { document.getElementById("upload_text_button").onclick = () => {
window.location.href = '/t/' window.location.href = '/t'
} }
/* /*
* Drag 'n Drop upload handlers * Drag 'n Drop upload handlers
*/ */
document.ondragover = function(e) { document.addEventListener("dragover", e => {
document.getElementById("file_drop_highlight").style.display = ""
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
} })
document.ondragenter = function(e) { document.addEventListener("dragenter", e => {
document.getElementById("file_drop_highlight").style.display = ""
e.preventDefault() e.preventDefault()
e.stopPropagation() 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) { if (e.dataTransfer && e.dataTransfer.files.length > 0) {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
@@ -214,37 +222,41 @@ document.addEventListener('drop', function(e){
window.setTimeout(handleUploads(e.dataTransfer.files), 1) 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 * Share buttons
*/ */
document.getElementById("btn_social_share").addEventListener("click", function() { document.getElementById("btn_social_share").addEventListener("click", () => {
window.navigator.share({ window.navigator.share({ title: "Pixeldrain", text: shareTitle, url: shareLink })
title: "Pixeldrain",
text: shareTitle,
url: shareLink
})
}) })
document.getElementById("btn_copy_link").addEventListener("click", function() { document.getElementById("btn_copy_link").addEventListener("click", () => {
copyLink() copyLink()
}) })
document.getElementById("btn_open_link").addEventListener("click", function() { document.getElementById("btn_open_link").addEventListener("click", () => {
window.open(shareLink, '_blank') 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) 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) 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) 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) 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) 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) { function renderListButton(apiURL, id, title, subtitle) {
let btn = document.createElement("a") let btn = document.createElement("a")
btn.classList = "file_button" btn.classList = "file_button"
btn.href = "/l/"+id btn.href = "/l/" + id
btn.target = "_blank" btn.target = "_blank"
let thumbnail = document.createElement("img") let thumbnail = document.createElement("img")
thumbnail.src = apiURL+"/list/"+id+"/thumbnail?width=80&height=80" thumbnail.src = apiURL + "/list/" + id + "/thumbnail?width=80&height=80"
thumbnail.alt = title thumbnail.alt = title
let titleSpan = document.createElement("span") let titleSpan = document.createElement("span")
titleSpan.classList = "file_button_title" titleSpan.classList = "file_button_title"
titleSpan.innerText = title titleSpan.innerText = title
let br = document.createElement("br") let br = document.createElement("br")
let subtitleSpan = document.createElement("span") let subtitleSpan = document.createElement("span")
subtitleSpan.classList = "file_button_subtitle" subtitleSpan.classList = "file_button_subtitle"
subtitleSpan.innerText = subtitle subtitleSpan.innerText = subtitle
@@ -276,12 +288,12 @@ function renderListButton(apiURL, id, title, subtitle) {
} }
// Create list button // Create list button
document.getElementById("btn_create_list").addEventListener("click", function(evt) { document.getElementById("btn_create_list").addEventListener("click", function (evt) {
let title = prompt( let title = prompt(
"You are creating a list containing " + uploader.finishedUploads().length + " files.\n" "You are creating a list containing " + uploader.finishedUploads().length + " files.\n"
+ "What do you want to call it?", "My New Album" + "What do you want to call it?", "My New Album"
) )
if(title === null){ if (title === null) {
return return
} }
createList(title, false).then(resp => { createList(title, false).then(resp => {
@@ -289,11 +301,11 @@ document.getElementById("btn_create_list").addEventListener("click", function(ev
renderListButton( renderListButton(
apiEndpoint, apiEndpoint,
resp.id, resp.id,
domainURL()+'/l/'+resp.id, domainURL() + '/l/' + resp.id,
"List creation finished!", "List creation finished!",
) )
) )
window.open('/l/'+resp.id, '_blank') window.open('/l/' + resp.id, '_blank')
}).catch(err => { }).catch(err => {
let div = document.createElement("div") let div = document.createElement("div")
div.className = "file_button" div.className = "file_button"
@@ -305,55 +317,55 @@ document.getElementById("btn_create_list").addEventListener("click", function(ev
}) })
let btnCopyLinks = document.getElementById("btn_copy_links") let btnCopyLinks = document.getElementById("btn_copy_links")
btnCopyLinks.addEventListener("click", function(){ btnCopyLinks.addEventListener("click", function () {
let text = "" let text = ""
let uploads = uploader.finishedUploads() let uploads = uploader.finishedUploads()
// Add the text to the textarea // Add the text to the textarea
for (let i = 0; i < uploads.length; i++) { for (let i = 0; i < uploads.length; i++) {
// Example: https://pixeldrain.com/u/abcd1234: Some_file.png // 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/")) { if (shareLink.includes("/l/")) {
text += "\n"+shareLink+" All "+uploads.length+" files\n" text += "\n" + shareLink + " All " + uploads.length + " files\n"
} }
// Copy the selected text // Copy the selected text
if(copyText(text)){ if (copyText(text)) {
btnCopyLinks.classList.add("button_highlight") btnCopyLinks.classList.add("button_highlight")
btnCopyLinks.innerHTML = "Links copied to clipboard!" btnCopyLinks.innerHTML = "Links copied to clipboard!"
}else{ } else {
btnCopyLinks.classList.add("button_red") btnCopyLinks.classList.add("button_red")
btnCopyLinks.innerHTML = "Copying links failed" btnCopyLinks.innerHTML = "Copying links failed"
} }
}) })
let btnCopyBBCode = document.getElementById("btn_copy_bbcode") let btnCopyBBCode = document.getElementById("btn_copy_bbcode")
btnCopyBBCode.addEventListener("click", function(){ btnCopyBBCode.addEventListener("click", function () {
let text = "" let text = ""
let uploads = uploader.finishedUploads() let uploads = uploader.finishedUploads()
// Add the text to the textarea // Add the text to the textarea
for (let i = 0; i < uploads.length; i++) { for (let i = 0; i < uploads.length; i++) {
// Example: [url=https://pixeldrain.com/u/abcd1234]Some_file.png[/url] // 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/")) { 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 // Copy the selected text
if(copyText(text)){ if (copyText(text)) {
btnCopyBBCode.classList.add("button_highlight") btnCopyBBCode.classList.add("button_highlight")
btnCopyBBCode.innerHTML = "BBCode copied to clipboard!" btnCopyBBCode.innerHTML = "BBCode copied to clipboard!"
}else{ } else {
btnCopyBBCode.classList.add("button_red") btnCopyBBCode.classList.add("button_red")
btnCopyBBCode.innerHTML = "Copying links failed" btnCopyBBCode.innerHTML = "Copying links failed"
} }
}) })
let btnCopyMarkdown = document.getElementById("btn_copy_markdown") let btnCopyMarkdown = document.getElementById("btn_copy_markdown")
btnCopyMarkdown.addEventListener("click", function(){ btnCopyMarkdown.addEventListener("click", function () {
let text = "" let text = ""
let uploads = uploader.finishedUploads() let uploads = uploader.finishedUploads()
@@ -362,17 +374,17 @@ btnCopyMarkdown.addEventListener("click", function(){
// Example: * [Some_file.png](https://pixeldrain.com/u/abcd1234) // Example: * [Some_file.png](https://pixeldrain.com/u/abcd1234)
if (uploads.length > 1) { text += " * " } 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/")) { if (shareLink.includes("/l/")) {
text += " * [All "+uploads.length+" files]("+shareLink+")\n" text += " * [All " + uploads.length + " files](" + shareLink + ")\n"
} }
// Copy the selected text // Copy the selected text
if(copyText(text)){ if (copyText(text)) {
btnCopyMarkdown.classList.add("button_highlight") btnCopyMarkdown.classList.add("button_highlight")
btnCopyMarkdown.innerHTML = "Markdown copied to clipboard!" btnCopyMarkdown.innerHTML = "Markdown copied to clipboard!"
}else{ } else {
btnCopyMarkdown.classList.add("button_red") btnCopyMarkdown.classList.add("button_red")
btnCopyMarkdown.innerHTML = "Copying links failed" btnCopyMarkdown.innerHTML = "Copying links failed"
} }
@@ -382,19 +394,35 @@ btnCopyMarkdown.addEventListener("click", function(){
/* /*
* Keyboard shortcuts * Keyboard shortcuts
*/ */
document.addEventListener("keydown", function(event){ document.addEventListener("keydown", e => {
if (event.ctrlKey || event.altKey || event.metaKey) { if (e.ctrlKey || e.altKey || e.metaKey) {
return // prevent custom shortcuts from interfering with system shortcuts return // prevent custom shortcuts from interfering with system shortcuts
} }
if (event.keyCode === 67) { // c if (e.key === "c") {
// Copy links to clipboard
document.getElementById("btn_copy_link").click() document.getElementById("btn_copy_link").click()
} else if (event.keyCode === 85) { // u } else if (e.key === "u") {
// Click the upload button
document.getElementById("file_input_field").click() document.getElementById("file_input_field").click()
} else if (event.keyCode === 84) { // t } else if (e.key === "t") {
// Click the text button
document.getElementById("upload_text_button").click() 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"> <div class="page_content">
{{if and .Authenticated .User.IsAdmin}} {{if and .Authenticated .User.IsAdmin}}
<div class="limit_width"> <div class="limit_width">
<a class="button" href="/admin/globals">Update global settings</a> <h3>Actions</h3>
<a class="button" href="/admin/abuse">Block files</a> </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> <h3>Bandwidth and views</h3>
</div> </div>
<div class="highlight_dark"> <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> <a href="/click/7wy9gg2J?target=%2F%23pro" class="button button_highlight">Pixeldrain Pro: Only €2 per month</a>
</div> </div>
</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 }} {{ else if not .Other.UserAdsEnabled }}
<div style="text-align: center; line-height: 1.3em; font-size: 13px;"> <div style="text-align: center; line-height: 1.3em; font-size: 13px;">
Thank you for supporting pixeldrain! Thank you for supporting pixeldrain!
@@ -295,7 +303,7 @@
{{ if eq .Other.AdType 5 }} {{ if eq .Other.AdType 5 }}
<!-- AdMaven --> <!-- AdMaven -->
<script data-cfasync="false" async src="//d227cncaprzd7y.cloudfront.net/?acncd=905608"></script> <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 --> <!-- PropellerAds -->
<script> <script>
// Load fires when the page is completely finished loading, // 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"> <img class="header_image" src="/res/img/header_orbitron_wide.png" alt="Header image">
</picture> </picture>
<br/> <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 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"> <div id="instruction_1" class="instruction_highlight" style="margin-top: 0;"><div class="limit_width">
<span class="big_number">1</span> <span class="big_number">1</span>
<span class="instruction_text">Select files to upload</span> <span class="instruction_text">Select files to upload</span>
<br/> <br/> You can also drop files on this page from your file
You can also drop files anywhere on this page from your file manager or paste an image from your clipboard
manager
</div></div> </div></div>
<input id="file_input_field" type="file" name="file" multiple="multiple"/> <input id="file_input_field" type="file" name="file" multiple="multiple"/>
<button id="upload_file_button" class="big_button button_highlight"> <button id="upload_file_button" class="big_button button_highlight">
<i class="icon small">cloud_upload</i> <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"> <button id="upload_text_button" class="big_button button_highlight">
<i class="icon small">text_fields</i> <i class="icon small">text_fields</i>
Upload <u>T</u>ext</button> Upload <u>T</u>ext
</button>
<br/> <br/>
<p> <p>
By uploading files to pixeldrain you acknowledge and accept our By uploading files to pixeldrain you acknowledge and accept our
<a href="/about#content-policy">content policy</a>. <a href="/about#content-policy">content policy</a>.
<p> <p>
<div id="instruction_2" class="instruction_highlight"> <div id="instruction_2" class="instruction_highlight">
<div class="limit_width"> <div class="limit_width">
<span class="big_number">2</span><span class="instruction_text">Wait for the files to finish uploading</span> <span class="big_number">2</span><span class="instruction_text">Wait for the files to finish uploading</span>
</div> </div>
</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="uploads_queue"></div>
<div id="instruction_3" class="instruction_highlight"> <div id="instruction_3" class="instruction_highlight">
@@ -197,30 +171,30 @@
{{template `copy.svg` .}}<br/><span><u>C</u>opy link</span> {{template `copy.svg` .}}<br/><span><u>C</u>opy link</span>
</button> </button>
<button id="btn_open_link" class="social_button" style="display: inline-block"> <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> </button>
<div id="social_buttons" style="display: inline-block"> <div id="social_buttons" style="display: inline-block">
<button id="btn_social_email" class="social_button"> <button id="btn_social_email" class="social_button">
{{template `email.svg` .}}<br/>E-Mail {{template `email.svg` .}}<br/><u>E</u>-Mail
</button> </button>
<button id="btn_social_twitter" class="social_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>
<button id="btn_social_facebook" class="social_button"> <button id="btn_social_facebook" class="social_button">
{{template `facebook.svg` .}}<br/>Facebook {{template `facebook.svg` .}}<br/><u>F</u>acebook
</button> </button>
<button id="btn_social_reddit" class="social_button"> <button id="btn_social_reddit" class="social_button">
{{template `reddit.svg` .}}<br/>Reddit {{template `reddit.svg` .}}<br/><u>R</u>eddit
</button> </button>
<button id="btc_social_tumblr" class="social_button"> <button id="btn_social_tumblr" class="social_button">
{{template `tumblr.svg` .}}<br/>Tumblr {{template `tumblr.svg` .}}<br/>Tu<u>m</u>blr
</button> </button>
</div> </div>
<br/><br/> <br/><br/>
<button id="btn_create_list"><i class="icon">list</i> Create list with uploaded files</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 all links to clipboard</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 markdown 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 BBCode to clipboard</button> <button id="btn_copy_bbcode"><i class="icon">content_copy</i> Copy <u>B</u>BCode to clipboard</button>
<br/> <br/>
<div id="created_lists"></div> <div id="created_lists"></div>
</div> </div>

View File

@@ -32,6 +32,7 @@ export default [
"filesystem", "filesystem",
"modal", "modal",
"user_buckets", "user_buckets",
"admin_abuse_reporters",
].map((name, index) => ({ ].map((name, index) => ({
input: `src/${name}.js`, input: `src/${name}.js`,
output: { 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> <script>
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher(); let dispatch = createEventDispatcher();
export let bucket_id; export let bucket_id;
export let target_dir; export let target_dir;
let upload_jobs = []; let upload_jobs = [];
let upload_threads = 0; let upload_threads = 0;
let max_upload_threads = 3; let max_upload_threads = 3;
// Adds files to the upload queue. The file_list parameter needs to be of type // 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 // FileList. Upload will also create the necessary directories to place nested
// files in. // files in.
export const upload = (file_list) => { export const upload = (file_list) => {
for (let i = 0; i < file_list.length; i++) { console.log(file_list)
upload_jobs.push({
file: file_list[i], for (let i = 0; i < file_list.length; i++) {
progress: 0, upload_jobs.push({
target_dir: target_dir.valueOf(), file: file_list[i],
uploading: false, progress: 0,
finished: false, target_dir: target_dir.valueOf(),
tries: 0, 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 if (
upload_jobs = upload_jobs; upload_jobs[i].uploading === false &&
upload_jobs[i].finished === false
while (upload_threads < max_upload_threads) { ) {
upload_threads++; job = upload_jobs[i];
setTimeout(upload_file, 1); job.uploading = true;
upload_jobs = upload_jobs;
break;
} }
}; }
if (job === null) {
upload_threads--;
const uploads_finished = () => { if (upload_threads === 0) {
dispatch("finished"); uploads_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 (job === null) { return;
upload_threads--; }
if (upload_threads === 0) { console.log(job);
uploads_finished();
} 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; return;
} }
console.log(job); if (xhr.status >= 100 && xhr.status < 400) {
// Request is a success
let form = new FormData(); // Finish the upload job
form.append("type", "file"); job.uploading = false;
form.append("file", job.file); job.finished = true;
upload_file();
let xhr = new XMLHttpRequest(); } else if (xhr.status >= 400) {
xhr.open( // Request failed
"POST", console.log(
"/api/filesystem/" + "Upload error. status: " +
bucket_id + xhr.status +
encodeURIComponent(job.target_dir + "/" + job.file.name), " response: " +
true xhr.response
); );
xhr.timeout = 21600000; // 6 hours, to account for slow connections let resp = JSON.parse(xhr.response);
if (job.tries === 3) {
// Report progress updates back to the caller // Upload failed
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; 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 { } else {
// Request did not arrive // Try again
if (job.tries === 3) { job.tries++;
// Upload failed job.uploading = false;
alert("upload failed " + xhr.responseText); job.finished = false;
job.uploading = false;
job.finished = false;
} else {
// Try again
job.tries++;
}
// Sleep the upload thread for 5 seconds
setTimeout(upload_file, 5000);
} }
upload_jobs = upload_jobs; // Sleep the upload thread for 5 seconds
}; setTimeout(upload_file, 5000);
xhr.send(form); } 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 // Sleep the upload thread for 5 seconds
let file_input; setTimeout(upload_file, 5000);
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());
} }
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> </script>
<style> <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> <script>
import { fs_delete_bucket } from "../filesystem/FilesystemAPI.svelte"; import { fs_delete_bucket } from "../filesystem/FilesystemAPI.svelte";
import { createEventDispatcher } from "svelte";
let dispatch = createEventDispatcher()
export let bucket export let bucket
let details_hidden = true let details_hidden = true
@@ -24,6 +26,8 @@ const delete_bucket = async () => {
} catch (err) { } catch (err) {
alert("Failed to delete bucket! "+err) alert("Failed to delete bucket! "+err)
} }
dispatch("refresh");
} }
</script> </script>

View File

@@ -1,17 +1,17 @@
<script> <script>
import { onMount } from "svelte"; import { onMount } from "svelte";
import Bucket from "./Bucket.svelte"; import UserBucket from "./UserBucket.svelte";
import Spinner from "../util/Spinner.svelte"; import Spinner from "../util/Spinner.svelte";
import { fs_get_buckets } from "../filesystem/FilesystemAPI.svelte"; import { fs_get_buckets, fs_create_bucket } from "../filesystem/FilesystemAPI.svelte";
import NewBucket from "./NewBucket.svelte";
let loading = true; let loading = true
let buckets = []; let buckets = []
let new_bucket; let creating_bucket = false
let creating_bucket = false; let new_bucket_name
const get_buckets = async () => { const get_buckets = async () => {
loading = true;
try { try {
let resp = await fs_get_buckets(); let resp = await fs_get_buckets();
buckets = resp.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); onMount(get_buckets);
</script> </script>
@@ -42,29 +59,40 @@ onMount(get_buckets);
</button> </button>
</div> </div>
{#if creating_bucket} {#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} {/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} {#each buckets as bucket}
<Bucket bucket={bucket}></Bucket> <UserBucket bucket={bucket} on:refresh={get_buckets}></UserBucket>
{/each} {/each}
<br/>
<h2>Temporary buckets</h2>
<p>
</p>
</div> </div>
</div> </div>
<style> <style>
.spinner_container { .spinner_container {
display: inline-block; position: absolute;
top: 10px;
left: 10px;
height: 100px; height: 100px;
width: 100px; width: 100px;
} }

View File

@@ -50,15 +50,15 @@ func adType() int {
switch i := rand.Intn(20); i { switch i := rand.Intn(20); i {
case 0, 1: // 10% case 0, 1: // 10%
return amarulaSolutions return amarulaSolutions
case 2: // 5% case 2: // 5%, also shows propellerads
return pdpro1 return pdpro1
case 3: // 5% case 3: // 5%, also shows propellerads
return pdpro2 return pdpro2
case 4: // 5% case 4: // 5%, also shows propellerads
return pdpro3 return pdpro3
case 5: // 5% case 5: // 5%, also shows propellerads
return pdpro4 return pdpro4
default: // 70% default: // 70%, also shows a-ads
return propellerAds return propellerAds
// default: // default:
// panic(fmt.Errorf("random number generator returned unrecognised number: %d", i)) // 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)}, {PST, "knoxfs_activate" /* */, wc.serveForm(wc.knoxfsLinkForm, true)},
// Admin settings // Admin settings
{GET, "admin" /* */, wc.serveTemplate("admin_panel", true)}, {GET, "admin" /* */, wc.serveTemplate("admin_panel", true)},
{GET, "admin/globals" /**/, wc.serveForm(wc.adminGlobalsForm, true)}, {GET, "admin/globals" /* */, wc.serveForm(wc.adminGlobalsForm, true)},
{PST, "admin/globals" /**/, wc.serveForm(wc.adminGlobalsForm, true)}, {PST, "admin/globals" /* */, wc.serveForm(wc.adminGlobalsForm, true)},
{GET, "admin/abuse" /* */, wc.serveForm(wc.adminAbuseForm, true)}, {GET, "admin/abuse" /* */, wc.serveForm(wc.adminAbuseForm, true)},
{PST, "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 // Advertising related
{GET, "click/:id" /* */, wc.serveAdClick}, {GET, "click/:id" /* */, wc.serveAdClick},