├── .DS_Store ├── .github └── pull_request_template.md ├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── docs └── menu.png ├── electron-editor-context-menu.sublime-project ├── package-lock.json ├── package.json └── src └── index.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixmaxhq/electron-editor-context-menu/d883416fbb013640e4d36a6da79ce49570df539c/.DS_Store -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | #### Changes Made 2 | 3 | #### Potential Risks 4 | 5 | 6 | #### Test Plan 7 | 8 | 9 | #### Checklist 10 | - [ ] I've increased test coverage 11 | - [ ] Since this is a public repository, I've checked I'm not publishing private data in the code, commit comments, or this PR. 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | # JetBrains 36 | .idea -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // If you are using SublimeLinter, after modifying this config file, be sure to close and reopen 3 | // the file(s) you were editing to see the changes take effect. 4 | 5 | // Prohibit the use of undeclared variables. 6 | "undef": true, 7 | 8 | // Make JSHint aware of Node globals. 9 | "node": true, 10 | 11 | // Warn for unused variables and function parameters. 12 | "unused": true 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mixmax, Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # electron-editor-context-menu 2 | 3 | In Electron, right-clicking in text editors does… nothing. 4 | 5 | This module enables the menu you'd expect, with optional 6 | [spell-checker integration][spell-checker integration]. 7 | 8 | menu 9 | 10 | ## Installation 11 | 12 | ```js 13 | npm install electron-editor-context-menu 14 | ``` 15 | or 16 | ```js 17 | npm install electron-editor-context-menu --save 18 | ``` 19 | 20 | ## Usage 21 | 22 | ```js 23 | // In the renderer process: 24 | var remote = require('electron').remote; 25 | // `remote.require` since `Menu` is a main-process module. 26 | var buildEditorContextMenu = remote.require('electron-editor-context-menu'); 27 | 28 | window.addEventListener('contextmenu', function(e) { 29 | // Only show the context menu in text editors. 30 | if (!e.target.closest('textarea, input, [contenteditable="true"]')) return; 31 | 32 | var menu = buildEditorContextMenu(); 33 | 34 | // The 'contextmenu' event is emitted after 'selectionchange' has fired but possibly before the 35 | // visible selection has changed. Try to wait to show the menu until after that, otherwise the 36 | // visible selection will update after the menu dismisses and look weird. 37 | setTimeout(function() { 38 | menu.popup(remote.getCurrentWindow()); 39 | }, 30); 40 | }); 41 | ``` 42 | 43 | ### Spell-checker integration 44 | 45 | Show spelling suggestions by passing a _selection_ object when building the menu: 46 | 47 | ```js 48 | var selection = { 49 | isMisspelled: true, 50 | spellingSuggestions: [ 51 | 'men', 52 | 'mean', 53 | 'menu' 54 | ] 55 | }; 56 | 57 | var menu = buildEditorContextMenu(selection); 58 | ``` 59 | 60 | Get these suggestions when your [spell-check provider][setSpellCheckProvider] runs 61 | —Electron will poll it immediately before the `'contextmenu'` event fires. 62 | 63 | For a complete example using `electron-spell-check-provider`, see 64 | [here][spell-checker integration example]. 65 | 66 | ### Customizing the menu 67 | 68 | You can add or remove items to the menu, or replace it entirely, by providing 69 | `mainTemplate` and/or `suggestionsTemplate` parameters when building the menu: 70 | 71 | ```js 72 | var menu = buildEditorContextMenu(selection, mainTemplate, suggestionsTemplate); 73 | ``` 74 | 75 | The `mainTemplate` parameter customizes the always-present menu items; the 76 | `suggestionsTemplate` parameter customizes the spelling suggestion items. 77 | Pass an array of items to replace the default items entirely; pass a function 78 | to add/remove/edit the default items. The function will be passed the default 79 | array of items as a parameter and should return an array of items. 80 | 81 | ## Credits 82 | 83 | Created by [Jeff Wear][Jeff Wear]. 84 | 85 | Thanks to https://github.com/atom/electron/pull/942#issuecomment-171445954 for 86 | the initial sketch of this. 87 | 88 | ## Copyright and License 89 | 90 | Copyright 2016 Mixmax, Inc., licensed under the MIT License. 91 | 92 | [spell-checker integration]: #spell-checker-integration 93 | [setSpellCheckProvider]: https://github.com/atom/electron/blob/master/docs/api/web-frame.md#webframesetspellcheckproviderlanguage-autocorrectword-provider 94 | [spell-checker integration example]: https://github.com/mixmaxhq/electron-spell-check-provider#but-how-do-i-show-spelling-suggestions-in-the-context-menu 95 | [Jeff Wear]: https://github.com/wearhere 96 | 97 | ## Release History 98 | 99 | * 1.1.1 Fix compatibility with electron-builder 100 | * 1.1.0 Add the ability to customize the main template and the suggestions template. 101 | * 1.0.0 Initial release. 102 | 103 | -------------------------------------------------------------------------------- /docs/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixmaxhq/electron-editor-context-menu/d883416fbb013640e4d36a6da79ce49570df539c/docs/menu.png -------------------------------------------------------------------------------- /electron-editor-context-menu.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [{ 3 | "path": ".", 4 | "folder_exclude_patterns": [ 5 | "node_modules" 6 | ], 7 | "file_exclude_patterns": [ 8 | // Yarn lock file 9 | "yarn.lock" 10 | ] 11 | }], 12 | "settings": { 13 | "tab_size": 2, 14 | "ensure_newline_at_eof_on_save": true, 15 | "translate_tabs_to_spaces": true, 16 | "trim_trailing_white_space_on_save": true, 17 | "detect_indentation": false, 18 | "rulers": [100] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-editor-context-menu", 3 | "version": "1.1.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "version": "1.1.1", 9 | "license": "MIT", 10 | "dependencies": { 11 | "lodash.clonedeep": "^4.3.0", 12 | "lodash.defaults": "^4.0.1", 13 | "lodash.isarray": "^4.0.0", 14 | "lodash.isempty": "^4.1.2", 15 | "lodash.isfunction": "^3.0.8" 16 | } 17 | }, 18 | "node_modules/lodash.clonedeep": { 19 | "version": "4.5.0", 20 | "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", 21 | "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" 22 | }, 23 | "node_modules/lodash.defaults": { 24 | "version": "4.2.0", 25 | "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", 26 | "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" 27 | }, 28 | "node_modules/lodash.isarray": { 29 | "version": "4.0.0", 30 | "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-4.0.0.tgz", 31 | "integrity": "sha1-KspJayjEym1yZxUxNZDALm6jRAM=", 32 | "deprecated": "This package is deprecated. Use Array.isArray." 33 | }, 34 | "node_modules/lodash.isempty": { 35 | "version": "4.4.0", 36 | "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", 37 | "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=" 38 | }, 39 | "node_modules/lodash.isfunction": { 40 | "version": "3.0.9", 41 | "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", 42 | "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" 43 | } 44 | }, 45 | "dependencies": { 46 | "lodash.clonedeep": { 47 | "version": "4.5.0", 48 | "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", 49 | "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" 50 | }, 51 | "lodash.defaults": { 52 | "version": "4.2.0", 53 | "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", 54 | "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" 55 | }, 56 | "lodash.isarray": { 57 | "version": "4.0.0", 58 | "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-4.0.0.tgz", 59 | "integrity": "sha1-KspJayjEym1yZxUxNZDALm6jRAM=" 60 | }, 61 | "lodash.isempty": { 62 | "version": "4.4.0", 63 | "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", 64 | "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=" 65 | }, 66 | "lodash.isfunction": { 67 | "version": "3.0.9", 68 | "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", 69 | "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-editor-context-menu", 3 | "version": "1.1.1", 4 | "description": "Enable the native right-click menu in Electron.", 5 | "main": "src/index.js", 6 | "directories": { 7 | "doc": "docs" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/mixmaxhq/electron-editor-context-menu.git" 15 | }, 16 | "keywords": [ 17 | "electron", 18 | "context", 19 | "menu", 20 | "contextmenu", 21 | "right-click", 22 | "spell-check", 23 | "spell", 24 | "check" 25 | ], 26 | "author": "Jeff Wear (https://mixmax.com)", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/mixmaxhq/electron-editor-context-menu/issues" 30 | }, 31 | "homepage": "https://github.com/mixmaxhq/electron-editor-context-menu#readme", 32 | "dependencies": { 33 | "lodash.clonedeep": "^4.3.0", 34 | "lodash.defaults": "^4.0.1", 35 | "lodash.isarray": "^4.0.0", 36 | "lodash.isempty": "^4.1.2", 37 | "lodash.isfunction": "^3.0.8" 38 | } 39 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var noop = function(){}; 2 | var defaults = require('lodash.defaults'); 3 | var isEmpty = require('lodash.isempty'); 4 | var isFunction = require('lodash.isfunction'); 5 | var isArray = require('lodash.isarray'); 6 | var cloneDeep = require('lodash.clonedeep'); 7 | var BrowserWindow = require('electron').BrowserWindow; 8 | var Menu = require('electron').Menu; 9 | 10 | 11 | var DEFAULT_MAIN_TPL = [{ 12 | label: 'Undo', 13 | role: 'undo' 14 | }, { 15 | label: 'Redo', 16 | role: 'redo' 17 | }, { 18 | type: 'separator' 19 | }, { 20 | label: 'Cut', 21 | role: 'cut' 22 | }, { 23 | label: 'Copy', 24 | role: 'copy' 25 | }, { 26 | label: 'Paste', 27 | role: 'paste' 28 | }, { 29 | label: 'Paste and Match Style', 30 | click: function() { 31 | BrowserWindow.getFocusedWindow().webContents.pasteAndMatchStyle(); 32 | } 33 | }, { 34 | label: 'Select All', 35 | role: 'selectall' 36 | }]; 37 | 38 | var DEFAULT_SUGGESTIONS_TPL = [ 39 | { 40 | label: 'No suggestions', 41 | click: noop 42 | }, { 43 | type: 'separator' 44 | } 45 | ]; 46 | 47 | /** 48 | * if passed a function, invoke it and pass a clone of the default (for safe mutations) 49 | * if passed an array, use as is 50 | * otherwise, just return a clone of the default 51 | * @param val {*} 52 | * @param defaultVal {Array} 53 | * @returns {Array} 54 | */ 55 | function getTemplate(val, defaultVal) { 56 | if(isFunction(val)) { 57 | return val(cloneDeep(defaultVal)); 58 | } 59 | else if(isArray(val)) { 60 | return val; 61 | } 62 | else { 63 | return cloneDeep(defaultVal); 64 | } 65 | } 66 | 67 | /** 68 | * Builds a context menu suitable for showing in a text editor. 69 | * 70 | * @param {Object=} selection - An object describing the current text selection. 71 | * @property {Boolean=false} isMisspelled - `true` if the selection is 72 | * misspelled, `false` if it is spelled correctly or is not text. 73 | * @property {Array=[]} spellingSuggestions - An array of suggestions 74 | * to show to correct the misspelling. Ignored if `isMisspelled` is `false`. 75 | * @param {Function|Array} mainTemplate - Optional. If it's an array, use as is. 76 | * If it's a function, used to customize the template of always-present menu items. 77 | * Receives the default template as a parameter. Should return a template. 78 | * @param {Function|Array} suggestionsTemplate - Optional. If it's an array, use as is. 79 | * If it's a function, used to customize the template of spelling suggestion items. 80 | * Receives the default suggestions template as a parameter. Should return a template. 81 | * @return {Menu} 82 | */ 83 | var buildEditorContextMenu = function(selection, mainTemplate, suggestionsTemplate) { 84 | 85 | selection = defaults({}, selection, { 86 | isMisspelled: false, 87 | spellingSuggestions: [] 88 | }); 89 | 90 | var template = getTemplate(mainTemplate, DEFAULT_MAIN_TPL); 91 | var suggestionsTpl = getTemplate(suggestionsTemplate, DEFAULT_SUGGESTIONS_TPL); 92 | 93 | if (selection.isMisspelled) { 94 | var suggestions = selection.spellingSuggestions; 95 | if (isEmpty(suggestions)) { 96 | template.unshift.apply(template, suggestionsTpl); 97 | } else { 98 | template.unshift.apply(template, suggestions.map(function(suggestion) { 99 | return { 100 | label: suggestion, 101 | click: function() { 102 | BrowserWindow.getFocusedWindow().webContents.replaceMisspelling(suggestion); 103 | } 104 | }; 105 | }).concat({ 106 | type: 'separator' 107 | })); 108 | } 109 | } 110 | 111 | return Menu.buildFromTemplate(template); 112 | }; 113 | 114 | module.exports = buildEditorContextMenu; 115 | --------------------------------------------------------------------------------