From 339d961b3f2e0c386e68e650ee0f05e705f38c9e Mon Sep 17 00:00:00 2001 From: Amine Mouafik Date: Mon, 2 Mar 2020 02:31:15 +0100 Subject: Remove conflicting context menu --- src/webview/contextMenu.js | 323 --------------------------------------------- src/webview/recipe.js | 11 +- 2 files changed, 1 insertion(+), 333 deletions(-) delete mode 100644 src/webview/contextMenu.js diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js deleted file mode 100644 index acd62d675..000000000 --- a/src/webview/contextMenu.js +++ /dev/null @@ -1,323 +0,0 @@ -// This is heavily based on https://github.com/sindresorhus/electron-context-menu -// ❤ @sindresorhus - -import { - clipboard, remote, ipcRenderer, shell, -} from 'electron'; - -import { isDevMode, isMac } from '../environment'; -import { SPELLCHECKER_LOCALES } from '../i18n/languages'; - -const debug = require('debug')('Ferdi:contextMenu'); - -const { Menu } = remote; - -// const win = remote.getCurrentWindow(); -const webContents = remote.getCurrentWebContents(); - -function delUnusedElements(menuTpl) { - let notDeletedPrevEl; - return menuTpl.filter(el => el.visible !== false).filter((el, i, array) => { - const toDelete = el.type === 'separator' && (!notDeletedPrevEl || i === array.length - 1 || array[i + 1].type === 'separator'); - notDeletedPrevEl = toDelete ? notDeletedPrevEl : el; - return !toDelete; - }); -} - -const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheckerLanguage, spellcheckerLanguage) => { - const { editFlags } = props; - const textSelection = props.selectionText.trim(); - const hasText = textSelection.length > 0; - const can = type => editFlags[`can${type}`] && hasText; - - const canGoBack = webContents.canGoBack(); - const canGoForward = webContents.canGoForward(); - - // @adlk: we can't use roles here due to a bug with electron where electron.remote.webContents.getFocusedWebContents() returns the first webview in DOM instead of the focused one - // Github issue creation is pending - let menuTpl = [ - { - type: 'separator', - }, { - id: 'createTodo', - label: `Create todo: "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`, - visible: hasText, - click() { - debug('Create todo from selected text', textSelection); - ipcRenderer.sendToHost('feature:todos', { - action: 'todos:create', - data: { - title: textSelection, - url: window.location.href, - }, - }); - }, - }, - { - type: 'separator', - }, { - id: 'lookup', - label: `Look Up "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`, - visible: isMac && props.mediaType === 'none' && hasText, - click() { - debug('Show definition for selection', textSelection); - webContents.showDefinitionForSelection(); - }, - }, { - type: 'separator', - }, { - id: 'cut', - label: 'Cut', - click() { - if (can('Cut')) { - webContents.cut(); - } - }, - enabled: can('Cut'), - visible: hasText && props.isEditable, - }, { - id: 'copy', - label: 'Copy', - click() { - if (can('Copy')) { - webContents.copy(); - } - }, - enabled: can('Copy'), - visible: props.isEditable || hasText, - }, { - id: 'paste', - label: 'Paste', - click() { - if (editFlags.canPaste) { - webContents.paste(); - } - }, - enabled: editFlags.canPaste, - visible: props.isEditable, - }, { - type: 'separator', - visible: props.isEditable && hasText, - }, { - id: 'searchTextSelection', - label: `Search Google for "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`, - visible: hasText, - click() { - const url = `https://www.google.com/search?q=${textSelection}`; - debug('Search on Google', url); - shell.openExternal(url); - }, - }, { - type: 'separator', - }, - ]; - - if (props.linkURL && props.mediaType === 'none') { - menuTpl = [{ - type: 'separator', - }, { - id: 'openLink', - label: 'Open Link in Browser', - click() { - debug('Open link in Browser', props.linkURL); - shell.openExternal(props.linkURL); - }, - }, { - id: 'copyLink', - label: 'Copy Link', - click() { - clipboard.write({ - bookmark: props.linkText, - text: props.linkURL, - }); - }, - }, { - type: 'separator', - }]; - } - - if (props.mediaType === 'image') { - menuTpl.push({ - type: 'separator', - }, { - id: 'openImage', - label: 'Open Image in Browser', - click() { - debug('Open image in Browser', props.srcURL); - shell.openExternal(props.srcURL); - }, - }, { - id: 'copyImageAddress', - label: 'Copy Image Address', - click() { - clipboard.write({ - bookmark: props.srcURL, - text: props.srcURL, - }); - }, - }, { - type: 'separator', - }); - } - - if (props.mediaType === 'image') { - menuTpl.push({ - id: 'saveImageAs', - label: 'Save Image As…', - async click() { - if (props.srcURL.startsWith('blob:')) { - const url = new window.URL(props.srcURL.substr(5)); - const fileName = url.pathname.substr(1); - const resp = await window.fetch(props.srcURL); - const blob = await resp.blob(); - const reader = new window.FileReader(); - reader.readAsDataURL(blob); - reader.onloadend = () => { - const base64data = reader.result; - - ipcRenderer.send('download-file', { - content: base64data, - fileOptions: { - name: fileName, - mime: blob.type, - }, - }); - }; - debug('binary string', blob); - } else { - ipcRenderer.send('download-file', { url: props.srcURL }); - } - }, - }, { - type: 'separator', - }); - } - - if (suggestions.length > 0) { - suggestions.reverse().map(suggestion => menuTpl.unshift({ - id: `suggestion-${suggestion}`, - label: suggestion, - click() { - webContents.replaceMisspelling(suggestion); - }, - })); - } - - if (canGoBack || canGoForward) { - menuTpl.push({ - type: 'separator', - }, { - id: 'goBack', - label: 'Go Back', - enabled: canGoBack, - click() { - webContents.goBack(); - }, - }, { - id: 'goForward', - label: 'Go Forward', - enabled: canGoForward, - click() { - webContents.goForward(); - }, - }, { - type: 'separator', - }); - } - - const spellcheckingLanguages = []; - Object.keys(SPELLCHECKER_LOCALES).sort(Intl.Collator().compare).forEach((key) => { - spellcheckingLanguages.push({ - id: `lang-${key}`, - label: SPELLCHECKER_LOCALES[key], - type: 'radio', - checked: spellcheckerLanguage === key, - click() { - debug('Setting service spellchecker to', key); - ipcRenderer.sendToHost('set-service-spellchecker-language', key); - }, - }); - }); - - menuTpl.push({ - type: 'separator', - }, { - id: 'spellchecker', - label: 'Spell Checking', - visible: isSpellcheckEnabled, - submenu: [ - { - id: 'spellchecker', - label: 'Available Languages', - enabled: false, - }, { - type: 'separator', - }, - { - id: 'resetToDefault', - label: `Reset to system default (${defaultSpellcheckerLanguage === 'automatic' ? 'Automatic' : SPELLCHECKER_LOCALES[defaultSpellcheckerLanguage]})`, - type: 'radio', - visible: defaultSpellcheckerLanguage !== spellcheckerLanguage || (defaultSpellcheckerLanguage !== 'automatic' && spellcheckerLanguage === 'automatic'), - click() { - debug('Resetting service spellchecker to system default'); - ipcRenderer.sendToHost('set-service-spellchecker-language', 'reset'); - }, - }, - { - id: 'automaticDetection', - label: 'Automatic language detection', - type: 'radio', - checked: spellcheckerLanguage === 'automatic', - click() { - debug('Detect language automatically'); - ipcRenderer.sendToHost('set-service-spellchecker-language', 'automatic'); - }, - }, - { - type: 'separator', - visible: defaultSpellcheckerLanguage !== spellcheckerLanguage, - }, - ...spellcheckingLanguages], - }); - - - if (isDevMode) { - menuTpl.push({ - type: 'separator', - }, { - id: 'inspect', - label: 'Inspect Element', - click() { - webContents.inspectElement(props.x, props.y); - }, - }); - } - - return delUnusedElements(menuTpl); -}; - -export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) { - webContents.on('context-menu', async (e, props) => { - e.preventDefault(); - - let suggestions = []; - if (spellcheckProvider && props.misspelledWord) { - debug('Mispelled word', props.misspelledWord); - suggestions = await spellcheckProvider.getSuggestion(props.misspelledWord); - - debug('Suggestions', suggestions); - } - - const menu = Menu.buildFromTemplate( - buildMenuTpl( - props, - suggestions.slice(0, 5), - isSpellcheckEnabled(), - getDefaultSpellcheckerLanguage(), - getSpellcheckerLanguage(), - ), - ); - - menu.popup(); - }); -} diff --git a/src/webview/recipe.js b/src/webview/recipe.js index 1a22542d8..07d29f477 100644 --- a/src/webview/recipe.js +++ b/src/webview/recipe.js @@ -23,7 +23,6 @@ import RecipeWebview from './lib/RecipeWebview'; import spellchecker, { switchDict, disable as disableSpellchecker, getSpellcheckerLocaleByFuzzyIdentifier } from './spellchecker'; import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; -import contextMenu from './contextMenu'; import './notifications'; import { DEFAULT_APP_SETTINGS } from '../config'; @@ -72,15 +71,7 @@ class RecipeController { debug('Send "hello" to host'); setTimeout(() => ipcRenderer.sendToHost('hello'), 100); - - this.spellcheckingProvider = await spellchecker(); - contextMenu( - this.spellcheckingProvider, - () => this.settings.app.enableSpellchecking, - () => this.settings.app.spellcheckerLanguage, - () => this.spellcheckerLanguage, - ); - + await spellchecker(); autorun(() => this.update()); } -- cgit v1.2.3-70-g09d2 From 9582acc6eb946ef49659e03a2166d52163e8aeab Mon Sep 17 00:00:00 2001 From: Amine Mouafik Date: Mon, 2 Mar 2020 03:00:11 +0100 Subject: Keep separation of concern between context menu / spellchecker --- src/webview/contextMenu.js | 9 +++++++++ src/webview/spellchecker.js | 12 +++--------- 2 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 src/webview/contextMenu.js diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js new file mode 100644 index 000000000..f02dd1a01 --- /dev/null +++ b/src/webview/contextMenu.js @@ -0,0 +1,9 @@ +import { ContextMenuBuilder, ContextMenuListener } from "electron-spellchecker"; + +export default async function setupContextMenu(handler) { + let contextMenuBuilder = new ContextMenuBuilder(handler); + // eslint-disable-next-line no-new + new ContextMenuListener(info => { + contextMenuBuilder.showPopupMenu(info); + }); +} diff --git a/src/webview/spellchecker.js b/src/webview/spellchecker.js index 8a1c8782b..a33a506b2 100644 --- a/src/webview/spellchecker.js +++ b/src/webview/spellchecker.js @@ -1,13 +1,12 @@ import { webFrame } from 'electron'; -import { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } from 'electron-spellchecker'; - +import { SpellCheckHandler } from 'electron-spellchecker'; import { SPELLCHECKER_LOCALES } from '../i18n/languages'; +import setupContextMenu from './contextMenu'; const debug = require('debug')('Franz:spellchecker'); let handler; let currentDict; -let contextMenuBuilder; let _isEnabled = false; export async function switchDict(locale) { @@ -46,12 +45,7 @@ export default async function initialize(languageCode = 'en-us') { debug('Init spellchecker'); switchDict(locale); - - contextMenuBuilder = new ContextMenuBuilder(handler); - // eslint-disable-next-line no-new - new ContextMenuListener((info) => { - contextMenuBuilder.showPopupMenu(info); - }); + setupContextMenu(handler); return handler; } catch (err) { -- cgit v1.2.3-70-g09d2 From b18ff4240e398ee338ad0aaf7dc639b962534c72 Mon Sep 17 00:00:00 2001 From: Amine Mouafik Date: Mon, 2 Mar 2020 03:06:30 +0100 Subject: Add paste as plain text to context menu Via https://github.com/electron-userland/electron-spellchecker/issues/125 --- src/webview/contextMenu.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js index f02dd1a01..7ab60d251 100644 --- a/src/webview/contextMenu.js +++ b/src/webview/contextMenu.js @@ -1,7 +1,33 @@ +import { remote } from "electron"; import { ContextMenuBuilder, ContextMenuListener } from "electron-spellchecker"; +const webContents = remote.getCurrentWebContents(); + export default async function setupContextMenu(handler) { - let contextMenuBuilder = new ContextMenuBuilder(handler); + const processMenu = (menu, menuInfo) => { + if ( + menuInfo.editFlags.canPaste && + !menuInfo.linkText && + !menuInfo.hasImageContents + ) { + menu.insert( + 3, + new remote.MenuItem({ + label: "Paste as plain text", + accelerator: "CommandOrControl+Shift+V", + click: () => webContents.pasteAndMatchStyle() + }) + ); + } + return menu; + }; + + const contextMenuBuilder = new ContextMenuBuilder( + handler, + null, + true, + processMenu + ); // eslint-disable-next-line no-new new ContextMenuListener(info => { contextMenuBuilder.showPopupMenu(info); -- cgit v1.2.3-70-g09d2 From 06a0e08a5b4e8f8696dab3ce2c6bdb8c4a901f9f Mon Sep 17 00:00:00 2001 From: vantezzen Date: Mon, 2 Mar 2020 10:02:58 +0100 Subject: Add "Open Link in Ferdi" to context menu --- src/webview/contextMenu.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js index 7ab60d251..e77ff2326 100644 --- a/src/webview/contextMenu.js +++ b/src/webview/contextMenu.js @@ -19,6 +19,19 @@ export default async function setupContextMenu(handler) { }) ); } + + if (menuInfo.linkURL) { + menu.insert( + 2, + new remote.MenuItem({ + label: "Open Link in Ferdi", + click: () => { + window.location.href = menuInfo.linkURL; + } + }) + ); + } + return menu; }; -- cgit v1.2.3-70-g09d2 From 581663761d44d91bf00a96956823b92d354d2bf1 Mon Sep 17 00:00:00 2001 From: vantezzen Date: Mon, 2 Mar 2020 10:05:58 +0100 Subject: Run linter --- src/lib/Menu.js | 2 +- src/stores/SettingsStore.js | 7 +++++-- src/webview/contextMenu.js | 32 +++++++++++++++++--------------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/lib/Menu.js b/src/lib/Menu.js index 1c4cc6ab5..6d5eb0095 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js @@ -819,7 +819,7 @@ export default class FranzMenu { locked: true, }, }); - } + }, }); if (serviceTpl.length > 0) { diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js index 26e83b725..71d4e1702 100644 --- a/src/stores/SettingsStore.js +++ b/src/stores/SettingsStore.js @@ -1,5 +1,7 @@ import { ipcRenderer, remote } from 'electron'; -import { action, computed, observable, reaction } from 'mobx'; +import { + action, computed, observable, reaction, +} from 'mobx'; import localStorage from 'mobx-localstorage'; import { DEFAULT_APP_SETTINGS, FILE_SYSTEM_SETTINGS_TYPES, LOCAL_SERVER } from '../config'; import { API } from '../environment'; @@ -12,6 +14,7 @@ const debug = require('debug')('Ferdi:SettingsStore'); export default class SettingsStore extends Store { @observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings'); + startup = true; fileSystemSettingsTypes = FILE_SYSTEM_SETTINGS_TYPES; @@ -103,7 +106,7 @@ export default class SettingsStore extends Store { // So we lock manually window.ferdi.stores.router.push('/auth/locked'); } - }) + }); } debug('Get appSettings resolves', resp.type, resp.data); Object.assign(this._fileSystemSettingsCache[resp.type], resp.data); diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js index e77ff2326..eeb825ece 100644 --- a/src/webview/contextMenu.js +++ b/src/webview/contextMenu.js @@ -1,34 +1,36 @@ -import { remote } from "electron"; -import { ContextMenuBuilder, ContextMenuListener } from "electron-spellchecker"; +import { remote } from 'electron'; +import { ContextMenuBuilder, ContextMenuListener } from 'electron-spellchecker'; const webContents = remote.getCurrentWebContents(); export default async function setupContextMenu(handler) { - const processMenu = (menu, menuInfo) => { + const addCustomMenuItems = (menu, menuInfo) => { + // Add "Paste as plain text" item when right-clicking editable content if ( - menuInfo.editFlags.canPaste && - !menuInfo.linkText && - !menuInfo.hasImageContents + menuInfo.editFlags.canPaste + && !menuInfo.linkText + && !menuInfo.hasImageContents ) { menu.insert( 3, new remote.MenuItem({ - label: "Paste as plain text", - accelerator: "CommandOrControl+Shift+V", - click: () => webContents.pasteAndMatchStyle() - }) + label: 'Paste as plain text', + accelerator: 'CommandOrControl+Shift+V', + click: () => webContents.pasteAndMatchStyle(), + }), ); } + // Add "Open Link in Ferdi" item for links if (menuInfo.linkURL) { menu.insert( 2, new remote.MenuItem({ - label: "Open Link in Ferdi", + label: 'Open Link in Ferdi', click: () => { window.location.href = menuInfo.linkURL; - } - }) + }, + }), ); } @@ -39,10 +41,10 @@ export default async function setupContextMenu(handler) { handler, null, true, - processMenu + addCustomMenuItems, ); // eslint-disable-next-line no-new - new ContextMenuListener(info => { + new ContextMenuListener((info) => { contextMenuBuilder.showPopupMenu(info); }); } -- cgit v1.2.3-70-g09d2