diff --git a/Dockerfile b/Dockerfile index b2594e8db..d868071c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:12.16.0-alpine +FROM node:12.16.1-alpine # Create app directory WORKDIR /usr/src/app diff --git a/bin/build-linux-x64.sh b/bin/build-linux-x64.sh index 473dd5c04..69d595cbe 100755 --- a/bin/build-linux-x64.sh +++ b/bin/build-linux-x64.sh @@ -11,11 +11,9 @@ echo "Copying required linux-x64 binaries" rm -r $SRC_DIR/node_modules/sqlite3/lib/binding/* rm -r $SRC_DIR/node_modules/pngquant-bin/vendor/* -rm -r $SRC_DIR/node_modules/@felixrieseberg/spellchecker/build/Release/* cp -r bin/deps/linux-x64/sqlite/* $SRC_DIR/node_modules/sqlite3/lib/binding/ cp bin/deps/linux-x64/image/pngquant $SRC_DIR/node_modules/pngquant-bin/vendor/ -cp bin/deps/linux-x64/spellchecker/* $SRC_DIR/node_modules/@felixrieseberg/spellchecker/build/Release/ ./node_modules/.bin/electron-packager $SRC_DIR --asar --out=dist --executable-name=trilium --platform=linux --arch=x64 --overwrite diff --git a/bin/build-mac-x64.sh b/bin/build-mac-x64.sh index 950ac086c..1d3ecc766 100755 --- a/bin/build-mac-x64.sh +++ b/bin/build-mac-x64.sh @@ -13,15 +13,11 @@ rm -r $SRC_DIR/node_modules/sqlite3/lib/binding/* rm -r $SRC_DIR/node_modules/mozjpeg/vendor/* rm -r $SRC_DIR/node_modules/pngquant-bin/vendor/* rm -r $SRC_DIR/node_modules/giflossy/vendor/* -rm -r $SRC_DIR/node_modules/@felixrieseberg/spellchecker/build/Release/* -rm -r $SRC_DIR/node_modules/keyboard-layout/build/Release/* cp -r bin/deps/mac-x64/sqlite/* $SRC_DIR/node_modules/sqlite3/lib/binding/ cp bin/deps/mac-x64/image/cjpeg $SRC_DIR/node_modules/mozjpeg/vendor/ cp bin/deps/mac-x64/image/pngquant $SRC_DIR/node_modules/pngquant-bin/vendor/ cp bin/deps/mac-x64/image/gifsicle $SRC_DIR/node_modules/giflossy/vendor/ -cp bin/deps/mac-x64/spellchecker/* $SRC_DIR/node_modules/@felixrieseberg/spellchecker/build/Release/ -cp bin/deps/mac-x64/keyboard-layout-manager.node $SRC_DIR/node_modules/keyboard-layout/build/Release/ ./node_modules/.bin/electron-packager $SRC_DIR --asar --out=dist --executable-name=trilium --platform=darwin --arch=x64 --overwrite --icon=images/app-icons/mac/icon.icns diff --git a/bin/build-server.sh b/bin/build-server.sh index 49a99ea03..d5c5eac4d 100755 --- a/bin/build-server.sh +++ b/bin/build-server.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash PKG_DIR=dist/trilium-linux-x64-server -NODE_VERSION=12.16.0 +NODE_VERSION=12.16.1 if [ "$1" != "DONTCOPY" ] then diff --git a/bin/build-win-x64.sh b/bin/build-win-x64.sh index c328d1731..db3d555b2 100755 --- a/bin/build-win-x64.sh +++ b/bin/build-win-x64.sh @@ -13,16 +13,11 @@ rm -r $SRC_DIR/node_modules/sqlite3/lib/binding/* rm -r $SRC_DIR/node_modules/mozjpeg/vendor/* rm -r $SRC_DIR/node_modules/pngquant-bin/vendor/* rm -r $SRC_DIR/node_modules/giflossy/vendor/* -rm -r $SRC_DIR/node_modules/@felixrieseberg/spellchecker/build/Release/* -rm -r $SRC_DIR/node_modules/keyboard-layout/build/Release/* -rm -r $SRC_DIR/node_modules/cld/build/Release/* cp -r bin/deps/win-x64/sqlite/* $SRC_DIR/node_modules/sqlite3/lib/binding/ cp bin/deps/win-x64/image/cjpeg.exe $SRC_DIR/node_modules/mozjpeg/vendor/ cp bin/deps/win-x64/image/pngquant.exe $SRC_DIR/node_modules/pngquant-bin/vendor/ cp bin/deps/win-x64/image/gifsicle.exe $SRC_DIR/node_modules/giflossy/vendor/ -cp bin/deps/win-x64/spellchecker/* $SRC_DIR/node_modules/@felixrieseberg/spellchecker/build/Release/ -cp bin/deps/win-x64/keyboard-layout-manager.node $SRC_DIR/node_modules/keyboard-layout/build/Release/ ./node_modules/.bin/electron-packager $SRC_DIR --asar --out=dist --executable-name=trilium --platform=win32 --arch=x64 --overwrite --icon=images/app-icons/win/icon.ico diff --git a/bin/copy-trilium.sh b/bin/copy-trilium.sh index 70cdee57d..da67db727 100755 --- a/bin/copy-trilium.sh +++ b/bin/copy-trilium.sh @@ -26,7 +26,4 @@ cp -r electron.js $DIR/ # run in subshell (so we return to original dir) (cd $DIR && npm install --only=prod) -rm -r $DIR/node_modules/cld/deps - -find $DIR/libraries -name "*.map" -type f -delete -find $DIR/libraries -name "hunspell.lib" -type f -delete \ No newline at end of file +find $DIR/libraries -name "*.map" -type f -delete \ No newline at end of file diff --git a/bin/deps/linux-x64/spellchecker/hunspell.a b/bin/deps/linux-x64/spellchecker/hunspell.a deleted file mode 100644 index d3e3e2515..000000000 Binary files a/bin/deps/linux-x64/spellchecker/hunspell.a and /dev/null differ diff --git a/bin/deps/linux-x64/spellchecker/spellchecker.node b/bin/deps/linux-x64/spellchecker/spellchecker.node deleted file mode 100644 index bebf4fa7e..000000000 Binary files a/bin/deps/linux-x64/spellchecker/spellchecker.node and /dev/null differ diff --git a/bin/deps/mac-x64/keyboard-layout-manager.node b/bin/deps/mac-x64/keyboard-layout-manager.node deleted file mode 100644 index 3c2882dcc..000000000 Binary files a/bin/deps/mac-x64/keyboard-layout-manager.node and /dev/null differ diff --git a/bin/deps/mac-x64/spellchecker/hunspell.a b/bin/deps/mac-x64/spellchecker/hunspell.a deleted file mode 100644 index 116af7a8b..000000000 Binary files a/bin/deps/mac-x64/spellchecker/hunspell.a and /dev/null differ diff --git a/bin/deps/mac-x64/spellchecker/spellchecker.node b/bin/deps/mac-x64/spellchecker/spellchecker.node deleted file mode 100644 index fe11eb3ce..000000000 Binary files a/bin/deps/mac-x64/spellchecker/spellchecker.node and /dev/null differ diff --git a/bin/deps/win-x64/keyboard-layout-manager.node b/bin/deps/win-x64/keyboard-layout-manager.node deleted file mode 100755 index 2b7c9bb2b..000000000 Binary files a/bin/deps/win-x64/keyboard-layout-manager.node and /dev/null differ diff --git a/bin/deps/win-x64/spellchecker/hunspell.lib b/bin/deps/win-x64/spellchecker/hunspell.lib deleted file mode 100755 index d013a8742..000000000 Binary files a/bin/deps/win-x64/spellchecker/hunspell.lib and /dev/null differ diff --git a/bin/deps/win-x64/spellchecker/spellchecker.lib b/bin/deps/win-x64/spellchecker/spellchecker.lib deleted file mode 100755 index 848c968d4..000000000 Binary files a/bin/deps/win-x64/spellchecker/spellchecker.lib and /dev/null differ diff --git a/bin/deps/win-x64/spellchecker/spellchecker.node b/bin/deps/win-x64/spellchecker/spellchecker.node deleted file mode 100755 index 80d668c5d..000000000 Binary files a/bin/deps/win-x64/spellchecker/spellchecker.node and /dev/null differ diff --git a/package-lock.json b/package-lock.json index 5de5daa81..d505faae5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2669,9 +2669,9 @@ "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==" }, "electron": { - "version": "9.0.0-beta.3", - "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0-beta.3.tgz", - "integrity": "sha512-se2XiC3sc6o8EUL/uE7bOknW7/gh37mQ+7uX8idugfYyK1oCISfr5CqtVXOMNTwMtx0opdFQ1HFC+W2ckNiPXg==", + "version": "9.0.0-beta.4", + "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.0-beta.4.tgz", + "integrity": "sha512-XznzkZ8nWTclg7B/Bue8PgXPyaYygZvtIqiNSmI3mgJ710F69RO2IrM3Rs1ULS3qWA3sTDY6PO0UPyUeTxhP7g==", "dev": true, "requires": { "@electron/get": "^1.0.1", diff --git a/package.json b/package.json index 42170b04e..ee96d214a 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "electron-debug": "3.0.1", "electron-dl": "3.0.0", "electron-find": "1.0.6", - "electron-spellchecker": "2.2.1", "electron-window-state": "5.0.3", "express": "4.17.1", "express-session": "1.17.0", @@ -75,7 +74,7 @@ "ws": "7.2.1" }, "devDependencies": { - "electron": "9.0.0-beta.3", + "electron": "9.0.0-beta.4", "electron-builder": "22.3.2", "electron-packager": "14.2.1", "electron-rebuild": "1.10.0", diff --git a/src/public/javascripts/desktop.js b/src/public/javascripts/desktop.js index bcd977b7e..0f2df5ba1 100644 --- a/src/public/javascripts/desktop.js +++ b/src/public/javascripts/desktop.js @@ -66,6 +66,7 @@ import RenderTypeWidget from "./widgets/type_widgets/render.js"; import RelationMapTypeWidget from "./widgets/type_widgets/relation_map.js"; import ProtectedSessionTypeWidget from "./widgets/type_widgets/protected_session.js"; import BookTypeWidget from "./widgets/type_widgets/book.js"; +import contextMenuService from "./services/context_menu.js"; if (utils.isElectron()) { require('electron').ipcRenderer.on('globalShortcut', async function(event, actionName) { @@ -83,4 +84,44 @@ appContext.start(); noteTooltipService.setupGlobalTooltip(); -noteAutocompleteService.init(); \ No newline at end of file +noteAutocompleteService.init(); + +if (utils.isElectron()) { + const {webContents} = require('electron').remote.getCurrentWindow(); + + webContents.on('context-menu', (event, params) => { + const items = [ + {title: "Hello", cmd: "openNoteInNewTab", uiIcon: "arrow-up-right"} + ]; + + if (params.misspelledWord) { + items.push({ + title: `Misspelled "${params.misspelledWord}"`, + cmd: "openNoteInNewTab", + uiIcon: "" + }); + + for (const suggestion of params.dictionarySuggestions) { + items.push({ + title: suggestion, + command: "replaceMisspelling", + spellingSuggestion: suggestion, + uiIcon: "" + }); + } + } + + contextMenuService.initContextMenu({ + x: params.x, + y: params.y, + items, + selectContextMenuItem: (e, {command, spellingSuggestion}) => { + if (command === 'replaceMisspelling') { + console.log("Replacing missspeling", spellingSuggestion); + + require('electron').remote.getCurrentWindow().webContents.insertText(spellingSuggestion); + } + } + }); + }); +} \ No newline at end of file diff --git a/src/public/javascripts/dialogs/options/other.js b/src/public/javascripts/dialogs/options/other.js index 5d366eb3e..b2b2a7050 100644 --- a/src/public/javascripts/dialogs/options/other.js +++ b/src/public/javascripts/dialogs/options/other.js @@ -16,11 +16,11 @@ const TPL = `
- +
-

