Dev 0.2.1 #30

Merged
LukeGus merged 47 commits from dev-0.2.1 into main 2025-03-24 03:17:56 +00:00
Showing only changes of commit ea631bd023 - Show all commits

View File

@@ -146,78 +146,108 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
event.preventDefault();
// Check if clipboard API is available
if (navigator.clipboard && navigator.clipboard.readText) {
navigator.clipboard.readText().then((text) => {
text = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
if (socketRef.current && socketRef.current.connected) {
const processedText = text.replace(/\n/g, "\r");
socketRef.current.emit("data", processedText);
setTimeout(() => {
isPasting = false;
}, 50);
} else {
isPasting = false;
// Use a multi-layered approach for clipboard access
const pasteFromClipboard = async () => {
try {
// Try modern Clipboard API first
if (navigator.clipboard && navigator.clipboard.readText) {
try {
const text = await navigator.clipboard.readText();
if (text && socketRef.current?.connected) {
const processedText = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\r");
socketRef.current.emit("data", processedText);
return true;
}
} catch (clipboardErr) {
console.warn("Clipboard API failed:", clipboardErr);
// Continue to fallbacks
}
}
}).catch((err) => {
console.error("Failed to read clipboard contents:", err);
// Try to handle paste manually using execCommand for fallback
tryFallbackPaste();
});
} else {
// Fallback for browsers where clipboard API is not yet available
tryFallbackPaste();
}
// Try execCommand fallback
if (document.queryCommandSupported && document.queryCommandSupported('paste')) {
const textarea = document.createElement('textarea');
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.focus();
try {
const successful = document.execCommand('paste');
if (successful) {
const text = textarea.value;
if (text && socketRef.current?.connected) {
const processedText = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\r");
socketRef.current.emit("data", processedText);
document.body.removeChild(textarea);
return true;
}
}
} catch (execErr) {
console.warn("execCommand paste failed:", execErr);
}
document.body.removeChild(textarea);
}
// Show permissions warning and instructions
terminalInstance.current.write("\r\n*** To paste: Right-click in terminal and select Paste from context menu ***\r\n");
return false;
} finally {
setTimeout(() => {
isPasting = false;
}, 100);
}
};
pasteFromClipboard();
return false;
}
return true;
});
// Add fallback paste method using execCommand or input element
const tryFallbackPaste = () => {
try {
// Create temporary textarea for paste operation
const textarea = document.createElement('textarea');
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
textarea.style.top = '0px';
document.body.appendChild(textarea);
textarea.focus();
// Try execCommand paste (works in some browsers)
const successful = document.execCommand('paste');
if (successful) {
const text = textarea.value;
if (text && socketRef.current && socketRef.current.connected) {
const processedText = text.replace(/\r\n/g, "\r").replace(/\n/g, "\r");
socketRef.current.emit("data", processedText);
}
} else {
console.log("Fallback paste failed, clipboard permissions may be needed");
terminalInstance.current.write("\r\n*** Paste failed: Please try again or grant clipboard permissions ***\r\n");
}
// Clean up
document.body.removeChild(textarea);
setTimeout(() => {
isPasting = false;
}, 50);
} catch (err) {
console.error("Fallback paste failed:", err);
terminalInstance.current.write("\r\n*** Paste failed: Try clicking in the terminal first ***\r\n");
isPasting = false;
}
};
terminalInstance.current.onKey(({ domEvent }) => {
if (domEvent.key === "c" && (domEvent.ctrlKey || domEvent.metaKey)) {
const selection = terminalInstance.current.getSelection();
if (selection) {
navigator.clipboard.writeText(selection);
// Use a try-catch to handle clipboard failures
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(selection)
.catch(err => {
console.warn("Clipboard write failed:", err);
terminalInstance.current.write("\r\n*** Copy failed: Text copied to internal buffer ***\r\n");
// Store selection in a variable as fallback
window.termixInternalClipboard = selection;
});
} else {
// Fallback for browsers without clipboard API
const textarea = document.createElement('textarea');
textarea.value = selection;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {
const successful = document.execCommand('copy');
if (!successful) {
terminalInstance.current.write("\r\n*** Copy failed: Text copied to internal buffer ***\r\n");
window.termixInternalClipboard = selection;
}
} catch (err) {
console.warn("execCommand copy failed:", err);
terminalInstance.current.write("\r\n*** Copy failed: Text copied to internal buffer ***\r\n");
window.termixInternalClipboard = selection;
}
document.body.removeChild(textarea);
}
} catch (err) {
console.error("Copy failed:", err);
terminalInstance.current.write("\r\n*** Copy failed: Text copied to internal buffer ***\r\n");
window.termixInternalClipboard = selection;
}
}
}
});
@@ -254,6 +284,127 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
socketRef.current.on("pong", () => {});
// Add right-click context menu for paste
const element = terminalInstance.current.element;
if (element) {
element.addEventListener('contextmenu', (event) => {
event.preventDefault();
// Create and show context menu
const contextMenu = document.createElement('div');
contextMenu.className = 'terminal-context-menu';
contextMenu.style.position = 'fixed';
contextMenu.style.left = `${event.clientX}px`;
contextMenu.style.top = `${event.clientY}px`;
contextMenu.style.backgroundColor = '#1e1e1e';
contextMenu.style.border = '1px solid #555';
contextMenu.style.borderRadius = '4px';
contextMenu.style.padding = '4px 0';
contextMenu.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
contextMenu.style.zIndex = '1000';
// Create copy option
const copyOption = document.createElement('div');
copyOption.innerText = 'Copy';
copyOption.className = 'terminal-context-menu-item';
copyOption.style.padding = '6px 12px';
copyOption.style.cursor = 'pointer';
copyOption.style.color = 'white';
copyOption.style.fontSize = '14px';
copyOption.onmouseover = () => {
copyOption.style.backgroundColor = '#3a3a3a';
};
copyOption.onmouseout = () => {
copyOption.style.backgroundColor = 'transparent';
};
// Handle copy action
copyOption.onclick = () => {
const selection = terminalInstance.current.getSelection();
if (selection) {
// Try to copy using clipboard API
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(selection)
.catch(err => {
console.warn("Clipboard write failed:", err);
window.termixInternalClipboard = selection;
terminalInstance.current.write("\r\n*** Copied to internal clipboard ***\r\n");
});
} else {
// Store in internal clipboard
window.termixInternalClipboard = selection;
terminalInstance.current.write("\r\n*** Copied to internal clipboard ***\r\n");
}
}
document.body.removeChild(contextMenu);
};
// Create paste option
const pasteOption = document.createElement('div');
pasteOption.innerText = 'Paste';
pasteOption.className = 'terminal-context-menu-item';
pasteOption.style.padding = '6px 12px';
pasteOption.style.cursor = 'pointer';
pasteOption.style.color = 'white';
pasteOption.style.fontSize = '14px';
pasteOption.onmouseover = () => {
pasteOption.style.backgroundColor = '#3a3a3a';
};
pasteOption.onmouseout = () => {
pasteOption.style.backgroundColor = 'transparent';
};
// Handle paste action
pasteOption.onclick = async () => {
try {
// Try clipboard API first
if (navigator.clipboard && navigator.clipboard.readText) {
try {
const text = await navigator.clipboard.readText();
if (text && socketRef.current?.connected) {
const processedText = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\r");
socketRef.current.emit("data", processedText);
}
} catch (err) {
// Use fallback or internal clipboard
if (window.termixInternalClipboard) {
const processedText = window.termixInternalClipboard.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\r");
socketRef.current.emit("data", processedText);
} else {
terminalInstance.current.write("\r\n*** Paste failed: No clipboard content available ***\r\n");
}
}
} else if (window.termixInternalClipboard) {
// Use internal clipboard if available
const processedText = window.termixInternalClipboard.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\r");
socketRef.current.emit("data", processedText);
} else {
terminalInstance.current.write("\r\n*** Paste failed: No clipboard content available ***\r\n");
}
} finally {
document.body.removeChild(contextMenu);
}
};
// Add options to menu
contextMenu.appendChild(copyOption);
contextMenu.appendChild(pasteOption);
document.body.appendChild(contextMenu);
// Remove menu when clicking elsewhere
const removeMenu = (e) => {
if (!contextMenu.contains(e.target)) {
document.body.removeChild(contextMenu);
document.removeEventListener('click', removeMenu);
}
};
setTimeout(() => {
document.addEventListener('click', removeMenu);
}, 0);
});
}
return () => {
clearInterval(pingInterval);
if (terminalInstance.current) {