diff --git a/ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js b/ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js
new file mode 100644
index 000000000..f29ca73e2
--- /dev/null
+++ b/ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js
@@ -0,0 +1,127 @@
+'use strict';
+const __awaiter = (this && this.__awaiter) || function(thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function(resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function(resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator['throw'](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+Object.defineProperty(exports, '__esModule', { value: true });
+const React = require('react');
+const react_1 = require('react');
+const ShortcutRecorder_1 = require('./ShortcutRecorder');
+const getLabel_1 = require('./utils/getLabel');
+const useKeymap_1 = require('./utils/useKeymap');
+const useCommandStatus_1 = require('./utils/useCommandStatus');
+const styles_1 = require('./styles');
+const { bridge } = require('electron').remote.require('./bridge');
+const { shim } = require('lib/shim');
+const { _ } = require('lib/locale');
+exports.KeymapConfigScreen = ({ themeId }) => {
+ const styles = styles_1.default(themeId);
+ const [filter, setFilter] = react_1.useState('');
+ const [keymapItems, keymapError, overrideKeymapItems, exportCustomKeymap, setAccelerator, resetAccelerator] = useKeymap_1.default();
+ const [recorderError, setRecorderError] = react_1.useState(null);
+ const [editing, enableEditing, disableEditing] = useCommandStatus_1.default();
+ const [hovering, enableHovering, disableHovering] = useCommandStatus_1.default();
+ const handleSave = (event) => {
+ const { commandName, accelerator } = event;
+ setAccelerator(commandName, accelerator);
+ disableEditing(commandName);
+ };
+ const handleReset = (event) => {
+ const { commandName } = event;
+ resetAccelerator(commandName);
+ disableEditing(commandName);
+ };
+ const handleCancel = (event) => {
+ const { commandName } = event;
+ disableEditing(commandName);
+ };
+ const handleError = (event) => {
+ const { recorderError } = event;
+ setRecorderError(recorderError);
+ };
+ const handleImport = () => __awaiter(void 0, void 0, void 0, function* () {
+ const filePath = bridge().showOpenDialog({
+ properties: ['openFile'],
+ defaultPath: 'keymap-desktop',
+ filters: [{ name: 'Joplin Keymaps (keymap-desktop.json)', extensions: ['json'] }],
+ });
+ if (filePath) {
+ const actualFilePath = filePath[0];
+ try {
+ const keymapFile = yield shim.fsDriver().readFile(actualFilePath, 'utf-8');
+ overrideKeymapItems(JSON.parse(keymapFile));
+ } catch (err) {
+ bridge().showErrorMessageBox(`${_('An unexpected error occured while importing the keymap!')}\n${err.message}`);
+ }
+ }
+ });
+ const handleExport = () => __awaiter(void 0, void 0, void 0, function* () {
+ const filePath = bridge().showSaveDialog({
+ defaultPath: 'keymap-desktop',
+ filters: [{ name: 'Joplin Keymaps (keymap-desktop.json)', extensions: ['json'] }],
+ });
+ if (filePath) {
+ try {
+ // KeymapService is already synchronized with the in-state keymap
+ yield exportCustomKeymap(filePath);
+ } catch (err) {
+ bridge().showErrorMessageBox(`${_('An unexpected error occured while exporting the keymap!')}\n${err.message}`);
+ }
+ }
+ });
+ const renderAccelerator = (accelerator) => {
+ return (React.createElement('div', null, accelerator.split('+').map(part => React.createElement('kbd', { style: styles.kbd, key: part }, part)).reduce((accumulator, part) => (accumulator.length ? [...accumulator, ' + ', part] : [part]), [])));
+ };
+ const renderStatus = (commandName) => {
+ if (editing[commandName]) {
+ return (recorderError && React.createElement('i', { className: 'fa fa-exclamation-triangle' }));
+ } else if (hovering[commandName]) {
+ return (React.createElement('i', { className: 'fa fa-pen' }));
+ } else {
+ return null;
+ }
+ };
+ const renderError = (error) => {
+ return (React.createElement('div', { style: styles.warning },
+ React.createElement('p', { style: styles.text },
+ React.createElement('span', null, error.message))));
+ };
+ const renderKeymapRow = ({ command, accelerator }) => {
+ const handleClick = () => enableEditing(command);
+ const handleMouseEnter = () => enableHovering(command);
+ const handleMouseLeave = () => disableHovering(command);
+ const cellContent = React.createElement('div', { style: styles.tableCell, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave },
+ editing[command] ?
+ React.createElement(ShortcutRecorder_1.ShortcutRecorder, { onSave: handleSave, onReset: handleReset, onCancel: handleCancel, onError: handleError, initialAccelerator: accelerator || '' /* Because accelerator is null if disabled */, commandName: command, themeId: themeId }) :
+ React.createElement('div', { style: styles.tableCellContent, onClick: handleClick }, accelerator
+ ? renderAccelerator(accelerator)
+ : React.createElement('div', { style: styles.disabled }, _('Disabled'))),
+ React.createElement('div', { style: styles.tableCellStatus, onClick: handleClick }, renderStatus(command)));
+ return (React.createElement('tr', { key: command },
+ React.createElement('td', { style: styles.tableCommandColumn }, getLabel_1.default(command)),
+ React.createElement('td', { style: styles.tableShortcutColumn }, cellContent)));
+ };
+ return (React.createElement('div', null,
+ keymapError && renderError(keymapError),
+ React.createElement('div', { style: styles.container },
+ React.createElement('div', { style: styles.actionsContainer },
+ React.createElement('label', { style: styles.label }, _('Search')),
+ React.createElement('input', { value: filter, onChange: event => setFilter(event.target.value), placeholder: _('Search...'), style: styles.filterInput }),
+ React.createElement('button', { style: styles.inlineButton, onClick: handleImport }, _('Import')),
+ React.createElement('button', { style: styles.inlineButton, onClick: handleExport }, _('Export'))),
+ React.createElement('table', { style: styles.table },
+ React.createElement('thead', null,
+ React.createElement('tr', null,
+ React.createElement('th', { style: styles.tableCommandColumn }, _('Command')),
+ React.createElement('th', { style: styles.tableShortcutColumn }, _('Keyboard Shortcut')))),
+ React.createElement('tbody', null, keymapItems.filter(({ command }) => {
+ const filterLowerCase = filter.toLowerCase();
+ return (command.toLowerCase().includes(filterLowerCase) || getLabel_1.default(command).toLowerCase().includes(filterLowerCase));
+ }).map(item => renderKeymapRow(item)))))));
+};
+// # sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiS2V5bWFwQ29uZmlnU2NyZWVuLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiS2V5bWFwQ29uZmlnU2NyZWVuLnRzeCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7OztBQUFBLCtCQUErQjtBQUMvQixpQ0FBaUM7QUFHakMseURBQXNEO0FBQ3RELCtDQUF3QztBQUN4QyxpREFBMEM7QUFDMUMsK0RBQXdEO0FBQ3hELHFDQUErQjtBQUUvQixNQUFNLEVBQUUsTUFBTSxFQUFFLEdBQUcsT0FBTyxDQUFDLFVBQVUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUM7QUFDbEUsTUFBTSxFQUFFLElBQUksRUFBRSxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQztBQUNyQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxDQUFDO0FBTXZCLFFBQUEsa0JBQWtCLEdBQUcsQ0FBQyxFQUFFLE9BQU8sRUFBMkIsRUFBRSxFQUFFO0lBQzFFLE1BQU0sTUFBTSxHQUFHLGdCQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7SUFFaEMsTUFBTSxDQUFDLE1BQU0sRUFBRSxTQUFTLENBQUMsR0FBRyxnQkFBUSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ3pDLE1BQU0sQ0FBQyxXQUFXLEVBQUUsV0FBVyxFQUFFLG1CQUFtQixFQUFFLGtCQUFrQixFQUFFLGNBQWMsRUFBRSxnQkFBZ0IsQ0FBQyxHQUFHLG1CQUFTLEVBQUUsQ0FBQztJQUMxSCxNQUFNLENBQUMsYUFBYSxFQUFFLGdCQUFnQixDQUFDLEdBQUcsZ0JBQVEsQ0FBUSxJQUFJLENBQUMsQ0FBQztJQUNoRSxNQUFNLENBQUMsT0FBTyxFQUFFLGFBQWEsRUFBRSxjQUFjLENBQUMsR0FBRywwQkFBZ0IsRUFBRSxDQUFDO0lBQ3BFLE1BQU0sQ0FBQyxRQUFRLEVBQUUsY0FBYyxFQUFFLGVBQWUsQ0FBQyxHQUFHLDBCQUFnQixFQUFFLENBQUM7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
diff --git a/ElectronClient/gui/KeymapConfig/ShortcutRecorder.js b/ElectronClient/gui/KeymapConfig/ShortcutRecorder.js
new file mode 100644
index 000000000..9923cc180
--- /dev/null
+++ b/ElectronClient/gui/KeymapConfig/ShortcutRecorder.js
@@ -0,0 +1,51 @@
+'use strict';
+Object.defineProperty(exports, '__esModule', { value: true });
+const React = require('react');
+const react_1 = require('react');
+const KeymapService_1 = require('../../lib/services/KeymapService');
+const styles_1 = require('./styles');
+const { _ } = require('lib/locale');
+const keymapService = KeymapService_1.default.instance();
+exports.ShortcutRecorder = ({ onSave, onReset, onCancel, onError, initialAccelerator, commandName, themeId }) => {
+ const styles = styles_1.default(themeId);
+ const [accelerator, setAccelerator] = react_1.useState(initialAccelerator);
+ const [saveAllowed, setSaveAllowed] = react_1.useState(true);
+ react_1.useEffect(() => {
+ try {
+ // Only perform validations if there's an accelerator provided
+ // Otherwise performing a save means that it's going to be disabled
+ if (accelerator) {
+ keymapService.validateAccelerator(accelerator);
+ keymapService.validateKeymap({ accelerator, command: commandName });
+ }
+ // Discard previous errors
+ onError({ recorderError: null });
+ setSaveAllowed(true);
+ } catch (recorderError) {
+ onError({ recorderError });
+ setSaveAllowed(false);
+ }
+ }, [accelerator]);
+ const handleKeydown = (event) => {
+ event.preventDefault();
+ const newAccelerator = keymapService.domToElectronAccelerator(event);
+ switch (newAccelerator) {
+ case 'Enter':
+ if (saveAllowed) { return onSave({ commandName, accelerator }); }
+ break;
+ case 'Escape':
+ return onCancel({ commandName });
+ case 'Backspace':
+ case 'Delete':
+ return setAccelerator('');
+ default:
+ setAccelerator(newAccelerator);
+ }
+ };
+ return (React.createElement('div', { style: styles.recorderContainer },
+ React.createElement('input', { value: accelerator, placeholder: _('Press the shortcut'), onKeyDown: handleKeydown, style: styles.recorderInput, readOnly: true, autoFocus: true }),
+ React.createElement('button', { style: styles.inlineButton, disabled: !saveAllowed, onClick: () => onSave({ commandName, accelerator }) }, _('Save')),
+ React.createElement('button', { style: styles.inlineButton, onClick: () => onReset({ commandName }) }, _('Restore')),
+ React.createElement('button', { style: styles.inlineButton, onClick: () => onCancel({ commandName }) }, _('Cancel'))));
+};
+// # sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2hvcnRjdXRSZWNvcmRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIlNob3J0Y3V0UmVjb3JkZXIudHN4Il0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsK0JBQStCO0FBQy9CLGlDQUEyRDtBQUUzRCxvRUFBNkQ7QUFDN0QscUNBQStCO0FBRS9CLE1BQU0sRUFBRSxDQUFDLEVBQUUsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUM7QUFDcEMsTUFBTSxhQUFhLEdBQUcsdUJBQWEsQ0FBQyxRQUFRLEVBQUUsQ0FBQztBQVlsQyxRQUFBLGdCQUFnQixHQUFHLENBQUMsRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsV0FBVyxFQUFFLE9BQU8sRUFBeUIsRUFBRSxFQUFFO0lBQzNJLE1BQU0sTUFBTSxHQUFHLGdCQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7SUFFaEMsTUFBTSxDQUFDLFdBQVcsRUFBRSxjQUFjLENBQUMsR0FBRyxnQkFBUSxDQUFDLGtCQUFrQixDQUFDLENBQUM7SUFDbkUsTUFBTSxDQUFDLFdBQVcsRUFBRSxjQUFjLENBQUMsR0FBRyxnQkFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBRXJELGlCQUFTLENBQUMsR0FBRyxFQUFFO1FBQ2QsSUFBSTtZQUNILDhEQUE4RDtZQUM5RCxtRUFBbUU7WUFDbkUsSUFBSSxXQUFXLEVBQUU7Z0JBQ2hCLGFBQWEsQ0FBQyxtQkFBbUIsQ0FBQyxXQUFXLENBQUMsQ0FBQztnQkFDL0MsYUFBYSxDQUFDLGNBQWMsQ0FBQyxFQUFFLFdBQVcsRUFBRSxPQUFPLEVBQUUsV0FBVyxFQUFFLENBQUMsQ0FBQzthQUNwRTtZQUVELDBCQUEwQjtZQUMxQixPQUFPLENBQUMsRUFBRSxhQUFhLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztZQUNqQyxjQUFjLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDckI7UUFBQyxPQUFPLGFBQWEsRUFBRTtZQUN2QixPQUFPLENBQUMsRUFBRSxhQUFhLEVBQUUsQ0FBQyxDQUFDO1lBQzNCLGNBQWMsQ0FBQyxLQUFLLENBQUMsQ0FBQztTQUN0QjtJQUNGLENBQUMsRUFBRSxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUM7SUFFbEIsTUFBTSxhQUFhLEdBQUcsQ0FBQyxLQUFvQyxFQUFFLEVBQUU7UUFDOUQsS0FBSyxDQUFDLGNBQWMsRUFBRSxDQUFDO1FBQ3ZCLE1BQU0sY0FBYyxHQUFHLGFBQWEsQ0FBQyx3QkFBd0IsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUVyRSxRQUFRLGNBQWMsRUFBRTtZQUN4QixLQUFLLE9BQU87Z0JBQ1gsSUFBSSxXQUFXO29CQUFFLE9BQU8sTUFBTSxDQUFDLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxDQUFDLENBQUM7Z0JBQzdELE1BQU07WUFDUCxLQUFLLFFBQVE7Z0JBQ1osT0FBTyxRQUFRLENBQUMsRUFBRSxXQUFXLEVBQUUsQ0FBQyxDQUFDO1lBQ2xDLEtBQUssV0FBVyxDQUFDO1lBQ2pCLEtBQUssUUFBUTtnQkFDWixPQUFPLGNBQWMsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUMzQjtnQkFDQyxjQUFjLENBQUMsY0FBYyxDQUFDLENBQUM7U0FDL0I7SUFDRixDQUFDLENBQUM7SUFFRixPQUFPLENBQ04sNkJBQUssS0FBSyxFQUFFLE1BQU0sQ0FBQyxpQkFBaUI7UUFDbkMsK0JBQ0MsS0FBSyxFQUFFLFdBQVcsRUFDbEIsV0FBVyxFQUFFLENBQUMsQ0FBQyxvQkFBb0IsQ0FBQyxFQUNwQyxTQUFTLEVBQUUsYUFBYSxFQUN4QixLQUFLLEVBQUUsTUFBTSxDQUFDLGFBQWEsRUFDM0IsUUFBUSxRQUNSLFNBQVMsU0FDUjtRQUVGLGdDQUFRLEtBQUssRUFBRSxNQUFNLENBQUMsWUFBWSxFQUFFLFFBQVEsRUFBRSxDQUFDLFdBQVcsRUFBRSxPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUMsTUFBTSxDQUFDLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxDQUFDLElBQzdHLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FDRjtRQUNULGdDQUFRLEtBQUssRUFBRSxNQUFNLENBQUMsWUFBWSxFQUFFLE9BQU8sRUFBRSxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsRUFBRSxXQUFXLEVBQUUsQ0FBQyxJQUN6RSxDQUFDLENBQUMsU0FBUyxDQUFDLENBQ0w7UUFDVCxnQ0FBUSxLQUFLLEVBQUUsTUFBTSxDQUFDLFlBQVksRUFBRSxPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLEVBQUUsV0FBVyxFQUFFLENBQUMsSUFDMUUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUNKLENBQ0osQ0FDTixDQUFDO0FBQ0gsQ0FBQyxDQUFDIn0=
diff --git a/ElectronClient/gui/KeymapConfig/styles/index.js b/ElectronClient/gui/KeymapConfig/styles/index.js
new file mode 100644
index 000000000..5592833e7
--- /dev/null
+++ b/ElectronClient/gui/KeymapConfig/styles/index.js
@@ -0,0 +1,53 @@
+'use strict';
+Object.defineProperty(exports, '__esModule', { value: true });
+const { buildStyle } = require('lib/theme');
+function styles(themeId) {
+ return buildStyle('KeymapConfigScreen', themeId, (theme) => {
+ return {
+ container: Object.assign(Object.assign({}, theme.containerStyle), { padding: 16 }),
+ actionsContainer: {
+ display: 'flex',
+ flexDirection: 'row',
+ },
+ recorderContainer: {
+ padding: 2,
+ flexGrow: 1,
+ },
+ filterInput: Object.assign(Object.assign({}, theme.inputStyle), { flexGrow: 1, minHeight: 29, alignSelf: 'center' }),
+ recorderInput: Object.assign(Object.assign({}, theme.inputStyle), { minHeight: 29 }),
+ label: Object.assign(Object.assign({}, theme.textStyle), { alignSelf: 'center', marginRight: 10 }),
+ table: Object.assign(Object.assign({}, theme.containerStyle), { marginTop: 16, overflow: 'auto', width: '100%' }),
+ tableShortcutColumn: Object.assign(Object.assign({}, theme.textStyle), { width: '60%' }),
+ tableCommandColumn: Object.assign(Object.assign({}, theme.textStyle), { width: 'auto' }),
+ tableCell: {
+ display: 'flex',
+ flexDirection: 'row',
+ },
+ tableCellContent: {
+ flexGrow: 1,
+ alignSelf: 'center',
+ },
+ tableCellStatus: {
+ height: '100%',
+ alignSelf: 'center',
+ },
+ kbd: {
+ fontFamily: 'sans-serif',
+ border: '1px solid',
+ borderRadius: 4,
+ backgroundColor: theme.raisedBackgroundColor,
+ padding: 2,
+ paddingLeft: 6,
+ paddingRight: 6,
+ },
+ disabled: {
+ color: theme.colorFaded,
+ fontStyle: 'italic',
+ },
+ inlineButton: Object.assign(Object.assign({}, theme.buttonStyle), { marginLeft: 12 }),
+ warning: Object.assign(Object.assign({}, theme.textStyle), { backgroundColor: theme.warningBackgroundColor, paddingLeft: 16, paddingRight: 16, paddingTop: 2, paddingBottom: 2 }),
+ };
+ });
+}
+exports.default = styles;
+// # sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLE1BQU0sRUFBRSxVQUFVLEVBQUUsR0FBRyxPQUFPLENBQUMsV0FBVyxDQUFDLENBQUM7QUFFNUMsU0FBd0IsTUFBTSxDQUFDLE9BQWU7SUFDN0MsT0FBTyxVQUFVLENBQUMsb0JBQW9CLEVBQUUsT0FBTyxFQUFFLENBQUMsS0FBVSxFQUFFLEVBQUU7UUFDL0QsT0FBTztZQUNOLFNBQVMsa0NBQ0wsS0FBSyxDQUFDLGNBQWMsS0FDdkIsT0FBTyxFQUFFLEVBQUUsR0FDWDtZQUNELGdCQUFnQixFQUFFO2dCQUNqQixPQUFPLEVBQUUsTUFBTTtnQkFDZixhQUFhLEVBQUUsS0FBSzthQUNwQjtZQUNELGlCQUFpQixFQUFFO2dCQUNsQixPQUFPLEVBQUUsQ0FBQztnQkFDVixRQUFRLEVBQUUsQ0FBQzthQUNYO1lBQ0QsV0FBVyxrQ0FDUCxLQUFLLENBQUMsVUFBVSxLQUNuQixRQUFRLEVBQUUsQ0FBQyxFQUNYLFNBQVMsRUFBRSxFQUFFLEVBQ2IsU0FBUyxFQUFFLFFBQVEsR0FDbkI7WUFDRCxhQUFhLGtDQUNULEtBQUssQ0FBQyxVQUFVLEtBQ25CLFNBQVMsRUFBRSxFQUFFLEdBQ2I7WUFDRCxLQUFLLGtDQUNELEtBQUssQ0FBQyxTQUFTLEtBQ2xCLFNBQVMsRUFBRSxRQUFRLEVBQ25CLFdBQVcsRUFBRSxFQUFFLEdBQ2Y7WUFDRCxLQUFLLGtDQUNELEtBQUssQ0FBQyxjQUFjLEtBQ3ZCLFNBQVMsRUFBRSxFQUFFLEVBQ2IsUUFBUSxFQUFFLE1BQU0sRUFDaEIsS0FBSyxFQUFFLE1BQU0sR0FDYjtZQUNELG1CQUFtQixrQ0FDZixLQUFLLENBQUMsU0FBUyxLQUNsQixLQUFLLEVBQUUsS0FBSyxHQUNaO1lBQ0Qsa0JBQWtCLGtDQUNkLEtBQUssQ0FBQyxTQUFTLEtBQ2xCLEtBQUssRUFBRSxNQUFNLEdBQ2I7WUFDRCxTQUFTLEVBQUU7Z0JBQ1YsT0FBTyxFQUFFLE1BQU07Z0JBQ2YsYUFBYSxFQUFFLEtBQUs7YUFDcEI7WUFDRCxnQkFBZ0IsRUFBRTtnQkFDakIsUUFBUSxFQUFFLENBQUM7Z0JBQ1gsU0FBUyxFQUFFLFFBQVE7YUFDbkI7WUFDRCxlQUFlLEVBQUU7Z0JBQ2hCLE1BQU0sRUFBRSxNQUFNO2dCQUNkLFNBQVMsRUFBRSxRQUFRO2FBQ25CO1lBQ0QsR0FBRyxFQUFFO2dCQUNKLFVBQVUsRUFBRSxZQUFZO2dCQUN4QixNQUFNLEVBQUUsV0FBVztnQkFDbkIsWUFBWSxFQUFFLENBQUM7Z0JBQ2YsZUFBZSxFQUFFLEtBQUssQ0FBQyxxQkFBcUI7Z0JBQzVDLE9BQU8sRUFBRSxDQUFDO2dCQUNWLFdBQVcsRUFBRSxDQUFDO2dCQUNkLFlBQVksRUFBRSxDQUFDO2FBQ2Y7WUFDRCxRQUFRLEVBQUU7Z0JBQ1QsS0FBSyxFQUFFLEtBQUssQ0FBQyxVQUFVO2dCQUN2QixTQUFTLEVBQUUsUUFBUTthQUNuQjtZQUNELFlBQVksa0NBQ1IsS0FBSyxDQUFDLFdBQVcsS0FDcEIsVUFBVSxFQUFFLEVBQUUsR0FDZDtZQUNELE9BQU8sa0NBQ0gsS0FBSyxDQUFDLFNBQVMsS0FDbEIsZUFBZSxFQUFFLEtBQUssQ0FBQyxzQkFBc0IsRUFDN0MsV0FBVyxFQUFFLEVBQUUsRUFDZixZQUFZLEVBQUUsRUFBRSxFQUNoQixVQUFVLEVBQUUsQ0FBQyxFQUNiLGFBQWEsRUFBRSxDQUFDLEdBQ2hCO1NBQ0QsQ0FBQztJQUNILENBQUMsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQW5GRCx5QkFtRkMifQ==
diff --git a/ElectronClient/gui/KeymapConfig/utils/getLabel.js b/ElectronClient/gui/KeymapConfig/utils/getLabel.js
new file mode 100644
index 000000000..9963ef63b
--- /dev/null
+++ b/ElectronClient/gui/KeymapConfig/utils/getLabel.js
@@ -0,0 +1,33 @@
+'use strict';
+Object.defineProperty(exports, '__esModule', { value: true });
+const CommandService_1 = require('../../../lib/services/CommandService');
+const { _ } = require('lib/locale');
+const { shim } = require('lib/shim');
+const commandService = CommandService_1.default.instance();
+const getLabel = (commandName) => {
+ if (commandService.exists(commandName)) { return commandService.label(commandName); }
+ // Some commands are not registered in CommandService at the moment
+ // Following hard-coded labels are used as a workaround
+ switch (commandName) {
+ case 'quit':
+ return _('Quit');
+ case 'insertTemplate':
+ return _('Insert template');
+ case 'zoomActualSize':
+ return _('Actual Size');
+ case 'gotoAnything':
+ return _('Goto Anything...');
+ case 'help':
+ return _('Website and documentation');
+ case 'hideApp':
+ return _('Hide Joplin');
+ case 'closeWindow':
+ return _('Close Window');
+ case 'config':
+ return shim.isMac() ? _('Preferences') : _('Options');
+ default:
+ throw new Error(`Command: ${commandName} is unknown`);
+ }
+};
+exports.default = getLabel;
+// # sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2V0TGFiZWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJnZXRMYWJlbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLHlFQUFrRTtBQUVsRSxNQUFNLEVBQUUsQ0FBQyxFQUFFLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxDQUFDO0FBQ3BDLE1BQU0sRUFBRSxJQUFJLEVBQUUsR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUM7QUFFckMsTUFBTSxjQUFjLEdBQUcsd0JBQWMsQ0FBQyxRQUFRLEVBQUUsQ0FBQztBQUVqRCxNQUFNLFFBQVEsR0FBRyxDQUFDLFdBQW1CLEVBQUUsRUFBRTtJQUN4QyxJQUFJLGNBQWMsQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDO1FBQUUsT0FBTyxjQUFjLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBRWpGLG1FQUFtRTtJQUNuRSx1REFBdUQ7SUFFdkQsUUFBUSxXQUFXLEVBQUU7UUFDckIsS0FBSyxNQUFNO1lBQ1YsT0FBTyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDbEIsS0FBSyxnQkFBZ0I7WUFDcEIsT0FBTyxDQUFDLENBQUMsaUJBQWlCLENBQUMsQ0FBQztRQUM3QixLQUFLLGdCQUFnQjtZQUNwQixPQUFPLENBQUMsQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUN6QixLQUFLLGNBQWM7WUFDbEIsT0FBTyxDQUFDLENBQUMsa0JBQWtCLENBQUMsQ0FBQztRQUM5QixLQUFLLE1BQU07WUFDVixPQUFPLENBQUMsQ0FBQywyQkFBMkIsQ0FBQyxDQUFDO1FBQ3ZDLEtBQUssU0FBUztZQUNiLE9BQU8sQ0FBQyxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQ3pCLEtBQUssYUFBYTtZQUNqQixPQUFPLENBQUMsQ0FBQyxjQUFjLENBQUMsQ0FBQztRQUMxQixLQUFLLFFBQVE7WUFDWixPQUFPLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDdkQ7WUFDQyxNQUFNLElBQUksS0FBSyxDQUFDLFlBQVksV0FBVyxhQUFhLENBQUMsQ0FBQztLQUN0RDtBQUNGLENBQUMsQ0FBQztBQUVGLGtCQUFlLFFBQVEsQ0FBQyJ9
diff --git a/ElectronClient/gui/KeymapConfig/utils/useCommandStatus.js b/ElectronClient/gui/KeymapConfig/utils/useCommandStatus.js
new file mode 100644
index 000000000..d63e0e788
--- /dev/null
+++ b/ElectronClient/gui/KeymapConfig/utils/useCommandStatus.js
@@ -0,0 +1,25 @@
+'use strict';
+Object.defineProperty(exports, '__esModule', { value: true });
+const react_1 = require('react');
+const KeymapService_1 = require('../../../lib/services/KeymapService');
+const keymapService = KeymapService_1.default.instance();
+const useCommandStatus = () => {
+ const [status, setStatus] = react_1.useState(() => keymapService.getCommandNames().reduce((accumulator, command) => {
+ accumulator[command] = false;
+ return accumulator;
+ }, {}));
+ const disableStatus = (commandName) => setStatus(prevStatus => (Object.assign(Object.assign({}, prevStatus), { [commandName]: false })));
+ const enableStatus = (commandName) => setStatus(prevStatus => {
+ // Falsify all the commands; Only one command should be truthy at any given time
+ const newStatus = Object.keys(prevStatus).reduce((accumulator, command) => {
+ accumulator[command] = false;
+ return accumulator;
+ }, {});
+ // Make the appropriate command truthful
+ newStatus[commandName] = true;
+ return newStatus;
+ });
+ return [status, enableStatus, disableStatus];
+};
+exports.default = useCommandStatus;
+// # sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXNlQ29tbWFuZFN0YXR1cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInVzZUNvbW1hbmRTdGF0dXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxpQ0FBaUM7QUFDakMsdUVBQWdFO0FBRWhFLE1BQU0sYUFBYSxHQUFHLHVCQUFhLENBQUMsUUFBUSxFQUFFLENBQUM7QUFNL0MsTUFBTSxnQkFBZ0IsR0FBRyxHQUFrRixFQUFFO0lBQzVHLE1BQU0sQ0FBQyxNQUFNLEVBQUUsU0FBUyxDQUFDLEdBQUcsZ0JBQVEsQ0FBZ0IsR0FBRyxFQUFFLENBQ3hELGFBQWEsQ0FBQyxlQUFlLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxXQUEwQixFQUFFLE9BQWUsRUFBRSxFQUFFO1FBQ3RGLFdBQVcsQ0FBQyxPQUFPLENBQUMsR0FBRyxLQUFLLENBQUM7UUFDN0IsT0FBTyxXQUFXLENBQUM7SUFDcEIsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUNOLENBQUM7SUFFRixNQUFNLGFBQWEsR0FBRyxDQUFDLFdBQW1CLEVBQUUsRUFBRSxDQUFDLFNBQVMsQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDLGlDQUFNLFVBQVUsS0FBRSxDQUFDLFdBQVcsQ0FBQyxFQUFFLEtBQUssSUFBRyxDQUFDLENBQUM7SUFDbEgsTUFBTSxZQUFZLEdBQUcsQ0FBQyxXQUFtQixFQUFFLEVBQUUsQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFDLEVBQUU7UUFDcEUsZ0ZBQWdGO1FBQ2hGLE1BQU0sU0FBUyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsV0FBMEIsRUFBRSxPQUFlLEVBQUUsRUFBRTtZQUNoRyxXQUFXLENBQUMsT0FBTyxDQUFDLEdBQUcsS0FBSyxDQUFDO1lBQzdCLE9BQU8sV0FBVyxDQUFDO1FBQ3BCLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztRQUVQLHdDQUF3QztRQUN4QyxTQUFTLENBQUMsV0FBVyxDQUFDLEdBQUcsSUFBSSxDQUFDO1FBQzlCLE9BQU8sU0FBUyxDQUFDO0lBQ2xCLENBQUMsQ0FBQyxDQUFDO0lBRUgsT0FBTyxDQUFDLE1BQU0sRUFBRSxZQUFZLEVBQUUsYUFBYSxDQUFDLENBQUM7QUFDOUMsQ0FBQyxDQUFDO0FBRUYsa0JBQWUsZ0JBQWdCLENBQUMifQ==
diff --git a/ElectronClient/gui/KeymapConfig/utils/useKeymap.js b/ElectronClient/gui/KeymapConfig/utils/useKeymap.js
new file mode 100644
index 000000000..cd7e72ad5
--- /dev/null
+++ b/ElectronClient/gui/KeymapConfig/utils/useKeymap.js
@@ -0,0 +1,68 @@
+'use strict';
+const __awaiter = (this && this.__awaiter) || function(thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function(resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function(resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator['throw'](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+Object.defineProperty(exports, '__esModule', { value: true });
+const react_1 = require('react');
+const KeymapService_1 = require('../../../lib/services/KeymapService');
+const keymapService = KeymapService_1.default.instance();
+// This custom hook provides a synchronized snapshot of the keymap residing at KeymapService
+// All the logic regarding altering and interacting with the keymap is isolated from the components
+const useKeymap = () => {
+ const [keymapItems, setKeymapItems] = react_1.useState(() => keymapService.getKeymapItems());
+ const [keymapError, setKeymapError] = react_1.useState(null);
+ const setAccelerator = (commandName, accelerator) => {
+ setKeymapItems(prevKeymap => {
+ const newKeymap = [...prevKeymap];
+ newKeymap.find(item => item.command === commandName).accelerator = accelerator || null /* Disabled */;
+ return newKeymap;
+ });
+ };
+ const resetAccelerator = (commandName) => {
+ const defaultAccelerator = keymapService.getDefaultAccelerator(commandName);
+ setKeymapItems(prevKeymap => {
+ const newKeymap = [...prevKeymap];
+ newKeymap.find(item => item.command === commandName).accelerator = defaultAccelerator;
+ return newKeymap;
+ });
+ };
+ const overrideKeymapItems = (customKeymapItems) => {
+ const oldKeymapItems = [...customKeymapItems];
+ keymapService.initialize(); // Start with a fresh keymap
+ try {
+ // First, try to update the in-memory keymap of KeymapService
+ // This function will throw if there are any issues with the new custom keymap
+ keymapService.overrideKeymap(customKeymapItems);
+ // Then, update the state with the data from KeymapService
+ // Side-effect: Changes will also be saved to the disk
+ setKeymapItems(keymapService.getKeymapItems());
+ } catch (err) {
+ // oldKeymapItems includes even the unchanged keymap items
+ // However, it is not an issue because the logic accounts for such scenarios
+ keymapService.overrideKeymap(oldKeymapItems);
+ throw err;
+ }
+ };
+ const exportCustomKeymap = (customKeymapPath) => __awaiter(void 0, void 0, void 0, function* () {
+ // KeymapService is already synchronized automatically with the in-state keymap
+ yield keymapService.saveCustomKeymap(customKeymapPath);
+ });
+ react_1.useEffect(() => {
+ try {
+ keymapService.overrideKeymap(keymapItems);
+ keymapService.saveCustomKeymap();
+ setKeymapError(null);
+ } catch (err) {
+ setKeymapError(err);
+ }
+ }, [keymapItems]);
+ return [keymapItems, keymapError, overrideKeymapItems, exportCustomKeymap, setAccelerator, resetAccelerator];
+};
+exports.default = useKeymap;
+// # sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXNlS2V5bWFwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsidXNlS2V5bWFwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7O0FBQUEsaUNBQTRDO0FBQzVDLHVFQUFnRjtBQUVoRixNQUFNLGFBQWEsR0FBRyx1QkFBYSxDQUFDLFFBQVEsRUFBRSxDQUFDO0FBRS9DLDRGQUE0RjtBQUM1RixtR0FBbUc7QUFFbkcsTUFBTSxTQUFTLEdBQUcsR0FPaEIsRUFBRTtJQUNILE1BQU0sQ0FBQyxXQUFXLEVBQUUsY0FBYyxDQUFDLEdBQUcsZ0JBQVEsQ0FBZSxHQUFHLEVBQUUsQ0FBQyxhQUFhLENBQUMsY0FBYyxFQUFFLENBQUMsQ0FBQztJQUNuRyxNQUFNLENBQUMsV0FBVyxFQUFFLGNBQWMsQ0FBQyxHQUFHLGdCQUFRLENBQVEsSUFBSSxDQUFDLENBQUM7SUFFNUQsTUFBTSxjQUFjLEdBQUcsQ0FBQyxXQUFtQixFQUFFLFdBQW1CLEVBQUUsRUFBRTtRQUNuRSxjQUFjLENBQUMsVUFBVSxDQUFDLEVBQUU7WUFDM0IsTUFBTSxTQUFTLEdBQUcsQ0FBQyxHQUFHLFVBQVUsQ0FBQyxDQUFDO1lBRWxDLFNBQVMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsT0FBTyxLQUFLLFdBQVcsQ0FBQyxDQUFDLFdBQVcsR0FBRyxXQUFXLElBQUksSUFBSSxDQUFDLGNBQWMsQ0FBQztZQUN0RyxPQUFPLFNBQVMsQ0FBQztRQUNsQixDQUFDLENBQUMsQ0FBQztJQUNKLENBQUMsQ0FBQztJQUVGLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxXQUFtQixFQUFFLEVBQUU7UUFDaEQsTUFBTSxrQkFBa0IsR0FBRyxhQUFhLENBQUMscUJBQXFCLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDNUUsY0FBYyxDQUFDLFVBQVUsQ0FBQyxFQUFFO1lBQzNCLE1BQU0sU0FBUyxHQUFHLENBQUMsR0FBRyxVQUFVLENBQUMsQ0FBQztZQUVsQyxTQUFTLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLE9BQU8sS0FBSyxXQUFXLENBQUMsQ0FBQyxXQUFXLEdBQUcsa0JBQWtCLENBQUM7WUFDdEYsT0FBTyxTQUFTLENBQUM7UUFDbEIsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDLENBQUM7SUFFRixNQUFNLG1CQUFtQixHQUFHLENBQUMsaUJBQStCLEVBQUUsRUFBRTtRQUMvRCxNQUFNLGNBQWMsR0FBRyxDQUFDLEdBQUcsaUJBQWlCLENBQUMsQ0FBQztRQUM5QyxhQUFhLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FBQyw0QkFBNEI7UUFFeEQsSUFBSTtZQUNILDZEQUE2RDtZQUM3RCw4RUFBOEU7WUFDOUUsYUFBYSxDQUFDLGNBQWMsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO1lBQ2hELDBEQUEwRDtZQUMxRCxzREFBc0Q7WUFDdEQsY0FBYyxDQUFDLGFBQWEsQ0FBQyxjQUFjLEVBQUUsQ0FBQyxDQUFDO1NBQy9DO1FBQUMsT0FBTyxHQUFHLEVBQUU7WUFDYiwwREFBMEQ7WUFDMUQsNEVBQTRFO1lBQzVFLGFBQWEsQ0FBQyxjQUFjLENBQUMsY0FBYyxDQUFDLENBQUM7WUFDN0MsTUFBTSxHQUFHLENBQUM7U0FDVjtJQUNGLENBQUMsQ0FBQztJQUVGLE1BQU0sa0JBQWtCLEdBQUcsQ0FBTyxnQkFBd0IsRUFBRSxFQUFFO1FBQzdELCtFQUErRTtRQUMvRSxNQUFNLGFBQWEsQ0FBQyxnQkFBZ0IsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0lBQ3hELENBQUMsQ0FBQSxDQUFDO0lBRUYsaUJBQVMsQ0FBQyxHQUFHLEVBQUU7UUFDZCxJQUFJO1lBQ0gsYUFBYSxDQUFDLGNBQWMsQ0FBQyxXQUFXLENBQUMsQ0FBQztZQUMxQyxhQUFhLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztZQUNqQyxjQUFjLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDckI7UUFBQyxPQUFPLEdBQUcsRUFBRTtZQUNiLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQztTQUNwQjtJQUNGLENBQUMsRUFBRSxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUM7SUFFbEIsT0FBTyxDQUFDLFdBQVcsRUFBRSxXQUFXLEVBQUUsbUJBQW1CLEVBQUUsa0JBQWtCLEVBQUUsY0FBYyxFQUFFLGdCQUFnQixDQUFDLENBQUM7QUFDOUcsQ0FBQyxDQUFDO0FBRUYsa0JBQWUsU0FBUyxDQUFDIn0=
diff --git a/README.md b/README.md
index 88a597950..d609c43aa 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ Linux | | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.337/joplin-v1.0.337.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.337/joplin-v1.0.337-32bit.apk)
+Android | | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.338/joplin-v1.0.338.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.0.338/joplin-v1.0.338-32bit.apk)
iOS | | -
## Terminal application
diff --git a/ReactNativeClient/android/app/build.gradle b/ReactNativeClient/android/app/build.gradle
index 968b6af6f..4da327e36 100644
--- a/ReactNativeClient/android/app/build.gradle
+++ b/ReactNativeClient/android/app/build.gradle
@@ -125,8 +125,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 2097573
- versionName "1.0.337"
+ versionCode 2097574
+ versionName "1.0.338"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}