Changes to the spell check options will take effect after application restart.

+

Multiple languages can be separated by comman. Changes to the spell check options will take effect after application restart.

diff --git a/src/public/javascripts/services/attribute_autocomplete.js b/src/public/javascripts/services/attribute_autocomplete.js index c0422ba54..694c9e0c0 100644 --- a/src/public/javascripts/services/attribute_autocomplete.js +++ b/src/public/javascripts/services/attribute_autocomplete.js @@ -21,9 +21,7 @@ function initAttributeNameAutocomplete({ $el, attributeType, open }) { const type = typeof attributeType === "function" ? attributeType() : attributeType; const names = await server.get(`attributes/names/?type=${type}&query=${encodeURIComponent(term)}`); - const result = names.map(name => { - return {name}; - }); + const result = names.map(name => ({name})); cb(result); } @@ -44,7 +42,7 @@ async function initLabelValueAutocomplete({ $el, open }) { } const attributeValues = (await server.get('attributes/values/' + encodeURIComponent(attributeName))) - .map(attribute => { return { value: attribute }; }); + .map(attribute => ({ value: attribute })); if (attributeValues.length === 0) { return; diff --git a/src/public/javascripts/services/context_menu.js b/src/public/javascripts/services/context_menu.js index a0baa028e..5d879daa0 100644 --- a/src/public/javascripts/services/context_menu.js +++ b/src/public/javascripts/services/context_menu.js @@ -3,15 +3,7 @@ const $contextMenuContainer = $("#context-menu-container"); let dateContextMenuOpenedMs = 0; -/** - * @param event - originating click event (used to get coordinates to display menu at position) - * @param {object} contextMenu - needs to have getContextMenuItems() and selectContextMenuItem(e, cmd) - */ -async function initContextMenu(event, contextMenu) { - event.stopPropagation(); - - $contextMenuContainer.empty(); - +async function initContextMenu(options) { function addItems($parent, items) { for (const item of items) { if (item.title === '----') { @@ -33,15 +25,14 @@ async function initContextMenu(event, contextMenu) { const $item = $("
  • ") .addClass("dropdown-item") .append($link) - .attr("data-cmd", item.cmd) - .on('click', function (e) { - const cmd = $(e.target).closest(".dropdown-item").attr("data-cmd"); + .on('mousedown', function (e) { + e.stopPropagation(); + + hideContextMenu(); e.originalTarget = event.target; - contextMenu.selectContextMenuItem(e, cmd); - - hideContextMenu(); + options.selectContextMenuItem(e, item); // it's important to stop the propagation especially for sub-menus, otherwise the event // might be handled again by top-level menu @@ -68,21 +59,22 @@ async function initContextMenu(event, contextMenu) { } } - addItems($contextMenuContainer, await contextMenu.getContextMenuItems()); + $contextMenuContainer.empty(); + + addItems($contextMenuContainer, options.items); keyboardActionService.updateDisplayedShortcuts($contextMenuContainer); // code below tries to detect when dropdown would overflow from page // in such case we'll position it above click coordinates so it will fit into client - const clickPosition = event.pageY; const clientHeight = document.documentElement.clientHeight; const contextMenuHeight = $contextMenuContainer.outerHeight() + 30; let top; - if (clickPosition + contextMenuHeight > clientHeight) { + if (options.y + contextMenuHeight > clientHeight) { top = clientHeight - contextMenuHeight - 10; } else { - top = event.pageY - 10; + top = options.y - 10; } dateContextMenuOpenedMs = Date.now(); @@ -90,7 +82,7 @@ async function initContextMenu(event, contextMenu) { $contextMenuContainer.css({ display: "block", top: top, - left: event.pageX - 20 + left: options.x - 20 }).addClass("show"); } diff --git a/src/public/javascripts/services/spell_check.js b/src/public/javascripts/services/spell_check.js index 8407d37ec..340ccec96 100644 --- a/src/public/javascripts/services/spell_check.js +++ b/src/public/javascripts/services/spell_check.js @@ -1,6 +1,6 @@ import options from "./options.js"; -export async function initSpellCheck() { +export async function initSpellCheck() {return; const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker'); const {remote, shell} = require('electron'); diff --git a/src/public/javascripts/widgets/note_tree.js b/src/public/javascripts/widgets/note_tree.js index e5ce35c8b..ae3f06cb0 100644 --- a/src/public/javascripts/widgets/note_tree.js +++ b/src/public/javascripts/widgets/note_tree.js @@ -120,10 +120,10 @@ export default class NoteTreeWidget extends TabAwareWidget { node.setSelected(true); - const notes = this.getSelectedNodes().map(node => { return { + const notes = this.getSelectedNodes().map(node => ({ noteId: node.data.noteId, title: node.title - }}); + })); data.dataTransfer.setData("text", JSON.stringify(notes)); diff --git a/src/public/javascripts/widgets/promoted_attributes.js b/src/public/javascripts/widgets/promoted_attributes.js index 348dd0d1c..d9587f3e5 100644 --- a/src/public/javascripts/widgets/promoted_attributes.js +++ b/src/public/javascripts/widgets/promoted_attributes.js @@ -125,7 +125,7 @@ export default class PromotedAttributesWidget extends TabAwareWidget { return; } - attributeValues = attributeValues.map(attribute => { return { value: attribute }; }); + attributeValues = attributeValues.map(attribute => ({ value: attribute })); $input.autocomplete({ appendTo: document.querySelector('body'), diff --git a/src/public/javascripts/widgets/type_widgets/text.js b/src/public/javascripts/widgets/type_widgets/text.js index 8dc602077..bc4d034d9 100644 --- a/src/public/javascripts/widgets/type_widgets/text.js +++ b/src/public/javascripts/widgets/type_widgets/text.js @@ -114,12 +114,10 @@ export default class TextTypeWidget extends TypeWidget { const codeBlockLanguages = (await mimeTypesService.getMimeTypes()) .filter(mt => mt.enabled) - .map(mt => { - return { + .map(mt => ({ language: mt.mime.toLowerCase().replace(/[\W_]+/g,"-"), label: mt.title - } - }); + })); // CKEditor since version 12 needs the element to be visible before initialization. At the same time // we want to avoid flicker - i.e. show editor only once everything is ready. That's why we have separate diff --git a/src/public/stylesheets/desktop.css b/src/public/stylesheets/desktop.css index 3e48e3abf..d95abf2df 100644 --- a/src/public/stylesheets/desktop.css +++ b/src/public/stylesheets/desktop.css @@ -43,6 +43,7 @@ body { min-height: 0; padding-left: 10px; width: 100%; + height: 100%; } .dropdown-menu { diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index fde86bb32..2bea84ece 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -133,12 +133,12 @@ async function getRelationMap(req) { resp.relations = resp.relations.concat((await note.getRelations()) .filter(relation => noteIds.includes(relation.value)) - .map(relation => { return { + .map(relation => ({ attributeId: relation.attributeId, sourceNoteId: relation.noteId, targetNoteId: relation.value, name: relation.name - }; })); + }))); for (const relationDefinition of await note.getRelationDefinitions()) { if (relationDefinition.value.inverseRelation) { diff --git a/src/services/export/tar.js b/src/services/export/tar.js index beff22a1b..f379a712c 100644 --- a/src/services/export/tar.js +++ b/src/services/export/tar.js @@ -121,15 +121,13 @@ async function exportToTar(taskContext, branch, format, res) { type: note.type, mime: note.mime, // we don't export utcDateCreated and utcDateModified of any entity since that would be a bit misleading - attributes: (await note.getOwnedAttributes()).map(attribute => { - return { + attributes: (await note.getOwnedAttributes()).map(attribute => ({ type: attribute.type, name: attribute.name, value: attribute.value, isInheritable: attribute.isInheritable, position: attribute.position - }; - }) + })) }; taskContext.increaseProgressCount(); diff --git a/src/services/options_init.js b/src/services/options_init.js index d6e35e56e..4f1ab32fa 100644 --- a/src/services/options_init.js +++ b/src/services/options_init.js @@ -102,13 +102,11 @@ async function initStartupOptions() { function getKeyboardDefaultOptions() { return keyboardActions.DEFAULT_KEYBOARD_ACTIONS .filter(ka => !!ka.actionName) - .map(ka => { - return { - name: "keyboardShortcuts" + ka.actionName.charAt(0).toUpperCase() + ka.actionName.slice(1), - value: JSON.stringify(ka.defaultShortcuts), - isSynced: false - }; - }); + .map(ka => ({ + name: "keyboardShortcuts" + ka.actionName.charAt(0).toUpperCase() + ka.actionName.slice(1), + value: JSON.stringify(ka.defaultShortcuts), + isSynced: false + })); } module.exports = { diff --git a/src/services/window.js b/src/services/window.js index 11f8b69f8..f9cd530ba 100644 --- a/src/services/window.js +++ b/src/services/window.js @@ -23,6 +23,8 @@ async function createMainWindow() { defaultHeight: 800 }); + const spellcheckEnabled = await optionService.getOptionBool('spellCheckEnabled'); + const {BrowserWindow} = require('electron'); // should not be statically imported mainWindow = new BrowserWindow({ x: mainWindowState.x, @@ -31,7 +33,8 @@ async function createMainWindow() { height: mainWindowState.height, title: 'Trilium Notes', webPreferences: { - nodeIntegration: true + nodeIntegration: true, + spellcheck: spellcheckEnabled }, frame: await optionService.getOptionBool('nativeTitleBarVisible'), icon: getIcon() @@ -43,15 +46,17 @@ async function createMainWindow() { mainWindow.loadURL('http://127.0.0.1:' + await port); mainWindow.on('closed', () => mainWindow = null); - mainWindow.webContents.on('new-window', (e, url) => { - if (url !== mainWindow.webContents.getURL()) { + const {webContents} = mainWindow; + + webContents.on('new-window', (e, url) => { + if (url !== webContents.getURL()) { e.preventDefault(); require('electron').shell.openExternal(url); } }); // prevent drag & drop to navigate away from trilium - mainWindow.webContents.on('will-navigate', (ev, targetUrl) => { + webContents.on('will-navigate', (ev, targetUrl) => { const parsedUrl = url.parse(targetUrl); // we still need to allow internal redirects from setup and migration pages @@ -59,6 +64,14 @@ async function createMainWindow() { ev.preventDefault(); } }); + + if (spellcheckEnabled) { + const languageCodes = (await optionService.getOption('spellCheckLanguageCode')) + .split('/') + .map(code => code.trim()); + + webContents.session.setSpellCheckerLanguages(languageCodes); + } } function getIcon() {