Add oembed tags. Fix sharing and copy link button. Add text and file viewer

This commit is contained in:
2023-05-17 19:27:46 +02:00
parent 277637511c
commit bd9359de44
16 changed files with 459 additions and 192 deletions

View File

@@ -1,7 +1,6 @@
<script>
import { tick } from "svelte";
let container
let text_type = ""

View File

@@ -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)}}>
{node.name}
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>

View File

@@ -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;">
<div class="form">
<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="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>
<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="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"/>
</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>

View File

@@ -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>

View File

@@ -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"

View File

@@ -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)

View File

@@ -1,62 +1,121 @@
<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>
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">
<path d="M22 4H2v16h20V4zm-2 4l-8 5-8-5V6l8 5 8-5v2z"/>
</svg>
<br/>
E-Mail
</button>
<button class="button_full_width" on:click={share_reddit}>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path d="M22,12.14C22,10.92 21,9.96 19.81,9.96C19.22,9.96 18.68,10.19 18.29,10.57C16.79,9.5 14.72,8.79 12.43,8.7L13.43,4L16.7,4.71C16.73,5.53 17.41,6.19 18.25,6.19C19.11,6.19 19.81,5.5 19.81,4.63C19.81,3.77 19.11,3.08 18.25,3.08C17.65,3.08 17.11,3.43 16.86,3.95L13.22,3.18C13.11,3.16 13,3.18 12.93,3.24C12.84,3.29 12.79,3.38 12.77,3.5L11.66,8.72C9.33,8.79 7.23,9.5 5.71,10.58C5.32,10.21 4.78,10 4.19,10C2.97,10 2,10.96 2,12.16C2,13.06 2.54,13.81 3.29,14.15C3.25,14.37 3.24,14.58 3.24,14.81C3.24,18.18 7.16,20.93 12,20.93C16.84,20.93 20.76,18.2 20.76,14.81C20.76,14.6 20.75,14.37 20.71,14.15C21.46,13.81 22,13.04 22,12.14M7,13.7C7,12.84 7.68,12.14 8.54,12.14C9.4,12.14 10.1,12.84 10.1,13.7A1.56,1.56 0 0,1 8.54,15.26C7.68,15.28 7,14.56 7,13.7M15.71,17.84C14.63,18.92 12.59,19 12,19C11.39,19 9.35,18.9 8.29,17.84C8.13,17.68 8.13,17.43 8.29,17.27C8.45,17.11 8.7,17.11 8.86,17.27C9.54,17.95 11,18.18 12,18.18C13,18.18 14.47,17.95 15.14,17.27C15.3,17.11 15.55,17.11 15.71,17.27C15.85,17.43 15.85,17.68 15.71,17.84M15.42,15.28C14.56,15.28 13.86,14.58 13.86,13.72A1.56,1.56 0 0,1 15.42,12.16C16.28,12.16 17,12.86 17,13.72C17,14.56 16.28,15.28 15.42,15.28Z" />
</svg>
<br/>
Reddit
</button>
<button class="button_full_width" on:click={share_twitter}>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path d="M22.46,6C21.69,6.35 20.86,6.58 20,6.69C20.88,6.16 21.56,5.32 21.88,4.31C21.05,4.81 20.13,5.16 19.16,5.36C18.37,4.5 17.26,4 16,4C13.65,4 11.73,5.92 11.73,8.29C11.73,8.63 11.77,8.96 11.84,9.27C8.28,9.09 5.11,7.38 3,4.79C2.63,5.42 2.42,6.16 2.42,6.94C2.42,8.43 3.17,9.75 4.33,10.5C3.62,10.5 2.96,10.3 2.38,10C2.38,10 2.38,10 2.38,10.03C2.38,12.11 3.86,13.85 5.82,14.24C5.46,14.34 5.08,14.39 4.69,14.39C4.42,14.39 4.15,14.36 3.89,14.31C4.43,16 6,17.26 7.89,17.29C6.43,18.45 4.58,19.13 2.56,19.13C2.22,19.13 1.88,19.11 1.54,19.07C3.44,20.29 5.7,21 8.12,21C16,21 20.33,14.46 20.33,8.79C20.33,8.6 20.33,8.42 20.32,8.23C21.16,7.63 21.88,6.87 22.46,6Z" />
</svg>
<br/>
Twitter
</button>
<button class="button_full_width" on:click={share_facebook}>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M18,5H15.5A3.5,3.5 0 0,0 12,8.5V11H10V14H12V21H15V14H18V11H15V9A1,1 0 0,1 16,8H18V5Z" />
</svg>
<br/>
Facebook
</button>
<button class="button_full_width" on:click={share_tumblr}>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path d="M17,11H13V15.5C13,16.44 13.28,17 14.5,17H17V21C17,21 15.54,21.05 14.17,21.05C10.8,21.05 9.5,19 9.5,16.75V11H7V7C10.07,6.74 10.27,4.5 10.5,3H13V7H17" />
</svg>
<br/>
Tumblr
</button>
{#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">
<path d="M22 4H2v16h20V4zm-2 4l-8 5-8-5V6l8 5 8-5v2z"/>
</svg>
<br/>
E-Mail
</button>
<button class="button_full_width" on:click={share_reddit}>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path d="M22,12.14C22,10.92 21,9.96 19.81,9.96C19.22,9.96 18.68,10.19 18.29,10.57C16.79,9.5 14.72,8.79 12.43,8.7L13.43,4L16.7,4.71C16.73,5.53 17.41,6.19 18.25,6.19C19.11,6.19 19.81,5.5 19.81,4.63C19.81,3.77 19.11,3.08 18.25,3.08C17.65,3.08 17.11,3.43 16.86,3.95L13.22,3.18C13.11,3.16 13,3.18 12.93,3.24C12.84,3.29 12.79,3.38 12.77,3.5L11.66,8.72C9.33,8.79 7.23,9.5 5.71,10.58C5.32,10.21 4.78,10 4.19,10C2.97,10 2,10.96 2,12.16C2,13.06 2.54,13.81 3.29,14.15C3.25,14.37 3.24,14.58 3.24,14.81C3.24,18.18 7.16,20.93 12,20.93C16.84,20.93 20.76,18.2 20.76,14.81C20.76,14.6 20.75,14.37 20.71,14.15C21.46,13.81 22,13.04 22,12.14M7,13.7C7,12.84 7.68,12.14 8.54,12.14C9.4,12.14 10.1,12.84 10.1,13.7A1.56,1.56 0 0,1 8.54,15.26C7.68,15.28 7,14.56 7,13.7M15.71,17.84C14.63,18.92 12.59,19 12,19C11.39,19 9.35,18.9 8.29,17.84C8.13,17.68 8.13,17.43 8.29,17.27C8.45,17.11 8.7,17.11 8.86,17.27C9.54,17.95 11,18.18 12,18.18C13,18.18 14.47,17.95 15.14,17.27C15.3,17.11 15.55,17.11 15.71,17.27C15.85,17.43 15.85,17.68 15.71,17.84M15.42,15.28C14.56,15.28 13.86,14.58 13.86,13.72A1.56,1.56 0 0,1 15.42,12.16C16.28,12.16 17,12.86 17,13.72C17,14.56 16.28,15.28 15.42,15.28Z" />
</svg>
<br/>
Reddit
</button>
<button class="button_full_width" on:click={share_twitter}>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path d="M22.46,6C21.69,6.35 20.86,6.58 20,6.69C20.88,6.16 21.56,5.32 21.88,4.31C21.05,4.81 20.13,5.16 19.16,5.36C18.37,4.5 17.26,4 16,4C13.65,4 11.73,5.92 11.73,8.29C11.73,8.63 11.77,8.96 11.84,9.27C8.28,9.09 5.11,7.38 3,4.79C2.63,5.42 2.42,6.16 2.42,6.94C2.42,8.43 3.17,9.75 4.33,10.5C3.62,10.5 2.96,10.3 2.38,10C2.38,10 2.38,10 2.38,10.03C2.38,12.11 3.86,13.85 5.82,14.24C5.46,14.34 5.08,14.39 4.69,14.39C4.42,14.39 4.15,14.36 3.89,14.31C4.43,16 6,17.26 7.89,17.29C6.43,18.45 4.58,19.13 2.56,19.13C2.22,19.13 1.88,19.11 1.54,19.07C3.44,20.29 5.7,21 8.12,21C16,21 20.33,14.46 20.33,8.79C20.33,8.6 20.33,8.42 20.32,8.23C21.16,7.63 21.88,6.87 22.46,6Z" />
</svg>
<br/>
Twitter
</button>
<button class="button_full_width" on:click={share_facebook}>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path d="M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M18,5H15.5A3.5,3.5 0 0,0 12,8.5V11H10V14H12V21H15V14H18V11H15V9A1,1 0 0,1 16,8H18V5Z" />
</svg>
<br/>
Facebook
</button>
<button class="button_full_width" on:click={share_tumblr}>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<path d="M17,11H13V15.5C13,16.44 13.28,17 14.5,17H17V21C17,21 15.54,21.05 14.17,21.05C10.8,21.05 9.5,19 9.5,16.75V11H7V7C10.07,6.74 10.27,4.5 10.5,3H13V7H17" />
</svg>
<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>

View File

@@ -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">
<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}>
<i class="icon">share</i> Share
</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={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 {

View File

@@ -98,6 +98,7 @@ td {
height: 32px;
width: 32px;
vertical-align: middle;
border-radius: 4px;
}
.node_name {
width: 100%;

View 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>

View File

@@ -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>

View 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>

View File

@@ -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">
<div class="title">{title}</div>
<button class="button round" on:click={hide}>
<i class="icon">close</i>
</button>
{#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;

View File

@@ -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>`)
}

View File

@@ -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
}

View File

@@ -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},