Compare commits

..

2 Commits

Author SHA1 Message Date
5b25f21f72 Update home page a bit 2026-07-30 23:15:00 +02:00
1536b8bd1b Block sharing if subscription does not support it 2026-07-30 23:13:55 +02:00
8 changed files with 82 additions and 94 deletions

View File

@@ -1,10 +1,11 @@
<script lang="ts"> <script lang="ts">
import type { FSNavigator } from "./FSNavigator"; import type { FSNavigator } from "./FSNavigator";
import { fs_node_icon, fs_share_hotlink_url, fs_share_url, fs_update, type FSNode, type FSPermissions } from "lib/FilesystemAPI.svelte"; import { fs_check_is_error, fs_node_icon, fs_share_hotlink_url, fs_share_url, fs_update, type FSNode, type FSPermissions } from "lib/FilesystemAPI.svelte";
import { copy_text } from "util/Util"; import { copy_text } from "util/Util";
import CopyButton from "layout/CopyButton.svelte"; import CopyButton from "layout/CopyButton.svelte";
import Dialog from "layout/Dialog.svelte"; import Dialog from "layout/Dialog.svelte";
import { fade } from "svelte/transition"; import { fade } from "svelte/transition";
import { get_user } from "lib/NovaAPI";
let { nav }: { let { nav }: {
nav: FSNavigator; nav: FSNavigator;
@@ -19,13 +20,33 @@ let is_parent = $state(false)
let parent_node: FSNode = $state() let parent_node: FSNode = $state()
let dialog: Dialog = $state() let dialog: Dialog = $state()
let dialog_not_allowed: Dialog = $state()
export const open = async (e: MouseEvent, p: FSNode[]) => { export const open = async (e: MouseEvent, p: FSNode[]) => {
path = p path = p
base = path[path.length-1] base = path[path.length-1]
const user = await get_user()
if (user.subscription.file_sharing === false && base.created_by === user.id) {
}
share_url = fs_share_url(path) share_url = fs_share_url(path)
if (share_url === "") { if (share_url === "") {
// File is not public, make public // File is not public, make public
try {
await make_public() await make_public()
} catch (err) {
if (fs_check_is_error(err, "sharing_not_allowed")) {
if (e.target instanceof HTMLButtonElement) {
dialog_not_allowed.open(e.target.getBoundingClientRect())
} else {
dialog_not_allowed.open((e.target as HTMLElement).parentElement.getBoundingClientRect())
}
return
} else {
alert("Error file making file public: "+err)
}
}
} }
await share() await share()
@@ -85,6 +106,16 @@ const share = async () => {
} }
</script> </script>
<Dialog bind:this={dialog_not_allowed}>
<div class="dialog_inner">
<div class="highlight_yellow" transition:fade>
Sharing is not allowed on free accounts. Upgrade to premium to
enable this feature
</div>
</div>
</Dialog>
<Dialog bind:this={dialog}> <Dialog bind:this={dialog}>
<div class="dialog_inner"> <div class="dialog_inner">
{#if toast !== ""} {#if toast !== ""}

View File

@@ -5,7 +5,6 @@ import BrandingOptions from "./BrandingOptions.svelte";
import { branding_from_props } from "./Branding"; import { branding_from_props } from "./Branding";
import FileOptions from "./FileOptions.svelte"; import FileOptions from "./FileOptions.svelte";
import SharingOptions from "./SharingOptions.svelte"; import SharingOptions from "./SharingOptions.svelte";
import AccessControl from "./AccessControl.svelte";
import type { FSNavigator } from "filesystem/FSNavigator"; import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading"; import { loading_finish, loading_start } from "lib/Loading";
@@ -120,12 +119,6 @@ const save = async (e: SubmitEvent) => {
<i class="icon">share</i> <i class="icon">share</i>
Sharing Sharing
</button> </button>
{#if $nav.permissions.owner}
<button class:button_highlight={tab === "access"} onclick={() => tab = "access"}>
<i class="icon">key</i>
Access control
</button>
{/if}
<button class:button_highlight={tab === "branding"} onclick={() => tab = "branding"}> <button class:button_highlight={tab === "branding"} onclick={() => tab = "branding"}>
<i class="icon">palette</i> <i class="icon">palette</i>
Branding Branding
@@ -148,8 +141,6 @@ const save = async (e: SubmitEvent) => {
bind:file bind:file
bind:options bind:options
/> />
{:else if tab === "access"}
<AccessControl bind:options />
{:else if tab === "branding"} {:else if tab === "branding"}
<BrandingOptions <BrandingOptions
bind:enabled={branding_enabled} bind:enabled={branding_enabled}

View File

@@ -1,10 +1,11 @@
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy';
import { domain_url } from "util/Util"; import { domain_url } from "util/Util";
import CopyButton from "layout/CopyButton.svelte"; import CopyButton from "layout/CopyButton.svelte";
import { formatDate } from "util/Formatting"; import { formatDate } from "util/Formatting";
import { type FSNode, type NodeOptions } from "lib/FilesystemAPI.svelte"; import { type FSNode, type NodeOptions } from "lib/FilesystemAPI.svelte";
import AccessControl from "./AccessControl.svelte"; import AccessControl from "./AccessControl.svelte";
import { onMount } from 'svelte';
import { user } from "lib/UserStore";
let { let {
file = $bindable(), file = $bindable(),
@@ -45,11 +46,18 @@ const toggle_example = () => {
} }
let share_link = $derived(window.location.protocol+"//"+window.location.host+"/d/"+file.id) let share_link = $derived(window.location.protocol+"//"+window.location.host+"/d/"+file.id)
run(() => { onMount(() => {
embed_iframe(file, options) embed_iframe(file, options)
}); });
</script> </script>
{#if $user.subscription.file_sharing === false}
<div class="highlight_yellow">
Your account does not support sharing features. Access controls will not
have any effect until you upgrade to premium.
</div>
{/if}
<fieldset> <fieldset>
<legend>Public link</legend> <legend>Public link</legend>

View File

@@ -22,10 +22,9 @@ import Euro from "util/Euro.svelte";
<Checkout/> <Checkout/>
{:else} {:else}
<p> <p>
You are currently logged in as '{$user.username}'. Use the Pick one of the subscription plans above to upgrade your
form below to activate prepaid on this account. account to premium.
</p> </p>
<Checkout/>
{/if} {/if}
</section> </section>
</div> </div>

View File

@@ -28,49 +28,23 @@ let upload_widget
content delivery. We will store and serve your files at an extremely content delivery. We will store and serve your files at an extremely
competitive rate. competitive rate.
</p> </p>
</section> <h2>Features</h2>
</div>
<header>
<h1>Features</h1>
</header>
<div class="page_content">
<section>
<ul> <ul>
<li> <li>
<span class="bold">Unlimited</span> storage space Cloud storage with <a href="/filesystem#toc_7">rclone</a> and
FTPS support
</li> </li>
<li> <li>
Cloud storage with <a href="/filesystem#toc_7">rclone</a> and <a Customizable download pages (custom branding colours, header and
href="/filesystem#toc_10">FTPS</a> support background images)
</li> </li>
<li> <li>
<span class="bold">Customizable</span> download pages (custom Fault tolerance for up to four storage server failures
branding colours, header and background images)
</li> </li>
<li> <li>
<span class="bold">Fault tolerance</span> for up to four storage Caching servers highly optimized for long-distance data transfer
server failures
</li>
<li>
<span class="bold">1.8 Terabits per second</span> of total bandwidth
capacity
</li>
<li>
Twenty-three high-performance caching servers optimized for data
transfer over long distances
</li>
<li>
File caching in
<span class="bold">Portland</span>,
<span class="bold">Querétaro</span>,
<span class="bold">New York</span>,
<span class="bold">São Paulo</span>,
<span class="bold">Frankfurt</span> and
<span class="bold">Singapore</span>
</li> </li>
</ul> </ul>
<img class="image" src="/res/img/map.webp" alt="A globe showing datacenter locations"/>
<h3>What you don't get</h3> <h3>What you don't get</h3>
<ul> <ul>
<li> <li>
@@ -78,10 +52,7 @@ let upload_widget
in case of emergency in case of emergency
</li> </li>
<li> <li>
Commercial support - If you have a question, please check the <a Commercial support - I'm busy, please don't bother me
href="/about">Q&A page</a> first. If that doesn't answer it, try
the <a href="https://discord.gg/UDjaBGwr4p"
target="_blank">support channel</a> on Discord.
</li> </li>
</ul> </ul>
</section> </section>
@@ -142,43 +113,8 @@ header > h1 {
background-size: contain; background-size: contain;
background-position: center; background-position: center;
} }
.image {
max-width: 100%;
border-radius: 12px;
}
.bold { .bold {
font-weight: bold; font-weight: bold;
color: var(--highlight_color); color: var(--highlight_color);
} }
.prices {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
}
.prices > div {
flex: 1 0 200px;
min-width: 200px;
display: flex;
flex-direction: row;
text-align: center;
border-radius: 6px;
overflow: hidden;
border: 2px solid var(--card_color);
}
.prices > div > div {
flex: 1 1 auto;
padding: 4px;
font-size: 1.3em;
}
.prices > div > div:nth-child(2) {
background: var(--card_color);
}
@media(max-width: 1000px) {
.prices > div {
flex-direction: column;
}
}
</style> </style>

View File

@@ -137,8 +137,8 @@ const plans: Plan[] = [
<div class="feat_table"> <div class="feat_table">
{#each plans as plan (plan.title)} {#each plans as plan (plan.title)}
<div class="plan"> <div class="plan">
<div class="title"> <div>
<span class="bold">{plan.title}</span> <span class="title">{plan.title}</span>
<br/> <br/>
<Euro amount={plan.monthly}/> / month <Euro amount={plan.monthly}/> / month
</div> </div>
@@ -212,6 +212,9 @@ const plans: Plan[] = [
word-wrap: break-word; word-wrap: break-word;
hyphens: auto; hyphens: auto;
} }
.title {
font-size: 1.5em;
}
.plan > .order { .plan > .order {
flex: 1 1 auto; flex: 1 1 auto;
display: flex; display: flex;

View File

@@ -314,6 +314,26 @@ export const fs_check_response = async (resp: Response) => {
return JSON.parse(text) return JSON.parse(text)
} }
export const fs_check_is_error = (err: any, target: string) => {
if (typeof err.value !== "string") {
return false
}
if (err.value === target) {
return true
}
if (err.value === "multiple_errors" && Array.isArray(err.errors)) {
for (const embedded_err of err.errors) {
if (embedded_err.value === target) {
return true
}
}
}
return false
}
export const fs_path_url = (path: string) => { export const fs_path_url = (path: string) => {
if (!path || path.length === 0) { if (!path || path.length === 0) {
return "" return ""

View File

@@ -129,7 +129,7 @@ const load_page = async (pathname: string, history: boolean): Promise<boolean> =
return true return true
} }
const click = (e: MouseEvent) => { const click = async (e: MouseEvent) => {
const origin = (e.target as Element).closest("a"); const origin = (e.target as Element).closest("a");
if (origin === null) { if (origin === null) {
@@ -145,7 +145,7 @@ const click = (e: MouseEvent) => {
// Try to load the page, if the page was found we cancel the browser // Try to load the page, if the page was found we cancel the browser
// navigation event so we can handle it ourselves // navigation event so we can handle it ourselves
if (load_page(url.pathname, true)) { if (await load_page(url.pathname, true) === true) {
e.preventDefault() e.preventDefault()
} }
} }
@@ -165,7 +165,7 @@ const popstate = (e: PopStateEvent) => {
{#if current_page !== null} {#if current_page !== null}
<current_page.component /> <current_page.component />
{#if current_page.footer === undefined || current_page.footer === true} {#if current_page.footer !== false}
<Footer/> <Footer/>
{/if} {/if}
{/if} {/if}