feat: add share button
This commit is contained in:
parent
d6318fca0f
commit
196d2d4e32
5 changed files with 95 additions and 13 deletions
25
README.md
25
README.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
38
ui/app/composables/useWebShare.ts
Normal file
38
ui/app/composables/useWebShare.ts
Normal 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 };
|
||||||
|
};
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue