1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-01-08 00:14:28 +02:00

Compare commits

...

39 Commits

Author SHA1 Message Date
Laurent Cozic
87045a7b60 notarization script 2020-11-29 00:53:17 +00:00
Laurent Cozic
35242b9735 Merge branch 'dev' into mac_notarization 2020-11-29 00:23:13 +00:00
Laurent Cozic
1851b0e7d1 Merge branch 'release-1.4' into dev 2020-11-29 00:22:17 +00:00
Laurent Cozic
0fcb6441de notarize 2020-11-29 00:18:39 +00:00
Laurent Cozic
76c4d99b87 Desktop release v1.4.18 2020-11-28 12:03:29 +00:00
Laurent Cozic
849ef418a6 Desktop: Fixed notifications on macOS 2020-11-28 12:03:06 +00:00
Laurent Cozic
8fbd1ae21a notarization 2020-11-27 20:42:23 +00:00
Laurent Cozic
d733c0e010 Desktop release v1.4.16 2020-11-27 18:19:31 +00:00
Laurent Cozic
a48e5cd4e8 Desktop: Fixed spell checker crash when no language is selected 2020-11-27 18:15:22 +00:00
Laurent Cozic
03942a0073 All: Fix sorting by title in a case insensitive way 2020-11-27 15:16:50 +00:00
Laurent Cozic
0bc53dc063 Merge branch 'release-1.4' into dev 2020-11-27 12:43:40 +00:00
Laurent Cozic
56605beea2 Desktop release v1.4.15 2020-11-27 12:41:24 +00:00
Laurent Cozic
8059d3fbd1 Desktop release v1.4.14 2020-11-27 12:41:12 +00:00
Laurent Cozic
46c38ce0e0 Desktop: Remove support for buggy asar packing as it breaks notifications on macOS. Also make sure only DMG is built for macOS. 2020-11-27 12:40:43 +00:00
Laurent Cozic
dfa928c1f7 Log info 2020-11-27 12:21:59 +00:00
Laurent Cozic
cb696276da Desktop: Fixes #4146: Prevents crash when invalid spell checker language is selected, and provide fallback for invalid language codes 2020-11-27 12:21:34 +00:00
Laurent Cozic
5f80628a4d Desktop: Fixed potential crash when watching note files or resources 2020-11-27 12:19:57 +00:00
Laurent Cozic
b77f868fc8 Log info 2020-11-27 12:03:32 +00:00
Laurent Cozic
6ad9931e43 Desktop: Fixes #4146: Prevents crash when invalid spell checker language is selected, and provide fallback for invalid language codes 2020-11-27 11:12:28 +00:00
Laurent Cozic
011a65f73b Desktop: Fixed potential crash when watching note files or resources 2020-11-27 11:08:42 +00:00
Laurent Cozic
72ccc90ea0 Merge branch 'release-1.4' into dev 2020-11-27 01:16:52 +00:00
Laurent Cozic
f3e6c0da32 Desktop release v1.4.13 2020-11-26 23:32:30 +00:00
Laurent Cozic
9308c3f38c Plugins: Fixed webview postMessage call 2020-11-26 23:31:31 +00:00
Laurent Cozic
c8a7c70838 ios-v10.4.1 2020-11-26 22:17:55 +00:00
Laurent Cozic
40f6dcfb4c Android release 1.4 2020-11-26 19:42:05 +00:00
Laurent Cozic
09785cf366 Tools: Ignored files 2020-11-26 18:15:20 +00:00
Laurent Cozic
7279b508db Tools: Fixed ignore file script 2020-11-26 18:14:49 +00:00
Laurent Cozic
d7996c9707 Merge branch 'release-1.4' into dev 2020-11-26 15:10:18 +00:00
Laurent Cozic
f0432e724a Fix CLI release 2020-11-26 15:09:51 +00:00
Laurent Cozic
2f9bb7b8c0 Doc: Typo 2020-11-26 14:42:50 +00:00
Laurent Cozic
f4b8b5b160 Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-11-26 14:42:21 +00:00
Zhang YANG
e2962322be Update zh_CN translations (#4121)
* Update zh_CN.po

* Update zh_CN translations
2020-11-26 14:41:11 +00:00
Mustafa Al-Dailemi
c982e42999 Update da_DK.po (#4117)
Co-authored-by: Mustafa Al-Dailemi <Mustafa-ALD@users.noreply.github.com>
2020-11-26 14:40:30 +00:00
Laurent Cozic
eed52a5cfd Tools: Fixed tests on CI 2020-11-26 14:40:16 +00:00
MichBoi
6272a2eb4f Desktop: Fixes #3917: Fixed numbered list bug in markdown editor (#4116) 2020-11-26 14:34:13 +00:00
Naveen M V
69a4a895d4 All: Fixed basic search when executing a query in Chinese (#4034) 2020-11-26 12:35:04 +00:00
Laurent Cozic
511e4b1da0 Merge branch 'release-1.4' into dev 2020-11-26 12:16:52 +00:00
Laurent Cozic
7fa483d27c Doc: Organise community links 2020-11-25 14:50:27 +00:00
Laurent Cozic
9b64c1fbdb Added no-floating-promises eslint rule 2020-11-25 14:40:25 +00:00
78 changed files with 3394 additions and 3356 deletions

View File

@@ -58,93 +58,6 @@ plugin_types/
readme/
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
Assets/TinyMCE/JoplinLists/src/main/ts/Main.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/Main.js
Assets/TinyMCE/JoplinLists/src/main/ts/Main.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/Plugin.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/Plugin.js
Assets/TinyMCE/JoplinLists/src/main/ts/Plugin.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/actions/Indendation.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/actions/Indendation.js
Assets/TinyMCE/JoplinLists/src/main/ts/actions/Indendation.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/actions/ToggleList.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/actions/ToggleList.js
Assets/TinyMCE/JoplinLists/src/main/ts/actions/ToggleList.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/api/Api.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/api/Api.js
Assets/TinyMCE/JoplinLists/src/main/ts/api/Api.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/api/Commands.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/api/Commands.js
Assets/TinyMCE/JoplinLists/src/main/ts/api/Commands.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/api/Events.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/api/Events.js
Assets/TinyMCE/JoplinLists/src/main/ts/api/Events.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/api/Settings.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/api/Settings.js
Assets/TinyMCE/JoplinLists/src/main/ts/api/Settings.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/Bookmark.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/Bookmark.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/Bookmark.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/Delete.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/Delete.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/Delete.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/DlIndentation.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/DlIndentation.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/DlIndentation.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/Keyboard.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/Keyboard.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/Keyboard.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/ListAction.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/ListAction.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/ListAction.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/NodeType.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/NodeType.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/NodeType.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/NormalizeLists.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/NormalizeLists.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/NormalizeLists.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/Range.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/Range.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/Range.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/Selection.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/Selection.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/Selection.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/SplitList.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/SplitList.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/SplitList.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/TextBlock.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/TextBlock.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/TextBlock.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/Util.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/Util.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/Util.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ComposeList.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ComposeList.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ComposeList.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Entry.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Entry.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Entry.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Indentation.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Indentation.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Indentation.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/JoplinListUtil.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/JoplinListUtil.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/JoplinListUtil.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ListsIndendation.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ListsIndendation.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ListsIndendation.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/NormalizeEntries.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/NormalizeEntries.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/NormalizeEntries.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ParseLists.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ParseLists.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ParseLists.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Util.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Util.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Util.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/ui/Buttons.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/ui/Buttons.js
Assets/TinyMCE/JoplinLists/src/main/ts/ui/Buttons.js.map
packages/app-cli/app/LinkSelector.d.ts
packages/app-cli/app/LinkSelector.js
packages/app-cli/app/LinkSelector.js.map

View File

@@ -126,6 +126,10 @@ module.exports = {
{
// enable the rule specifically for TypeScript files
'files': ['*.ts', '*.tsx'],
'parserOptions': {
// Required for @typescript-eslint/no-floating-promises
'project': './tsconfig.eslint.json',
},
'rules': {
// Warn only because it would make it difficult to convert JS classes to TypeScript, unless we
// make everything public which is not great. New code however should specify member accessibility.
@@ -152,6 +156,7 @@ module.exports = {
'requireLast': false,
},
}],
'@typescript-eslint/no-floating-promises': ['error'],
},
},
],

87
.gitignore vendored
View File

@@ -50,93 +50,6 @@ packages/tools/github_oauth_token.txt
lerna-debug.log
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
Assets/TinyMCE/JoplinLists/src/main/ts/Main.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/Main.js
Assets/TinyMCE/JoplinLists/src/main/ts/Main.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/Plugin.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/Plugin.js
Assets/TinyMCE/JoplinLists/src/main/ts/Plugin.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/actions/Indendation.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/actions/Indendation.js
Assets/TinyMCE/JoplinLists/src/main/ts/actions/Indendation.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/actions/ToggleList.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/actions/ToggleList.js
Assets/TinyMCE/JoplinLists/src/main/ts/actions/ToggleList.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/api/Api.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/api/Api.js
Assets/TinyMCE/JoplinLists/src/main/ts/api/Api.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/api/Commands.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/api/Commands.js
Assets/TinyMCE/JoplinLists/src/main/ts/api/Commands.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/api/Events.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/api/Events.js
Assets/TinyMCE/JoplinLists/src/main/ts/api/Events.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/api/Settings.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/api/Settings.js
Assets/TinyMCE/JoplinLists/src/main/ts/api/Settings.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/Bookmark.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/Bookmark.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/Bookmark.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/Delete.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/Delete.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/Delete.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/DlIndentation.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/DlIndentation.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/DlIndentation.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/Keyboard.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/Keyboard.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/Keyboard.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/ListAction.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/ListAction.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/ListAction.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/NodeType.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/NodeType.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/NodeType.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/NormalizeLists.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/NormalizeLists.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/NormalizeLists.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/Range.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/Range.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/Range.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/Selection.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/Selection.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/Selection.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/SplitList.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/SplitList.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/SplitList.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/TextBlock.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/TextBlock.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/TextBlock.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/core/Util.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/core/Util.js
Assets/TinyMCE/JoplinLists/src/main/ts/core/Util.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ComposeList.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ComposeList.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ComposeList.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Entry.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Entry.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Entry.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Indentation.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Indentation.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Indentation.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/JoplinListUtil.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/JoplinListUtil.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/JoplinListUtil.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ListsIndendation.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ListsIndendation.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ListsIndendation.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/NormalizeEntries.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/NormalizeEntries.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/NormalizeEntries.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ParseLists.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ParseLists.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/ParseLists.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Util.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Util.js
Assets/TinyMCE/JoplinLists/src/main/ts/listModel/Util.js.map
Assets/TinyMCE/JoplinLists/src/main/ts/ui/Buttons.d.ts
Assets/TinyMCE/JoplinLists/src/main/ts/ui/Buttons.js
Assets/TinyMCE/JoplinLists/src/main/ts/ui/Buttons.js.map
packages/app-cli/app/LinkSelector.d.ts
packages/app-cli/app/LinkSelector.js
packages/app-cli/app/LinkSelector.js.map

View File

@@ -91,7 +91,7 @@ Note that you should most likely always specify a scope because otherwise it wil
## TypeScript
The application was originally written JavaScript, however it has slowly been migrated to [TypeScript](https://www.typescriptlang.org/). New classes and files should be written in TypeScript. All compiled files are generated next to the .ts or .tsx file. So for example, if there's a file "lib/MyClass.ts", there will be a generated "lib/MyClass.js" next to it. It is implemented that way as it requires minimal changes to integrate TypeScript in the existing JavaScript code base.
The application was originally written in JavaScript, however it has slowly been migrated to [TypeScript](https://www.typescriptlang.org/). New classes and files should be written in TypeScript. All compiled files are generated next to the .ts or .tsx file. So for example, if there's a file "lib/MyClass.ts", there will be a generated "lib/MyClass.js" next to it. It is implemented that way as it requires minimal changes to integrate TypeScript in the existing JavaScript code base.
## Hot reload

View File

@@ -28,7 +28,7 @@ Linux | <a href='https://github.com/laurent22/joplin/releases/download/
Operating System | Download | Alt. Download
-----------------|----------|----------------
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://joplinapp.org/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.3.13/joplin-v1.3.13.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.3.13/joplin-v1.3.13-32bit.apk)
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://joplinapp.org/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.4.11/joplin-v1.4.11.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.4.11/joplin-v1.4.11-32bit.apk)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://joplinapp.org/images/BadgeIOS.png'/></a> | -
## Terminal application
@@ -402,13 +402,14 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
# Community
- For general discussion about Joplin, user support, software development questions, and to discuss new features, go to the [Joplin Forum](https://discourse.joplinapp.org/). It is possible to login with your GitHub account.
- Also see here for information about [the latest releases and general news](https://discourse.joplinapp.org/c/news).
- For bug reports go to the [GitHub Issue Tracker](https://github.com/laurent22/joplin/issues). Please follow the template accordingly.
- Feature requests must not be opened on GitHub unless they have been discussed and accepted on the forum.
- The latest news are posted [on the Patreon page](https://www.patreon.com/joplin).
- You can also follow us on <a rel="me" href="https://mastodon.social/@joplinapp">the Mastodon feed</a> or [the Twitter feed](https://twitter.com/joplinapp).
- You can join the live community on [the JoplinApp discord server](https://discordapp.com/invite/d2HMPwE) to get help with Joplin or to discuss anything Joplin related.
Name | Description
--- | ---
[Support Forum](https://discourse.joplinapp.org/) | This is the main place for general discussion about Joplin, user support, software development questions, and to discuss new features. Also where the latest beta versions are released and discussed.
[Sub-reddit](https://www.reddit.com/r/joplinapp/) | Also a good place to get help
[Discord server](https://discordapp.com/invite/d2HMPwE) | Our chat server
[Patreon page](https://www.patreon.com/joplin) |The latest news are often posted there
[Mastodon feed](https://mastodon.social/@joplinapp) | Follow us on Mastodon
[Twitter feed](https://twitter.com/joplinapp) | Follow us on Twitter
# Contributing

View File

@@ -31,7 +31,7 @@
],
"owner": "Laurent Cozic"
},
"version": "1.4.7",
"version": "1.4.9",
"bin": {
"joplin": "./main.js"
},

View File

@@ -447,7 +447,7 @@ describe('services_SearchEngine', function() {
expect((await engine.search('title:bla 我是')).length).toBe(0);
// For non-alpha char, only the first field is looked at, the following ones are ignored
expect((await engine.search('title:你好 title:hello')).length).toBe(1);
// expect((await engine.search('title:你好 title:hello')).length).toBe(1);
}));
it('should parse normal query strings', asyncTest(async () => {

View File

@@ -1,6 +1,7 @@
document.addEventListener('click', event => {
const element = event.target;
if (element.className === 'toc-item-link') {
console.debug('TOC Plugin Webview: Sending scrollToHash message', element.dataset.slug);
webviewApi.postMessage({
name: 'scrollToHash',
hash: element.dataset.slug,

View File

@@ -156,7 +156,7 @@ export default class InteropServiceHelper {
if (Array.isArray(path)) path = path[0];
CommandService.instance().execute('showModalMessage', _('Exporting to "%s" as "%s" format. Please wait...', path, module.format));
void CommandService.instance().execute('showModalMessage', _('Exporting to "%s" as "%s" format. Please wait...', path, module.format));
const exportOptions: ExportOptions = {};
exportOptions.path = path;
@@ -177,7 +177,7 @@ export default class InteropServiceHelper {
bridge().showErrorMessageBox(_('Could not export notes: %s', error.message));
}
CommandService.instance().execute('hideModalMessage');
void CommandService.instance().execute('hideModalMessage');
}
}

View File

@@ -424,7 +424,7 @@ class Application extends BaseApplication {
const contextMenu = Menu.buildFromTemplate([
{ label: _('Open %s', app.electronApp().name), click: () => { app.window().show(); } },
{ type: 'separator' },
{ label: _('Quit'), click: () => { app.quit(); } },
{ label: _('Quit'), click: () => { void app.quit(); } },
]);
app.createTray(contextMenu);
}
@@ -664,7 +664,7 @@ class Application extends BaseApplication {
this.updateTray();
shim.setTimeout(() => {
AlarmService.garbageCollect();
void AlarmService.garbageCollect();
}, 1000 * 60 * 60);
if (Setting.value('startMinimized') && Setting.value('showTrayIcon')) {
@@ -676,12 +676,12 @@ class Application extends BaseApplication {
ResourceService.runInBackground();
if (Setting.value('env') === 'dev') {
AlarmService.updateAllNotifications();
void AlarmService.updateAllNotifications();
} else {
reg.scheduleSync(1000).then(() => {
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
AlarmService.updateAllNotifications();
void AlarmService.updateAllNotifications();
DecryptionWorker.instance().scheduleStart();
});

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>

View File

@@ -11,7 +11,7 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async () => {
bridge().openItem(Setting.value('profileDir'));
void bridge().openItem(Setting.value('profileDir'));
},
};
};

View File

@@ -18,7 +18,7 @@ export const runtime = (): CommandRuntime => {
try {
const note = await Note.load(noteId);
ExternalEditWatcher.instance().openAndWatch(note);
void ExternalEditWatcher.instance().openAndWatch(note);
} catch (error) {
bridge().showErrorMessageBox(_('Error opening note in editor: %s', error.message));
}

View File

@@ -13,7 +13,7 @@ export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext, noteId: string = null) => {
noteId = noteId || stateUtils.selectedNoteId(context.state);
ExternalEditWatcher.instance().stopWatching(noteId);
void ExternalEditWatcher.instance().stopWatching(noteId);
},
enabledCondition: 'oneNoteSelected',
};

View File

@@ -17,9 +17,9 @@ export const runtime = (): CommandRuntime => {
if (!noteId) return;
if (context.state.watchedNoteFiles.includes(noteId)) {
CommandService.instance().execute('stopExternalEditing', noteId);
void CommandService.instance().execute('stopExternalEditing', noteId);
} else {
CommandService.instance().execute('startExternalEditing', noteId);
void CommandService.instance().execute('startExternalEditing', noteId);
}
},
enabledCondition: 'oneNoteSelected',

View File

@@ -696,7 +696,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
const needRestartComp: any = this.state.needRestart ? (
<div style={{ ...theme.textStyle, padding: 10, paddingLeft: 24, backgroundColor: theme.warningBackgroundColor, color: theme.color }}>
{this.restartMessage()}
<a style={{ ...theme.urlStyle, marginLeft: 10 }} href="#" onClick={() => { this.restartApp(); }}>{_('Restart now')}</a>
<a style={{ ...theme.urlStyle, marginLeft: 10 }} href="#" onClick={() => { void this.restartApp(); }}>{_('Restart now')}</a>
</div>
) : null;

View File

@@ -86,7 +86,7 @@ const useKeymap = (): [
}
}
saveKeymap();
void saveKeymap();
}, [keymapItems, mustSave]);
return [keymapItems, keymapError, overrideKeymapItems, setAccelerator, resetAccelerator];

View File

@@ -30,6 +30,7 @@ import { themeStyle } from '@joplin/lib/theme';
import validateLayout from '../ResizableLayout/utils/validateLayout';
import iterateItems from '../ResizableLayout/utils/iterateItems';
import removeItem from '../ResizableLayout/utils/removeItem';
import Logger from '@joplin/lib/Logger';
const { connect } = require('react-redux');
const { PromptDialog } = require('../PromptDialog.min.js');
@@ -38,6 +39,8 @@ const PluginManager = require('@joplin/lib/services/PluginManager');
const EncryptionService = require('@joplin/lib/services/EncryptionService');
const ipcRenderer = require('electron').ipcRenderer;
const logger = Logger.create('MainScreen');
interface LayerModalState {
visible: boolean;
message: string;
@@ -330,7 +333,7 @@ class MainScreenComponent extends React.Component<Props, State> {
layoutModeListenerKeyDown(event: any) {
if (event.key !== 'Escape') return;
if (!this.props.layoutMoveMode) return;
CommandService.instance().execute('toggleLayoutMoveMode');
void CommandService.instance().execute('toggleLayoutMoveMode');
}
componentDidMount() {
@@ -564,6 +567,7 @@ class MainScreenComponent extends React.Component<Props, State> {
}
userWebview_message(event: any) {
logger.debug('Got message (WebView => Plugin) (2)', event);
PluginService.instance().pluginById(event.pluginId).viewController(event.viewId).emitMessage(event);
}

View File

@@ -18,9 +18,9 @@ export const runtime = (comp: any): CommandRuntime => {
onClose: async (answer: any) => {
if (answer) {
if (noteType === 'note' || noteType === 'todo') {
CommandService.instance().execute('newNote', answer.value, noteType === 'todo');
void CommandService.instance().execute('newNote', answer.value, noteType === 'todo');
} else {
CommandService.instance().execute('insertText', TemplateUtils.render(answer.value));
void CommandService.instance().execute('insertText', TemplateUtils.render(answer.value));
}
}

View File

@@ -18,7 +18,7 @@ export const runtime = (comp: any): CommandRuntime => {
noteId: noteId,
visible: true,
onRevisionLinkClick: () => {
CommandService.instance().execute('showRevisions');
void CommandService.instance().execute('showRevisions');
},
},
});

View File

@@ -112,7 +112,7 @@ function useMenu(props: Props) {
const [modulesLastChangeTime, setModulesLastChangeTime] = useState(Date.now());
const onMenuItemClick = useCallback((commandName: string) => {
CommandService.instance().execute(commandName);
void CommandService.instance().execute(commandName);
}, []);
const onImportModuleClick = useCallback(async (module: Module, moduleSource: string) => {
@@ -134,7 +134,7 @@ function useMenu(props: Props) {
const modalMessage = _('Importing from "%s" as "%s" format. Please wait...', path, module.format);
CommandService.instance().execute('showModalMessage', modalMessage);
void CommandService.instance().execute('showModalMessage', modalMessage);
const importOptions = {
path,
@@ -145,7 +145,7 @@ function useMenu(props: Props) {
return `${key}: ${status[key]}`;
});
CommandService.instance().execute('showModalMessage', `${modalMessage}\n\n${statusStrings.join('\n')}`);
void CommandService.instance().execute('showModalMessage', `${modalMessage}\n\n${statusStrings.join('\n')}`);
},
onError: console.warn,
destinationFolderId: !module.isNoteArchive && moduleSource === 'file' ? props.selectedFolderId : null,
@@ -159,7 +159,7 @@ function useMenu(props: Props) {
bridge().showErrorMessageBox(error.message);
}
CommandService.instance().execute('hideModalMessage');
void CommandService.instance().execute('hideModalMessage');
}, [props.selectedFolderId]);
const onMenuItemClickRef = useRef(null);
@@ -177,7 +177,7 @@ function useMenu(props: Props) {
const quitMenuItem = {
label: _('Quit'),
accelerator: keymapService.getAccelerator('quit'),
click: () => { bridge().electronApp().quit(); },
click: () => { void bridge().electronApp().quit(); },
};
const sortNoteFolderItems = (type: string) => {
@@ -284,23 +284,23 @@ function useMenu(props: Props) {
templateItems.push({
label: _('Create note from template'),
click: () => {
CommandService.instance().execute('selectTemplate', 'note');
void CommandService.instance().execute('selectTemplate', 'note');
},
}, {
label: _('Create to-do from template'),
click: () => {
CommandService.instance().execute('selectTemplate', 'todo');
void CommandService.instance().execute('selectTemplate', 'todo');
},
}, {
label: _('Insert template'),
accelerator: keymapService.getAccelerator('insertTemplate'),
click: () => {
CommandService.instance().execute('selectTemplate');
void CommandService.instance().execute('selectTemplate');
},
}, {
label: _('Open template directory'),
click: () => {
bridge().openItem(Setting.value('templateDir'));
void bridge().openItem(Setting.value('templateDir'));
},
}, {
label: _('Refresh templates'),

View File

@@ -323,7 +323,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
}
}
loadScripts();
void loadScripts();
return () => {
cancelled = true;
@@ -630,7 +630,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
editorPasteText();
} else {
// To handle pasting images
onEditorPaste();
void onEditorPaste();
}
},
})

View File

@@ -1,3 +1,5 @@
import markdownUtils from '@joplin/lib/markdownUtils';
// Helper functions that use the cursor
export default function useCursorUtils(CodeMirror: any) {
@@ -78,6 +80,8 @@ export default function useCursorUtils(CodeMirror: any) {
for (let i = 0; i < selectedStrings.length; i++) {
const selected = selectedStrings[i];
let num = markdownUtils.olLineNumber(string1);
const lines = selected.split(/\r?\n/);
// Save the newline character to restore it later
const newLines = selected.match(/\r?\n/);
@@ -87,7 +91,12 @@ export default function useCursorUtils(CodeMirror: any) {
// Only add the list token if it's not already there
// if it is, remove it
if (!line.startsWith(string1)) {
lines[j] = string1 + line;
if (num) {
lines[j] = `${num.toString()}. ${line}`;
num++;
} else {
lines[j] = string1 + line;
}
} else {
lines[j] = line.substr(string1.length, line.length - string1.length);
}

View File

@@ -5,7 +5,7 @@ import shim from '@joplin/lib/shim';
export default function useKeymap(CodeMirror: any) {
function save() {
CommandService.instance().execute('synchronize');
void CommandService.instance().execute('synchronize');
}
function setupEmacs() {

View File

@@ -371,7 +371,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
setScriptLoaded(true);
}
loadScripts();
void loadScripts();
return () => {
cancelled = true;
@@ -661,7 +661,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
tooltip: _('Insert Date Time'),
icon: 'insert-time',
onAction: function() {
CommandService.instance().execute('insertDateTime');
void CommandService.instance().execute('insertDateTime');
},
});
@@ -670,7 +670,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
tooltip: CommandService.instance().label(pluginCommandName),
icon: CommandService.instance().iconName(pluginCommandName, 'tinymce'),
onAction: function() {
CommandService.instance().execute(pluginCommandName);
void CommandService.instance().execute(pluginCommandName);
},
});
}
@@ -708,7 +708,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
setEditor(editors[0]);
};
loadEditor();
void loadEditor();
}, [scriptLoaded]);
// -----------------------------------------------------------------------------------------
@@ -832,7 +832,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
dispatchDidUpdate(editor);
};
loadContent();
void loadContent();
return () => {
cancelled = true;
@@ -914,7 +914,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
// the note.
useEffect(() => {
return () => {
execOnChangeEvent();
void execOnChangeEvent();
};
}, []);
@@ -942,7 +942,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
onChangeHandlerTimeoutRef.current = shim.setTimeout(async () => {
onChangeHandlerTimeoutRef.current = null;
execOnChangeEvent();
void execOnChangeEvent();
}, 1000);
}

View File

@@ -113,7 +113,7 @@ function NoteEditor(props: NoteEditorProps) {
return { ...prev, user_updated_time: savedNote.user_updated_time };
});
ExternalEditWatcher.instance().updateNoteFile(savedNote);
void ExternalEditWatcher.instance().updateNoteFile(savedNote);
props.dispatch({
type: 'EDITOR_NOTE_STATUS_REMOVE',
@@ -141,7 +141,7 @@ function NoteEditor(props: NoteEditorProps) {
}
async function saveNoteAndWait(formNote: FormNote) {
saveNoteIfWillChange(formNote);
await saveNoteIfWillChange(formNote);
return formNote.saveActionQueue.waitForAllDone();
}
@@ -184,7 +184,7 @@ function NoteEditor(props: NoteEditorProps) {
value: props.selectedNoteHash ? props.selectedNoteHash : props.lastEditorScrollPercents[props.noteId] || 0,
});
ResourceEditWatcher.instance().stopWatchingAll();
void ResourceEditWatcher.instance().stopWatchingAll();
}, [formNote.id, previousNoteId]);
const onFieldChange = useCallback((field: string, value: any, changeId = 0) => {
@@ -365,7 +365,7 @@ function NoteEditor(props: NoteEditorProps) {
function renderTagBar() {
const theme = themeStyle(props.themeId);
const noteIds = [formNote.id];
const instructions = <span onClick={() => { CommandService.instance().execute('setTags', noteIds); }} style={{ ...theme.clickableTextStyle, whiteSpace: 'nowrap' }}>Click to add tags...</span>;
const instructions = <span onClick={() => { void CommandService.instance().execute('setTags', noteIds); }} style={{ ...theme.clickableTextStyle, whiteSpace: 'nowrap' }}>Click to add tags...</span>;
const tagList = props.selectedNoteTags.length ? <TagList items={props.selectedNoteTags} /> : null;
return (

View File

@@ -82,9 +82,9 @@ export default function NoteTitleBar(props: Props) {
event.preventDefault();
if (event.shiftKey) {
CommandService.instance().execute('focusElement', 'noteList');
void CommandService.instance().execute('focusElement', 'noteList');
} else {
CommandService.instance().execute('focusElement', 'noteBody');
void CommandService.instance().execute('focusElement', 'noteBody');
}
}
}, []);

View File

@@ -18,7 +18,7 @@ export default function(dependencies: HookDependencies) {
setFolder(f);
}
loadFolder();
void loadFolder();
return function() {
cancelled = true;

View File

@@ -133,7 +133,7 @@ export default function useFormNote(dependencies: HookDependencies) {
await initNoteState(n);
};
loadNote();
void loadNote();
return () => {
cancelled = true;
@@ -183,7 +183,7 @@ export default function useFormNote(dependencies: HookDependencies) {
handleAutoFocus(!!n.is_todo);
}
loadNote();
void loadNote();
return () => {
cancelled = true;
@@ -207,7 +207,7 @@ export default function useFormNote(dependencies: HookDependencies) {
useEffect(() => {
if (previousNoteId !== formNote.id) {
onResourceChange();
void onResourceChange();
}
}, [previousNoteId, formNote.id, onResourceChange]);
@@ -222,7 +222,7 @@ export default function useFormNote(dependencies: HookDependencies) {
});
}
runEffect();
void runEffect();
return () => {
cancelled = true;

View File

@@ -389,9 +389,9 @@ class NoteListComponent extends React.Component {
event.preventDefault();
if (event.shiftKey) {
CommandService.instance().execute('focusElement', 'sideBar');
void CommandService.instance().execute('focusElement', 'sideBar');
} else {
CommandService.instance().execute('focusElement', 'noteTitle');
void CommandService.instance().execute('focusElement', 'noteTitle');
}
}

View File

@@ -41,11 +41,11 @@ export default function NoteListControls(props: Props) {
}, []);
function onNewTodoButtonClick() {
CommandService.instance().execute('newTodo');
void CommandService.instance().execute('newTodo');
}
function onNewNoteButtonClick() {
CommandService.instance().execute('newNote');
void CommandService.instance().execute('newNote');
}
function renderNewNoteButtons() {

View File

@@ -161,7 +161,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
}
componentDidMount() {
this.reloadResources(this.state.sorting);
void this.reloadResources(this.state.sorting);
}
onResourceDelete(resource: InnerResource) {
@@ -177,7 +177,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
bridge().showErrorMessageBox(error.message);
})
.finally(() => {
this.reloadResources(this.state.sorting);
void this.reloadResources(this.state.sorting);
});
}
@@ -200,7 +200,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
};
}
this.setState({ sorting: newSorting });
this.reloadResources(newSorting);
void this.reloadResources(newSorting);
}
render() {

View File

@@ -103,17 +103,17 @@ function SearchBar(props: Props) {
const onKeyDown = useCallback((event: any) => {
if (event.key === 'Escape') {
if (document.activeElement) (document.activeElement as any).blur();
onExitSearch();
void onExitSearch();
}
}, [onExitSearch]);
const onSearchButtonClick = useCallback(() => {
onExitSearch();
void onExitSearch();
}, [onExitSearch]);
useEffect(() => {
if (props.notesParentType !== 'Search') {
onExitSearch(false);
void onExitSearch(false);
}
}, [props.notesParentType, onExitSearch]);

View File

@@ -79,7 +79,7 @@ export default function ShareNoteDialog(props: ShareNoteDialogProps) {
setNotes(result);
}
fetchNotes();
void fetchNotes();
}, [props.noteIds]);
const appApi = async () => {

View File

@@ -510,9 +510,9 @@ class SideBarComponent extends React.Component<Props, State> {
event.preventDefault();
if (event.shiftKey) {
CommandService.instance().execute('focusElement', 'noteBody');
void CommandService.instance().execute('focusElement', 'noteBody');
} else {
CommandService.instance().execute('focusElement', 'noteList');
void CommandService.instance().execute('focusElement', 'noteList');
}
}
@@ -559,14 +559,14 @@ class SideBarComponent extends React.Component<Props, State> {
iconAnimation={iconAnimation}
title={label}
onClick={() => {
CommandService.instance().execute('synchronize', type !== 'sync');
void CommandService.instance().execute('synchronize', type !== 'sync');
}}
/>
);
}
onAddFolderButtonClick() {
CommandService.instance().execute('newFolder');
void CommandService.instance().execute('newFolder');
}
// componentDidUpdate(prevProps:any, prevState:any) {

View File

@@ -41,7 +41,7 @@ function StatusScreen(props: Props) {
}
useEffect(() => {
resfreshScreen();
void resfreshScreen();
}, []);
const theme = themeStyle(props.themeId);
@@ -91,7 +91,7 @@ function StatusScreen(props: Props) {
if (item.canRetry) {
const onClick = async () => {
await item.retryHandler();
resfreshScreen();
void resfreshScreen();
};
retryLink = (

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "1.4.12",
"version": "1.4.18",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -5956,6 +5956,75 @@
"resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-0.3.0.tgz",
"integrity": "sha1-FOb9pcaOnk7L7/nM8DfL18BcWv4="
},
"electron-notarize": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-1.0.0.tgz",
"integrity": "sha512-dsib1IAquMn0onCrNMJ6gtEIZn/azG8hZMCYOuZIMVMUeRMgBYHK1s5TK9P8xAcrAjh/2aN5WYHzgVSWX314og==",
"dev": true,
"requires": {
"debug": "^4.1.1",
"fs-extra": "^9.0.1"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"fs-extra": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
"dev": true,
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^1.0.0"
}
},
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"dependencies": {
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"dev": true
}
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"universalify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==",
"dev": true
}
}
},
"electron-publish": {
"version": "22.9.1",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.9.1.tgz",
@@ -8828,9 +8897,7 @@
"is-docker": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz",
"integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==",
"dev": true,
"optional": true
"integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw=="
},
"is-dotfile": {
"version": "1.0.3",
@@ -9021,9 +9088,12 @@
"dev": true
},
"is-wsl": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz",
"integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog=="
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"requires": {
"is-docker": "^2.0.0"
}
},
"is-yarn-global": {
"version": "0.3.0",
@@ -11707,21 +11777,30 @@
"dev": true
},
"node-notifier": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-6.0.0.tgz",
"integrity": "sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.0.tgz",
"integrity": "sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA==",
"requires": {
"growly": "^1.3.0",
"is-wsl": "^2.1.1",
"semver": "^6.3.0",
"is-wsl": "^2.2.0",
"semver": "^7.3.2",
"shellwords": "^0.1.1",
"which": "^1.3.1"
"uuid": "^8.3.0",
"which": "^2.0.2"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"requires": {
"isexe": "^2.0.0"
}
}
}
},
@@ -14984,9 +15063,7 @@
"uuid": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==",
"dev": true,
"optional": true
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
},
"v8-to-istanbul": {
"version": "7.0.0",
@@ -15202,6 +15279,7 @@
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "1.4.12",
"version": "1.4.19",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
@@ -12,7 +12,7 @@
"watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json",
"start": "gulp build && electron . --env dev --log-level debug --no-welcome --open-dev-tools",
"test": "jest",
"test-ci": "test"
"test-ci": "npm run test"
},
"repository": {
"type": "git",
@@ -27,6 +27,7 @@
"appId": "net.cozic.joplin-desktop",
"productName": "Joplin",
"npmRebuild": false,
"afterSign": "./tools/notarizeMacApp.js",
"extraResources": [
"build/icons/*",
"build/images/*"
@@ -72,7 +73,10 @@
"artifactName": "${productName}Portable.${ext}"
},
"mac": {
"icon": "../../Assets/macOs.icns"
"icon": "../../Assets/macOs.icns",
"target": "dmg",
"hardenedRuntime": true,
"entitlements": "./build-mac/entitlements.mac.inherit.plist"
},
"linux": {
"icon": "../../Assets/LinuxIcons",
@@ -100,6 +104,7 @@
"babel-preset-react": "^6.24.1",
"electron": "^10.1.6",
"electron-builder": "22.9.1",
"electron-notarize": "^1.0.0",
"electron-rebuild": "^2.3.2",
"glob": "^7.1.6",
"gulp": "^4.0.2",
@@ -136,7 +141,7 @@
"md5": "^2.2.1",
"moment": "^2.22.2",
"node-fetch": "^1.7.3",
"node-notifier": "^6.0.0",
"node-notifier": "^8.0.0",
"pretty-bytes": "^5.3.0",
"re-resizable": "^6.5.4",
"react": "16.13.1",

View File

@@ -381,7 +381,7 @@ class Dialog extends React.PureComponent<Props, State> {
});
if (item.type === BaseModel.TYPE_COMMAND) {
CommandService.instance().execute(item.id);
void CommandService.instance().execute(item.id);
return;
}
@@ -423,7 +423,7 @@ class Dialog extends React.PureComponent<Props, State> {
const parentId = event.currentTarget.getAttribute('data-parent-id');
const itemType = Number(event.currentTarget.getAttribute('data-type'));
this.gotoItem({
void this.gotoItem({
id: itemId,
parent_id: parentId,
type: itemType,
@@ -496,7 +496,7 @@ class Dialog extends React.PureComponent<Props, State> {
const item = this.selectedItem();
if (!item) return;
this.gotoItem(item);
void this.gotoItem(item);
}
}

View File

@@ -6,8 +6,11 @@ import bridge from '../bridge';
import Setting from '@joplin/lib/models/Setting';
import { EventHandlers } from '@joplin/lib/services/plugins/utils/mapEventHandlersToIds';
import shim from '@joplin/lib/shim';
import Logger from '@joplin/lib/Logger';
const ipcRenderer = require('electron').ipcRenderer;
const logger = Logger.create('PluginRunner');
enum PluginMessageTarget {
MainWindow = 'mainWindow',
Plugin = 'plugin',
@@ -127,7 +130,9 @@ export default class PluginRunner extends BasePluginRunner {
const mappedArgs = mapEventIdsToHandlers(plugin.id, message.args);
const fullPath = `joplin.${message.path}`;
this.logger().debug(`PluginRunner: execute call: ${fullPath}: ${mappedArgs}`);
// Don't log complete HTML code, which can be long, for setHtml calls
const debugMappedArgs = fullPath.includes('setHtml') ? '<hidden>' : mappedArgs;
logger.debug(`Got message (3): ${fullPath}: ${debugMappedArgs}`);
let result: any = null;
let error: any = null;

View File

@@ -7,7 +7,10 @@ import useSubmitHandler from './hooks/useSubmitHandler';
import useHtmlLoader from './hooks/useHtmlLoader';
import useWebviewToPluginMessages from './hooks/useWebviewToPluginMessages';
import useScriptLoader from './hooks/useScriptLoader';
const styled = require('styled-components').default;
import Logger from '@joplin/lib/Logger';
import styled from 'styled-components';
const logger = Logger.create('UserWebview');
export interface Props {
html: string;
@@ -72,6 +75,9 @@ function UserWebview(props: Props, ref: any) {
function postMessage(name: string, args: any = null) {
const win = frameWindow();
if (!win) return;
logger.debug('Got message', name, args);
win.postMessage({ target: 'webview', name, args }, '*');
}
@@ -112,6 +118,7 @@ function UserWebview(props: Props, ref: any) {
useWebviewToPluginMessages(
frameWindow(),
isReady,
props.onMessage,
props.pluginId,
props.viewId

View File

@@ -58,10 +58,10 @@ const webviewApi = {
setHtml: (args) => {
contentElement.innerHTML = args.html;
console.debug('UserWebView frame: setting html to', args.html);
// console.debug('UserWebviewIndex: setting html to', args.html);
window.requestAnimationFrame(() => {
console.debug('UserWebView frame: setting html callback', args.hash);
console.debug('UserWebviewIndex: setting html callback', args.hash);
window.postMessage({ target: 'UserWebview', message: 'htmlIsSet', hash: args.hash }, '*');
});
},
@@ -105,6 +105,7 @@ const webviewApi = {
if (!ipc[callName]) {
console.warn('Missing IPC function:', event.data);
} else {
console.debug('UserWebviewIndex: Got message', callName, args);
ipc[callName](args);
}
}));
@@ -115,7 +116,7 @@ const webviewApi = {
// Need to send it with a delay to make sure all listeners are
// ready when the message is sent.
window.requestAnimationFrame(() => {
console.debug('UserWebView frame: calling isReady');
console.debug('UserWebViewIndex: calling isReady');
window.postMessage({ target: 'UserWebview', message: 'ready' }, '*');
});
});

View File

@@ -39,7 +39,7 @@ export default function(frameWindow: any, isReady: boolean, postMessage: Functio
if (!isReady) return;
console.info('useHtmlLoader: setHtml', htmlHash, html);
console.info('useHtmlLoader: setHtml', htmlHash);
postMessage('setHtml', {
hash: htmlHash,

View File

@@ -49,7 +49,7 @@ export default function useThemeCss(dep: HookDependencies) {
setCssFilePath(filePath);
}
createThemeStyleSheet();
void createThemeStyleSheet();
return () => {
cancelled = true;

View File

@@ -1,11 +1,20 @@
import Logger from '@joplin/lib/Logger';
import { useEffect } from 'react';
export default function(frameWindow: any, onMessage: Function, pluginId: string, viewId: string) {
const logger = Logger.create('useWebviewToPluginMessages');
export default function(frameWindow: any, isReady: boolean, onMessage: Function, pluginId: string, viewId: string) {
useEffect(() => {
if (!frameWindow) return () => {};
function onMessage(event: any) {
function onMessage_(event: any) {
if (!event.data || event.data.target !== 'plugin') return;
// The message is passed from one component or service to the next
// till it reaches its destination, so if something doesn't work
// follow the chain of messages searching for the string "Got message"
logger.debug('Got message (WebView => Plugin) (1)', pluginId, viewId, event.data.message);
onMessage({
pluginId: pluginId,
viewId: viewId,
@@ -13,10 +22,10 @@ export default function(frameWindow: any, onMessage: Function, pluginId: string,
});
}
frameWindow.addEventListener('message', onMessage);
frameWindow.addEventListener('message', onMessage_);
return () => {
frameWindow.removeEventListener('message', onMessage);
frameWindow.removeEventListener('message', onMessage_);
};
}, [onMessage, pluginId, viewId]);
}, [frameWindow, onMessage, isReady, pluginId, viewId]);
}

View File

@@ -2,6 +2,10 @@
import SpellCheckerServiceDriverBase from '@joplin/lib/services/spellChecker/SpellCheckerServiceDriverBase';
import bridge from '../bridge';
import { languageCodeOnly, localesFromLanguageCode } from '@joplin/lib/locale';
import Logger from '@joplin/lib/Logger';
const logger = Logger.create('SpellCheckerServiceDriverNative');
export default class SpellCheckerServiceDriverNative extends SpellCheckerServiceDriverBase {
@@ -17,7 +21,31 @@ export default class SpellCheckerServiceDriverNative extends SpellCheckerService
public setLanguage(v: string) {
// If we pass an empty array, it disables spell checking
// https://github.com/electron/electron/issues/25228
this.session().setSpellCheckerLanguages(v ? [v] : []);
if (!v) {
this.session().setSpellCheckerLanguages([]);
return;
}
// The below function will throw an error if the provided language is
// not supported, so we provide fallbacks.
// https://github.com/laurent22/joplin/issues/4146
const languagesToTry = [
v,
languageCodeOnly(v),
].concat(localesFromLanguageCode(languageCodeOnly(v), this.availableLanguages));
for (const toTry of languagesToTry) {
try {
this.session().setSpellCheckerLanguages([toTry]);
logger.info(`Set effective language from "${v}" to "${toTry}"`);
return;
} catch (error) {
logger.warn(`Failed to set language to "${toTry}". Will try the next one in this list: ${JSON.stringify(languagesToTry)}`);
logger.warn('Error was:', error);
}
}
logger.error(`Could not set language to: ${v}`);
}
public get language(): string {

View File

@@ -0,0 +1,45 @@
const fs = require('fs');
const path = require('path');
const electron_notarize = require('electron-notarize');
module.exports = async function(params) {
if (process.platform !== 'darwin') return;
if (!process.env.APPLE_ID || !process.env.APPLE_ID_PASSWORD) {
console.warn('Environment variables APPLE_ID and APPLE_ID_PASSWORD not found - notarization will NOT be done.');
return;
}
// Same appId in electron-builder.
const appId = 'net.cozic.joplin-desktop';
const appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`);
if (!fs.existsSync(appPath)) {
throw new Error(`Cannot find application at: ${appPath}`);
}
console.log(`Notarizing ${appId} found at ${appPath}`);
await electron_notarize.notarize({
appBundleId: appId,
appPath: appPath,
// Apple Developer email address
appleId: process.env.APPLE_ID,
// App-specific password: https://support.apple.com/en-us/HT204397
appleIdPassword: process.env.APPLE_ID_PASSWORD,
// When Apple ID is attached to multiple providers (eg if the
// account has been used to build multiple apps for different
// companies), in that case the provider "Team Short Name" (also
// known as "ProviderShortname") must be provided.
//
// Use this to get it:
//
// xcrun altool --list-providers -u APPLE_ID -p APPLE_ID_PASSWORD
ascProvider: process.env.APPLE_ASC_PROVIDER,
});
console.log(`Done notarizing ${appId}`);
};

View File

@@ -6,5 +6,6 @@
],
"exclude": [
"**/node_modules",
"**/dist",
],
}

View File

@@ -138,8 +138,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097608
versionName "1.4.6"
versionCode 2097613
versionName "1.4.11"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -186,7 +186,7 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
setSource(undefined);
setInjectedJs([]);
} else {
renderNote();
void renderNote();
}
return () => {

View File

@@ -259,11 +259,11 @@ class NoteScreenComponent extends BaseScreenComponent {
}
screenHeader_undoButtonPress() {
this.undoRedo('undo');
void this.undoRedo('undo');
}
screenHeader_redoButtonPress() {
this.undoRedo('redo');
void this.undoRedo('redo');
}
styles() {
@@ -404,7 +404,7 @@ class NoteScreenComponent extends BaseScreenComponent {
// Although it is async, we don't wait for the answer so that if permission
// has already been granted, it doesn't slow down opening the note. If it hasn't
// been granted, the popup will open anyway.
this.requestGeoLocationPermissions();
void this.requestGeoLocationPermissions();
}
onMarkForDownload(event: any) {
@@ -703,7 +703,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}
cameraView_onPhoto(data: any) {
this.attachFile(
void this.attachFile(
{
uri: data.uri,
didCancel: false,
@@ -810,14 +810,14 @@ class NoteScreenComponent extends BaseScreenComponent {
output.push({
title: _('View on map'),
onPress: () => {
this.showOnMap_onPress();
void this.showOnMap_onPress();
},
});
if (note.source_url) {
output.push({
title: _('Go to source URL'),
onPress: () => {
this.showSource_onPress();
void this.showSource_onPress();
},
});
}
@@ -866,8 +866,8 @@ class NoteScreenComponent extends BaseScreenComponent {
const buttonId = await dialogs.pop(this, _('Choose an option'), buttons);
if (buttonId === 'takePhoto') this.takePhoto_onPress();
if (buttonId === 'attachFile') this.attachFile_onPress();
if (buttonId === 'attachPhoto') this.attachPhoto_onPress();
if (buttonId === 'attachFile') void this.attachFile_onPress();
if (buttonId === 'attachPhoto') void this.attachPhoto_onPress();
},
});
}
@@ -884,7 +884,7 @@ class NoteScreenComponent extends BaseScreenComponent {
output.push({
title: _('Share'),
onPress: () => {
this.share_onPress();
void this.share_onPress();
},
});
if (isSaved) {
@@ -918,7 +918,7 @@ class NoteScreenComponent extends BaseScreenComponent {
output.push({
title: _('Delete'),
onPress: () => {
this.deleteNote_onPress();
void this.deleteNote_onPress();
},
});
@@ -1010,7 +1010,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}
onBodyViewerCheckboxChange(newBody: string) {
this.saveOneProperty('body', newBody);
void this.saveOneProperty('body', newBody);
}
render() {

View File

@@ -338,13 +338,13 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 56;
CURRENT_PROJECT_VERSION = 57;
DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.4.0;
MARKETING_VERSION = 10.4.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -365,12 +365,12 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 56;
CURRENT_PROJECT_VERSION = 57;
DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 10.4.0;
MARKETING_VERSION = 10.4.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",

View File

@@ -54,7 +54,7 @@ export default class AsyncActionQueue {
this.scheduleProcessingIID_ = shim.setTimeout(() => {
this.scheduleProcessingIID_ = null;
this.processQueue();
void this.processQueue();
}, interval);
}

View File

@@ -255,14 +255,6 @@ class BaseModel {
if (!options) options = {};
if (options.order && options.order.length) {
// const items = [];
// for (let i = 0; i < options.order.length; i++) {
// const o = options.order[i];
// let item = `\`${o.by}\``;
// if (options.caseInsensitive === true) item += ' COLLATE NOCASE';
// if (o.dir) item += ` ${o.dir}`;
// items.push(item);
// }
sql += ` ORDER BY ${paginationToSql(options)}`;
}

View File

@@ -354,7 +354,7 @@ export default class Synchronizer {
this.lockHandler().startAutoLockRefresh(syncLock, (error: any) => {
this.logger().warn('Could not refresh lock - cancelling sync. Error was:', error);
this.syncTargetIsLocked_ = true;
this.cancel();
void this.cancel();
});
// ========================================================================

View File

@@ -572,6 +572,12 @@ function languageCode() {
return languageCodeOnly(currentLocale_);
}
function localesFromLanguageCode(languageCode: string, locales: string[]): string[] {
return locales.filter((l: string) => {
return languageCodeOnly(l) === languageCode;
});
}
function _(s: string, ...args: any[]) {
const strings = localeStrings(currentLocale_);
let result = strings[s];
@@ -588,4 +594,4 @@ function _n(singular: string, plural: string, n: number, ...args: any[]) {
return _(singular, ...args);
}
export { _, _n, supportedLocales, countryDisplayName, localeStrings, setLocale, supportedLocalesToLanguages, defaultLocale, closestSupportedLocale, languageCode, countryCodeOnly };
export { _, _n, supportedLocales, localesFromLanguageCode, languageCodeOnly, countryDisplayName, localeStrings, setLocale, supportedLocalesToLanguages, defaultLocale, closestSupportedLocale, languageCode, countryCodeOnly };

View File

@@ -446,6 +446,12 @@ class Setting extends BaseModel {
options: () => themeOptions(),
},
notificationPermission: {
value: '',
type: SettingItemType.String,
public: false,
},
showNoteCounts: { value: true, type: SettingItemType.Bool, public: false, advanced: true, appTypes: ['desktop'], label: () => _('Show note counts') },
layoutButtonSequence: {

View File

@@ -6,7 +6,7 @@ export default function(pagination: Pagination): string {
for (let i = 0; i < pagination.order.length; i++) {
const o = pagination.order[i];
let item = `\`${o.by}\``;
if (o.caseInsensitive === true) item += ' COLLATE NOCASE';
if (!!o.caseInsensitive || !!pagination.caseInsensitive) item += ' COLLATE NOCASE';
item += ` ${o.dir}`;
sql.push(item);
}

View File

@@ -13,4 +13,5 @@ export interface Pagination {
limit: number;
order: PaginationOrder[];
page: number;
caseInsensitive?: boolean;
}

View File

@@ -1,6 +1,7 @@
import eventManager from '../eventManager';
import { Notification } from '../models/Alarm';
import shim from '../shim';
import Setting from '../models/Setting';
const notifier = require('node-notifier');
const bridge = require('electron').remote.require('./bridge').default;
@@ -37,12 +38,96 @@ export default class AlarmServiceDriverNode {
return id in this.notifications_;
}
async clearNotification(id: number) {
clearNotification(id: number) {
if (!this.notificationIsSet(id)) return;
shim.clearTimeout(this.notifications_[id].timeoutId);
delete this.notifications_[id];
}
private displayDefaultNotification(notification: Notification) {
const o: any = {
appID: this.appName_,
title: notification.title,
icon: `${bridge().electronApp().buildDir()}/icons/512x512.png`,
};
if ('body' in notification) o.message = notification.body;
// Message is required on Windows 7 however we don't want to repeat the title so
// make it an empty string.
// https://github.com/laurent22/joplin/issues/2144
if (!o.message) o.message = '-';
this.logger().info('AlarmServiceDriverNode::scheduleNotification: Triggering notification (default):', o);
notifier.notify(o, (error: any, response: any) => {
this.logger().info('AlarmServiceDriverNode::scheduleNotification: node-notifier response:', error, response);
});
}
private displayMacNotification(notification: Notification) {
// On macOS, node-notifier is broken:
//
// https://github.com/mikaelbr/node-notifier/issues/352
//
// However we can use the native browser notification as described
// there:
//
// https://www.electronjs.org/docs/tutorial/notifications
//
// In fact it's likely that we could use this on other platforms too
try {
const options: any = {
body: notification.body ? notification.body : '-',
onerror: (error: any) => {
this.logger().error('AlarmServiceDriverNode::displayMacNotification', error);
},
};
this.logger().info('AlarmServiceDriverNode::displayMacNotification: Triggering notification (macOS):', notification.title, options);
new Notification(notification.title, options);
} catch (error) {
this.logger().error('AlarmServiceDriverNode::displayMacNotification', error);
}
}
private async checkPermission() {
if (shim.isMac() && shim.isElectron()) {
this.logger().info(`AlarmServiceDriverNode::checkPermission: Permission in settings is "${Setting.value('notificationPermission')}"`);
if (Setting.value('notificationPermission') !== '') return Setting.value('notificationPermission');
// In theory `Notification.requestPermission()` should be used to
// ask for permission but in practice this API is unreliable. In
// particular, it returns "granted" immediately even when
// notifications definitely aren't allowed (and creating a new
// notification would fail).
//
// Because of that, our approach is to trigger a notification, which
// should prompt macOS to ask for permission. Once this is done we
// manually save the result in the settings. Of course it means that
// if permission is changed afterwards, for example from the
// notification center, we won't know it and notifications will
// fail.
//
// All this means that for now this checkPermission function always
// returns "granted" and the setting has only two values: "granted"
// or "" (which means we need to do the check permission trick).
//
// The lack of "denied" value is acceptable in our context because
// if a user doesn't want notifications, they can simply not set
// alarms.
new Notification('Checking permissions...', {
body: 'Permission has been granted',
});
Setting.setValue('notificationPermission', 'granted');
}
return 'granted';
}
async scheduleNotification(notification: Notification) {
const now = Date.now();
const interval = notification.date.getTime() - now;
@@ -52,6 +137,12 @@ export default class AlarmServiceDriverNode {
throw new Error(`Trying to create a notification from an invalid object: ${JSON.stringify(notification)}`);
}
const permission = await this.checkPermission();
if (permission !== 'granted') {
this.logger().info(`AlarmServiceDriverNode::scheduleNotification: Notification ${notification.id}: Cancelled because permission was not granted.`);
return;
}
this.logger().info(`AlarmServiceDriverNode::scheduleNotification: Notification ${notification.id} with interval: ${interval}ms`);
if (this.notifications_[notification.id]) shim.clearTimeout(this.notifications_[notification.id].timeoutId);
@@ -72,27 +163,15 @@ export default class AlarmServiceDriverNode {
this.logger().info(`AlarmServiceDriverNode::scheduleNotification: Notification ${notification.id} has been deleted - not rescheduling it`);
return;
}
this.scheduleNotification(this.notifications_[notification.id]);
void this.scheduleNotification(this.notifications_[notification.id]);
}, maxInterval);
} else {
timeoutId = shim.setTimeout(() => {
const o: any = {
appID: this.appName_,
title: notification.title,
icon: `${bridge().electronApp().buildDir()}/icons/512x512.png`,
};
if ('body' in notification) o.message = notification.body;
// Message is required on Windows 7 however we don't want to repeat the title so
// make it an empty string.
// https://github.com/laurent22/joplin/issues/2144
if (!o.message) o.message = '-';
this.logger().info('AlarmServiceDriverNode::scheduleNotification: Triggering notification:', o);
notifier.notify(o, (error: any, response: any) => {
this.logger().info('AlarmServiceDriverNode::scheduleNotification: node-notifier response:', error, response);
});
if (shim.isMac() && shim.isElectron()) {
this.displayMacNotification(notification);
} else {
this.displayDefaultNotification(notification);
}
this.clearNotification(notification.id);

View File

@@ -232,7 +232,7 @@ export default class CommandService extends BaseService {
public scheduleExecute(commandName: string, args: any) {
shim.setTimeout(() => {
this.execute(commandName, args);
void this.execute(commandName, args);
}, 10);
}

View File

@@ -81,7 +81,10 @@ export default class ExternalEditWatcher {
if (!this.chokidar_) return;
if (!this.watcher_) {
this.watcher_ = this.chokidar_.watch(fileToWatch);
this.watcher_ = this.chokidar_.watch(fileToWatch, {
useFsEvents: false,
});
this.watcher_.on('all', async (event: string, path: string) => {
this.logger().debug(`ExternalEditWatcher: Event: ${event}: ${path}`);
@@ -100,7 +103,7 @@ export default class ExternalEditWatcher {
if (!note) {
this.logger().warn(`ExternalEditWatcher: Watched note has been deleted: ${id}`);
this.stopWatching(id);
void this.stopWatching(id);
return;
}
@@ -339,7 +342,7 @@ export default class ExternalEditWatcher {
// avoid update loops. We only want to listen to file changes made by the user.
this.skipNextChangeEvent_[note.id] = true;
this.writeNoteToFile_(note);
await this.writeNoteToFile_(note);
}
async writeNoteToFile_(note: NoteEntity) {

View File

@@ -156,9 +156,14 @@ export default class ResourceEditWatcher {
};
if (!this.watcher_) {
this.watcher_ = this.chokidar_.watch(fileToWatch);
this.watcher_.on('all', async (event: any, path: string) => {
path = toSystemSlashes(path, 'linux');
this.watcher_ = this.chokidar_.watch(fileToWatch, {
// Need to turn off fs-events because when it's on Chokidar
// keeps emitting "modified" events (on "raw" handler), several
// times per seconds, even when nothing is changed.
useFsEvents: false,
});
this.watcher_.on('all', (event: any, path: string) => {
path = path ? toSystemSlashes(path, 'linux') : '';
this.logger().info(`ResourceEditWatcher: Event: ${event}: ${path}`);
@@ -170,7 +175,7 @@ export default class ResourceEditWatcher {
// See: https://github.com/laurent22/joplin/issues/710#issuecomment-420997167
// this.watcher_.unwatch(path);
} else if (event === 'change') {
handleChangeEvent(path);
void handleChangeEvent(path);
} else if (event === 'error') {
this.logger().error('ResourceEditWatcher: error');
}
@@ -185,14 +190,14 @@ export default class ResourceEditWatcher {
// https://github.com/laurent22/joplin/issues/3407
//
// @ts-ignore Leave unused path variable
this.watcher_.on('raw', async (event: string, path: string, options: any) => {
const watchedPath = toSystemSlashes(options.watchedPath, 'linux');
this.watcher_.on('raw', (event: string, path: string, options: any) => {
const watchedPath = options.watchedPath ? toSystemSlashes(options.watchedPath, 'linux') : '';
this.logger().debug(`ResourceEditWatcher: Raw event: ${event}: ${watchedPath}`);
if (event === 'rename') {
this.watcher_.unwatch(watchedPath);
this.watcher_.add(watchedPath);
handleChangeEvent(watchedPath);
void handleChangeEvent(watchedPath);
}
});
} else {

View File

@@ -154,11 +154,11 @@ export default class ResourceService extends BaseService {
const service = this.instance();
service.maintenanceTimer1_ = shim.setTimeout(() => {
service.maintenance();
void service.maintenance();
}, 1000 * 30);
service.maintenanceTimer2_ = shim.setInterval(() => {
service.maintenance();
void service.maintenance();
}, 1000 * 60 * 60 * 4);
}

View File

@@ -52,8 +52,8 @@ export default class ToolbarButtonUtils {
tooltip: this.service.label(commandName),
iconName: command.declaration.iconName,
enabled: newEnabled,
onClick: async () => {
this.service.execute(commandName);
onClick: () => {
void this.service.execute(commandName);
},
title: newTitle,
};

View File

@@ -27,7 +27,7 @@ export default class InteropService_Importer_Md extends InteropService_Importer_
parentFolderId = this.options_.destinationFolder.id;
}
this.importDirectory(sourcePath, parentFolderId);
await this.importDirectory(sourcePath, parentFolderId);
} else {
if (!this.options_.destinationFolder) throw new Error(_('Please specify the notebook where the notes should be imported to.'));
parentFolderId = this.options_.destinationFolder.id;
@@ -52,9 +52,9 @@ export default class InteropService_Importer_Md extends InteropService_Importer_
if (stat.isDirectory()) {
const folderTitle = await Folder.findUniqueItemTitle(basename(stat.path));
const folder = await Folder.save({ title: folderTitle, parent_id: parentFolderId });
this.importDirectory(`${dirPath}/${basename(stat.path)}`, folder.id);
await this.importDirectory(`${dirPath}/${basename(stat.path)}`, folder.id);
} else if (supportedFileExtension.indexOf(fileExtension(stat.path).toLowerCase()) >= 0) {
this.importFile(`${dirPath}/${stat.path}`, parentFolderId);
await this.importFile(`${dirPath}/${stat.path}`, parentFolderId);
}
}
}

View File

@@ -103,7 +103,7 @@ export default class WebviewController extends ViewController {
});
}
public async close() {
public close() {
this.setStoreProp('opened', false);
}

View File

@@ -36,7 +36,7 @@ export default class JoplinPlugins {
// We don't use `await` when calling onStart because the plugin might be awaiting
// in that call too (for example, when opening a dialog on startup) so we don't
// want to get stuck here.
script.onStart({}).catch((error: any) => {
void script.onStart({}).catch((error: any) => {
// For some reason, error thrown from the executed script do not have the type "Error"
// but are instead plain object. So recreate the Error object here so that it can
// be handled correctly by loggers, etc.

View File

@@ -636,7 +636,7 @@ class SearchEngine {
for (const key of parsedQuery.keys) {
if (parsedQuery.terms[key].length === 0) continue;
const term = parsedQuery.terms[key][0].value;
const term = parsedQuery.terms[key].map(x => x.value).join(' ');
if (key === '_') searchOptions.anywherePattern = `*${term}*`;
if (key === 'title') searchOptions.titlePattern = `*${term}*`;
if (key === 'body') searchOptions.bodyPattern = `*${term}*`;

View File

@@ -66,7 +66,7 @@ export default class SpellCheckerService {
public setLanguage(language: string) {
Setting.setValue('spellChecker.language', language);
this.applyStateToDriver();
this.addLatestSelectedLanguage(language);
void this.addLatestSelectedLanguage(language);
}
public get language(): string {
@@ -98,7 +98,7 @@ export default class SpellCheckerService {
output.push({
label: suggestion,
click: () => {
CommandService.instance().execute('replaceSelection', suggestion);
void CommandService.instance().execute('replaceSelection', suggestion);
},
});
}
@@ -115,7 +115,7 @@ export default class SpellCheckerService {
output.push({
label: _('Add to dictionary'),
click: () => {
this.addToDictionary(this.language, misspelledWord);
void this.addToDictionary(this.language, misspelledWord);
},
});

View File

@@ -51,7 +51,7 @@ export default function useSyncTargetUpgrade(): SyncTargetUpgradeResult {
}
useEffect(function() {
upgradeSyncTarget();
void upgradeSyncTarget();
}, []);
return upgradeResult;

View File

@@ -14,7 +14,7 @@
"tsc": "node node_modules/typescript/bin/tsc --project tsconfig.json",
"watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json",
"test": "jest",
"test-ci": "test"
"test-ci": "npm run test"
},
"author": "",
"license": "MIT",

View File

@@ -2,25 +2,27 @@ const utils = require('../utils');
const glob = require('glob');
const rootDir = utils.rootDir();
console.info(rootDir);
module.exports = {
src: '',
fn: async function() {
const tsFiles = glob.sync(`${rootDir}{/**/*.ts,/**/*.tsx}`, {
const tsFiles = glob.sync('{**/*.ts,**/*.tsx}', {
cwd: rootDir,
ignore: [
'**/.git/**',
'**/api-cli/build/**',
'**/api-cli/tests-build/**',
'**/api-cli/tests/support/plugins/**',
'**/app-desktop/dist/**',
'**/Assets/*',
'**/app-mobile/android/**',
'**/app-mobile/ios/**',
'**/node_modules/**',
'**/plugin_types/**',
'Assets/**/*',
'packages/api-cli/tests/support/plugins/**/*',
'packages/api-desktop/dist/**/*',
'packages/app-cli/build/**/*',
'packages/app-cli/tests-build/**/*',
'packages/app-desktop/dist/**/*',
'packages/app-mobile/android/**/*',
'packages/app-mobile/ios/**/*',
'packages/lib/plugin_types/**/*',
],
})
.filter(f => !f.endsWith('.d.ts'))
.map(f => f.substr(rootDir.length + 1));
}).filter(f => !f.endsWith('.d.ts'));
const ignoredJsFiles = tsFiles.map(f => {
const s = f.split('.');

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,21 +5,10 @@ const fetch = require('node-fetch');
const uriTemplate = require('uri-template');
const projectName = 'joplin-android';
const rnDir = `${__dirname}/../../packages/app-mobile`;
const rootDir = path.dirname(__dirname);
const rootDir = path.dirname(path.dirname(__dirname));
const rnDir = `${rootDir}/packages/app-mobile`;
const releaseDir = `${rnDir}/dist`;
// function wslToWinPath(wslPath) {
// const s = wslPath.split('/');
// if (s.length < 3) return s.join('\\');
// s.splice(0, 1);
// if (s[0] !== 'mnt' || s[1].length !== 1) return s.join('\\');
// s.splice(0, 1);
// s[0] = `${s[0].toUpperCase()}:`;
// while (s.length && !s[s.length - 1]) s.pop();
// return s.join('\\');
// }
function increaseGradleVersionCode(content) {
const newContent = content.replace(/versionCode\s+(\d+)/, function(a, versionCode) {
const n = Number(versionCode);
@@ -128,11 +117,11 @@ async function createRelease(name, tagName, version) {
await fs.mkdirp(releaseDir);
console.info(`Copying APK to ${apkFilePath}`);
await fs.copy('app-mobile/android/app/build/outputs/apk/release/app-release.apk', apkFilePath);
await fs.copy(`${rnDir}/android/app/build/outputs/apk/release/app-release.apk`, apkFilePath);
if (name === 'main') {
console.info(`Copying APK to ${releaseDir}/joplin-latest.apk`);
await fs.copy('app-mobile/android/app/build/outputs/apk/release/app-release.apk', `${releaseDir}/joplin-latest.apk`);
await fs.copy(`${rnDir}/android/app/build/outputs/apk/release/app-release.apk`, `${releaseDir}/joplin-latest.apk`);
}
for (const filename in originalContents) {
@@ -170,10 +159,10 @@ async function main() {
if (!isPreRelease) {
console.info('Updating Readme URL...');
let readmeContent = await fs.readFile('README.md', 'utf8');
let readmeContent = await fs.readFile(`${rootDir}/README.md`, 'utf8');
readmeContent = readmeContent.replace(/(https:\/\/github.com\/laurent22\/joplin-android\/releases\/download\/android-v\d+\.\d+\.\d+\/joplin-v\d+\.\d+\.\d+\.apk)/, releaseFiles['main'].downloadUrl);
readmeContent = readmeContent.replace(/(https:\/\/github.com\/laurent22\/joplin-android\/releases\/download\/android-v\d+\.\d+\.\d+\/joplin-v\d+\.\d+\.\d+-32bit\.apk)/, releaseFiles['32bit'].downloadUrl);
await fs.writeFile('README.md', readmeContent);
await fs.writeFile(`${rootDir}/README.md`, readmeContent);
}
await execCommandVerbose('git', ['pull']);

View File

@@ -1,6 +1,6 @@
# Joplin terminal app changelog
## [cli-v1.4.7](https://github.com/laurent22/joplin/releases/tag/cli-v1.4.7) - 2020-11-26T12:15:28Z
## [cli-v1.4.9](https://github.com/laurent22/joplin/releases/tag/cli-v1.4.9) - 2020-11-26T15:00:37Z
- Improved: Allow exporting conflict notes (#4095)
- Improved: Allow lowercase filters when doing search

7
tsconfig.eslint.json Normal file
View File

@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"include": [
"**/*.ts",
"**/*.tsx",
],
}