Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [v2.24.1]

### Fixes

- Fixed Firefox web paste to use the browser's native clipboard shortcuts and context menu [#3349](https://github.com/Automattic/simplenote-electron/pull/3349)

## [v2.24.0]

### New Features
Expand Down
60 changes: 32 additions & 28 deletions lib/note-content-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
getNotePosition,
setNotePosition,
} from './utils/note-scroll-position';
import { isMac, isSafari } from './utils/platform';
import { useMonacoContextMenu, useMonacoPasteShortcut } from './utils/platform';
import {
withCheckboxCharacters,
withCheckboxSyntax,
Expand Down Expand Up @@ -648,28 +648,30 @@ class NoteContentEditor extends Component<Props> {
},
});

/* remove unwanted context menu items */
// see https://github.com/Microsoft/monaco-editor/issues/1058#issuecomment-468681208
const idsToRemove = [
'editor.action.changeAll',
'editor.action.quickCommand',
];
if (useMonacoContextMenu) {
/* remove unwanted context menu items */
// see https://github.com/Microsoft/monaco-editor/issues/1058#issuecomment-468681208
const idsToRemove = [
'editor.action.changeAll',
'editor.action.quickCommand',
];

const contextmenu = this.editor.getContribution(
'editor.contrib.contextmenu'
);
const contextmenu = this.editor.getContribution(
'editor.contrib.contextmenu'
);

// @ts-ignore undocumented internals
const realMethod = contextmenu._getMenuActions;
// @ts-ignore undocumented internals
const realMethod = contextmenu._getMenuActions;

// @ts-ignore undocumented internals
contextmenu._getMenuActions = function (...args) {
const items = realMethod.apply(contextmenu, args);
// @ts-ignore undocumented internals
contextmenu._getMenuActions = function (...args) {
const items = realMethod.apply(contextmenu, args);

return items.filter(function (item: Editor.IActionDescriptor) {
return !idsToRemove.includes(item.id);
});
};
return items.filter(function (item: Editor.IActionDescriptor) {
return !idsToRemove.includes(item.id);
});
};
}

