feat: add share button

This commit is contained in:
arabcoders 2026-06-09 18:30:39 +03:00
parent d6318fca0f
commit 196d2d4e32
5 changed files with 95 additions and 13 deletions

View file

@ -50,17 +50,22 @@ Please read the [FAQ](FAQ.md) for more information.
## Run using docker command ## Run using docker command
```bash ```bash
mkdir -p ./{config,downloads/files,downloads/tmp} && docker run -itd --rm --user "${UID}:${UID}" --name ytptube \ mkdir -p ./{config,downloads/{files,tmp}} && docker run -itd --rm --user "${UID}:${UID}" --name ytptube \
-e YTP_TEMP_PATH=/downloads/tmp -e YTP_DOWNLOAD_PATH=/downloads/files \ -e YTP_TEMP_PATH=/downloads/tmp -e YTP_DOWNLOAD_PATH=/downloads/files \
-p 8081:8081 -v ./config:/config:rw -v ./downloads:/downloads:rw \ -p 8081:8081 -v ./config:/config:rw -v ./downloads:/downloads:rw \
ghcr.io/arabcoders/ytptube:latest ghcr.io/arabcoders/ytptube:latest
``` ```
Then you can access the WebUI at `http://localhost:8081`. ## Run using podman
> [!NOTE] ```bash
> If you are using `podman` instead of `docker`, you can use the same command, but you need to change the user to `0:0` mkdir -p ./{config,downloads/{files,tmp}} && podman run -itd --rm --userns=keep-id --name ytptube \
> it will appears to be running as root, but it will run as the user who started the container. -e YTP_TEMP_PATH=/downloads/tmp -e YTP_DOWNLOAD_PATH=/downloads/files \
-p 8081:8081 -v ./config:/config:rw -v ./downloads:/downloads:rw \
arabcoders/ytptube:latest
```
Then you can access the WebUI at `http://localhost:8081`.
## Using compose file ## Using compose file
@ -70,6 +75,8 @@ The following is an example of a `compose.yaml` file that can be used to run YTP
services: services:
ytptube: ytptube:
user: "${UID:-1000}:${UID:-1000}" # change this to your user id and group id. user: "${UID:-1000}:${UID:-1000}" # change this to your user id and group id.
# comment out the above line and uncomment the below line if you are using podman-compose.
#userns_mode: keep-id
image: ghcr.io/arabcoders/ytptube:latest image: ghcr.io/arabcoders/ytptube:latest
container_name: ytptube container_name: ytptube
restart: unless-stopped restart: unless-stopped
@ -84,18 +91,14 @@ services:
``` ```
> [!IMPORTANT] > [!IMPORTANT]
> Make sure to change the `user` line to match your user id and group id. > Make sure to change the `user` line to match your user id and group id in docker setups, or use `userns_mode: keep-id` in podman setups.
```bash ```bash
mkdir -p ./{config,downloads/files,downloads/tmp} && docker compose -f compose.yaml up -d mkdir -p ./{config,downloads/{files,tmp}} && docker compose -f compose.yaml up -d
``` ```
Then you can access the WebUI at `http://localhost:8081`. Then you can access the WebUI at `http://localhost:8081`.
> [!NOTE]
> you can use podman-compose instead of docker-compose, as it supports the same syntax. However, you should change the
> user to `0:0` it will appears to be running as root, but it will run as the user who started the container.
## Unraid ## Unraid
For `Unraid` users You can install the `Community Applications` plugin, and search for **ytptube** it comes For `Unraid` users You can install the `Community Applications` plugin, and search for **ytptube** it comes

View file

@ -37,6 +37,7 @@
base: 'min-h-[7.25rem] bg-elevated/60 ring-default focus-visible:ring-primary', base: 'min-h-[7.25rem] bg-elevated/60 ring-default focus-visible:ring-primary',
}" }"
@keydown="handleKeyDown" @keydown="handleKeyDown"
autofocus
/> />
<UInput <UInput
v-else v-else
@ -55,6 +56,7 @@
}" }"
@keydown="handleKeyDown" @keydown="handleKeyDown"
@paste="handlePaste" @paste="handlePaste"
autofocus
/> />
</div> </div>

View file

