Add oembed tags. Fix sharing and copy link button. Add text and file viewer
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
<script>
|
||||
import { tick } from "svelte";
|
||||
|
||||
|
||||
let container
|
||||
let text_type = ""
|
||||
|
||||
|
@@ -4,12 +4,19 @@ export let navigator
|
||||
</script>
|
||||
|
||||
{#each state.path as node, i (node.path)}
|
||||
{@const shared = node.id !== undefined && node.id !== "me"}
|
||||
<a
|
||||
href={state.path_root+node.path}
|
||||
class="breadcrumb button"
|
||||
class:button_highlight={state.base_index === i}
|
||||
on:click|preventDefault={() => {navigator.navigate(node.path, true)}}>
|
||||
on:click|preventDefault={() => {navigator.navigate(node.path, true)}}
|
||||
>
|
||||
{#if shared}
|
||||
<i class="icon small">share</i>
|
||||
{/if}
|
||||
<div class="node_name" class:nopad={shared}>
|
||||
{node.name}
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
|
||||
@@ -17,8 +24,19 @@ export let navigator
|
||||
.breadcrumb {
|
||||
min-width: 1em;
|
||||
text-align: center;
|
||||
padding: 6px 8px;
|
||||
margin: 4px;
|
||||
word-break: break-all;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.node_name {
|
||||
margin: 6px 8px;
|
||||
}
|
||||
.nopad {
|
||||
margin-left: 0;
|
||||
}
|
||||
.icon {
|
||||
margin: 2px 4px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,11 +1,9 @@
|
||||
<script>
|
||||
import { fs_rename, fs_update } from "./FilesystemAPI";
|
||||
import { fs_delete_all, fs_rename, fs_update } from "./FilesystemAPI";
|
||||
import Modal from "../util/Modal.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let bucket = ""
|
||||
export let navigator
|
||||
let file = {
|
||||
path: "",
|
||||
name: "",
|
||||
@@ -14,7 +12,9 @@ let file = {
|
||||
};
|
||||
|
||||
export let visible
|
||||
export const edit = (f) => {
|
||||
export const edit = (f, t = "file") => {
|
||||
tab = t
|
||||
|
||||
console.log("Editing file", f)
|
||||
file = f
|
||||
|
||||
@@ -26,6 +26,8 @@ export const edit = (f) => {
|
||||
visible = true
|
||||
}
|
||||
|
||||
let tab = "file"
|
||||
|
||||
let file_name = ""
|
||||
let shared = false
|
||||
let read_password = ""
|
||||
@@ -33,6 +35,7 @@ let write_password = ""
|
||||
let mode = ""
|
||||
|
||||
const save = async () => {
|
||||
console.debug("Saving file", file.path)
|
||||
try {
|
||||
await fs_update(
|
||||
bucket,
|
||||
@@ -58,28 +61,83 @@ const save = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
dispatch("navigate", {path: file.path, push_history: false})
|
||||
navigator.navigate(file.path, false)
|
||||
}
|
||||
const delete_file = async () => {
|
||||
try {
|
||||
await fs_delete_all(bucket, file.path)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
alert(err)
|
||||
return
|
||||
}
|
||||
|
||||
navigator.navigate(file.path, false)
|
||||
visible = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:visible={visible} title="Edit" width="700px">
|
||||
<form on:submit|preventDefault={save} style="display: flex; padding: 8px;">
|
||||
<Modal bind:visible={visible} title="Edit {file.name}" width="600px" on:save={save} form="file_edit_form">
|
||||
<div class="tab_bar">
|
||||
<button class:button_highlight={tab === "file"} on:click={() => tab = "file"}>
|
||||
<i class="icon">edit</i>
|
||||
Edit file
|
||||
</button>
|
||||
<button class:button_highlight={tab === "share"} on:click={() => tab = "share"}>
|
||||
<i class="icon">share</i>
|
||||
Sharing settings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="file_edit_form" on:submit|preventDefault={save} style="display: flex; padding: 8px;">
|
||||
{#if tab === "file"}
|
||||
<div class="form">
|
||||
<span class="header">File settings</span>
|
||||
<label for="file_name">Name:</label>
|
||||
<input bind:value={file_name} id="file_name" type="text" class="form_input"/>
|
||||
<label for="file_name">Shared:</label>
|
||||
<input bind:checked={shared} id="file_name" type="checkbox" class="form_input"/>
|
||||
<label for="mode">Mode:</label>
|
||||
<input bind:value={mode} id="mode" type="text" class="form_input"/>
|
||||
<span class="header">Delete</span>
|
||||
<p>
|
||||
Delete this file or directory. If this is a directory then all
|
||||
subfiles will be deleted as well. This action cannot be undone.
|
||||
</p>
|
||||
<button on:click|preventDefault={delete_file} class="button_red" style="align-self: flex-start;">
|
||||
<i class="icon small">delete</i> Delete
|
||||
</button>
|
||||
</div>
|
||||
{:else if tab === "share"}
|
||||
<div class="form">
|
||||
<span class="header">
|
||||
Sharing settings
|
||||
</span>
|
||||
<p>
|
||||
When a file or directory is shared it can be accessed
|
||||
through a unique link. You can get the URL with the 'Copy
|
||||
link' button on the toolbar, or share the link with the
|
||||
'Share' button. If you share a directory all the files
|
||||
within the directory are also accessible from the link.
|
||||
</p>
|
||||
<div>
|
||||
<input bind:checked={shared} id="shared" type="checkbox" class="form_input"/>
|
||||
<label for="shared">Share this file or directory</label>
|
||||
</div>
|
||||
<label for="read_password">Read password:</label>
|
||||
<input bind:value={read_password} id="read_password" type="text" class="form_input"/>
|
||||
<label for="write_password">Write password:</label>
|
||||
<input bind:value={write_password} id="write_password" type="text" class="form_input"/>
|
||||
<label for="mode">Mode:</label>
|
||||
<input bind:value={mode} id="mode" type="text" class="form_input"/>
|
||||
<button type="submit" style="flex: 0 0 auto" class="button_highlight">
|
||||
<i class="icon">save</i> Save
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.header {
|
||||
margin: 0.5em 0;
|
||||
font-size: 1.5em;
|
||||
border-bottom: 1px var(--separator) solid;
|
||||
}
|
||||
.tab_bar {
|
||||
border-bottom: 2px solid var(--separator);
|
||||
}
|
||||
</style>
|
||||
|
@@ -132,6 +132,8 @@ const download = () => {
|
||||
toolbar_visible={toolbar_visible}
|
||||
edit_window={edit_window}
|
||||
on:loading={e => {state.loading = e.detail}}
|
||||
on:open_sibling={e => navigator.open_sibling(e.detail)}
|
||||
on:download={download}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -151,7 +153,7 @@ const download = () => {
|
||||
bind:this={edit_window}
|
||||
bind:visible={edit_visible}
|
||||
bucket={state.root.id}
|
||||
on:navigate={e => navigator.navigate(e.path, e.push_history)}
|
||||
navigator={navigator}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@@ -16,7 +16,9 @@ export const fs_thumbnail_url = (bucket, path, width = 128, height = 128) => {
|
||||
}
|
||||
|
||||
export const fs_node_type = node => {
|
||||
if (node.file_type === "application/bittorrent" || node.file_type === "application/x-bittorrent") {
|
||||
if (node.type === "dir") {
|
||||
return "dir"
|
||||
} else if (node.file_type === "application/bittorrent" || node.file_type === "application/x-bittorrent") {
|
||||
return "torrent"
|
||||
} else if (node.file_type === "application/zip") {
|
||||
return "zip"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { fs_get_node } from "./FilesystemAPI";
|
||||
import { fs_split_path } from "./FilesystemUtil";
|
||||
import { fs_node_type, fs_split_path } from "./FilesystemUtil";
|
||||
|
||||
export let state = {
|
||||
// Parts of the raw API response
|
||||
@@ -69,13 +69,20 @@ export const open_node = (node, push_history) => {
|
||||
node.path.forEach(cleanup_func)
|
||||
node.children.forEach(cleanup_func)
|
||||
|
||||
// Update window title and navigation history
|
||||
// Update window title and navigation history. If push_history is false we
|
||||
// still replace the URL with replaceState. This way the user is not greeted
|
||||
// to a 404 page when refreshing after renaming a file
|
||||
window.document.title = node.path[node.base_index].name+" ~ pixeldrain"
|
||||
if (push_history) {
|
||||
window.history.pushState(
|
||||
{}, window.document.title,
|
||||
"/d/"+node.path[0].id+node.path[node.base_index].path,
|
||||
)
|
||||
} else {
|
||||
window.history.replaceState(
|
||||
{}, window.document.title,
|
||||
"/d/"+node.path[0].id+node.path[node.base_index].path,
|
||||
)
|
||||
}
|
||||
|
||||
// If the new node is a child of the previous node we save the parent's
|
||||
@@ -99,30 +106,7 @@ export const open_node = (node, push_history) => {
|
||||
state.permissions = node.permissions
|
||||
|
||||
// Update the viewer area with the right viewer type
|
||||
if (state.base.type === "bucket" || state.base.type === "dir") {
|
||||
state.viewer_type = "dir"
|
||||
} else if (state.base.file_type.startsWith("image")) {
|
||||
state.viewer_type = "image"
|
||||
} else if (
|
||||
state.base.file_type.startsWith("audio") ||
|
||||
state.base.file_type === "application/ogg" ||
|
||||
state.base.name.endsWith(".mp3")
|
||||
) {
|
||||
state.viewer_type = "audio"
|
||||
} else if (
|
||||
state.base.file_type.startsWith("video") ||
|
||||
state.base.file_type === "application/matroska" ||
|
||||
state.base.file_type === "application/x-matroska"
|
||||
) {
|
||||
state.viewer_type = "video"
|
||||
} else if (
|
||||
state.base.file_type === "application/pdf" ||
|
||||
state.base.file_type === "application/x-pdf"
|
||||
) {
|
||||
state.viewer_type = "pdf"
|
||||
} else {
|
||||
state.viewer_type = ""
|
||||
}
|
||||
state.viewer_type = fs_node_type(state.base)
|
||||
|
||||
console.debug("Opened node", node)
|
||||
|
||||
|
@@ -1,26 +1,78 @@
|
||||
<script context="module">
|
||||
export const generate_share_url = path => {
|
||||
let share_url = ""
|
||||
let bucket_idx = -1
|
||||
|
||||
// Find the first node in the path that has a public ID
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
if (path[i].id !== undefined && path[i].id !== "me") {
|
||||
bucket_idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (bucket_idx !== -1) {
|
||||
share_url = window.location.protocol+"//"+
|
||||
window.location.host+"/d/"+
|
||||
path[bucket_idx].id
|
||||
|
||||
// Construct the path starting from the bucket
|
||||
for (let i = bucket_idx+1; i < path.length; i++) {
|
||||
share_url += "/" + encodeURI(path[i].name)
|
||||
}
|
||||
}
|
||||
|
||||
return share_url
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { fs_update } from "./FilesystemAPI";
|
||||
|
||||
export let visible = false
|
||||
export let share_url = ""
|
||||
$: {
|
||||
if (share_url === "") {
|
||||
visible = false
|
||||
}
|
||||
}
|
||||
|
||||
const share = async () => {
|
||||
console.debug("Making file sharable", state.base)
|
||||
try {
|
||||
await fs_update(state.root.id, state.base.path, {shared: true})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
alert(err)
|
||||
return
|
||||
}
|
||||
|
||||
navigator.navigate(state.base.path, false)
|
||||
}
|
||||
|
||||
export let state
|
||||
export let navigator
|
||||
|
||||
const share_email = () => {
|
||||
window.open(
|
||||
'mailto:please@set.address?subject=File%20on%20pixeldrain&body='+encodeURIComponent(window.location.href)
|
||||
'mailto:please@set.address?subject=File%20on%20pixeldrain&body='+encodeURIComponent(share_url)
|
||||
);
|
||||
}
|
||||
const share_reddit = () => {
|
||||
window.open('https://www.reddit.com/submit?url='+encodeURIComponent(window.location.href));
|
||||
window.open('https://www.reddit.com/submit?url='+encodeURIComponent(share_url));
|
||||
}
|
||||
const share_twitter = () => {
|
||||
window.open('https://twitter.com/share?url='+encodeURIComponent(window.location.href));
|
||||
window.open('https://twitter.com/share?url='+encodeURIComponent(share_url));
|
||||
}
|
||||
const share_facebook = () => {
|
||||
window.open('http://www.facebook.com/sharer.php?u='+encodeURIComponent(window.location.href));
|
||||
window.open('http://www.facebook.com/sharer.php?u='+encodeURIComponent(share_url));
|
||||
}
|
||||
const share_tumblr = () => {
|
||||
window.open('http://www.tumblr.com/share/link?url='+encodeURIComponent(window.location.href));
|
||||
window.open('http://www.tumblr.com/share/link?url='+encodeURIComponent(share_url));
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="sharebar" class:visible>
|
||||
{#if share_url !== ""}
|
||||
Share on:<br/>
|
||||
<button class="button_full_width" on:click={share_email}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
@@ -57,6 +109,13 @@ const share_tumblr = () => {
|
||||
<br/>
|
||||
Tumblr
|
||||
</button>
|
||||
{:else}
|
||||
This file or directory is not currently shared. Would you like to make it sharable?
|
||||
<button on:click={share}>
|
||||
<i class="icon">share</i>
|
||||
Make sharable
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@@ -1,7 +1,8 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import Sharebar from "./Sharebar.svelte";
|
||||
import Sharebar, { generate_share_url } from "./Sharebar.svelte";
|
||||
import { formatDataVolume, formatThousands } from "../util/Formatting.svelte";
|
||||
import { copy_text } from "../util/Util.svelte";
|
||||
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
@@ -31,6 +32,35 @@ $: {
|
||||
$: total_directories = state.children.reduce((acc, cur) => cur.type === "dir" ? acc + 1 : acc, 0)
|
||||
$: total_files = state.children.reduce((acc, cur) => cur.type === "file" ? acc + 1 : acc, 0)
|
||||
$: total_file_size = state.children.reduce((acc, cur) => acc + cur.file_size, 0)
|
||||
|
||||
$: share_url = generate_share_url(state.path)
|
||||
let link_copied = false
|
||||
const copy_link = () => {
|
||||
if (share_url === "") {
|
||||
edit_window.edit(state.base, "share")
|
||||
return
|
||||
}
|
||||
|
||||
copy_text(share_url)
|
||||
link_copied = true
|
||||
setTimeout(() => {link_copied = false}, 60000)
|
||||
}
|
||||
let share = () => {
|
||||
if (share_url === "") {
|
||||
edit_window.edit(state.base, "share")
|
||||
return
|
||||
}
|
||||
|
||||
if (navigator.share) {
|
||||
navigator.share({
|
||||
title: state.base.name,
|
||||
text: "I would like to share '" + state.base.name + "' with you",
|
||||
url: share_url
|
||||
})
|
||||
} else {
|
||||
sharebar_visible = !sharebar_visible
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="toolbar" class:toolbar_visible={visible}>
|
||||
@@ -65,18 +95,25 @@ $: total_file_size = state.children.reduce((acc, cur) => acc + cur.file_size, 0)
|
||||
<i class="icon">save</i> Download
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<button id="btn_download_list" class="toolbar_button" style="display: none;">
|
||||
<i class="icon">save</i> DL all files
|
||||
</button>
|
||||
<button id="btn_copy" class="toolbar_button">
|
||||
|
||||
{#if state.base.path !== "/"}
|
||||
<button id="btn_copy" class="toolbar_button" on:click={copy_link} class:button_highlight={link_copied}>
|
||||
<i class="icon">content_copy</i> <u>C</u>opy Link
|
||||
</button>
|
||||
<button on:click={() => sharebar_visible = !sharebar_visible} class="toolbar_button" class:button_highlight={sharebar_visible}>
|
||||
|
||||
<button on:click={share} class="toolbar_button" class:button_highlight={sharebar_visible}>
|
||||
<i class="icon">share</i> Share
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<button on:click={() => details_visible = !details_visible} class="toolbar_button" class:button_highlight={details_visible}>
|
||||
<i class="icon">help</i> Deta<u>i</u>ls
|
||||
</button>
|
||||
|
||||
{#if state.base.path !== "/"}
|
||||
<button on:click={() => edit_window.edit(state.base)} class="toolbar_button" class:button_highlight={edit_visible}>
|
||||
<i class="icon">edit</i> <u>E</u>dit
|
||||
@@ -84,7 +121,7 @@ $: total_file_size = state.children.reduce((acc, cur) => acc + cur.file_size, 0)
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Sharebar visible={sharebar_visible}/>
|
||||
<Sharebar visible={sharebar_visible} state={state} navigator={navigator} share_url={share_url}/>
|
||||
|
||||
<style>
|
||||
.toolbar {
|
||||
|
@@ -98,6 +98,7 @@ td {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
vertical-align: middle;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.node_name {
|
||||
width: 100%;
|
||||
|
31
svelte/src/filesystem/viewers/File.svelte
Normal file
31
svelte/src/filesystem/viewers/File.svelte
Normal file
@@ -0,0 +1,31 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import IconBlock from "../../file_viewer/viewers/IconBlock.svelte";
|
||||
import { fs_thumbnail_url } from "../FilesystemUtil";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let state
|
||||
</script>
|
||||
|
||||
<h1>{state.base.name}</h1>
|
||||
|
||||
<IconBlock icon_href={fs_thumbnail_url(state.root.id, state.base.path)}>
|
||||
Type: {state.base.file_type}<br/>
|
||||
No preview is available for this file type. Download to view it locally.
|
||||
<br/>
|
||||
<button class="button_highlight" on:click={() => {dispatch("download")}}>
|
||||
<i class="icon">download</i>
|
||||
<span>Download</span>
|
||||
</button>
|
||||
</IconBlock>
|
||||
|
||||
<style>
|
||||
h1 {
|
||||
text-shadow: 1px 1px 3px var(--shadow_color);
|
||||
line-break: anywhere;
|
||||
}
|
||||
.icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
@@ -1,8 +1,10 @@
|
||||
<script>
|
||||
import FileManager from "../filemanager/FileManager.svelte";
|
||||
import Audio from "./Audio.svelte";
|
||||
import File from "./File.svelte";
|
||||
import Image from "./Image.svelte";
|
||||
import Pdf from "./PDF.svelte";
|
||||
import Text from "./Text.svelte";
|
||||
import Video from "./Video.svelte";
|
||||
|
||||
export let navigator
|
||||
@@ -20,13 +22,17 @@ export let edit_window
|
||||
on:loading
|
||||
/>
|
||||
{:else if state.viewer_type === "audio"}
|
||||
<Audio state={state} on:open_sibling={e => {navigator.open_sibling(e.detail)}}/>
|
||||
<Audio state={state} on:open_sibling/>
|
||||
{:else if state.viewer_type === "image"}
|
||||
<Image state={state} on:open_sibling={e => {navigator.open_sibling(e.detail)}}/>
|
||||
<Image state={state} on:open_sibling/>
|
||||
{:else if state.viewer_type === "video"}
|
||||
<Video state={state} on:open_sibling={e => {navigator.open_sibling(e.detail)}}/>
|
||||
<Video state={state} on:open_sibling/>
|
||||
{:else if state.viewer_type === "pdf"}
|
||||
<Pdf state={state}/>
|
||||
{:else if state.viewer_type === "text"}
|
||||
<Text state={state}/>
|
||||
{:else}
|
||||
<File state={state}/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
53
svelte/src/filesystem/viewers/Text.svelte
Normal file
53
svelte/src/filesystem/viewers/Text.svelte
Normal file
@@ -0,0 +1,53 @@
|
||||
<script>
|
||||
import { fs_file_url } from "../FilesystemUtil";
|
||||
|
||||
export let state
|
||||
let text_pre
|
||||
|
||||
$: set_file(state.base)
|
||||
|
||||
export const set_file = file => {
|
||||
console.debug("Loading text file", file.name)
|
||||
|
||||
if (file.size > 1 << 22) { // File larger than 4 MiB
|
||||
text_pre.innerText = "File is too large to view online.\nPlease download and view it locally."
|
||||
return
|
||||
}
|
||||
|
||||
fetch(fs_file_url(state.root.id, file.path)).then(resp => {
|
||||
if (!resp.ok) { return Promise.reject(resp.status) }
|
||||
return resp.text()
|
||||
}).then(resp => {
|
||||
text_pre.innerText = resp
|
||||
}).catch(err => {
|
||||
text_pre.innerText = "Error loading file: " + err
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<pre bind:this={text_pre}>
|
||||
Loading...
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
background: var(--body_color);
|
||||
text-align: left;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
line-height: 1.5em;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.container > pre {
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
white-space: pre-wrap;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
@@ -3,10 +3,15 @@
|
||||
// incremented every time a modal is shown
|
||||
let global_index = 10000;
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
// Form can be used to turn the modal into a save dialog. Enter the ID of a form
|
||||
// inside the modal and the modal will provide a submit button for that form
|
||||
export let form = ""
|
||||
|
||||
export let title = "";
|
||||
export let width = "800px";
|
||||
export let height = "auto";
|
||||
@@ -50,13 +55,36 @@ const keydown = e => {
|
||||
{#if visible}
|
||||
<div class="background" use:load_bg on:click={hide} transition:fade={{duration: 200}} on:keydown={keydown}>
|
||||
<div class="top_padding"></div>
|
||||
<div class="window" use:load_modal on:click|stopPropagation role="dialog" aria-modal="true" on:keydown={keydown}>
|
||||
<div
|
||||
class="window"
|
||||
class:small_radius={form !== ""}
|
||||
use:load_modal
|
||||
on:click|stopPropagation
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
on:keydown={keydown}
|
||||
>
|
||||
<div class="header">
|
||||
<slot name="title">
|
||||
{#if form !== ""}
|
||||
<button class="button" on:click={hide}>
|
||||
<i class="icon">close</i> Cancel
|
||||
</button>
|
||||
<div class="title">{title}</div>
|
||||
<button
|
||||
class="button button_highlight"
|
||||
type="submit"
|
||||
form="{form}"
|
||||
on:click={() => {dispatch("save"); hide()}}
|
||||
>
|
||||
<i class="icon">save</i> Save
|
||||
</button>
|
||||
{:else}
|
||||
<div class="title">{title}</div>
|
||||
<button class="button round" on:click={hide}>
|
||||
<i class="icon">close</i>
|
||||
</button>
|
||||
{/if}
|
||||
</slot>
|
||||
</div>
|
||||
<div class="body" class:padding>
|
||||
@@ -69,7 +97,7 @@ const keydown = e => {
|
||||
|
||||
<style>
|
||||
.background {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -95,10 +123,13 @@ these padding divs to move it 25% up */
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
border-radius: 20px 20px 8px 8px;
|
||||
border-radius: 16px 16px 8px 8px;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
}
|
||||
.small_radius {
|
||||
border-radius: 8px;
|
||||
}
|
||||
.header {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
@@ -1,77 +0,0 @@
|
||||
package webcontroller
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"fornaxian.tech/log"
|
||||
"fornaxian.tech/util"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
blackfriday "github.com/russross/blackfriday/v2"
|
||||
)
|
||||
|
||||
// ServeFilePreview controller for GET /u/:id/preview
|
||||
func (wc *WebController) serveFilePreview(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
if p.ByName("id") == "demo" || p.ByName("id") == "adsplus" || p.ByName("id") == "pixfuture" {
|
||||
serveFilePreviewDemo(w) // Required for a-ads.com quality check
|
||||
return
|
||||
}
|
||||
|
||||
apiKey, _ := wc.getAPIKey(r)
|
||||
api := wc.api.Login(apiKey).RealIP(util.RemoteAddress(r)).RealAgent(r.UserAgent())
|
||||
|
||||
file, err := api.GetFileInfo(p.ByName("id")) // TODO: Error handling
|
||||
if err != nil {
|
||||
wc.serveNotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(file.MimeType, "text") &&
|
||||
(strings.HasSuffix(file.Name, ".md") || strings.HasSuffix(file.Name, ".markdown")) {
|
||||
if file.Size > 1<<22 { // Prevent out of memory errors
|
||||
w.Write([]byte("File is too large to view online.\nPlease download and view it locally."))
|
||||
return
|
||||
}
|
||||
|
||||
body, err := api.GetFile(file.ID)
|
||||
if err != nil {
|
||||
log.Error("Can't download text file for preview: %s", err)
|
||||
w.Write([]byte("An error occurred while downloading this file."))
|
||||
return
|
||||
}
|
||||
defer body.Close()
|
||||
|
||||
bodyBytes, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
log.Error("Can't read text file for preview: %s", err)
|
||||
w.Write([]byte("An error occurred while reading this file."))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(bluemonday.UGCPolicy().SanitizeBytes(blackfriday.Run(bodyBytes)))
|
||||
}
|
||||
}
|
||||
|
||||
// ServeFilePreviewDemo serves the content of the demo file. It contains a nice
|
||||
// message to the human reviewers of the a-ads ad network so they can properly
|
||||
// categorize the website.
|
||||
func serveFilePreviewDemo(w http.ResponseWriter) {
|
||||
io.WriteString(w,
|
||||
`<pre style="line-height: 1em; white-space: pre-wrap; overflow: hidden;">
|
||||
, __ _
|
||||
/|/ \o | | | o
|
||||
|___/ _ | | __| ,_ __, _ _
|
||||
| | /\/ |/ |/ / | / | / | | / |/ |
|
||||
| |_/ /\_/|__/|__/\_/|_/ |_/\_/|_/|_/ | |_/
|
||||
|
||||
This is a demonstration of pixeldrain's file viewer.
|
||||
|
||||
The website automatically detects what kind of file you requested and prepares a page for viewing it properly. This is what a text file would look like on pixeldrain. You can upload your own text file at pixeldrain.com/t.
|
||||
|
||||
Pixeldrain is a free service for sharing files with large or small groups of people. For more information visit the home page by pressing the home button on the toolbar at the left side of the screen.
|
||||
|
||||
Press the Details button or "i" for more info about pixeldrain's file viewer.
|
||||
</pre>`)
|
||||
}
|
@@ -3,9 +3,11 @@ package webcontroller
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"fornaxian.tech/log"
|
||||
"fornaxian.tech/pixeldrain_api_client/pixelapi"
|
||||
"fornaxian.tech/util"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
@@ -35,8 +37,70 @@ func (wc *WebController) serveDirectory(w http.ResponseWriter, r *http.Request,
|
||||
|
||||
td.Title = fmt.Sprintf("%s ~ pixeldrain", node.Path[node.BaseIndex].Name)
|
||||
td.Other = node
|
||||
td.OGData = wc.metadataFromFilesystem(node)
|
||||
err = wc.templates.Get().ExecuteTemplate(w, "filesystem", td)
|
||||
if err != nil && !util.IsNetError(err) {
|
||||
log.Error("Error executing template filesystem: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *WebController) metadataFromFilesystem(f pixelapi.FilesystemPath) (og ogData) {
|
||||
var (
|
||||
base = f.Path[f.BaseIndex]
|
||||
name = base.Name
|
||||
filetype = base.Type
|
||||
filepath = url.PathEscape(f.Path[0].ID + base.Path)
|
||||
pageurl = wc.config.WebsiteAddress + "/d/" + filepath
|
||||
fileurl = wc.config.WebsiteAddress + "/api/filesystem/" + filepath
|
||||
thumbnailurl = wc.config.WebsiteAddress + "/api/filesystem/" + filepath + "?thumbnail"
|
||||
)
|
||||
|
||||
og.addProp("og:title", name)
|
||||
og.addProp("og:site_name", "pixeldrain")
|
||||
og.addProp("og:description", "This file has been shared with you on pixeldrain")
|
||||
og.addProp("og:url", pageurl)
|
||||
og.addProp("description", "This file has been shared with you on pixeldrain")
|
||||
og.addName("description", "This file has been shared with you on pixeldrain")
|
||||
og.addName("keywords", "pixeldrain,shared,sharing,upload,file,free")
|
||||
og.addName("twitter:title", name)
|
||||
og.addName("twitter:site", "@Fornax96")
|
||||
og.addName("twitter:domain", "pixeldrain.com")
|
||||
|
||||
if strings.HasPrefix(filetype, "image") {
|
||||
og.addProp("og:type", "article")
|
||||
og.addProp("og:image", fileurl)
|
||||
og.addProp("og:image:url", fileurl)
|
||||
og.addProp("og:image:secure_url", fileurl)
|
||||
og.addProp("og:image:type", filetype)
|
||||
|
||||
og.addName("twitter:card", "summary_large_image")
|
||||
og.addName("twitter:image", fileurl)
|
||||
og.addLink("image_src", fileurl)
|
||||
} else if strings.HasPrefix(filetype, "video") {
|
||||
og.addProp("og:type", "video.other")
|
||||
og.addProp("og:image", thumbnailurl)
|
||||
og.addProp("og:video", fileurl)
|
||||
og.addProp("og:video:url", fileurl)
|
||||
og.addProp("og:video:secure_url", fileurl)
|
||||
og.addProp("og:video:type", filetype)
|
||||
|
||||
og.addName("twitter:card", "player")
|
||||
og.addName("twitter:image", thumbnailurl)
|
||||
og.addName("twitter:player", fileurl)
|
||||
og.addName("twitter:player:stream", fileurl)
|
||||
og.addName("twitter:player:stream:content_type", filetype)
|
||||
og.addLink("image_src", thumbnailurl)
|
||||
} else if strings.HasPrefix(filetype, "audio") {
|
||||
og.addProp("og:type", "music.song")
|
||||
og.addProp("og:image", thumbnailurl)
|
||||
og.addProp("og:audio", fileurl)
|
||||
og.addProp("og:audio:secure_url", fileurl)
|
||||
og.addProp("og:audio:type", filetype)
|
||||
og.addLink("image_src", thumbnailurl)
|
||||
} else {
|
||||
og.addProp("og:type", "website")
|
||||
og.addProp("og:image", thumbnailurl)
|
||||
og.addLink("image_src", thumbnailurl)
|
||||
}
|
||||
return og
|
||||
}
|
||||
|
@@ -137,7 +137,6 @@ func New(r *httprouter.Router, prefix string, conf Config) (wc *WebController) {
|
||||
{GET, "" /* */, wc.serveTemplate("home", handlerOpts{})},
|
||||
{GET, "api" /* */, wc.serveMarkdown("api.md", handlerOpts{})},
|
||||
{GET, "history" /* */, wc.serveTemplate("history_cookies", handlerOpts{})},
|
||||
{GET, "u/:id/preview" /* */, wc.serveFilePreview},
|
||||
{GET, "u/:id" /* */, wc.serveFileViewer},
|
||||
{GET, "l/:id" /* */, wc.serveListViewer},
|
||||
{GET, "d/*path" /* */, wc.serveDirectory},
|
||||
|
Reference in New Issue
Block a user