// remove some default keybindings
// @see https://github.com/microsoft/monaco-editor/issues/3623#issuecomment-1472578786
Expand Down Expand Up @@ -697,10 +699,8 @@ class NoteContentEditor extends Component<Props> {

// disable editor keybindings for Electron since it is handled by editorCommand
// doing it this way will always show the keyboard hint in the context menu!
editor.createContextKey(
'allowBrowserKeybinding',
window.electron ? false : true
);
const allowBrowserKeybinding = !window.electron;
editor.createContextKey('allowBrowserKeybinding', allowBrowserKeybinding);

editor.addAction({
id: 'context_undo',
Expand Down Expand Up @@ -729,8 +729,8 @@ class NoteContentEditor extends Component<Props> {
},
});

// re-add keybindings for cut/copy/paste so they show labels
monaco.editor.addKeybindingRules([
// Firefox web is more reliable when the browser handles clipboard UI natively.
const clipboardKeybindingRules = [
{
keybinding: monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyX,
command: 'editor.action.clipboardCutAction',
Expand All @@ -741,12 +741,15 @@ class NoteContentEditor extends Component<Props> {
command: 'editor.action.clipboardCopyAction',
when: 'allowBrowserKeybinding && editorTextFocus',
},
{
];
if (useMonacoPasteShortcut) {
clipboardKeybindingRules.push({
keybinding: monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyV,
command: 'editor.action.clipboardPasteAction',
when: 'allowBrowserKeybinding && editorTextFocus',
},
]);
});
}
monaco.editor.addKeybindingRules(clipboardKeybindingRules);

// cancel selection that bubbles up to clear any search terms
editor.addAction({
Expand Down Expand Up @@ -1212,6 +1215,7 @@ class NoteContentEditor extends Component<Props> {
// @ts-ignore, @see https://github.com/microsoft/monaco-editor/issues/3829
'bracketPairColorization.enabled': false,
codeLens: false,
contextmenu: useMonacoContextMenu,
folding: false,
fontFamily:
'"Simplenote Tasks", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", sans-serif',
Expand Down
63 changes: 63 additions & 0 deletions lib/utils/platform.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
canUseMonacoContextMenu,
canUseMonacoPasteShortcut,
isFirefoxUserAgent,
isSafariUserAgent,
} from './platform';

const firefoxDesktopUserAgent =
'Mozilla/5.0 (X11; Linux x86_64; rv:145.0) Gecko/20100101 Firefox/145.0';

const firefoxIosUserAgent =
'Mozilla/5.0 (iPhone; CPU iPhone OS 18_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/150.0 Mobile/15E148 Safari/605.1.15';

const chromeDesktopUserAgent =
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36';

const safariDesktopUserAgent =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15';

describe('platform user-agent detection', () => {
it('detects Firefox user agents', () => {
expect(isFirefoxUserAgent(firefoxDesktopUserAgent)).toBe(true);
});

it('detects Firefox for iOS user agents', () => {
expect(isFirefoxUserAgent(firefoxIosUserAgent)).toBe(true);
});

it('does not detect Chromium as Firefox', () => {
expect(isFirefoxUserAgent(chromeDesktopUserAgent)).toBe(false);
});

it('keeps Safari detection separate from Chromium', () => {
expect(isSafariUserAgent(safariDesktopUserAgent)).toBe(true);
expect(isSafariUserAgent(chromeDesktopUserAgent)).toBe(false);
});

it('disables Monaco paste shortcut in Firefox web mode', () => {
expect(canUseMonacoPasteShortcut(false, firefoxDesktopUserAgent)).toBe(
false
);
});

it('keeps Monaco paste shortcut enabled in Electron mode', () => {
expect(canUseMonacoPasteShortcut(true, firefoxDesktopUserAgent)).toBe(true);
});

it('keeps Monaco paste shortcut enabled outside Firefox web mode', () => {
expect(canUseMonacoPasteShortcut(false, chromeDesktopUserAgent)).toBe(true);
});

it('disables Monaco context menu in Firefox web mode', () => {
expect(canUseMonacoContextMenu(false, firefoxDesktopUserAgent)).toBe(false);
});

it('keeps Monaco context menu enabled in Electron mode', () => {
expect(canUseMonacoContextMenu(true, firefoxDesktopUserAgent)).toBe(true);
});

it('keeps Monaco context menu enabled outside Firefox web mode', () => {
expect(canUseMonacoContextMenu(false, chromeDesktopUserAgent)).toBe(true);
});
});
30 changes: 29 additions & 1 deletion lib/utils/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,35 @@ export const isMac = isElectron

export const CmdOrCtrl = isElectron && isMac ? 'Cmd' : 'Ctrl';

export const isSafari = /^((?!chrome|android).)*safari/i.test(
export const isSafariUserAgent = (userAgent: string) =>
/^((?!chrome|android).)*safari/i.test(userAgent);

export const isFirefoxUserAgent = (userAgent: string) =>
/(firefox|fxios|librewolf|iceweasel)/i.test(userAgent);

export const isSafari = isSafariUserAgent(window.navigator.userAgent);

export const isFirefox = isFirefoxUserAgent(window.navigator.userAgent);

const shouldUseNativeBrowserClipboardUi = (
useElectron: boolean,
userAgent: string
) => !useElectron && isFirefoxUserAgent(userAgent);

export const canUseMonacoPasteShortcut = (
useElectron: boolean,
userAgent: string
) => !shouldUseNativeBrowserClipboardUi(useElectron, userAgent);

export const useMonacoPasteShortcut = canUseMonacoPasteShortcut(
isElectron,
window.navigator.userAgent
);

export const canUseMonacoContextMenu = canUseMonacoPasteShortcut;

export const useMonacoContextMenu = canUseMonacoContextMenu(
isElectron,
window.navigator.userAgent
);

Expand Down