Release 2.3.0
A share can now hold a private text (a password, a key, a short note) with its files or on its own. The upload page's new "Private text" card takes up to 100 KB; the browser encrypts the text and sends it through the same chunk pipeline as a file, flagged is_text on share_files, so it gets the share's password, expiry, download limit and cleanup. The recipient sees the text only after pressing "Show text", which counts as their download, so a messenger link preview cannot use up a share limited to one download. The text is left out of the file list, the ZIP and the file counts; the admin dashboard marks shares that hold one with "Text". The website gains a Private text feature card and a fifth phone screenshot; every screenshot is retaken. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
202813a1e6
commit
f9a7839ad3
@@ -2,10 +2,10 @@
|
||||
iOS before Safari 18.4, which cannot position them. --}}
|
||||
|
||||
<x-page brand>
|
||||
{{-- Each state is one card under the page's h1: the card holds everything the recipient acts
|
||||
on, and it is the shape SealShare has always shown them. --}}
|
||||
{{-- Under the page's h1, what the recipient acts on is in cards: the password, or the share's
|
||||
private text and its files, each in its own card, with the share's expiry and limit under them. --}}
|
||||
@if (! $authenticated)
|
||||
<x-card :title="__('Password Required')" :subtitle="__('Enter the password to access these files')" heading="h2" variant="outlined">
|
||||
<x-card :title="__('Password Required')" :subtitle="__('Enter the password to open this share')" heading="h2" variant="outlined">
|
||||
<x-form wire:submit="verifyPassword">
|
||||
<x-password
|
||||
full
|
||||
@@ -22,32 +22,100 @@
|
||||
</x-form>
|
||||
</x-card>
|
||||
@else
|
||||
<x-card :title="__('Shared Files')" heading="h2" variant="outlined">
|
||||
{{-- A download link does not render the page again: the download limit's note switches
|
||||
on the first press here, and the server draws the open window on the next visit. --}}
|
||||
<x-stack gap="space200" x-data="{ downloaded: false }">
|
||||
<x-stack gap="space100">
|
||||
<x-list :label="__('Shared Files')">
|
||||
@foreach ($share->files as $file)
|
||||
<x-list-item
|
||||
:title="$file->relative_path ?: $file->original_name"
|
||||
icon="description"
|
||||
wire:key="file-{{ $file->id }}"
|
||||
>
|
||||
<x-slot:description><span class="md-tabular">{{ Number::fileSize($file->file_size) }}</span></x-slot:description>
|
||||
<x-slot:end>
|
||||
<x-button icon="download" :link="route('share.download.file', [$share, $file])" no-wire-navigate :aria-label="__('Download :name', ['name' => $file->original_name])" x-on:click="downloaded = true" />
|
||||
</x-slot:end>
|
||||
</x-list-item>
|
||||
@endforeach
|
||||
</x-list>
|
||||
{{-- A download link does not render the page again: the download limit's note switches on
|
||||
the first press of any download or "Show text", and the server draws the open window on
|
||||
the next visit. --}}
|
||||
<x-stack gap="space200" x-data="{ downloaded: false }">
|
||||
{{-- The text is fetched only when the recipient asks for it, never with the page: a link
|
||||
preview must not use up a share limited to one download. It stays in Alpine and is
|
||||
drawn as plain text. --}}
|
||||
@if ($hasText)
|
||||
<x-card :title="__('Private text')" heading="h2" variant="outlined">
|
||||
<x-stack
|
||||
gap="space200"
|
||||
x-data="{
|
||||
text: null,
|
||||
async reveal() {
|
||||
const text = await $wire.revealText();
|
||||
|
||||
if (text === null) {
|
||||
window.materialToast({{ \Illuminate\Support\Js::from(__('This text is no longer available.')) }}, { type: 'error' });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.text = text;
|
||||
downloaded = true;
|
||||
},
|
||||
}"
|
||||
>
|
||||
<template x-if="text !== null">
|
||||
<x-stack gap="space200">
|
||||
<div class="share-text md-type-body-lg" x-text="text" data-test="shared-text"></div>
|
||||
|
||||
<x-row justify="end">
|
||||
<x-button
|
||||
:label="__('Copy')"
|
||||
icon="content_copy"
|
||||
variant="tonal"
|
||||
x-on:click="navigator.clipboard.writeText(text).then(() => window.materialToast({{ \Illuminate\Support\Js::from(__('Copied to the clipboard')) }}, { type: 'success' }))"
|
||||
data-test="copy-text"
|
||||
/>
|
||||
</x-row>
|
||||
</x-stack>
|
||||
</template>
|
||||
|
||||
<x-row justify="end" x-show="text === null">
|
||||
<x-button :label="__('Show text')" icon="visibility" variant="filled" x-on:click="reveal()" spinner="revealText" data-test="show-text" />
|
||||
</x-row>
|
||||
</x-stack>
|
||||
</x-card>
|
||||
@endif
|
||||
|
||||
@if ($files->isNotEmpty())
|
||||
<x-card :title="__('Shared Files')" heading="h2" variant="outlined">
|
||||
<x-stack gap="space200">
|
||||
<x-list :label="__('Shared Files')">
|
||||
@foreach ($files as $file)
|
||||
<x-list-item
|
||||
:title="$file->relative_path ?: $file->original_name"
|
||||
icon="description"
|
||||
wire:key="file-{{ $file->id }}"
|
||||
>
|
||||
<x-slot:description><span class="md-tabular">{{ Number::fileSize($file->file_size) }}</span></x-slot:description>
|
||||
<x-slot:end>
|
||||
<x-button icon="download" :link="route('share.download.file', [$share, $file])" no-wire-navigate :aria-label="__('Download :name', ['name' => $file->original_name])" x-on:click="downloaded = true" />
|
||||
</x-slot:end>
|
||||
</x-list-item>
|
||||
@endforeach
|
||||
</x-list>
|
||||
|
||||
<x-row justify="end">
|
||||
@if ($files->count() > 1)
|
||||
<x-button :label="__('Download All as ZIP')" icon="download" variant="filled" :link="route('share.download.all', $share)" no-wire-navigate x-on:click="downloaded = true" />
|
||||
@else
|
||||
<x-button :label="__('Download')" icon="download" variant="filled" :link="route('share.download.file', [$share, $files->first()])" no-wire-navigate x-on:click="downloaded = true" />
|
||||
@endif
|
||||
</x-row>
|
||||
</x-stack>
|
||||
</x-card>
|
||||
@endif
|
||||
|
||||
@if ($share->expires_at || $share->max_downloads)
|
||||
<x-stack gap="space100">
|
||||
@if ($share->expires_at)
|
||||
<p class="md-type-body-sm md-ink-variant">{{ __('Expires') }}: {{ $share->expires_at->diffForHumans() }}</p>
|
||||
@endif
|
||||
|
||||
@if ($share->max_downloads)
|
||||
@if ($downloadWindowEndsAt)
|
||||
@if ($hasText)
|
||||
@if ($downloadWindowEndsAt)
|
||||
<p class="md-type-body-sm md-ink-variant">{{ __('You can open this share for another :time.', ['time' => $downloadWindowEndsAt->diffForHumans(syntax: \Carbon\CarbonInterface::DIFF_ABSOLUTE)]) }}</p>
|
||||
@elseif ($remainingDownloads > 0)
|
||||
<p class="md-type-body-sm md-ink-variant" x-show="! downloaded">{{ trans_choice('{1} Showing the text or downloading uses the last remaining download. You then have :window to open this share.|[2,*] Showing the text or downloading uses 1 of :count remaining downloads. You then have :window to open this share.', $remainingDownloads, ['window' => $downloadWindow]) }}</p>
|
||||
<p class="md-type-body-sm md-ink-variant" x-show="downloaded" x-cloak>{{ __('You have :window to open this share.', ['window' => $downloadWindow]) }}</p>
|
||||
@endif
|
||||
@elseif ($downloadWindowEndsAt)
|
||||
<p class="md-type-body-sm md-ink-variant">{{ __('You can download these files for another :time.', ['time' => $downloadWindowEndsAt->diffForHumans(syntax: \Carbon\CarbonInterface::DIFF_ABSOLUTE)]) }}</p>
|
||||
@elseif ($remainingDownloads > 0)
|
||||
<p class="md-type-body-sm md-ink-variant" x-show="! downloaded">{{ trans_choice('{1} Downloading uses the last remaining download. You then have :window to download the files.|[2,*] Downloading uses 1 of :count remaining downloads. You then have :window to download the files.', $remainingDownloads, ['window' => $downloadWindow]) }}</p>
|
||||
@@ -55,15 +123,7 @@
|
||||
@endif
|
||||
@endif
|
||||
</x-stack>
|
||||
|
||||
<x-row justify="end">
|
||||
@if ($share->files->count() > 1)
|
||||
<x-button :label="__('Download All as ZIP')" icon="download" variant="filled" :link="route('share.download.all', $share)" no-wire-navigate x-on:click="downloaded = true" />
|
||||
@else
|
||||
<x-button :label="__('Download')" icon="download" variant="filled" :link="route('share.download.file', [$share, $share->files->first()])" no-wire-navigate x-on:click="downloaded = true" />
|
||||
@endif
|
||||
</x-row>
|
||||
</x-stack>
|
||||
</x-card>
|
||||
@endif
|
||||
</x-stack>
|
||||
@endif
|
||||
</x-page>
|
||||
|
||||
Reference in New Issue
Block a user