├── .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 |
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 |
--------------------------------------------------------------------------------