From c0ce31e42afff4cef99a9b07e40ff694ae50921c Mon Sep 17 00:00:00 2001 From: wolfmanster <68727977+wolfmanster@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:43:13 +0800 Subject: [PATCH] feat: paste clipboard link from new button --- .../xmedia/archive/plugin/LocalArchivePlugin.kt | 14 ++++++++++++++ apps/client/src/App.tsx | 12 +++++++++--- apps/client/src/api.ts | 10 ++++++++++ apps/client/src/nativeArchive.ts | 1 + 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/Android/project/app/src/main/kotlin/com/xmedia/archive/plugin/LocalArchivePlugin.kt b/Android/project/app/src/main/kotlin/com/xmedia/archive/plugin/LocalArchivePlugin.kt index a62baaac..69a293b8 100644 --- a/Android/project/app/src/main/kotlin/com/xmedia/archive/plugin/LocalArchivePlugin.kt +++ b/Android/project/app/src/main/kotlin/com/xmedia/archive/plugin/LocalArchivePlugin.kt @@ -1,5 +1,7 @@ package com.xmedia.archive.plugin +import android.content.ClipboardManager +import android.content.Context import android.content.Intent import android.net.Uri import androidx.activity.result.ActivityResult @@ -116,6 +118,18 @@ class LocalArchivePlugin : Plugin() { } } + @PluginMethod + fun readClipboard(call: PluginCall) { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager + val text = clipboard?.primaryClip + ?.takeIf { it.itemCount > 0 } + ?.getItemAt(0) + ?.coerceToText(context) + ?.toString() + .orEmpty() + call.resolve(JSObject().put("text", text)) + } + @PluginMethod fun openMedia(call: PluginCall) = launchMediaIntent(call, Intent.ACTION_VIEW) diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index 372ffd79..9d3aac3f 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -27,7 +27,7 @@ import { X, Youtube, } from 'lucide-react'; -import { cancelJob, clearHistory, clearXSession, createJobs, getDownloadFolder, getXSessionStatus, listJobs, openMedia, retryJob, selectDownloadFolder, startXLogin, subscribeJobs, xLoginSupported } from './api'; +import { cancelJob, clearHistory, clearXSession, createJobs, getDownloadFolder, getXSessionStatus, listJobs, openMedia, readClipboardText, retryJob, selectDownloadFolder, startXLogin, subscribeJobs, xLoginSupported } from './api'; import type { DownloadJob, JobStatus, MediaItem } from './types'; const STATUS: Record = { @@ -284,8 +284,14 @@ export default function App() { const completedTotal = jobs.filter((job) => job.status === 'completed').length; function focusComposer() { - document.querySelector('.ingest-panel')?.scrollIntoView({ behavior: 'smooth', block: 'center' }); - window.setTimeout(() => document.querySelector('.ingest-panel textarea')?.focus(), 350); + const panel = document.querySelector('.ingest-panel'); + const textarea = panel?.querySelector('textarea'); + panel?.scrollIntoView({ behavior: 'smooth', block: 'center' }); + textarea?.focus(); + void readClipboardText().then((clipboardText) => { + const value = clipboardText.trim(); + if (value) setInput((current) => current.trim() ? `${current.trim()}\n${value}` : value); + }).catch(() => undefined); } async function connectX() { diff --git a/apps/client/src/api.ts b/apps/client/src/api.ts index e05c5d81..535374fe 100644 --- a/apps/client/src/api.ts +++ b/apps/client/src/api.ts @@ -69,6 +69,16 @@ export function clearXSession() { return Promise.resolve({ configured: false }); } +export async function readClipboardText() { + try { + if (native) return (await LocalArchive.readClipboard()).text; + if (!navigator.clipboard?.readText) return ''; + return await navigator.clipboard.readText(); + } catch { + return ''; + } +} + export function openMedia(id: string) { if (native) return LocalArchive.openMedia({ id }); window.open(`/api/jobs/media/${id}`, '_blank', 'noopener'); diff --git a/apps/client/src/nativeArchive.ts b/apps/client/src/nativeArchive.ts index 7940986f..2574c819 100644 --- a/apps/client/src/nativeArchive.ts +++ b/apps/client/src/nativeArchive.ts @@ -15,6 +15,7 @@ export interface LocalArchivePlugin { getXSessionStatus(): Promise<{ configured: boolean }>; startXLogin(): Promise<{ configured: boolean; canceled: boolean }>; clearXSession(): Promise<{ configured: boolean }>; + readClipboard(): Promise<{ text: string }>; openMedia(input: { id: string }): Promise; selectDownloadFolder(): Promise<{ selected: boolean; uri?: string }>; getDownloadFolder(): Promise<{ selected: boolean }>;