diff --git a/ui/src/app/app.html b/ui/src/app/app.html
index b2ae0db..26f496a 100644
--- a/ui/src/app/app.html
+++ b/ui/src/app/app.html
@@ -854,6 +854,9 @@
@if (entry[1].filename) {
}
+ @if (entry[1].filename && canShareDownloads()) {
+
+ }
diff --git a/ui/src/app/app.ts b/ui/src/app/app.ts
index a73f879..03448ee 100644
--- a/ui/src/app/app.ts
+++ b/ui/src/app/app.ts
@@ -7,7 +7,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { NgSelectModule } from '@ng-select/ng-select';
-import { faTrashAlt, faCheckCircle, faTimesCircle, faRedoAlt, faSun, faMoon, faCheck, faCircleHalfStroke, faDownload, faExternalLinkAlt, faFileImport, faFileExport, faCopy, faClock, faTachometerAlt, faSortAmountDown, faSortAmountUp, faChevronRight, faChevronDown, faUpload, faPause, faPlay } from '@fortawesome/free-solid-svg-icons';
+import { faTrashAlt, faCheckCircle, faTimesCircle, faRedoAlt, faSun, faMoon, faCheck, faCircleHalfStroke, faDownload, faExternalLinkAlt, faFileImport, faFileExport, faCopy, faClock, faTachometerAlt, faSortAmountDown, faSortAmountUp, faChevronRight, faChevronDown, faUpload, faPause, faPlay, faShareNodes } from '@fortawesome/free-solid-svg-icons';
import { faGithub } from '@fortawesome/free-brands-svg-icons';
import { CookieService } from 'ngx-cookie-service';
import { AddDownloadPayload, DownloadsService } from './services/downloads.service';
@@ -185,6 +185,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
faUpload = faUpload;
faPause = faPause;
faPlay = faPlay;
+ faShareNodes = faShareNodes;
subtitleLanguages = [
{ id: 'en', text: 'English' },
{ id: 'ar', text: 'Arabic' },
@@ -1189,6 +1190,51 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
return baseDir + encodeURIComponent(download.filename);
}
+ // Web Share API support — primarily for iOS Safari / Chrome, lets the user
+ // hand the downloaded file off to the platform share sheet (Photos.app,
+ // Files, third-party apps, AirDrop). Falls back silently to the standard
+ // download flow on platforms without navigator.share / canShare.
+ canShareDownloads(): boolean {
+ // navigator.share alone is not enough — Desktop Safari implements
+ // navigator.share but not canShare with files. We explicitly require
+ // both, since we always intend to share a file (not a URL).
+ return typeof navigator !== 'undefined'
+ && typeof navigator.share === 'function'
+ && typeof navigator.canShare === 'function';
+ }
+
+ async shareDownload(download: Download): Promise {
+ if (!this.canShareDownloads()) {
+ return;
+ }
+ try {
+ const response = await fetch(this.buildDownloadLink(download));
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status} fetching file for share`);
+ }
+ const blob = await response.blob();
+ const file = new File([blob], download.filename, {
+ type: blob.type || 'application/octet-stream',
+ });
+ const payload: ShareData = { files: [file], title: download.title };
+ if (!navigator.canShare(payload)) {
+ // File type not shareable on this platform (e.g. desktop browsers,
+ // or some MIME types iOS refuses). Bail out so the user can still
+ // use the regular download button right next to this one.
+ console.warn('navigator.canShare rejected payload for', download.filename);
+ return;
+ }
+ await navigator.share(payload);
+ } catch (err: any) {
+ // AbortError = user dismissed the share sheet → silent no-op.
+ // Other errors (network, file too big, …) get logged but we don't
+ // surface a UI error: the regular download link remains a fallback.
+ if (err?.name !== 'AbortError') {
+ console.error('Share failed:', err);
+ }
+ }
+ }
+
buildResultItemTooltip(download: Download) {
const parts = [];
if (download.msg) {