@ -0,0 +1,38 @@
import { useDialog } from './useDialog';
import { useYtpConfig } from './useYtpConfig';
import { makeDownload } from '~/utils';
import type { StoreItem } from '~/types/store';
export const useWebShare = () => {
const canShare = (): boolean =>
typeof navigator !== 'undefined' && typeof navigator.share === 'function';
const shareUrl = async (download: StoreItem): Promise<void> => {
if (!canShare()) {
useNotification().error('Web Share API is not supported in this browser.');
return;
}
try {
const title = download.title || download.filename || 'Download';
await navigator.share({
title: title,
text: download.description || title,
url: makeDownload(useYtpConfig(), download),
});
} catch (err: any) {
if (err?.name === 'AbortError') {
return;
}
console.error('Share failed:', err);
await useDialog().alertDialog({
title: 'Share Failed',
message: `Share failed: ${err?.message || 'unknown error'}`,
});
}
};
return { canShare, shareUrl };
};

View file

@ -28,6 +28,16 @@
</div> </div>
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end"> <div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end">
<UButton
color="neutral"
variant="outline"
size="sm"
icon="i-lucide-plus"
@click="addNewDownload"
>
<span>Add</span>
</UButton>
<UButton <UButton
color="neutral" color="neutral"
:variant="showFilter ? 'soft' : 'outline'" :variant="showFilter ? 'soft' : 'outline'"
@ -326,6 +336,15 @@
Retry Retry
</UButton> </UButton>
<UButton
v-if="item.filename && canShareUrl"
color="neutral"
variant="outline"
size="xs"
icon="i-lucide-share"
@click="() => shareUrl(item)"
/>
<UButton <UButton
v-if="item.filename" v-if="item.filename"
color="neutral" color="neutral"
@ -620,6 +639,17 @@
Retry Retry
</UButton> </UButton>
<UButton
v-if="item.filename && canShareUrl"
color="neutral"
variant="outline"
icon="i-lucide-share"
class="w-full justify-center"
@click="() => shareUrl(item)"
>
Share
</UButton>
<UButton <UButton
v-if="item.filename" v-if="item.filename"
color="neutral" color="neutral"
@ -774,6 +804,7 @@ import { useAppSocket } from '~/composables/useAppSocket';
import { useExpandableMeta } from '~/composables/useExpandableMeta'; import { useExpandableMeta } from '~/composables/useExpandableMeta';
import { useHistoryState } from '~/composables/useHistoryState'; import { useHistoryState } from '~/composables/useHistoryState';
import { useMediaQuery } from '~/composables/useMediaQuery'; import { useMediaQuery } from '~/composables/useMediaQuery';
import { useWebShare } from '~/composables/useWebShare';
import type { item_request } from '~/types/item'; import type { item_request } from '~/types/item';
import type { StoreItem } from '~/types/store'; import type { StoreItem } from '~/types/store';
import { import {
@ -798,6 +829,7 @@ const toast = useNotification();
const box = useConfirm(); const box = useConfirm();
const { confirmDialog, promptDialog } = useDialog(); const { confirmDialog, promptDialog } = useDialog();
const { toggleExpand, expandClass } = useExpandableMeta(); const { toggleExpand, expandClass } = useExpandableMeta();
const { canShare, shareUrl } = useWebShare();
const pendingDownloadFormItem = useState<item_request | Record<string, never>>( const pendingDownloadFormItem = useState<item_request | Record<string, never>>(
'pending-download-form-item', 'pending-download-form-item',
() => ({}), () => ({}),
@ -885,6 +917,8 @@ watch(video_item, (value) => {
document.querySelector('body')?.setAttribute('style', `opacity: ${value ? 1 : bg_opacity.value}`); document.querySelector('body')?.setAttribute('style', `opacity: ${value ? 1 : bg_opacity.value}`);
}); });
const canShareUrl = computed(() => canShare());
watch(embed_url, (value) => { watch(embed_url, (value) => {
if (!bg_enable.value) { if (!bg_enable.value) {
return; return;
@ -933,6 +967,12 @@ const changeDisplay = (): void => {
display_style.value = display_style.value === 'grid' ? 'list' : 'grid'; display_style.value = display_style.value === 'grid' ? 'list' : 'grid';
}; };
const addNewDownload = async (): Promise<void> => {
config.showForm = true;
await nextTick();
await navigateTo('/');
};
const toNewDownload = async (item: item_request | Partial<StoreItem>): Promise<void> => { const toNewDownload = async (item: item_request | Partial<StoreItem>): Promise<void> => {
if (!item) { if (!item) {
return; return;

View file

@ -476,8 +476,7 @@ const makeDownload = (
} }
if (item.folder) { if (item.folder) {
item.folder = item.folder.replace(/#/g, '%23'); baseDir += item.folder.replace(/#/g, '%23') + '/';
baseDir += item.folder + '/';
} }
if (!item.filename) { if (!item.filename) {