Skip to content

Commit 6638b61

Browse files
committed
chore: cut copy paste works in popped out live preview and clipboard reliability in browser
1 parent 317b9be commit 6638b61

File tree

3 files changed

+103
-22
lines changed

3 files changed

+103
-22
lines changed

src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,63 @@
461461
});
462462
}
463463

464+
// Cut: Ctrl+X / Cmd+X - operates on selected element
465+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "x") {
466+
// Check if user is editing text content - if so, allow normal text cut
467+
const activeElement = document.activeElement;
468+
const isEditingText = activeElement &&
469+
activeElement.hasAttribute("contenteditable") &&
470+
activeElement.hasAttribute("data-brackets-id");
471+
472+
// Only handle element cut if not editing text and in edit mode
473+
if (!isEditingText &&
474+
window._LD &&
475+
window._LD.handleCutElement &&
476+
window._LD.getMode &&
477+
window._LD.getMode() === 'edit') {
478+
e.preventDefault();
479+
window._LD.handleCutElement();
480+
}
481+
}
482+
483+
// Copy: Ctrl+C / Cmd+C - operates on selected element
484+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "c") {
485+
// Check if user is editing text content - if so, allow normal text copy
486+
const activeElement = document.activeElement;
487+
const isEditingText = activeElement &&
488+
activeElement.hasAttribute("contenteditable") &&
489+
activeElement.hasAttribute("data-brackets-id");
490+
491+
// Only handle element copy if not editing text and in edit mode
492+
if (!isEditingText &&
493+
window._LD &&
494+
window._LD.handleCopyElement &&
495+
window._LD.getMode &&
496+
window._LD.getMode() === 'edit') {
497+
e.preventDefault();
498+
window._LD.handleCopyElement();
499+
}
500+
}
501+
502+
// Paste: Ctrl+V / Cmd+V - operates on selected element
503+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "v") {
504+
// Check if user is editing text content - if so, allow normal text paste
505+
const activeElement = document.activeElement;
506+
const isEditingText = activeElement &&
507+
activeElement.hasAttribute("contenteditable") &&
508+
activeElement.hasAttribute("data-brackets-id");
509+
510+
// Only handle element paste if not editing text and in edit mode
511+
if (!isEditingText &&
512+
window._LD &&
513+
window._LD.handlePasteElement &&
514+
window._LD.getMode &&
515+
window._LD.getMode() === 'edit') {
516+
e.preventDefault();
517+
window._LD.handlePasteElement();
518+
}
519+
}
520+
464521
// for save
465522
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") {
466523
e.preventDefault();

src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2313,7 +2313,8 @@ function RemoteFunctions(config = {}) {
23132313
"updateConfig": updateConfig,
23142314
"dismissUIAndCleanupState": dismissUIAndCleanupState,
23152315
"showToastMessage": showToastMessage,
2316-
"escapeKeyPressInEditor": _escapeKeyPressInEditor
2316+
"escapeKeyPressInEditor": _escapeKeyPressInEditor,
2317+
"getMode": function() { return config.mode; }
23172318
};
23182319

23192320
// the below code comment is replaced by added scripts for extensibility

src/phoenix/shell.js

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ let cliArgs, cliCWD, singleInstanceCLIHandler, quitTimeAppUpdateHandler;
4848
const PHOENIX_WINDOW_PREFIX = 'phcode-';
4949
const PHOENIX_EXTENSION_WINDOW_PREFIX = 'extn-';
5050

51+
// this shared a copy of the text that was most recently copied to clipboard. This is used as a fallback for
52+
// clipboardReadText when clipboardReadText fails in the browser(browser disables clipboard
53+
// apis when page is not in focus)
54+
let copiedClipboardText = "";
55+
5156
async function _getTauriWindowLabel(prefix) {
5257
// cannot use tauri sync api here as it returns stale window list window.__TAURI__.window.getAll();
5358
const tauriWindowLabels = await window.__TAURI__.invoke('_get_window_labels');
@@ -177,14 +182,6 @@ Phoenix.app = {
177182
window.__TAURI__.window.getCurrent().setFocus();
178183
window.__TAURI__.window.getCurrent().setAlwaysOnTop(false);
179184
},
180-
clipboardReadText: function () {
181-
if(Phoenix.isNativeApp){
182-
return window.__TAURI__.clipboard.readText();
183-
} else if(window.navigator && window.navigator.clipboard){
184-
return window.navigator.clipboard.readText();
185-
}
186-
return Promise.reject(new Error("clipboardReadText: Not supported."));
187-
},
188185
/**
189186
* Gets the commandline argument in desktop builds and null in browser builds.
190187
* Will always return CLI of the current process only.
@@ -278,6 +275,21 @@ Phoenix.app = {
278275
});
279276
}
280277
},
278+
clipboardReadText: async function () {
279+
try{
280+
let textRead;
281+
if(Phoenix.isNativeApp){
282+
textRead = await window.__TAURI__.clipboard.readText();
283+
} else if(window.navigator && window.navigator.clipboard){
284+
textRead = await window.navigator.clipboard.readText();
285+
}
286+
return textRead;
287+
} catch (e) {
288+
// we silently bail out with fallback text. see `copyToClipboard` on why we do this.
289+
console.error("Error while reading clipboard: ", e);
290+
}
291+
return copiedClipboardText;
292+
},
281293
clipboardReadFiles: function () {
282294
return new Promise((resolve, reject)=>{
283295
if(Phoenix.isNativeApp){
@@ -298,19 +310,30 @@ Phoenix.app = {
298310
}
299311
});
300312
},
301-
copyToClipboard: function (textToCopy) {
302-
if(Phoenix.isNativeApp){
303-
return window.__TAURI__.clipboard.writeText(textToCopy);
304-
} else if(window.navigator && window.navigator.clipboard){
305-
return window.navigator.clipboard.writeText(textToCopy);
306-
}
307-
const textArea = document.createElement("textarea");
308-
textArea.value = textToCopy;
309-
document.body.appendChild(textArea);
310-
textArea.select();
311-
document.execCommand("copy");
312-
document.body.removeChild(textArea);
313-
return Promise.resolve();
313+
copyToClipboard: async function (textToCopy) {
314+
try{
315+
copiedClipboardText = textToCopy;
316+
if(Phoenix.isNativeApp){
317+
await window.__TAURI__.clipboard.writeText(textToCopy);
318+
return;
319+
} else if(window.navigator && window.navigator.clipboard){
320+
await window.navigator.clipboard.writeText(textToCopy);
321+
return;
322+
}
323+
const textArea = document.createElement("textarea");
324+
textArea.value = textToCopy;
325+
document.body.appendChild(textArea);
326+
textArea.select();
327+
document.execCommand("copy");
328+
document.body.removeChild(textArea);
329+
} catch (e) {
330+
// we silently bail out as we will fallback to copiedClipboardText local variable.
331+
// in browsers, when tab is not in focus, the clipboard apis can fail as its expected browser behavior.
332+
// in popped out live previews, it may edit the clipboard for copy-paste operations which is normal
333+
// behavior which should work with fallback text. Since this is normal behavior, we dont want to
334+
// report the errors upstream but just log for now.
335+
console.error("Error while copying to clipboard: ", e);
336+
}
314337
},
315338
isFullscreen: function () {
316339
if(!Phoenix.isNativeApp) {

0 commit comments

Comments
 (0)