Skip to content

Commit 6fb4adc

Browse files
committed
feat(web): add fallback paste mechanism for mobile terminal
- Add hidden textarea as fallback when Clipboard API unavailable - Improve error handling: throw exceptions instead of silent failures - Show user-friendly toast prompts guiding manual paste operation - Add translations for paste error/fallback messages (en/zh) - Automatically send pasted text to terminal from textarea input - Handle non-HTTPS, older browsers, and permission restrictions gracefully
1 parent 44eb1b0 commit 6fb4adc

4 files changed

Lines changed: 58 additions & 18 deletions

File tree

packages/web/src/features/terminal-panel/uploads/use-paste-drop-upload.ts

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,7 @@ export function usePasteDropUpload(opts: Options): PasteDropUploadActions {
121121

122122
const clipboard = navigator.clipboard;
123123
if (!clipboard) {
124-
pushToast({
125-
kind: "error",
126-
title: "Paste failed",
127-
body: "Clipboard API not available",
128-
duration: 3_000,
129-
});
130-
return;
124+
throw new Error("Clipboard API not available");
131125
}
132126

133127
try {
@@ -160,13 +154,7 @@ export function usePasteDropUpload(opts: Options): PasteDropUploadActions {
160154
try {
161155
const readText = clipboard.readText?.bind(clipboard);
162156
if (!readText) {
163-
pushToast({
164-
kind: "error",
165-
title: "Paste failed",
166-
body: "Clipboard text read not available",
167-
duration: 3_000,
168-
});
169-
return;
157+
throw new Error("Clipboard text read not available");
170158
}
171159

172160
const text = await readText();
@@ -181,13 +169,14 @@ export function usePasteDropUpload(opts: Options): PasteDropUploadActions {
181169
}
182170

183171
await handleText(text);
184-
} catch (_error) {
172+
} catch (error) {
185173
pushToast({
186174
kind: "error",
187175
title: "Paste failed",
188176
body: "Could not read from clipboard. Please check permissions.",
189177
duration: 3_000,
190178
});
179+
throw error;
191180
}
192181
}, [enabled, handleFiles, handleText, pushToast]);
193182

packages/web/src/features/terminal-panel/views/shared/xterm-host.tsx

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2168,9 +2168,41 @@ export function XtermHost({
21682168

21692169
const fileInputRef = useRef<HTMLInputElement | null>(null);
21702170

2171-
const handleMobilePaste = useCallback(() => {
2172-
void handleClipboardPaste();
2173-
}, [handleClipboardPaste]);
2171+
const pasteTextareaRef = useRef<HTMLTextAreaElement | null>(null);
2172+
2173+
const handleMobilePaste = useCallback(async () => {
2174+
// Try modern Clipboard API first
2175+
if (navigator.clipboard) {
2176+
try {
2177+
await handleClipboardPaste();
2178+
return;
2179+
} catch (error) {
2180+
console.debug("Clipboard API failed, trying fallback:", error);
2181+
}
2182+
}
2183+
2184+
// Fallback: use hidden textarea for manual paste
2185+
const textarea = pasteTextareaRef.current;
2186+
if (!textarea) {
2187+
pushToast({
2188+
kind: "error",
2189+
title: t("terminal.mobile_paste_failed_title"),
2190+
body: t("terminal.mobile_paste_failed_body"),
2191+
});
2192+
return;
2193+
}
2194+
2195+
textarea.value = "";
2196+
textarea.focus();
2197+
textarea.select();
2198+
2199+
pushToast({
2200+
kind: "info",
2201+
title: t("terminal.mobile_paste_manual_title"),
2202+
body: t("terminal.mobile_paste_manual_body"),
2203+
duration: 3_000,
2204+
});
2205+
}, [handleClipboardPaste, pushToast, t]);
21742206

21752207
const handleMobileUpload = useCallback(() => {
21762208
fileInputRef.current?.click();
@@ -2245,6 +2277,17 @@ export function XtermHost({
22452277
void handleFileInputChange(event);
22462278
}}
22472279
/>
2280+
<textarea
2281+
ref={pasteTextareaRef}
2282+
hidden
2283+
onChange={async (event) => {
2284+
const text = event.currentTarget.value;
2285+
if (text) {
2286+
await sendTextToTerminal(text);
2287+
event.currentTarget.value = "";
2288+
}
2289+
}}
2290+
/>
22482291
<div
22492292
ref={containerRef}
22502293
className="xterm-host"

packages/web/src/locales/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@
262262
"copied_current_line": "Copied current line",
263263
"mobile_copy_current_line_failed_title": "Current line copy failed",
264264
"mobile_copy_current_line_failed_body": "Try long-pressing the current line again",
265+
"mobile_paste_failed_title": "Paste failed",
266+
"mobile_paste_failed_body": "Could not paste from clipboard",
267+
"mobile_paste_manual_title": "Paste",
268+
"mobile_paste_manual_body": "Please paste your text in the text area",
265269
"hide": "Hide Terminal",
266270
"show": "Show Terminal",
267271
"selector": {

packages/web/src/locales/zh.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@
262262
"copied_current_line": "已复制当前行",
263263
"mobile_copy_current_line_failed_title": "当前行复制失败",
264264
"mobile_copy_current_line_failed_body": "请重试长按当前行",
265+
"mobile_paste_failed_title": "粘贴失败",
266+
"mobile_paste_failed_body": "无法从剪贴板粘贴",
267+
"mobile_paste_manual_title": "粘贴",
268+
"mobile_paste_manual_body": "请在文本框中粘贴您的文本",
265269
"hide": "隐藏终端",
266270
"show": "显示终端",
267271
"selector": {

0 commit comments

Comments
 (0)