1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-09-02 20:46:21 +02:00

Compare commits

...

131 Commits

Author SHA1 Message Date
Laurent Cozic
7a9afd4aff Desktop release v2.11.6 2023-05-31 20:33:57 +01:00
Laurent Cozic
e647775608 All: When resetting the master password, also create a new master key with that password 2023-05-31 20:31:44 +01:00
Laurent Cozic
577aa519e0 All: Fixes #8254: Improve selection of active E2EE key 2023-05-31 20:17:22 +01:00
renovate[bot]
03cfef6a8d Update dependency react-native-reanimated to v3.1.0 (#8255)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-31 19:39:01 +01:00
github-actions[bot]
1219a30dff @cwilby has signed the CLA in laurent22/joplin#8259 2023-05-31 02:07:47 +00:00
Linkosred
801719e3ba Doc: Update faq_joplin_cloud.md (#8250)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2023-05-29 14:04:05 +01:00
Laurent Cozic
02098dbd79 iOS 12.11.3 2023-05-29 13:28:04 +01:00
Laurent Cozic
e149e4b5fd Revert "Mobile: Fix "Download interrupted" error"
This reverts commit b023f58497.

Due to:

https://discourse.joplinapp.org/t/the-latest-ios-version-of-joplin-12-11-2-works-weird/31045
2023-05-29 13:27:10 +01:00
Laurent Cozic
b19f1a1023 Mobile: Fixed regression in biometric check
Due to f0ade02435
2023-05-29 13:27:09 +01:00
Laurent Cozic
192bfb5555 iOS: Fixed broken domain detection 2023-05-29 13:27:07 +01:00
Andrey Mukamolov
fd578d1c36 Android: Fixes #7766: Support monochrome icons (#7772) 2023-05-29 11:36:47 +01:00
jcgurango
230e7f6914 Mobile: Resolves #8193: Implement parenting of notebooks (#7980) 2023-05-29 11:31:21 +01:00
GitStart
12bba9da29 Desktop: Fixes #7933: Don't display "obsolete encryption method" message if the key is disabled (#8025)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2023-05-29 11:27:53 +01:00
Calum Lind
03424f76ea Desktop: Fixes #7506: Improve sidebar workaround for Linux w/Intel GPU (#8126) 2023-05-29 11:20:11 +01:00
Christopher O'Toole
745849023d Desktop: Fixes #6431: Preserve Table Alignment When Editing a Note With the Rich Text Editor (#8214) 2023-05-29 11:10:17 +01:00
github-actions[bot]
3e3b1764b7 @rio-codes has signed the CLA in laurent22/joplin#8249 2023-05-28 03:55:26 +00:00
Joplin Bot
902f0c3bf7 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-28 00:43:11 +00:00
Joplin Bot
0f51249a76 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-27 18:14:29 +00:00
Laurent Cozic
3621e252d1 Desktop release v2.11.5 2023-05-27 17:24:51 +01:00
Laurent Cozic
5669b1f04f iOS 12.11.2 2023-05-27 16:33:33 +01:00
Laurent Cozic
91c99960ba Chore: iOS: Fix build 2023-05-27 16:30:25 +01:00
Laurent Cozic
b309504ffb lock file 2023-05-27 14:46:21 +01:00
Laurent Cozic
22ab4b7473 iOS 12.11.1 2023-05-27 14:38:02 +01:00
Laurent Cozic
04ea9343b0 Doc: Mention Joplin Cloud in download page 2023-05-27 14:28:02 +01:00
Laurent Cozic
b93f91078b Doc: Added Liberapay method 2023-05-27 12:43:46 +01:00
renovate[bot]
b82e4be5c5 Update dependency @react-native-community/netinfo to v9.3.10 (#8239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-25 23:25:58 +00:00
Joplin Bot
77a6e6f617 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-25 18:16:45 +00:00
Laurent Cozic
959ae59af0 Doc: Add sponsor 2023-05-25 19:00:18 +01:00
Joplin Bot
98dd926cb9 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-25 12:20:03 +00:00
Joplin Bot
8197c7aa7a Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-25 06:18:05 +00:00
renovate[bot]
1687adf015 Update dependency react-select to v5.7.3 (#8230)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-23 11:39:08 +00:00
renovate[bot]
7542ca907c Update dependency tar to v6.1.14 (#8228)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-23 02:19:51 +00:00
renovate[bot]
1652b06e8c Update dependency jsdom to v21.1.2 (#8223)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-21 18:13:20 +00:00
Milo Ivir
1a835ef094 All: Translation: Update hr_HR.po (#8219) 2023-05-20 18:27:29 -04:00
Joplin Bot
575f55b22c Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-20 18:15:16 +00:00
Joplin Bot
d105387ece Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-20 06:16:16 +00:00
renovate[bot]
587eceb7c0 Update dependency react-native-safe-area-context to v4.5.2 (#8218)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-19 19:51:19 +00:00
Laurent Cozic
c90d7756c0 Renovate - exclude xml2js 2023-05-19 20:50:37 +01:00
Laurent Cozic
9e90d9016d All: Security: Prevent XSS by sanitizing certain HTML attributes 2023-05-19 11:00:31 +01:00
Laurent Cozic
ccec93eaa3 fix tests 2023-05-19 10:44:49 +01:00
github-actions[bot]
7bf823e0df @christopher-o-toole has signed the CLA in laurent22/joplin#8213 2023-05-18 23:44:24 +00:00
Daniel Junho
b533d8d164 Linux: Fix the ldconfig dependency (#8205)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
Co-authored-by: Helmut K. C. Tessarek <tessarek@evermeet.cx>
2023-05-18 12:46:58 -04:00
ScriptInfra
97477bb751 Doc: Update README.md (#8197)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
Co-authored-by: Helmut K. C. Tessarek <tessarek@evermeet.cx>
2023-05-18 00:49:06 -04:00
renovate[bot]
9cf863979b Update dependency yargs to v17.7.2 (#8207)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-18 00:01:12 +00:00
Laurent Cozic
9ffe990c0b Fixed tests 2023-05-17 19:59:22 +01:00
Joplin Bot
2c57e949b9 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-17 18:16:02 +00:00
Laurent Cozic
a7e185eb11 Server: Fixed copyright year 2023-05-17 18:23:38 +01:00
Laurent Cozic
091eff9bc2 Server: Allow giving a different version number to forks 2023-05-17 18:16:29 +01:00
Laurent Cozic
a04be2b28a Tools: Ignore unsupport tags 2023-05-17 17:38:37 +01:00
Laurent Cozic
caf66068bf Desktop, Mobile: Security: Disable SVG tag support in editor to prevent XSS 2023-05-17 16:00:24 +01:00
Laurent Cozic
8deba24d7d Chore: Improve TS types 2023-05-17 15:46:35 +01:00
Laurent Cozic
84b130e0cb Server: Process orphaned items 2023-05-17 15:11:55 +01:00
github-actions[bot]
e6209f449e @sandrotanner has signed the CLA in laurent22/joplin#8204 2023-05-17 13:32:16 +00:00
Joplin Bot
faa4c5b9fc Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-17 12:20:02 +00:00
Laurent Cozic
df9bfc7635 Doc: Describe how to escape Markdown in coding style 2023-05-17 10:04:31 +01:00
renovate[bot]
a46648f1ee Update dependency sharp to v0.32.1 (#8128)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-17 09:36:45 +01:00
github-actions[bot]
215434bb4f @ScriptInfra has signed the CLA in laurent22/joplin#8197 2023-05-16 21:29:02 +00:00
jcgurango
40e0037d50 Mobile: Fixes #7918: Use react-native-drawer-layout instead of react-native-side-menu-updated (#7953) 2023-05-16 20:51:51 +01:00
renovate[bot]
6afd839ae8 Update dependency lint-staged to v13.2.2 (#8196)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-16 18:02:46 +00:00
renovate[bot]
63595d2469 Update dependency @react-native-community/push-notification-ios to v1.11.0 (#8195)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-16 15:18:08 +01:00
Joplin Bot
d715e1ba6b Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-16 12:18:50 +00:00
Daniel Junho
fb8a0c9ea9 Linux: Check libfuse2 dependency in install script (#8079) 2023-05-16 11:57:50 +01:00
github-actions[bot]
ad20eba65e @laurent22 has signed the CLA in laurent22/joplin#8189 2023-05-16 10:56:45 +00:00
renovate[bot]
ea73e20115 Update dependency react-native-paper to v5.8.0 (#8183)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-16 11:43:30 +01:00
renovate[bot]
1b8e9271a6 Update dependency markdown-it-multimd-table to v4.2.2 (#8180)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-16 11:43:21 +01:00
renovate[bot]
3a8a7a592d Update dependency nodemailer to v6.9.2 (#8164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-16 11:43:06 +01:00
renovate[bot]
9d5c63de4f Update dependency sass to v1.62.1 (#8119)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-16 11:42:35 +01:00
Laurent Cozic
e37c95b9e5 Tools: Set pruneBranchAfterAutomerge for Renovate 2023-05-16 11:40:18 +01:00
Laurent Cozic
201c7d893e Desktop release v2.11.4 2023-05-16 10:26:01 +01:00
Laurent Cozic
c12108d1ac Desktop: Fixes #8087: Fix slow startup time 2023-05-16 10:25:45 +01:00
Laurent Cozic
6ffd1046bd Desktop release v2.11.3 2023-05-16 09:32:38 +01:00
Laurent Cozic
d4f49d6a02 CI: Fix Android build 2023-05-15 19:58:59 +01:00
Laurent Cozic
cff24af099 Server v2.11.1 2023-05-15 18:16:42 +01:00
Laurent Cozic
b99ba85acd Fixed tests 2023-05-15 18:14:26 +01:00
Laurent Cozic
a0b707cbda Desktop, Server: Improved handling of items with duplicate IDs 2023-05-15 17:49:26 +01:00
Joplin Bot
1d7ffe358e Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-14 18:15:33 +00:00
Laurent Cozic
9160f0e2a2 Android 2.11.22 2023-05-14 14:49:59 +01:00
Laurent Cozic
92272533e5 Merge branch 'dev' of github.com:laurent22/joplin into dev 2023-05-14 14:34:27 +01:00
Laurent Cozic
648e091523 Chore: Fix Android release script 2023-05-14 14:33:13 +01:00
Laurent Cozic
b023f58497 Mobile: Fix "Download interrupted" error 2023-05-14 14:31:55 +01:00
Laurent Cozic
c639791d4f Include yarn patches 2023-05-14 14:24:58 +01:00
Joplin Bot
6d52288e28 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-14 12:19:54 +00:00
Laurent Cozic
3e2f4b163b Android 2.11.21 2023-05-14 12:20:01 +01:00
renovate[bot]
e0dbd198d8 Update dependency react-native-paper to v5.6.0 (#8181)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-13 18:40:18 +01:00
Laurent Cozic
a76c5c8746 Tools: Allow publishing varients 2023-05-13 18:25:29 +01:00
Laurent Cozic
20b43ce56e fix 2023-05-13 16:31:24 +01:00
Laurent Cozic
8215ce14c6 Tools: Allow different Android variants 2023-05-13 15:54:17 +01:00
renovate[bot]
3fead0a8a7 Update dependency styled-components to v5.3.10 (#8178)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-13 05:50:55 +00:00
Jonatan
c8bf3e8583 All: Translation: Update sv.po (#8169) 2023-05-12 13:57:39 -04:00
Dmitriy Q
42dee6c275 All: Translation: Update ru_RU.po (#8161) 2023-05-12 13:56:10 -04:00
Laurent Cozic
6f3f866f78 Android 2.11.16 2023-05-12 13:44:15 +01:00
Laurent Cozic
adf2e7322d fix 2023-05-12 13:18:29 +01:00
Laurent Cozic
0df170926a Tools: Restore content after patching Android app 2023-05-12 13:18:28 +01:00
Laurent Cozic
20a26732a9 Tools: Restore content after patching Android app 2023-05-12 13:18:27 +01:00
Laurent Cozic
0da3e91a29 Tools: Exclude Vosk model files when building regular app 2023-05-12 13:17:01 +01:00
renovate[bot]
9ad56dc373 Update dependency gettext-extractor to v3.7.2 (#8167)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-12 05:32:43 +00:00
Arda Kılıçdağı
29dab26dce All: Translation: Update tr_TR.po (#8162)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2023-05-11 18:22:36 -04:00
github-actions[bot]
10e8fbcdab @Ardakilic has signed the CLA in laurent22/joplin#8162 2023-05-11 19:11:11 +00:00
Laurent Cozic
6c4f566765 Doc: Fix download links 2023-05-11 19:12:39 +01:00
Joplin Bot
78df302e86 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-11 17:55:45 +00:00
Laurent Cozic
f52dd4f098 Doc: Update download links 2023-05-11 18:44:50 +01:00
github-actions[bot]
dbab786c7b @marph91 has signed the CLA in laurent22/joplin#8163 2023-05-11 14:17:59 +00:00
Laurent Cozic
3eb44d27b2 Mobile: Sync as soon as the app starts, and immediately after changing a note 2023-05-11 14:58:19 +01:00
Laurent Cozic
52bea115ac Tools: Allow uploading Android version to different repo 2023-05-11 14:48:33 +01:00
Laurent Cozic
19bdda25c6 Desktop: Security: Prevent XSS and potential RCE when using a special HTML tag 2023-05-11 14:17:37 +01:00
Laurent Cozic
b26bc9ed5f Desktop: Security: Fixed possible XSS injection 2023-05-10 16:27:16 +01:00
Laurent Cozic
865cedc24f Android 2.11.14 2023-05-10 13:28:43 +01:00
Laurent Cozic
33f0811ad2 Server: Resolves #8153: Allow setting NTP server using NTP_SERVER env variable 2023-05-10 12:50:48 +01:00
Laurent Cozic
8cedf27fea Desktop, Mobile: Resolves #8154: Translate Welcome notes 2023-05-10 12:20:04 +01:00
Laurent Cozic
052a829167 Desktop: Auto-detect locale on startup 2023-05-10 12:20:03 +01:00
Laurent Cozic
5371c97ccd Chore: Refactor build-welcome script 2023-05-10 12:20:02 +01:00
Joplin Bot
c53b957293 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-09 18:17:54 +00:00
Laurent Cozic
e6f8dc96df Fixed CI 2023-05-09 17:21:40 +01:00
Laurent Cozic
21648b1b1b Desktop: Fixes #8149: Application cannot be installed on Windows 10 in some cases 2023-05-09 12:56:23 +01:00
Laurent Cozic
83db6f6596 Desktop release v2.11.2 2023-05-09 11:13:38 +01:00
Laurent Cozic
3adfa574c0 Desktop: Fixes #8149: Application cannot be installed on Windows 10 in some cases 2023-05-09 11:13:23 +01:00
Laurent Cozic
4d0ffc5beb clean up 2023-05-09 11:12:53 +01:00
Laurent Cozic
69f9b160dd Chore: Cleanup Android release script 2023-05-09 11:01:43 +01:00
Laurent Cozic
c17b02cfb5 Android 2.11.13 2023-05-09 08:49:00 +01:00
Laurent Cozic
6dd57b63a6 Chore: Enable Hermes again on Android to try to fix crash 2023-05-08 21:03:11 +01:00
Laurent Cozic
248c8014c8 Android 2.11.12 2023-05-08 21:02:28 +01:00
Joplin Bot
5fe2766a6b Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-08 18:16:06 +00:00
Laurent Cozic
000e0ad517 Chore: Android: Remove non longer needed joplin/react-native-vosk package 2023-05-08 18:54:23 +01:00
Laurent Cozic
c047375143 Chore: Android: Trying to fix random crash 2023-05-08 18:44:52 +01:00
Laurent Cozic
bd9e62cbd2 Android 2.11.11 2023-05-08 18:27:18 +01:00
Laurent Cozic
5ecae17538 Mobile: Tells whether Hermes engine is enabled or not 2023-05-08 17:50:19 +01:00
Laurent Cozic
35037e2dc9 fix tests 2023-05-08 17:30:30 +01:00
Laurent Cozic
059202be09 Desktop: Fixes #8072: Enter Key No Longer Saves and Closes The Tag Dialog 2023-05-08 17:07:55 +01:00
Laurent Cozic
6672f63981 Desktop: Fixes #8143: Fixes crash when using multiple profiles along with certain plugins 2023-05-08 16:45:18 +01:00
Laurent Cozic
f390eca4de Desktop: Fixes #8143: Fixes crash when using multiple profiles along with certain plugins 2023-05-08 15:34:38 +01:00
Joplin Bot
edc5e33559 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-08 13:12:39 +00:00
186 changed files with 3271 additions and 8213 deletions

View File

@@ -363,6 +363,7 @@ packages/app-mobile/components/CameraView.js
packages/app-mobile/components/CustomButton.js
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/ExtendedWebView.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnMessage.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js
@@ -403,7 +404,6 @@ packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
packages/app-mobile/components/ScreenHeader.js
packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/TextInput.js
packages/app-mobile/components/app-nav.js
packages/app-mobile/components/biometrics/BiometricPopup.js
@@ -425,8 +425,8 @@ packages/app-mobile/services/AlarmServiceDriver.android.js
packages/app-mobile/services/AlarmServiceDriver.ios.js
packages/app-mobile/services/e2ee/RSA.react-native.js
packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/vosk.dummy.js
packages/app-mobile/services/voiceTyping/vosk.js
packages/app-mobile/services/voiceTyping/vosk.android.js
packages/app-mobile/services/voiceTyping/vosk.ios.js
packages/app-mobile/setupQuickActions.js
packages/app-mobile/tools/buildInjectedJs.js
packages/app-mobile/utils/ShareExtension.js
@@ -483,6 +483,7 @@ packages/lib/SyncTargetOneDrive.js
packages/lib/SyncTargetRegistry.js
packages/lib/Synchronizer.js
packages/lib/TaskQueue.js
packages/lib/WelcomeUtils.js
packages/lib/array.js
packages/lib/callbackUrlUtils.js
packages/lib/callbackUrlUtils.test.js
@@ -518,6 +519,7 @@ packages/lib/import-enex-md-gen.js
packages/lib/import-enex-md-gen.test.js
packages/lib/import-enex.js
packages/lib/locale.js
packages/lib/locale.test.js
packages/lib/markdownUtils.js
packages/lib/markdownUtils.test.js
packages/lib/markdownUtils2.test.js
@@ -811,7 +813,6 @@ packages/plugins/ToggleSidebars/api/index.js
packages/plugins/ToggleSidebars/api/types.js
packages/plugins/ToggleSidebars/src/index.js
packages/react-native-saf-x/src/index.js
packages/react-native-vosk/src/index.js
packages/renderer/HtmlToHtml.js
packages/renderer/InMemoryCache.js
packages/renderer/MarkupToHtml.js
@@ -845,6 +846,7 @@ packages/renderer/noteStyle.js
packages/renderer/pathUtils.js
packages/renderer/utils.js
packages/tools/build-release-stats.js
packages/tools/build-welcome.js
packages/tools/buildServerDocker.js
packages/tools/buildServerDocker.test.js
packages/tools/bundleDefaultPlugins.js

View File

@@ -42,6 +42,8 @@ module.exports = {
'zxcvbn': 'readonly',
'tinymce': 'readonly',
'JSX': 'readonly',
},
'parserOptions': {
'ecmaVersion': 2018,

View File

@@ -183,12 +183,32 @@ if [[ $GIT_TAG_NAME = v* ]]; then
# cd "$ROOT_DIR/packages/tools"
# node bundleDefaultPlugins.js
cd "$ROOT_DIR/packages/app-desktop"
USE_HARD_LINKS=false yarn run dist
if [ "$IS_MACOS" == "1" ]; then
# This is to fix this error:
#
# Exit code: ENOENT. spawn /usr/bin/python ENOENT
#
# Ref: https://github.com/electron-userland/electron-builder/issues/6767#issuecomment-1096589528
#
# It can be removed once we upgrade to electron-builder@23, however we
# cannot currently do this due to this error:
# https://github.com/laurent22/joplin/issues/8149
PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn run dist
else
USE_HARD_LINKS=false yarn run dist
fi
elif [[ $IS_LINUX = 1 ]] && [[ $GIT_TAG_NAME = $SERVER_TAG_PREFIX-* ]]; then
echo "Step: Building Docker Image..."
cd "$ROOT_DIR"
yarn run buildServerDocker --tag-name $GIT_TAG_NAME --push-images --repository $SERVER_REPOSITORY
else
echo "Step: Building but *not* publishing desktop application..."
USE_HARD_LINKS=false yarn run dist --publish=never
if [ "$IS_MACOS" == "1" ]; then
# See above why we need to specify Python
PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn run dist --publish=never
else
USE_HARD_LINKS=false yarn run dist --publish=never
fi
fi

View File

@@ -17,9 +17,8 @@ jobs:
concurrent_skipping: 'same_content_newer'
BuildAndroidDebug:
if: github.repository == 'laurent22/joplin'
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true'
if: github.repository == 'laurent22/joplin' && needs.pre_job.outputs.should_skip != 'true'
runs-on: ubuntu-latest
steps:
- name: Install Linux dependencies

10
.gitignore vendored
View File

@@ -349,6 +349,7 @@ packages/app-mobile/components/CameraView.js
packages/app-mobile/components/CustomButton.js
packages/app-mobile/components/Dropdown.js
packages/app-mobile/components/ExtendedWebView.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnMessage.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js
@@ -389,7 +390,6 @@ packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
packages/app-mobile/components/ScreenHeader.js
packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/TextInput.js
packages/app-mobile/components/app-nav.js
packages/app-mobile/components/biometrics/BiometricPopup.js
@@ -411,8 +411,8 @@ packages/app-mobile/services/AlarmServiceDriver.android.js
packages/app-mobile/services/AlarmServiceDriver.ios.js
packages/app-mobile/services/e2ee/RSA.react-native.js
packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/vosk.dummy.js
packages/app-mobile/services/voiceTyping/vosk.js
packages/app-mobile/services/voiceTyping/vosk.android.js
packages/app-mobile/services/voiceTyping/vosk.ios.js
packages/app-mobile/setupQuickActions.js
packages/app-mobile/tools/buildInjectedJs.js
packages/app-mobile/utils/ShareExtension.js
@@ -469,6 +469,7 @@ packages/lib/SyncTargetOneDrive.js
packages/lib/SyncTargetRegistry.js
packages/lib/Synchronizer.js
packages/lib/TaskQueue.js
packages/lib/WelcomeUtils.js
packages/lib/array.js
packages/lib/callbackUrlUtils.js
packages/lib/callbackUrlUtils.test.js
@@ -504,6 +505,7 @@ packages/lib/import-enex-md-gen.js
packages/lib/import-enex-md-gen.test.js
packages/lib/import-enex.js
packages/lib/locale.js
packages/lib/locale.test.js
packages/lib/markdownUtils.js
packages/lib/markdownUtils.test.js
packages/lib/markdownUtils2.test.js
@@ -797,7 +799,6 @@ packages/plugins/ToggleSidebars/api/index.js
packages/plugins/ToggleSidebars/api/types.js
packages/plugins/ToggleSidebars/src/index.js
packages/react-native-saf-x/src/index.js
packages/react-native-vosk/src/index.js
packages/renderer/HtmlToHtml.js
packages/renderer/InMemoryCache.js
packages/renderer/MarkupToHtml.js
@@ -831,6 +832,7 @@ packages/renderer/noteStyle.js
packages/renderer/pathUtils.js
packages/renderer/utils.js
packages/tools/build-release-stats.js
packages/tools/build-welcome.js
packages/tools/buildServerDocker.js
packages/tools/buildServerDocker.test.js
packages/tools/bundleDefaultPlugins.js

View File

@@ -15,7 +15,6 @@
"@joplin/tools",
"@joplin/react-native-saf-x",
"@joplin/react-native-alarm-notification",
"@joplin/react-native-vosk",
"@joplin/utils"
]
}

View File

@@ -105,6 +105,70 @@ index 441e41cc402cca3a60b34978ef4fea976076259c..a173acebb4b314402550442ad471e0f7
unload: () => void;
onResult: (onResult: (e: VoskEvent) => void) => EventSubscription;
onFinalResult: (onFinalResult: (e: VoskEvent) => void) => EventSubscription;
diff --git a/package.json b/package.json
index 707eddb8d68007f93071ac659c5b087c935c5f01..90ebe20f224eeec472c377df1fef9b15f2ff8200 100644
--- a/package.json
+++ b/package.json
@@ -11,12 +11,9 @@
"src",
"lib",
"android",
- "ios",
"cpp",
- "react-native-vosk.podspec",
"!lib/typescript/example",
"!android/build",
- "!ios/build",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__"
diff --git a/react-native-vosk.podspec b/react-native-vosk.podspec
deleted file mode 100644
index e3d41b90c5eef890c7a5108aaf16ac07d34a698b..0000000000000000000000000000000000000000
--- a/react-native-vosk.podspec
+++ /dev/null
@@ -1,41 +0,0 @@
-require "json"
-
-package = JSON.parse(File.read(File.join(__dir__, "package.json")))
-folly_version = '2021.06.28.00-v2'
-folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
-
-Pod::Spec.new do |s|
- s.name = "react-native-vosk"
- s.version = package["version"]
- s.summary = package["description"]
- s.homepage = package["homepage"]
- s.license = package["license"]
- s.authors = package["author"]
-
- s.platforms = { :ios => "10.0" }
- s.source = { :git => "https://github.com/riderodd/react-native-vosk.git", :tag => "#{s.version}" }
-
- s.source_files = "ios/**/*.{h,m,mm,swift}"
- s.resource_bundles = { 'Vosk' => ['ios/Vosk/*'] }
-
- s.dependency "React-Core"
- s.frameworks = "Accelerate"
- s.library = "c++"
- s.vendored_frameworks = "ios/libvosk.xcframework"
- s.requires_arc = true
-
- # Don't install the dependencies when we run `pod install` in the old architecture.
- if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
- s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
- s.pod_target_xcconfig = {
- "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
- "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
- }
-
- s.dependency "React-Codegen"
- s.dependency "RCT-Folly", folly_version
- s.dependency "RCTRequired"
- s.dependency "RCTTypeSafety"
- s.dependency "ReactCommon/turbomodule/core"
- end
-end
diff --git a/src/index.tsx b/src/index.tsx
index d9f90c921d89b1b4d85e145443ed3376546a368a..29e4068dbd7500828a73145bd25497a52c9bf638 100644
--- a/src/index.tsx

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 108 108"
height="108"
width="108"
xml:space="preserve"
id="svg2"
version="1.1"
sodipodi:docname="JoplinLetter.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata23">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
inkscape:document-rotation="0"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1094"
id="namedview21"
showgrid="false"
inkscape:zoom="2.8"
inkscape:cx="-2.1428571"
inkscape:cy="73.214286"
inkscape:window-x="-11"
inkscape:window-y="-11"
inkscape:window-maximized="1"
inkscape:current-layer="svg2"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" />
<defs
id="defs6" />
<g
id="g865"
transform="translate(44, 40), scale(0.33,0.33)"
style="fill:none;fill-opacity:1"
inkscape:label="Letter container"><path
id="path30"
fill="#ffffff"
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1.17263;stroke-dasharray:none;stroke-opacity:1;stop-opacity:1"
d="M 92.634508,-48.8373 H 28.444063 c -0.606678,0 -1.097079,0.490859 -1.097079,1.096592 v 20.383573 c 0,0.686004 0.555444,1.241912 1.241922,1.241912 h 8.800852 c 3.559682,0 6.44623,2.738013 6.755314,6.217395 v 13.9751869 7.78593 74.1585111 0.52545 h -0.0016 c 0.02333,0.77689 -0.0096,1.54086 -0.08442,2.29329 -0.0096,0.10981 -0.02333,0.21821 -0.03653,0.32709 -0.05078,0.43965 -0.107028,0.87699 -0.192454,1.30511 -0.04661,0.24405 -0.112627,0.48164 -0.171063,0.72199 -0.07882,0.3197 -0.142579,0.64448 -0.240789,0.95727 -0.580372,1.85779 -1.49796,3.58087 -2.767117,5.09773 -0.04647,0.054 -0.104299,0.10381 -0.150278,0.15732 -0.387038,0.44843 -0.793477,0.88531 -1.243283,1.2945 -0.453957,0.4129 -0.934667,0.79673 -1.439823,1.15335 -3.591033,2.54379 -8.424421,3.66298 -13.824789,3.14629 -6.885879,-0.64724 -13.717305,-3.77002 -19.2390127,-8.79395 -5.52077603,-5.02346 -8.953577,-11.23901 -9.664036,-17.50346 -0.638943,-5.60429 1.040305,-10.53595 4.72500997,-13.88846 0.0096,-0.007 0.01866,-0.0142 0.0258,-0.0222 0.142742,-0.12779 0.299405,-0.23989 0.446104,-0.36307 2.63699903,-2.209329 6.07439703,-3.548119 9.99711673,-3.91719 0.04421,-0.0042 0.08683,-0.0112 0.128653,-0.0147 0.412878,-0.03644 0.833625,-0.05307 1.258052,-0.06875 0.221801,-0.0075 0.441028,-0.02309 0.666165,-0.02489 0.07465,-10e-4 0.145635,-0.0089 0.220634,-0.0089 0.133761,0 0.272634,0.0175 0.406421,0.01936 0.618197,0.0098 1.240993,0.03506 1.876236,0.08951 0.08027,0.0065 0.156833,0.0037 0.239457,0.0112 0.05865,0.0061 0.116802,0.0189 0.175239,0.02494 3.598402,0.360764 7.161287,1.43521 10.542386,3.06325 0.0705,0.003 0.152191,0.0249 0.254215,0.0812 1.030611,0.56929 1.228062,-0.0415 1.258503,-0.44519 V 33.605817 20.558368 c 0,-0.878838 -0.611246,-1.656187 -1.468421,-1.844874 -18.2116207,-4.008068 -36.079989,0.163761 -48.68826,11.641302 -11.014353,10.01925 -16.342292,24.477454 -14.615976,39.665024 1.5404,13.5277 8.484385,26.52393 19.555018,36.599 10.7901423,9.81673 24.5286593,15.9557 38.700372,17.29085 1.952829,0.18076 3.914888,0.27393 5.829429,0.27393 13.534593,0 26.093507,-4.64092 35.363086,-13.07322 8.781041,-7.99493 14.040701,-18.97328 14.812501,-30.91676 l 0.0714,-78.3303471 h 0.006 V -19.079901 h 0.002 v -0.365378 c 0.0793,-3.696201 3.09325,-6.669963 6.80976,-6.669963 h 8.80085 c 0.68555,0 1.24192,-0.555907 1.24192,-1.241909 v -20.383571 c 0,-0.605733 -0.4904,-1.096592 -1.09705,-1.096592"
inkscape:connector-curvature="0"
inkscape:label="Letter" /></g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="#fff" d="M5.841 0C2.63 0 0 2.628 0 5.841V18.16C0 21.372 2.629 24 5.841 24H18.16C21.372 24 24 21.372 24 18.159V5.84C24 2.628 21.372 0 18.159 0Zm6.207 3.21h6.522a.111.111 0 0 1 .111.111v2.071c0 .07-.056.127-.126.127h-.894a.692.692 0 0 0-.692.677v2.165h-.001l-.007 7.96a4.628 4.628 0 0 1-1.505 3.14c-.942.858-2.218 1.33-3.594 1.33-.194 0-.393-.01-.592-.029a6.843 6.843 0 0 1-3.932-1.757c-1.125-1.023-1.83-2.344-1.987-3.718-.175-1.544.366-3.013 1.485-4.03 1.281-1.167 3.097-1.59 4.947-1.183.087.019.15.098.15.187v3.117c-.004.041-.024.104-.128.046a.059.059 0 0 0-.026-.009 3.22 3.22 0 0 0-1.072-.31c-.005-.001-.011-.003-.017-.003-.009-.001-.016 0-.025-.002a2.753 2.753 0 0 0-.19-.009l-.042-.002-.022.001c-.023 0-.045.002-.068.003a2.53 2.53 0 0 0-.127.007l-.014.001c-.398.038-.747.174-1.015.398l-.046.037a.02.02 0 0 1-.002.002c-.375.341-.545.842-.48 1.412.072.636.42 1.268.981 1.778.562.51 1.256.828 1.955.894.55.052 1.04-.062 1.405-.32a1.52 1.52 0 0 0 .146-.117c.046-.042.087-.086.127-.132l.015-.016c.129-.154.222-.329.281-.518.01-.031.017-.064.025-.097l.017-.073c.009-.044.014-.088.02-.133l.003-.033a1.81 1.81 0 0 0 .009-.233v-9.8a.69.69 0 0 0-.686-.631h-.895a.126.126 0 0 1-.126-.127v-2.07a.111.111 0 0 1 .112-.112Z" style="stroke-width:.015625"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -9,7 +9,9 @@ function getOs() {
function getFilename(path) {
if (!path) return '';
const s = path.split('/');
return s.pop();
const urlWithParams = s.pop();
const s2 = urlWithParams.split('?');
return s2[0];
}
function getMobileOs() {

View File

@@ -114,6 +114,20 @@ elif [[ $ARCHITECTURE =~ .*i386.*|.*i686.* ]] ; then
exit 1
fi
#-----------------------------------------------------
print "Checking dependencies..."
## Check if libfuse2 is present.
if [[ $(command -v ldconfig) ]]; then
LIBFUSE=$(ldconfig -p | grep "libfuse.so.2" || echo '')
else
LIBFUSE=$(find /lib /usr/lib /lib64 /usr/lib64 /usr/local/lib -name "libfuse.so.2" 2>/dev/null | grep "libfuse.so.2" || echo '')
fi
if [[ $LIBFUSE == "" ]] ; then
print "${COLOR_RED}Error: Can't get libfuse2 on system, please install libfuse2${COLOR_RESET}"
print "See https://joplinapp.org/faq/#desktop-application-will-not-launch-on-linux and https://github.com/AppImage/AppImageKit/wiki/FUSE for more information"
exit 1
fi
#-----------------------------------------------------
# Download Joplin
#-----------------------------------------------------
@@ -134,10 +148,16 @@ else
print "The latest version is ${RELEASE_VERSION}, but you have ${CURRENT_VERSION:-no version} installed."
fi
# Check if it's an update or a new install
DOWNLOAD_TYPE="New"
if [[ -f ~/.joplin/Joplin.AppImage ]]; then
DOWNLOAD_TYPE="Update"
fi
#-----------------------------------------------------
print 'Downloading Joplin...'
TEMP_DIR=$(mktemp -d)
wget -O "${TEMP_DIR}/Joplin.AppImage" "https://github.com/laurent22/joplin/releases/download/v${RELEASE_VERSION}/Joplin-${RELEASE_VERSION}.AppImage"
wget -O "${TEMP_DIR}/Joplin.AppImage" "https://objects.joplinusercontent.com/v${RELEASE_VERSION}/Joplin-${RELEASE_VERSION}.AppImage?source=LinuxInstallScript&type=$DOWNLOAD_TYPE"
wget -O "${TEMP_DIR}/joplin.png" https://joplinapp.org/images/Icon512.png
#-----------------------------------------------------

View File

@@ -22,21 +22,23 @@ Three types of applications are available: for **desktop** (Windows, macOS and L
Operating System | Download
---|---
Windows (32 and 64-bit) | <a href='https://github.com/laurent22/joplin/releases/download/v2.9.17/Joplin-Setup-2.9.17.exe'><img alt='Get it on Windows' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeWindows.png'/></a>
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v2.9.17/Joplin-2.9.17.dmg'><img alt='Get it on macOS' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeMacOS.png'/></a>
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v2.9.17/Joplin-2.9.17.AppImage'><img alt='Get it on Linux' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeLinux.png'/></a>
Windows (32 and 64-bit) | <a href='https://objects.joplinusercontent.com/v2.10.19/Joplin-Setup-2.10.19.exe?source=JoplinWebsite&type=New'><img alt='Get it on Windows' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeWindows.png'/></a>
macOS | <a href='https://objects.joplinusercontent.com/v2.10.19/Joplin-2.10.19.dmg?source=JoplinWebsite&type=New'><img alt='Get it on macOS' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeMacOS.png'/></a>
Linux | <a href='https://objects.joplinusercontent.com/v2.10.19/Joplin-2.10.19.AppImage?source=JoplinWebsite&type=New'><img alt='Get it on Linux' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeLinux.png'/></a>
**On Windows**, you may also use the <a href='https://github.com/laurent22/joplin/releases/download/v2.9.17/JoplinPortable.exe'>Portable version</a>. The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
**On Windows**, you may also use the <a href='https://objects.joplinusercontent.com/v2.10.19/JoplinPortable.exe?source=JoplinWebsite&type=New'>Portable version</a>. The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
**On Linux**, the recommended way is to use the following installation script as it will handle the desktop icon too:
<pre><code style="word-break: break-all">wget -O - https://raw.githubusercontent.com/laurent22/joplin/dev/Joplin_install_and_update.sh | bash</code></pre>
The install and update script supports the [following flags](https://github.com/laurent22/joplin/blob/dev/Joplin_install_and_update.sh#L50) (around line 50 at the time of this writing).
## Mobile applications
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://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.9.8/joplin-v2.9.8.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.9.8/joplin-v2.9.8-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://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://objects.joplinusercontent.com/android-v2.9.8/joplin-v2.9.8.apk?source=JoplinWebsite&type=New) [32-bit](https://objects.joplinusercontent.com/android-v2.9.8/joplin-v2.9.8-32bit.apk?source=JoplinWebsite&type=New)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeIOS.png'/></a> | -
## Terminal application
@@ -64,7 +66,7 @@ A community maintained list of these distributions can be found here: [Unofficia
# Sponsors
<!-- SPONSORS-ORG -->
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://residence-greece.com/"><img title="Greece Golden Visa" width="256" src="https://joplinapp.org/images/sponsors/ResidenceGreece.jpg"/></a> <a href="https://grundstueckspreise.info/"><img title="SP Software GmbH" width="256" src="https://joplinapp.org/images/sponsors/Grundstueckspreise.png"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&amp;mtm_kwd=joplinapp&amp;mtm_source=joplinapp-webseite&amp;mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://residence-greece.com/"><img title="Greece Golden Visa" width="256" src="https://joplinapp.org/images/sponsors/ResidenceGreece.jpg"/></a> <a href="https://grundstueckspreise.info/"><img title="SP Software GmbH" width="256" src="https://joplinapp.org/images/sponsors/Grundstueckspreise.png"/></a> <a href="https://tranio.com/spain/"><img title="Property for sale in Spain" width="256" src="https://joplinapp.org/images/sponsors/TranioOverseasProperty.jpg"/></a>
<!-- SPONSORS-ORG -->
* * *

View File

@@ -107,7 +107,10 @@
".eslintignore": true,
".gitignore": true,
".vscode/*": true,
".yarn": true,
".yarn/cache": true,
".yarn/install-state.gz": true,
".yarn/plugins": true,
".yarn/releases": true,
"*.sublime-workspace": true,
"**/_mydocs": true,
"**/_mydocs/EnexSamples/*.enex": true,

View File

@@ -81,9 +81,10 @@
"gulp": "4.0.2",
"husky": "3.1.0",
"lerna": "3.22.1",
"lint-staged": "13.2.1",
"lint-staged": "13.2.2",
"madge": "6.0.0",
"npm-package-json-lint": "6.4.0",
"svg2vectordrawable": "^2.9.1",
"typedoc": "0.17.8",
"typescript": "4.9.4"
},

View File

@@ -57,7 +57,7 @@
"proper-lockfile": "4.1.2",
"read-chunk": "2.1.0",
"server-destroy": "1.0.1",
"sharp": "0.32.0",
"sharp": "0.32.1",
"sprintf-js": "1.1.2",
"sqlite3": "5.1.6",
"string-padding": "1.0.2",

View File

@@ -0,0 +1,19 @@
<table>
<tbody>
<tr>
<td style="text-align:left">Left</td>
<td style="text-align:center">Centered</td>
<td style="text-align:right">Right</td>
</tr>
<tr>
<td style="text-align:left">Left</td>
<td style="text-align:center">Centered</td>
<td style="text-align:right">Right</td>
</tr>
<tr>
<td style="text-align:left">Left</td>
<td style="text-align:center">Centered</td>
<td style="text-align:right">Right</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,5 @@
| | | |
| :--- | :---: | ---: |
| Left | Centered | Right |
| Left | Centered | Right |
| Left | Centered | Right |

View File

@@ -0,0 +1,26 @@
<table>
<thead>
<tr>
<th style="text-align:left">Left-aligned Column</th>
<th style="text-align:center">Center-aligned Column</th>
<th style="text-align:right">Right-aligned Column</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">Left</td>
<td style="text-align:center">Centered</td>
<td style="text-align:right">Right</td>
</tr>
<tr>
<td style="text-align:left">Left</td>
<td style="text-align:center">Centered</td>
<td style="text-align:right">Right</td>
</tr>
<tr>
<td style="text-align:left">Left</td>
<td style="text-align:center">Centered</td>
<td style="text-align:right">Right</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,5 @@
| Left-aligned Column | Center-aligned Column | Right-aligned Column |
| :--- | :---: | ---: |
| Left | Centered | Right |
| Left | Centered | Right |
| Left | Centered | Right |

View File

@@ -0,0 +1,14 @@
<table>
<thead>
<tr>
<th align="center">abc</th>
<th align="right">defghi</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">bar</td>
<td align="right">baz</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,3 @@
| abc | defghi |
| :---: | ---: |
| bar | baz |

View File

@@ -0,0 +1,29 @@
<table>
<thead>
<tr>
<th style="text-align:left">Left-aligned Column</th>
<th>This header cell's text is unaligned, but a majority of the text in this column is center-aligned so the
column will be center-aligned</th>
<th style="text-align:right">Right-aligned Column</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">Left</td>
<td style="text-align:center">Centered</td>
<td style="text-align:right">Right</td>
</tr>
<tr>
<td style="text-align:right">This is the only right-aligned cell in this column. This is possible if a user
edits a cell's alignment using the cell properties dialog.</td>
<td style="text-align:center">Centered</td>
<td style="text-align:right">Right</td>
</tr>
<tr>
<td style="text-align:center">This is the only center-aligned cell in this column. This is possible if a
user edits a cell's alignment using the cell properties dialog.</td>
<td style="text-align:center">Centered</td>
<td style="text-align:right">Right</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,5 @@
| Left-aligned Column | This header cell's text is unaligned, but a majority of the text in this column is center-aligned so the column will be center-aligned | Right-aligned Column |
| :--- | :---: | ---: |
| Left | Centered | Right |
| This is the only right-aligned cell in this column. This is possible if a user edits a cell's alignment using the cell properties dialog. | Centered | Right |
| This is the only center-aligned cell in this column. This is possible if a user edits a cell's alignment using the cell properties dialog. | Centered | Right |

View File

@@ -0,0 +1 @@
<div class="jop-noMdConv">

View File

@@ -0,0 +1 @@
<div><svg><style></svg><iframe srcdoc="<script>top.require('child_process').execSync('calc')</script>"></iframe></div>

View File

@@ -0,0 +1 @@
<a href="#" class="jop-noMdConv">XSS</a>

View File

@@ -0,0 +1 @@
<a data-from-md="" href="javascript:top.require('child_process').execSync('open -a Calculator')">XSS</a>

View File

@@ -0,0 +1 @@
<use href="#" class="jop-noMdConv">

View File

@@ -0,0 +1 @@
<svg><use href="data:image/svg+xml,&lt;svg id='x' xmlns='http://www.w3.org/2000/svg'&gt;&lt;image href='asdf' onerror='top.require(`child_process`).execSync(`calc.exe`)' /&gt;&lt;/svg&gt;#x" />

After

Width:  |  Height:  |  Size: 193 B

View File

@@ -0,0 +1 @@
<map name="test" class="jop-noMdConv"><area coords="0,0,1000,1000" href="#" class="jop-noMdConv"/></map><img usemap="#test" src="https://github.com/Ry0taK.png" class="jop-noMdConv"/>

View File

@@ -0,0 +1 @@
<map name="test"><area coords="0,0,1000,1000" href="javascript:top.require(`child_process`).execSync(`calc.exe`)"></map><img usemap="#test" src="https://github.com/Ry0taK.png">

View File

@@ -68,6 +68,10 @@ export class Bridge {
return process.argv;
}
public getLocale = () => {
return this.electronApp().electronApp().getLocale();
};
// Applies to electron-context-menu@3:
//
// For now we have to disable spell checking in non-editor text

View File

@@ -97,7 +97,8 @@ async function fetchLatestRelease(options: CheckForUpdateOptions) {
}
if (found) {
downloadUrl = asset.browser_download_url;
downloadUrl = asset.browser_download_url.replace('github.com/laurent22/joplin/releases/download', 'objects.joplinusercontent.com');
downloadUrl.concat('?source=DesktopApp&type=Update');
break;
}
}

View File

@@ -866,6 +866,7 @@ class MainScreenComponent extends React.Component<Props, State> {
const mapStateToProps = (state: AppState) => {
const syncInfo = localSyncInfoFromState(state);
const showNeedUpgradingEnabledMasterKeyMessage = !!EncryptionService.instance().masterKeysThatNeedUpgrading(syncInfo.masterKeys.filter((k) => !!k.enabled)).length;
return {
themeId: state.settings.theme,
@@ -873,7 +874,7 @@ const mapStateToProps = (state: AppState) => {
hasDisabledSyncItems: state.hasDisabledSyncItems,
hasDisabledEncryptionItems: state.hasDisabledEncryptionItems,
showMissingMasterKeyMessage: showMissingMasterKeyMessage(syncInfo, state.notLoadedMasterKeys),
showNeedUpgradingMasterKeyMessage: !!EncryptionService.instance().masterKeysThatNeedUpgrading(syncInfo.masterKeys).length,
showNeedUpgradingMasterKeyMessage: showNeedUpgradingEnabledMasterKeyMessage,
showShouldReencryptMessage: state.settings['encryption.shouldReencrypt'] >= Setting.SHOULD_REENCRYPT_YES,
shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO,
pluginsLegacy: state.pluginsLegacy,

View File

@@ -26,11 +26,15 @@ export default class PromptDialog extends React.Component<Props, any> {
private focusInput_: boolean;
private styles_: any;
private styleKey_: string;
private menuIsOpened_: boolean = false;
public constructor(props: Props) {
super(props);
this.answerInput_ = React.createRef();
this.select_menuOpen = this.select_menuOpen.bind(this);
this.select_menuClose = this.select_menuClose.bind(this);
}
public UNSAFE_componentWillMount() {
@@ -39,6 +43,7 @@ export default class PromptDialog extends React.Component<Props, any> {
answer: this.props.defaultValue ? this.props.defaultValue : '',
});
this.focusInput_ = true;
this.menuIsOpened_ = false;
}
public UNSAFE_componentWillReceiveProps(newProps: Props) {
@@ -52,6 +57,14 @@ export default class PromptDialog extends React.Component<Props, any> {
}
}
private select_menuOpen() {
this.menuIsOpened_ = true;
}
private select_menuClose() {
this.menuIsOpened_ = false;
}
public componentDidUpdate() {
if (this.focusInput_ && this.answerInput_.current) this.answerInput_.current.focus();
this.focusInput_ = false;
@@ -224,16 +237,14 @@ export default class PromptDialog extends React.Component<Props, any> {
const onKeyDown = (event: any) => {
if (event.key === 'Enter') {
if (this.props.inputType === 'tags' || this.props.inputType === 'dropdown') {
// If the dropdown is open, we don't close the dialog - instead
// the currently item will be selcted. If it is closed however
// we confirm the dialog.
if ((this.props.inputType === 'tags' || this.props.inputType === 'dropdown') && this.menuIsOpened_) {
// Do nothing
} else {
onClose(true);
}
// } else if (this.answerInput_.current && !this.answerInput_.current.state.menuIsOpen) {
// // The menu will be open if the user is selecting a new item
// onClose(true);
// }
} else if (event.key === 'Escape') {
onClose(false);
}
@@ -246,9 +257,9 @@ export default class PromptDialog extends React.Component<Props, any> {
if (this.props.inputType === 'datetime') {
inputComp = <Datetime className="datetime-picker" value={this.state.answer} inputProps={{ style: styles.input }} dateFormat={time.dateFormat()} timeFormat={time.timeFormat()} onChange={(momentObject: any) => onDateTimeChange(momentObject)} />;
} else if (this.props.inputType === 'tags') {
inputComp = <CreatableSelect className="tag-selector" styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} value={this.state.answer} placeholder="" components={makeAnimated()} isMulti={true} isClearable={false} backspaceRemovesValue={true} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={(event: any) => onKeyDown(event)} />;
inputComp = <CreatableSelect className="tag-selector" onMenuOpen={this.select_menuOpen} onMenuClose={this.select_menuClose} styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} value={this.state.answer} placeholder="" components={makeAnimated()} isMulti={true} isClearable={false} backspaceRemovesValue={true} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={(event: any) => onKeyDown(event)} />;
} else if (this.props.inputType === 'dropdown') {
inputComp = <Select className="item-selector" styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} components={makeAnimated()} value={this.props.answer} defaultValue={this.props.defaultValue} isClearable={false} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={(event: any) => onKeyDown(event)} />;
inputComp = <Select className="item-selector" onMenuOpen={this.select_menuOpen} onMenuClose={this.select_menuClose} styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} components={makeAnimated()} value={this.props.answer} defaultValue={this.props.defaultValue} isClearable={false} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={(event: any) => onKeyDown(event)} />;
} else {
inputComp = <input style={styles.input} ref={this.answerInput_} value={this.state.answer} type="text" onChange={event => onChange(event)} onKeyDown={event => onKeyDown(event)} />;
}

View File

@@ -27,7 +27,7 @@ import StyleSheetContainer from './StyleSheets/StyleSheetContainer';
import ImportScreen from './ImportScreen';
const { ResourceScreen } = require('./ResourceScreen.js');
import Navigator from './Navigator';
const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
import WelcomeUtils from '@joplin/lib/WelcomeUtils';
const { ThemeProvider, StyleSheetManager, createGlobalStyle } = require('styled-components');
const bridge = require('@electron/remote').require('./bridge').default;
@@ -141,7 +141,7 @@ class RootComponent extends React.Component<Props, any> {
});
}
await WelcomeUtils.install(this.props.dispatch);
await WelcomeUtils.install(Setting.value('locale'), this.props.dispatch);
}
private renderModalMessage(props: ModalDialogProps) {

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import { useEffect, useRef, useCallback, useMemo } from 'react';
import styled from 'styled-components';
import styled, { css } from 'styled-components';
import shim from '@joplin/lib/shim';
import { StyledRoot, StyledAddButton, StyledShareIcon, StyledHeader, StyledHeaderIcon, StyledAllNotesIcon, StyledHeaderLabel, StyledListItem, StyledListItemAnchor, StyledExpandLink, StyledNoteCount, StyledSyncReportText, StyledSyncReport, StyledSynchronizeButton } from './styles';
import { ButtonLevel } from '../Button/Button';
@@ -40,24 +40,15 @@ const { clipboard } = require('electron');
const logger = Logger.create('Sidebar');
const StyledFoldersHolder = styled.div`
// linux bug: https://github.com/laurent22/joplin/issues/7506#issuecomment-1447101057
& a.list-item {
${shim.isLinux() && {
opacity: 1,
}}
}
`;
const TagsHolder = styled.div`
// linux bug: https://github.com/laurent22/joplin/issues/8000
// solution ref: https://github.com/laurent22/joplin/issues/7506#issuecomment-1447101057
& a.list-item {
${shim.isLinux() && {
opacity: 1,
}}
}
// Workaround sidebar rendering bug on Linux Intel GPU.
// https://github.com/laurent22/joplin/issues/7506
const StyledSpanFix = styled.span`
${shim.isLinux() && css`
position: relative;
`}
`;
interface Props {
themeId: number;
dispatch: Function;
@@ -138,7 +129,7 @@ function FolderItem(props: any) {
}}
onDoubleClick={onFolderToggleClick_}
>
{showFolderIcon ? renderFolderIcon(folderIcon) : null}<span className="title" style={{ lineHeight: 0 }}>{folderTitle}</span>
{showFolderIcon ? renderFolderIcon(folderIcon) : null}<StyledSpanFix className="title" style={{ lineHeight: 0 }}>{folderTitle}</StyledSpanFix>
{shareIcon} {noteCountComp}
</StyledListItemAnchor>
</StyledListItem>
@@ -573,7 +564,7 @@ const SidebarComponent = (props: Props) => {
tagItem_click(tag);
}}
>
<span className="tag-label">{Tag.displayTitle(tag)}</span>
<StyledSpanFix className="tag-label">{Tag.displayTitle(tag)}</StyledSpanFix>
{noteCount}
</StyledListItemAnchor>
</StyledListItem>
@@ -725,13 +716,13 @@ const SidebarComponent = (props: Props) => {
const folderItems = [renderAllNotesItem(theme, allNotesSelected)].concat(result.items);
folderItemsOrder_.current = result.order;
items.push(
<StyledFoldersHolder
<div
className={`folders ${props.folderHeaderIsExpanded ? 'expanded' : ''}`}
key="folder_items"
style={foldersStyle}
>
{folderItems}
</StyledFoldersHolder>
</div>
);
}
@@ -747,9 +738,9 @@ const SidebarComponent = (props: Props) => {
tagItemsOrder_.current = result.order;
items.push(
<TagsHolder className="tags" key="tag_items" style={{ display: props.tagHeaderIsExpanded ? 'block' : 'none' }}>
<div className="tags" key="tag_items" style={{ display: props.tagHeaderIsExpanded ? 'block' : 'none' }}>
{tagItems}
</TagsHolder>
</div>
);
}

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.11.1",
"version": "2.11.6",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
@@ -12,7 +12,7 @@
"electronRebuild": "gulp electronRebuild",
"tsc": "tsc --project tsconfig.json",
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json",
"start": "gulp build && electron . --env dev --log-level debug --no-welcome --open-dev-tools",
"start": "gulp build && electron . --env dev --log-level debug --open-dev-tools",
"test": "jest",
"test-ci": "yarn test"
},
@@ -27,7 +27,7 @@
},
"build": {
"appId": "net.cozic.joplin-desktop",
"compression": "maximum",
"compression": "normal",
"productName": "Joplin",
"npmRebuild": false,
"afterSign": "./tools/notarizeMacApp.js",
@@ -116,7 +116,7 @@
"@types/react-redux": "7.1.25",
"@types/styled-components": "5.1.26",
"electron": "19.1.4",
"electron-builder": "23.6.0",
"electron-builder": "22.11.7",
"electron-notarize": "1.2.2",
"electron-rebuild": "3.2.9",
"glob": "8.1.0",
@@ -163,7 +163,7 @@
"react-datetime": "3.2.0",
"react-dom": "18.2.0",
"react-redux": "8.0.5",
"react-select": "5.7.2",
"react-select": "5.7.3",
"react-toggle-button": "2.2.0",
"react-tooltip": "4.5.1",
"redux": "4.2.1",
@@ -171,7 +171,7 @@
"roboto-fontface": "0.10.0",
"smalltalk": "2.5.1",
"sqlite3": "5.1.6",
"styled-components": "5.3.9",
"styled-components": "5.3.10",
"styled-system": "5.1.5",
"taboverride": "4.0.3",
"tinymce": "5.10.6"

View File

@@ -79,9 +79,13 @@ import org.apache.tools.ant.taskdefs.condition.Os
*/
project.ext.react = [
// 2023/05/07: Leave that to `false` for now because Hermes is rubbish at
// reporting errors, which it makes it impossible to investigate crashes.
enableHermes: false, // clean and rebuild if changing
// 2023-05-09: This seems to be optional, but it's not. Without it, the app
// will crash on certain devices with this error:
//
// > java.lang.UnsatisfiedLinkError: couldn't find DSO to load: libhermes.so"
//
// https://github.com/laurent22/joplin/issues/8144#issuecomment-1539629812
enableHermes: true, // clean and rebuild if changing
]
apply from: "../../node_modules/react-native/react.gradle"
@@ -152,8 +156,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097695
versionName "2.11.10"
versionCode 2097707
versionName "2.11.22"
// ndk {
// abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
// }
@@ -300,8 +304,11 @@ dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
// implementation "com.facebook.react:react-native:+" // From node_modules
implementation ("com.facebook.react:react-native") version {
strictly "0.70.6" // pass in your react-native version
}
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M5.84 0C2.63 0 0 2.63 0 5.84V18.16C0 21.37 2.63 24 5.84 24H18.16C21.37 24 24 21.37 24 18.16V5.84C24 2.63 21.37 0 18.16 0Zm6.21 3.21h6.52a0.11 0.11 0 0 1 0.11 0.11v2.07c0 0.07-0.06 0.13-0.12 0.13h-0.9a0.69 0.69 0 0 0-0.69 0.68v2.16h0l-0.01 7.96a4.63 4.63 0 0 1-1.5 3.14c-0.94 0.86-2.22 1.33-3.6 1.33-0.19 0-0.39-0.01-0.59-0.03a6.84 6.84 0 0 1-3.93-1.75c-1.13-1.02-1.83-2.34-1.99-3.72-0.17-1.54 0.37-3.01 1.49-4.03 1.28-1.17 3.1-1.59 4.94-1.19 0.09 0.02 0.15 0.1 0.15 0.19v3.12c0 0.04-0.02 0.1-0.12 0.04a0.06 0.06 0 0 0-0.03 0 3.22 3.22 0 0 0-1.07-0.31c-0.01 0-0.01 0-0.02-0.01-0.01 0-0.02 0-0.02 0a2.75 2.75 0 0 0-0.19-0.01l-0.05 0-0.02 0c-0.02 0-0.04 0-0.07 0a2.53 2.53 0 0 0-0.12 0.01l-0.02 0c-0.4 0.04-0.75 0.17-1.01 0.4l-0.05 0.04a0.02 0.02 0 0 1 0 0c-0.38 0.34-0.55 0.84-0.48 1.41 0.07 0.64 0.42 1.27 0.98 1.78 0.56 0.51 1.26 0.83 1.96 0.89 0.55 0.05 1.04-0.06 1.4-0.32a1.52 1.52 0 0 0 0.15-0.11c0.05-0.04 0.09-0.09 0.12-0.14l0.02-0.01c0.13-0.15 0.22-0.33 0.28-0.52 0.01-0.03 0.02-0.06 0.02-0.1l0.02-0.07c0.01-0.04 0.01-0.09 0.02-0.13l0-0.04a1.81 1.81 0 0 0 0.01-0.23v-9.8a0.69 0.69 0 0 0-0.68-0.63h-0.9a0.13 0.13 0 0 1-0.12-0.13v-2.07a0.11 0.11 0 0 1 0.11-0.11Z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="0.33"
android:scaleY="0.33"
android:translateX="44"
android:translateY="40">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M92.63-48.84H28.44c-0.61 0-1.1 0.49-1.09 1.1v20.38c0 0.69 0.56 1.24 1.24 1.24h8.8c3.56 0 6.45 2.74 6.76 6.22v13.98 7.78 74.16 0.53h-0.01c0.02 0.78-0.01 1.54-0.08 2.29-0.01 0.11-0.02 0.22-0.04 0.33-0.05 0.44-0.11 0.88-0.19 1.3-0.05 0.24-0.11 0.48-0.17 0.72-0.08 0.32-0.14 0.64-0.24 0.96-0.58 1.86-1.5 3.58-2.77 5.1-0.05 0.05-0.1 0.1-0.15 0.16-0.39 0.45-0.79 0.89-1.24 1.29-0.45 0.41-0.93 0.8-1.44 1.15-3.59 2.54-8.42 3.66-13.83 3.15-6.89-0.65-13.72-3.77-19.24-8.79-5.52-5.02-8.95-11.24-9.66-17.51-0.64-5.6 1.04-10.54 4.72-13.88 0.01-0.01 0.02-0.01 0.03-0.03 0.14-0.13 0.3-0.24 0.45-0.36 2.64-2.21 6.07-3.55 9.99-3.92 0.04 0 0.09-0.01 0.13-0.01 0.41-0.04 0.83-0.05 1.26-0.07 0.22-0.01 0.44-0.02 0.67-0.03 0.07 0 0.15-0.01 0.22 0 0.13 0 0.27 0.02 0.4 0.01 0.62 0.01 1.24 0.04 1.88 0.09 0.08 0.01 0.16 0 0.24 0.02 0.06 0.01 0.12 0.02 0.17 0.02 3.6 0.36 7.16 1.44 10.55 3.06 0.07 0 0.15 0.02 0.25 0.09 1.03 0.57 1.23-0.04 1.26-0.45V33.61 20.56c0-0.88-0.61-1.66-1.47-1.85-18.21-4.01-36.08 0.16-48.69 11.64-11.01 10.02-16.34 24.48-14.61 39.67 1.54 13.53 8.48 26.52 19.55 36.6 10.79 9.82 24.53 15.96 38.7 17.29 1.95 0.18 3.91 0.27 5.83 0.27 13.53 0 26.09-4.64 35.37-13.07 8.78-7.99 14.04-18.97 14.81-30.92l0.07-78.33h0.01V-19.08h0v-0.37c0.08-3.7 3.09-6.67 6.81-6.67h8.8c0.69 0 1.24-0.56 1.24-1.24v-20.38c0-0.61-0.49-1.1-1.1-1.1"/>
</group>
</vector>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -1,3 +1,4 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: ['react-native-reanimated/plugin'],
};

View File

@@ -0,0 +1,90 @@
const React = require('react');
import { FunctionComponent } from 'react';
import { _ } from '@joplin/lib/locale';
import Folder, { FolderEntityWithChildren } from '@joplin/lib/models/Folder';
const { themeStyle } = require('./global-style.js');
import Dropdown, { DropdownListItem, OnValueChangedListener } from './Dropdown';
import { FolderEntity } from '@joplin/lib/services/database/types';
interface FolderPickerProps {
disabled?: boolean;
selectedFolderId?: string;
onValueChange?: OnValueChangedListener;
mustSelect?: boolean;
folders: FolderEntity[];
placeholder?: string;
darkText?: boolean;
themeId?: string;
}
const FolderPicker: FunctionComponent<FolderPickerProps> = ({
disabled,
selectedFolderId,
onValueChange,
mustSelect,
folders,
placeholder,
darkText,
themeId,
}) => {
const theme = themeStyle(themeId);
const addFolderChildren = (
folders: FolderEntityWithChildren[], pickerItems: DropdownListItem[], indent: number
) => {
folders.sort((a, b) => {
const aTitle = a && a.title ? a.title : '';
const bTitle = b && b.title ? b.title : '';
return aTitle.toLowerCase() < bTitle.toLowerCase() ? -1 : +1;
});
for (let i = 0; i < folders.length; i++) {
const f = folders[i];
const icon = Folder.unserializeIcon(f.icon);
const iconString = icon ? `${icon.emoji} ` : '';
pickerItems.push({ label: `${' '.repeat(indent)} ${iconString + Folder.displayTitle(f)}`, value: f.id });
pickerItems = addFolderChildren(f.children, pickerItems, indent + 1);
}
return pickerItems;
};
const titlePickerItems = (mustSelect: boolean) => {
const folderList = folders.filter(f => f.id !== Folder.conflictFolderId());
let output = [];
if (mustSelect) output.push({ label: placeholder || _('Move to notebook...'), value: '' });
const folderTree = Folder.buildTree(folderList);
output = addFolderChildren(folderTree, output, 0);
return output;
};
return (
<Dropdown
items={titlePickerItems(!!mustSelect)}
disabled={disabled}
labelTransform="trim"
selectedValue={selectedFolderId || ''}
itemListStyle={{
backgroundColor: theme.backgroundColor,
}}
headerStyle={{
color: darkText ? theme.colorFaded : theme.colorBright2,
fontSize: theme.fontSize,
opacity: disabled ? theme.disabledOpacity : 1,
}}
itemStyle={{
color: theme.color,
fontSize: theme.fontSize,
}}
onValueChange={(folderId) => {
if (onValueChange) {
onValueChange(folderId);
}
}}
/>
);
};
export default FolderPicker;

View File

@@ -10,9 +10,9 @@ import { Menu, MenuOptions, MenuOption, MenuTrigger } from 'react-native-popup-m
import { _ } from '@joplin/lib/locale';
import Setting from '@joplin/lib/models/Setting';
import Note from '@joplin/lib/models/Note';
import Folder, { FolderEntityWithChildren } from '@joplin/lib/models/Folder';
import Folder from '@joplin/lib/models/Folder';
const { themeStyle } = require('./global-style.js');
import Dropdown, { DropdownListItem, OnValueChangedListener } from './Dropdown';
import { OnValueChangedListener } from './Dropdown';
const { dialogs } = require('../utils/dialogs.js');
const DialogBox = require('react-native-dialogbox').default;
import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils';
@@ -20,6 +20,7 @@ import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils';
import { FolderEntity } from '@joplin/lib/services/database/types';
import { State } from '@joplin/lib/reducer';
import CustomButton from './CustomButton';
import FolderPicker from './FolderPicker';
// We need this to suppress the useless warning
// https://github.com/oblador/react-native-vector-icons/issues/1465
@@ -494,58 +495,14 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
}
const createTitleComponent = (disabled: boolean) => {
const themeId = Setting.value('theme');
const theme = themeStyle(themeId);
const folderPickerOptions = this.props.folderPickerOptions;
if (folderPickerOptions && folderPickerOptions.enabled) {
const addFolderChildren = (
folders: FolderEntityWithChildren[], pickerItems: DropdownListItem[], indent: number
) => {
folders.sort((a, b) => {
const aTitle = a && a.title ? a.title : '';
const bTitle = b && b.title ? b.title : '';
return aTitle.toLowerCase() < bTitle.toLowerCase() ? -1 : +1;
});
for (let i = 0; i < folders.length; i++) {
const f = folders[i];
const icon = Folder.unserializeIcon(f.icon);
const iconString = icon ? `${icon.emoji} ` : '';
pickerItems.push({ label: `${' '.repeat(indent)} ${iconString + Folder.displayTitle(f)}`, value: f.id });
pickerItems = addFolderChildren(f.children, pickerItems, indent + 1);
}
return pickerItems;
};
const titlePickerItems = (mustSelect: boolean) => {
const folders = this.props.folders.filter(f => f.id !== Folder.conflictFolderId());
let output = [];
if (mustSelect) output.push({ label: _('Move to notebook...'), value: null });
const folderTree = Folder.buildTree(folders);
output = addFolderChildren(folderTree, output, 0);
return output;
};
return (
<Dropdown
items={titlePickerItems(!!folderPickerOptions.mustSelect)}
<FolderPicker
themeId={themeId}
disabled={disabled}
labelTransform="trim"
selectedValue={'selectedFolderId' in folderPickerOptions ? folderPickerOptions.selectedFolderId : null}
itemListStyle={{
backgroundColor: theme.backgroundColor,
}}
headerStyle={{
color: theme.colorBright2,
fontSize: theme.fontSize,
opacity: disabled ? theme.disabledOpacity : 1,
}}
itemStyle={{
color: theme.color,
fontSize: theme.fontSize,
}}
selectedFolderId={'selectedFolderId' in folderPickerOptions ? folderPickerOptions.selectedFolderId : null}
onValueChange={async (folderId) => {
// If onValueChange is specified, use this as a callback, otherwise do the default
// which is to take the selectedNoteIds from the state and move them to the
@@ -570,6 +527,8 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
await Note.moveToFolder(noteIds[i], folderId);
}
}}
mustSelect={!!folderPickerOptions.mustSelect}
folders={this.props.folders}
/>
);
} else {

View File

@@ -1,22 +0,0 @@
const { connect } = require('react-redux');
const SideMenu_ = require('react-native-side-menu-updated').default;
import { Dimensions } from 'react-native';
import { State } from '@joplin/lib/reducer';
class SideMenuComponent extends SideMenu_ {
public onLayoutChange(e: any) {
const { width, height } = e.nativeEvent.layout;
const openMenuOffsetPercentage = this.props.openMenuOffset / Dimensions.get('window').width;
const openMenuOffset = width * openMenuOffsetPercentage;
const hiddenMenuOffset = width * this.state.hiddenMenuOffsetPercentage;
this.setState({ width, height, openMenuOffset, hiddenMenuOffset });
}
}
const SideMenu = connect((state: State) => {
return {
isOpen: state.showSideMenu,
};
})(SideMenuComponent);
export default SideMenu;

View File

@@ -98,6 +98,8 @@ class ConfigScreenComponent extends BaseScreenComponent {
}
};
this.saveButton_press = this.saveButton_press.bind(this);
this.syncStatusButtonPress_ = () => {
void NavService.go('Status');
};
@@ -199,6 +201,8 @@ class ConfigScreenComponent extends BaseScreenComponent {
this.logButtonPress_ = () => {
void NavService.go('Log');
};
this.handleSetting = this.handleSetting.bind(this);
}
public async checkFilesystemPermission() {
@@ -482,8 +486,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
if (key === 'security.biometricsEnabled' && !!value) {
try {
await biometricAuthenticate();
shared.updateSettingValue(this, key, value);
await this.saveButton_press();
shared.updateSettingValue(this, key, value, async () => await this.saveButton_press());
} catch (error) {
shared.updateSettingValue(this, key, false);
Alert.alert(error.message);
@@ -492,8 +495,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
}
if (key === 'security.biometricsEnabled' && !value) {
shared.updateSettingValue(this, key, value);
await this.saveButton_press();
shared.updateSettingValue(this, key, value, async () => await this.saveButton_press());
return true;
}
@@ -751,6 +753,12 @@ class ConfigScreenComponent extends BaseScreenComponent {
</View>
);
settingComps.push(
<View key="version_info_hermes" style={this.styles().settingContainer}>
<Text style={this.styles().settingText}>{_('Hermes enabled: %d', (global as any).HermesInternal ? 1 : 0)}</Text>
</View>
);
return (
<View style={this.rootStyle(this.props.themeId).root}>
<ScreenHeader title={_('Configuration')} showSaveButton={true} showSearchButton={false} showSideMenuButton={false} saveButtonDisabled={!this.state.changedSettingKeys.length} onSaveButtonPress={this.saveButton_press} />

View File

@@ -1,6 +1,6 @@
const React = require('react');
const { View } = require('react-native');
const { View, StyleSheet } = require('react-native');
const { connect } = require('react-redux');
const Folder = require('@joplin/lib/models/Folder').default;
const BaseModel = require('@joplin/lib/BaseModel').default;
@@ -8,6 +8,7 @@ const { ScreenHeader } = require('../ScreenHeader');
const { BaseScreenComponent } = require('../base-screen.js');
const { dialogs } = require('../../utils/dialogs.js');
const { _ } = require('@joplin/lib/locale');
const { default: FolderPicker } = require('../FolderPicker');
const TextInput = require('../TextInput').default;
class FolderScreenComponent extends BaseScreenComponent {
@@ -60,10 +61,16 @@ class FolderScreenComponent extends BaseScreenComponent {
this.folderComponent_change('title', text);
}
parent_changeValue(parent) {
this.folderComponent_change('parent_id', parent);
}
async saveFolderButton_press() {
let folder = Object.assign({}, this.state.folder);
try {
if (folder.id && !(await Folder.canNestUnder(folder.id, folder.parent_id))) throw new Error(_('Cannot move notebook to this location'));
folder = await Folder.save(folder, { userSideValidation: true });
} catch (error) {
dialogs.error(this, _('The notebook could not be saved: %s', error.message));
@@ -83,7 +90,7 @@ class FolderScreenComponent extends BaseScreenComponent {
}
render() {
const saveButtonDisabled = !this.isModified();
const saveButtonDisabled = !this.isModified() || !this.state.folder.title;
return (
<View style={this.rootStyle(this.props.themeId).root}>
@@ -94,7 +101,20 @@ class FolderScreenComponent extends BaseScreenComponent {
autoFocus={true}
value={this.state.folder.title}
onChangeText={text => this.title_changeText(text)}
disabled={this.state.folder.encryption_applied}
/>
<View style={styles.folderPickerContainer}>
<FolderPicker
themeId={this.props.themeId}
placeholder={_('Select parent notebook')}
folders={this.props.folders}
selectedFolderId={this.state.folder.parent_id}
onValueChange={newValue => this.parent_changeValue(newValue)}
mustSelect
darkText
/>
</View>
<View style={{ flex: 1 }} />
<dialogs.DialogBox
ref={dialogbox => {
this.dialogbox = dialogbox;
@@ -109,7 +129,18 @@ const FolderScreen = connect(state => {
return {
folderId: state.selectedFolderId,
themeId: state.settings.theme,
folders: state.folders.filter((folder) => folder.id !== state.selectedFolderId),
};
})(FolderScreenComponent);
const styles = StyleSheet.create({
folderPickerContainer: {
height: 46,
paddingLeft: 14,
paddingRight: 14,
paddingTop: 12,
paddingBottom: 12,
},
});
module.exports = { FolderScreen };

View File

@@ -140,13 +140,8 @@ const SideMenuContentComponent = (props: Props) => {
_('Notebook: %s', folder.title),
[
{
text: _('Rename'),
text: _('Edit'),
onPress: () => {
if (folder.encryption_applied) {
alert(_('Encrypted notebooks cannot be renamed'));
return;
}
props.dispatch({ type: 'SIDE_MENU_CLOSE' });
props.dispatch({

View File

@@ -10,6 +10,9 @@
import 'react-native-get-random-values';
import 'react-native-url-polyfill/auto';
// Set up required for react-native-drawer-layout (See: https://reactnavigation.org/docs/drawer-layout/ v6.x)
import 'react-native-gesture-handler';
import { LogBox, AppRegistry } from 'react-native';
const Root = require('./root').default;

View File

@@ -420,7 +420,6 @@
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/react-native-vosk/Vosk.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
@@ -441,7 +440,6 @@
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Vosk.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -517,13 +515,13 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 89;
CURRENT_PROJECT_VERSION = 92;
DEVELOPMENT_TEAM = A9BXAFS6CT;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 12.11.0;
MARKETING_VERSION = 12.11.3;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -546,12 +544,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
CURRENT_PROJECT_VERSION = 89;
CURRENT_PROJECT_VERSION = 92;
DEVELOPMENT_TEAM = A9BXAFS6CT;
INFOPLIST_FILE = Joplin/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 12.11.0;
MARKETING_VERSION = 12.11.3;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -698,14 +696,14 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 89;
CURRENT_PROJECT_VERSION = 92;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 12.11.0;
MARKETING_VERSION = 12.11.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
@@ -729,14 +727,14 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 89;
CURRENT_PROJECT_VERSION = 92;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = A9BXAFS6CT;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ShareExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 12.11.0;
MARKETING_VERSION = 12.11.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -310,13 +310,13 @@ PODS:
- React-Core
- react-native-image-resizer (1.4.5):
- React-Core
- react-native-netinfo (9.3.9):
- react-native-netinfo (9.3.10):
- React-Core
- react-native-rsa-native (2.0.5):
- React
- react-native-saf-x (2.11.0):
- React-Core
- react-native-safe-area-context (4.5.1):
- react-native-safe-area-context (4.5.2):
- RCT-Folly
- RCTRequired
- RCTTypeSafety
@@ -328,8 +328,6 @@ PODS:
- React-Core
- react-native-version-info (1.1.1):
- React-Core
- react-native-vosk (0.1.12):
- React-Core
- react-native-webview (11.26.1):
- React-Core
- React-perflogger (0.70.6)
@@ -402,7 +400,7 @@ PODS:
- React-Core
- RNCClipboard (1.5.1):
- React-Core
- RNCPushNotificationIOS (1.10.1):
- RNCPushNotificationIOS (1.11.0):
- React-Core
- RNDateTimePicker (6.7.5):
- React-Core
@@ -412,8 +410,37 @@ PODS:
- React-Core
- RNFS (2.20.0):
- React-Core
- RNGestureHandler (2.9.0):
- React-Core
- RNQuickAction (0.3.13):
- React
- RNReanimated (3.0.2):
- DoubleConversion
- FBLazyVector
- FBReactNativeSpec
- glog
- RCT-Folly
- RCTRequired
- RCTTypeSafety
- React-callinvoker
- React-Core
- React-Core/DevSupport
- React-Core/RCTWebSocket
- React-CoreModules
- React-cxxreact
- React-jsi
- React-jsiexecutor
- React-jsinspector
- React-RCTActionSheet
- React-RCTAnimation
- React-RCTBlob
- React-RCTImage
- React-RCTLinking
- React-RCTNetwork
- React-RCTSettings
- React-RCTText
- ReactCommon/turbomodule/core
- Yoga
- RNSecureRandom (1.0.1):
- React
- RNShare (8.2.2):
@@ -486,7 +513,6 @@ DEPENDENCIES:
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
- react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`)
- react-native-version-info (from `../node_modules/react-native-version-info`)
- react-native-vosk (from `../node_modules/react-native-vosk`)
- react-native-webview (from `../node_modules/react-native-webview`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
@@ -507,7 +533,9 @@ DEPENDENCIES:
- RNExitApp (from `../node_modules/react-native-exit-app`)
- RNFileViewer (from `../node_modules/react-native-file-viewer`)
- RNFS (from `../node_modules/react-native-fs`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNQuickAction (from `../node_modules/react-native-quick-actions`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNSecureRandom (from `../node_modules/react-native-securerandom`)
- RNShare (from `../node_modules/react-native-share`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
@@ -604,8 +632,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-sqlite-storage"
react-native-version-info:
:path: "../node_modules/react-native-version-info"
react-native-vosk:
:path: "../node_modules/react-native-vosk"
react-native-webview:
:path: "../node_modules/react-native-webview"
React-perflogger:
@@ -646,8 +672,12 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-file-viewer"
RNFS:
:path: "../node_modules/react-native-fs"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
RNQuickAction:
:path: "../node_modules/react-native-quick-actions"
RNReanimated:
:path: "../node_modules/react-native-reanimated"
RNSecureRandom:
:path: "../node_modules/react-native-securerandom"
RNShare:
@@ -700,14 +730,13 @@ SPEC CHECKSUMS:
react-native-get-random-values: a6ea6a8a65dc93e96e24a11105b1a9c8cfe1d72a
react-native-image-picker: ec9b713e248760bfa0f879f0715391de4651a7cb
react-native-image-resizer: d9fb629a867335bdc13230ac2a58702bb8c8828f
react-native-netinfo: 22c082970cbd99071a4e5aa7a612ac20d66b08f0
react-native-netinfo: ccbe1085dffd16592791d550189772e13bf479e2
react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a
react-native-saf-x: 9bd5238d3b43d76bbec64aa82c173ac20a4bce9f
react-native-safe-area-context: f5549f36508b1b7497434baa0cd97d7e470920d4
react-native-safe-area-context: 1d596539b05a78f2b346e954e7c577f6f9be7544
react-native-slider: 33b8d190b59d4f67a541061bb91775d53d617d9d
react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261
react-native-version-info: a106f23009ac0db4ee00de39574eb546682579b9
react-native-vosk: 33b8e82a46cc56f31bb4847a40efa2d160270e2e
react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1
React-perflogger: 8c79399b0500a30ee8152d0f9f11beae7fc36595
React-RCTActionSheet: 7316773acabb374642b926c19aef1c115df5c466
@@ -723,12 +752,14 @@ SPEC CHECKSUMS:
ReactCommon: 349be31adeecffc7986a0de875d7fb0dcf4e251c
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
RNCPushNotificationIOS: 87b8d16d3ede4532745e05b03c42cff33a36cc45
RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8
RNDateTimePicker: 65e1d202799460b286ff5e741d8baf54695e8abd
RNExitApp: c4e052df2568b43bec8a37c7cd61194d4cfee2c3
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
RNReanimated: 0a5f87ec1da472cca3e835333fdebe51d983c411
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef
RNShare: d82e10f6b7677f4b0048c23709bd04098d5aee6c
RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8

View File

@@ -24,7 +24,6 @@ const localPackages = {
'@joplin/fork-uslug': path.resolve(__dirname, '../fork-uslug/'),
'@joplin/react-native-saf-x': path.resolve(__dirname, '../react-native-saf-x/'),
'@joplin/react-native-alarm-notification': path.resolve(__dirname, '../react-native-alarm-notification/'),
'@joplin/react-native-vosk': path.resolve(__dirname, '../react-native-vosk/'),
};
const watchedFolders = [];

View File

@@ -5,7 +5,7 @@
"version": "2.11.0",
"private": true,
"scripts": {
"start": "react-native start --reset-cache",
"start": "BROWSERSLIST_IGNORE_OLD_DATA=true react-native start --reset-cache",
"android": "react-native run-android",
"build": "gulp build",
"tsc": "tsc --project tsconfig.json",
@@ -25,8 +25,8 @@
"@react-native-community/clipboard": "1.5.1",
"@react-native-community/datetimepicker": "6.7.5",
"@react-native-community/geolocation": "2.1.0",
"@react-native-community/netinfo": "9.3.9",
"@react-native-community/push-notification-ios": "1.10.1",
"@react-native-community/netinfo": "9.3.10",
"@react-native-community/push-notification-ios": "1.11.0",
"@react-native-community/slider": "4.4.2",
"assert-browserify": "2.0.0",
"buffer": "6.0.3",
@@ -45,23 +45,25 @@
"react-native-camera": "4.2.1",
"react-native-dialogbox": "0.6.10",
"react-native-document-picker": "8.2.0",
"react-native-drawer-layout": "3.2.0",
"react-native-dropdownalert": "4.5.1",
"react-native-exit-app": "1.1.0",
"react-native-file-viewer": "2.1.5",
"react-native-fingerprint-scanner": "6.0.0",
"react-native-fs": "2.20.0",
"react-native-gesture-handler": "2.9.0",
"react-native-get-random-values": "1.8.0",
"react-native-image-picker": "5.3.1",
"react-native-image-resizer": "1.4.5",
"react-native-modal-datetime-picker": "14.0.1",
"react-native-paper": "5.5.2",
"react-native-paper": "5.8.0",
"react-native-popup-menu": "0.16.1",
"react-native-quick-actions": "0.3.13",
"react-native-reanimated": "3.1.0",
"react-native-rsa-native": "2.0.5",
"react-native-safe-area-context": "4.5.1",
"react-native-safe-area-context": "4.5.2",
"react-native-securerandom": "1.0.1",
"react-native-share": "8.2.2",
"react-native-side-menu-updated": "1.3.2",
"react-native-sqlite-storage": "6.0.1",
"react-native-url-polyfill": "1.3.0",
"react-native-vector-icons": "9.2.0",
@@ -106,7 +108,7 @@
"jest": "29.4.3",
"jest-environment-jsdom": "29.4.3",
"jetifier": "2.0.0",
"jsdom": "21.1.1",
"jsdom": "21.1.2",
"md5-file": "5.0.0",
"metro-react-native-babel-preset": "0.72.3",
"nodemon": "2.0.22",

View File

@@ -28,7 +28,7 @@ import SyncTargetJoplinCloud from '@joplin/lib/SyncTargetJoplinCloud';
import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive';
import initProfile from '@joplin/lib/services/profileConfig/initProfile';
const VersionInfo = require('react-native-version-info').default;
const { Keyboard, NativeModules, BackHandler, Animated, View, StatusBar, Platform, Dimensions } = require('react-native');
const { Keyboard, NativeModules, BackHandler, View, StatusBar, Platform, Dimensions } = require('react-native');
import { AppState as RNAppState, EmitterSubscription, Linking, NativeEventSubscription } from 'react-native';
import getResponsiveValue from './components/getResponsiveValue';
import NetInfo from '@react-native-community/netinfo';
@@ -67,7 +67,7 @@ const { OneDriveLoginScreen } = require('./components/screens/onedrive-login.js'
import EncryptionConfigScreen from './components/screens/encryption-config';
const { DropboxLoginScreen } = require('./components/screens/dropbox-login.js');
const { MenuContext } = require('react-native-popup-menu');
import SideMenu from './components/SideMenu';
import { Drawer } from 'react-native-drawer-layout';
import SideMenuContent from './components/side-menu-content';
const { SideMenuContentNote } = require('./components/side-menu-content-note.js');
const { DatabaseDriverReactNative } = require('./utils/database-driver-react-native');
@@ -76,7 +76,7 @@ const { defaultState } = require('@joplin/lib/reducer');
const { FileApiDriverLocal } = require('@joplin/lib/file-api-driver-local');
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
import WelcomeUtils from '@joplin/lib/WelcomeUtils';
const { themeStyle } = require('./components/global-style.js');
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
const SyncTargetFilesystem = require('@joplin/lib/SyncTargetFilesystem.js');
@@ -116,6 +116,9 @@ import ProfileEditor from './components/ProfileSwitcher/ProfileEditor';
import sensorInfo from './components/biometrics/sensorInfo';
import { getCurrentProfile } from '@joplin/lib/services/profileConfig';
import { getDatabaseName, getProfilesRootDir, getResourceDir, setDispatch } from './services/profiles';
import { ReactNode } from 'react';
type SideMenuPosition = 'left' | 'right';
const logger = Logger.create('root');
@@ -142,7 +145,7 @@ const generalMiddleware = (store: any) => (next: any) => async (action: any) =>
if (action.type === 'NAV_GO') Keyboard.dismiss();
if (['NOTE_UPDATE_ONE', 'NOTE_DELETE', 'FOLDER_UPDATE_ONE', 'FOLDER_DELETE'].indexOf(action.type) >= 0) {
if (!await reg.syncTarget().syncStarted()) void reg.scheduleSync(5 * 1000, { syncSteps: ['update_remote', 'delete_remote'] }, true);
if (!await reg.syncTarget().syncStarted()) void reg.scheduleSync(1000, { syncSteps: ['update_remote', 'delete_remote'] }, true);
SearchEngine.instance().scheduleSyncTables();
}
@@ -659,7 +662,7 @@ async function initialize(dispatch: Function) {
// doWifiConnectionCheck set to true so initial sync
// doesn't happen on mobile data
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
void reg.scheduleSync(1000, null, true).then(() => {
void reg.scheduleSync(100, null, true).then(() => {
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
void AlarmService.updateAllNotifications();
@@ -667,7 +670,7 @@ async function initialize(dispatch: Function) {
void DecryptionWorker.instance().scheduleStart();
});
await WelcomeUtils.install(dispatch);
await WelcomeUtils.install(Setting.value('locale'), dispatch);
// Collect revisions more frequently on mobile because it doesn't auto-save
// and it cannot collect anything when the app is not active.
@@ -713,7 +716,6 @@ class AppComponent extends React.Component {
super();
this.state = {
sideMenuContentOpacity: new Animated.Value(0),
sideMenuWidth: this.getSideMenuWidth(),
sensorInfo: null,
};
@@ -864,14 +866,7 @@ class AppComponent extends React.Component {
}
}
public componentDidUpdate(prevProps: any) {
if (this.props.showSideMenu !== prevProps.showSideMenu) {
Animated.timing(this.state.sideMenuContentOpacity, {
toValue: this.props.showSideMenu ? 0.5 : 0,
duration: 600,
}).start();
}
public async componentDidUpdate(prevProps: any) {
if (this.props.biometricsDone !== prevProps.biometricsDone && this.props.biometricsDone) {
logger.info('Sharing: componentDidUpdate: biometricsDone');
void this.handleShareData();
@@ -950,8 +945,8 @@ class AppComponent extends React.Component {
if (this.props.appState !== 'ready') return null;
const theme: Theme = themeStyle(this.props.themeId);
let sideMenuContent = null;
let menuPosition = 'left';
let sideMenuContent: ReactNode = null;
let menuPosition: SideMenuPosition = 'left';
if (this.props.routeName === 'Note') {
sideMenuContent = <SafeAreaView style={{ flex: 1, backgroundColor: theme.backgroundColor }}><SideMenuContentNote options={this.props.noteSideMenuOptions}/></SafeAreaView>;
@@ -984,24 +979,27 @@ class AppComponent extends React.Component {
const biometricIsEnabled = !!this.state.sensorInfo && this.state.sensorInfo.enabled;
const shouldShowMainContent = !biometricIsEnabled || this.props.biometricsDone;
logger.info('root.biometrics: biometricsDone', this.props.biometricsDone);
logger.info('root.biometrics: biometricIsEnabled', biometricIsEnabled);
logger.info('root.biometrics: shouldShowMainContent', shouldShowMainContent);
logger.info('root.biometrics: this.state.sensorInfo', this.state.sensorInfo);
const mainContent = (
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
<SideMenu
menu={sideMenuContent}
edgeHitWidth={5}
openMenuOffset={this.state.sideMenuWidth}
menuPosition={menuPosition}
onChange={(isOpen: boolean) => this.sideMenu_change(isOpen)}
onSliding={(percent: number) => {
this.props.dispatch({
type: 'SIDE_MENU_OPEN_PERCENT',
value: percent,
});
<Drawer
// Need to reset the key here based on menu position, otherwise
// the drawer will flash open on screen and close every time the
// drawer position switches (i.e. when opening or closing a note)
key={`main-drawer-${menuPosition}`}
open={this.props.showSideMenu}
onOpen={() => this.sideMenu_change(true)}
onClose={() => this.sideMenu_change(false)}
drawerPosition={menuPosition}
swipeEdgeWidth={15}
drawerStyle={{
width: this.state.sideMenuWidth,
}}
renderDrawerContent={() => sideMenuContent}
>
<StatusBar barStyle={statusBarStyle} />
<MenuContext style={{ flex: 1 }}>
@@ -1011,15 +1009,14 @@ class AppComponent extends React.Component {
{ shouldShowMainContent && <AppNav screens={appNavInit} dispatch={this.props.dispatch} /> }
</View>
<DropdownAlert ref={(ref: any) => this.dropdownAlert_ = ref} tapToCloseEnabled={true} />
<Animated.View pointerEvents='none' style={{ position: 'absolute', backgroundColor: 'black', opacity: this.state.sideMenuContentOpacity, width: '100%', height: '120%' }}/>
{ this.state.sensorInfo && <BiometricPopup
{ !shouldShowMainContent && <BiometricPopup
dispatch={this.props.dispatch}
themeId={this.props.themeId}
sensorInfo={this.state.sensorInfo}
/> }
</SafeAreaView>
</MenuContext>
</SideMenu>
</Drawer>
</View>
);
@@ -1058,6 +1055,7 @@ const mapStateToProps = (state: any) => {
themeId: state.settings.theme,
noteSideMenuOptions: state.noteSideMenuOptions,
biometricsDone: state.biometricsDone,
biometricsEnabled: state.settings['security.biometricsEnabled'],
};
};

View File

@@ -53,8 +53,7 @@ export default class AlarmServiceDriver {
title: notification.title,
message: notification.body ? notification.body : '-', // Required
channel: 'net.cozic.joplin.notification',
small_icon: 'ic_launcher_foreground', // Android requires the icon to be transparent
color: 'blue',
small_icon: 'ic_notification',
data: {
joplinNotificationId: notification.id,
noteId: notification.noteId,

View File

@@ -1,3 +1,5 @@
// Currently disabled on iOS
type Vosk = any;
export { Vosk };

View File

@@ -13,6 +13,7 @@
"tools/*.ts",
],
"compilerOptions": {
"types": ["jest", "node"]
"types": ["jest", "node"],
"moduleSuffixes": [".ios", ".android", ".native", ""]
}
}

View File

@@ -840,13 +840,8 @@ export default class BaseApplication {
}
if (Setting.value('firstStart')) {
// If it's a sub-profile, the locale must come from the root
// profile.
if (!Setting.value('isSubProfile')) {
const locale = shim.detectAndSetLocale(Setting);
reg.logger().info(`First start: detected locale as ${locale}`);
}
const locale = shim.detectAndSetLocale(Setting);
reg.logger().info(`First start: detected locale as ${locale}`);
Setting.skipDefaultMigrations();
if (Setting.value('env') === 'dev') {

View File

@@ -45,12 +45,13 @@ export default class JoplinServerApi {
private options_: Options;
private session_: Session;
private debugRequests_: boolean = false;
private debugRequestsShowPasswords_: boolean = false;
public constructor(options: Options) {
this.options_ = options;
if (options.env === Env.Dev) {
// this.debugRequests_ = true;
if (options.env !== Env.Dev) {
this.debugRequestsShowPasswords_ = false;
}
}
@@ -97,15 +98,15 @@ export default class JoplinServerApi {
try {
const output = JSON.parse(o);
if (!output) return o;
if (output.password) output.password = '******';
if (output.password && !this.debugRequestsShowPasswords_) output.password = '******';
return JSON.stringify(output);
} catch (error) {
return o;
}
} else {
const output = { ...o };
if (output.password) output.password = '******';
if (output['X-API-AUTH']) output['X-API-AUTH'] = '******';
if (output.password && !this.debugRequestsShowPasswords_) output.password = '******';
if (output['X-API-AUTH'] && !this.debugRequestsShowPasswords_) output['X-API-AUTH'] = '******';
return output;
}
}

View File

@@ -1,80 +0,0 @@
const welcomeAssets = require('./welcomeAssets');
const Note = require('./models/Note').default;
const Setting = require('./models/Setting').default;
const Folder = require('./models/Folder').default;
const Tag = require('./models/Tag').default;
const shim = require('./shim').default;
const uuid = require('./uuid').default;
const { fileExtension, basename } = require('./path-utils');
const { pregQuote } = require('./string-utils');
class WelcomeUtils {
static async createWelcomeItems() {
const output = {
defaultFolderId: null,
};
const folderAssets = welcomeAssets.folders;
const tempDir = Setting.value('resourceDir');
for (let i = 0; i < folderAssets.length; i++) {
const folderAsset = folderAssets[i];
const folder = await Folder.save({ title: `${folderAsset.title} (${Setting.appTypeToLabel(Setting.value('appType'))})` });
if (!output.defaultFolderId) output.defaultFolderId = folder.id;
}
const noteAssets = welcomeAssets.notes;
for (let i = noteAssets.length - 1; i >= 0; i--) {
const noteAsset = noteAssets[i];
let noteBody = noteAsset.body;
for (const resourceUrl in noteAsset.resources) {
if (!noteAsset.resources.hasOwnProperty(resourceUrl)) continue;
const resourceAsset = noteAsset.resources[resourceUrl];
const ext = fileExtension(resourceUrl);
const tempFilePath = `${tempDir}/${uuid.create()}.tmp.${ext}`;
await shim.fsDriver().writeFile(tempFilePath, resourceAsset.body, 'base64');
const resource = await shim.createResourceFromPath(tempFilePath, {
title: basename(resourceUrl),
});
await shim.fsDriver().remove(tempFilePath);
const regex = new RegExp(pregQuote(`(${resourceUrl})`), 'g');
noteBody = noteBody.replace(regex, `(:/${resource.id})`);
}
const note = await Note.save({
parent_id: output.defaultFolderId,
title: noteAsset.title,
body: noteBody,
});
if (noteAsset.tags) await Tag.setNoteTagsByTitles(note.id, noteAsset.tags);
}
return output;
}
static async install(dispatch) {
if (!Setting.value('welcome.enabled')) {
Setting.setValue('welcome.wasBuilt', true);
return;
}
if (!Setting.value('welcome.wasBuilt')) {
const result = await WelcomeUtils.createWelcomeItems();
Setting.setValue('welcome.wasBuilt', true);
dispatch({
type: 'FOLDER_SELECT',
id: result.defaultFolderId,
});
Setting.setValue('activeFolderId', result.defaultFolderId);
}
}
}
module.exports = WelcomeUtils;

View File

@@ -0,0 +1,122 @@
const welcomeAssetsAny = require('./welcomeAssets');
import Note from './models/Note';
import Setting from './models/Setting';
import Folder from './models/Folder';
import shim from './shim';
import uuid from './uuid';
import { fileExtension, basename } from './path-utils';
import { _ } from './locale';
const { pregQuote } = require('./string-utils');
export interface ItemMetadatum {
id: string;
}
export type ItemMetadata = Record<string, ItemMetadatum>;
export interface CreateWelcomeItemsResult {
defaultFolderId: string;
}
export interface WelcomeAssetResource {
id: string;
body: string;
}
export interface WelcomeAssetNote {
id: string;
parent_id: string;
title: string;
body: string;
resources: Record<string, WelcomeAssetResource>;
}
export interface WelcomeAssetFolder {
id: string;
title: string;
}
export interface AssetContent {
notes: WelcomeAssetNote[];
folders: WelcomeAssetFolder[];
timestamp: number;
}
export type WelcomeAssets = Record<string, AssetContent>;
class WelcomeUtils {
public static async createWelcomeItems(locale: string): Promise<CreateWelcomeItemsResult> {
const output: CreateWelcomeItemsResult = {
defaultFolderId: null,
};
const allWelcomeAssets = welcomeAssetsAny as WelcomeAssets;
const welcomeAssets = locale in allWelcomeAssets ? allWelcomeAssets[locale] : allWelcomeAssets['en_GB'];
const enGbWelcomeAssets = allWelcomeAssets['en_GB'];
const folderAssets = welcomeAssets.folders;
const tempDir = Setting.value('resourceDir');
// Actually we don't really support mutiple folders at this point, because not needed
for (let i = 0; i < folderAssets.length; i++) {
const folder = await Folder.save({ title: _('Welcome!') });
if (!output.defaultFolderId) output.defaultFolderId = folder.id;
}
const noteAssets = welcomeAssets.notes;
for (let i = noteAssets.length - 1; i >= 0; i--) {
const noteAsset = noteAssets[i];
const enGbNoteAsset = enGbWelcomeAssets.notes[i];
let noteBody = noteAsset.body;
for (const resourceUrl in enGbNoteAsset.resources) {
if (!enGbNoteAsset.resources.hasOwnProperty(resourceUrl)) continue;
const resourceAsset = enGbNoteAsset.resources[resourceUrl];
const ext = fileExtension(resourceUrl);
const tempFilePath = `${tempDir}/${uuid.create()}.tmp.${ext}`;
await shim.fsDriver().writeFile(tempFilePath, resourceAsset.body, 'base64');
const resource = await shim.createResourceFromPath(tempFilePath, {
title: basename(resourceUrl),
});
await shim.fsDriver().remove(tempFilePath);
const regex = new RegExp(pregQuote(`(${resourceUrl})`), 'g');
noteBody = noteBody.replace(regex, `(:/${resource.id})`);
}
await Note.save({
parent_id: output.defaultFolderId,
title: noteAsset.title,
body: noteBody,
});
// if (noteAsset.tags) await Tag.setNoteTagsByTitles(note.id, noteAsset.tags);
}
return output;
}
public static async install(locale: string, dispatch: Function) {
if (!Setting.value('welcome.enabled')) {
Setting.setValue('welcome.wasBuilt', true);
return;
}
if (!Setting.value('welcome.wasBuilt')) {
const result = await WelcomeUtils.createWelcomeItems(locale);
Setting.setValue('welcome.wasBuilt', true);
dispatch({
type: 'FOLDER_SELECT',
id: result.defaultFolderId,
});
Setting.setValue('activeFolderId', result.defaultFolderId);
}
}
}
export default WelcomeUtils;

View File

@@ -75,7 +75,9 @@ shared.checkSyncConfigMessages = function(comp) {
return output;
};
shared.updateSettingValue = function(comp, key, value) {
shared.updateSettingValue = function(comp, key, value, callback = null) {
if (!callback) callback = () => {};
comp.setState(state => {
// @react-native-community/slider (4.4.0) will emit a valueChanged event
// when the component is mounted, even though the value hasn't changed.
@@ -99,7 +101,7 @@ shared.updateSettingValue = function(comp, key, value) {
settings: settings,
changedSettingKeys: changedSettingKeys,
};
});
}, callback);
};
shared.scheduleSaveSettings = function(comp) {

View File

@@ -175,6 +175,10 @@ export default class FileApiDriverJoplinServer {
// they can have names such as ".resources/xxxxxxxxxx'
}
private isRejectedBySyncTargetError(error: any) {
return error.code === 413 || error.code === 409 || error.httpCode === 413 || error.httpCode === 409;
}
public async put(path: string, content: any, options: any = null) {
try {
const output = await this.api().exec('PUT', `${this.apiFilePath_(path)}/content`, options && options.shareId ? { share_id: options.shareId } : null, content, {
@@ -182,7 +186,7 @@ export default class FileApiDriverJoplinServer {
}, options);
return output;
} catch (error) {
if (error.code === 413) {
if (this.isRejectedBySyncTargetError(error)) {
throw new JoplinError(error.message, 'rejectedByTarget');
}
throw error;
@@ -190,7 +194,15 @@ export default class FileApiDriverJoplinServer {
}
public async multiPut(items: MultiPutItem[], options: any = null) {
return this.api().exec('PUT', 'api/batch_items', null, { items: items }, null, options);
const output = await this.api().exec('PUT', 'api/batch_items', null, { items: items }, null, options);
for (const [, response] of Object.entries<any>(output.items)) {
if (response.error && this.isRejectedBySyncTargetError(response.error)) {
response.error.code = 'rejectedByTarget';
}
}
return output;
}
public async delete(path: string) {

View File

@@ -0,0 +1,18 @@
import { closestSupportedLocale } from './locale';
describe('locale', () => {
it('should find the closest matching locale', () => {
const testCases: [string, string[], string][] = [
['fr', ['fr_FR', 'en_GB'], 'fr_FR'],
['pt-br', ['fr_FR', 'en_GB', 'pt_BR'], 'pt_BR'],
['ro', ['fr_FR', 'en_GB', 'pt_BR'], 'en_GB'],
];
for (const [input, locales, expected] of testCases) {
const actual = closestSupportedLocale(input, true, locales);
expect(actual).toBe(expected);
}
});
});

View File

@@ -584,14 +584,7 @@ function localesFromLanguageCode(languageCode: string, locales: string[]): strin
}
function _(s: string, ...args: any[]): string {
const strings = localeStrings(currentLocale_);
let result = strings[s];
if (result === '' || result === undefined) result = s;
try {
return sprintf(result, ...args);
} catch (error) {
return `${result} ${args.join(', ')} (Translation error: ${error.message})`;
}
return stringByLocale(currentLocale_, s, ...args);
}
function _n(singular: string, plural: string, n: number, ...args: any[]) {
@@ -599,4 +592,15 @@ function _n(singular: string, plural: string, n: number, ...args: any[]) {
return _(singular, ...args);
}
const stringByLocale = (locale: string, s: string, ...args: any[]): string => {
const strings = localeStrings(locale);
let result = strings[s];
if (result === '' || result === undefined) result = s;
try {
return sprintf(result, ...args);
} catch (error) {
return `${result} ${args.join(', ')} (Translation error: ${error.message})`;
}
};
export { _, _n, supportedLocales, currentLocale, localesFromLanguageCode, languageCodeOnly, countryDisplayName, localeStrings, setLocale, supportedLocalesToLanguages, defaultLocale, closestSupportedLocale, languageCode, countryCodeOnly };

View File

@@ -1,4 +1,4 @@
import Setting, { SettingSectionSource, SettingStorage } from '../models/Setting';
import Setting, { SettingItemType, SettingSectionSource, SettingStorage } from '../models/Setting';
import { setupDatabaseAndSynchronizer, switchClient, expectThrow, expectNotThrow, msleep } from '../testing/test-utils';
import { readFile, stat, mkdirp, writeFile, pathExists, readdir } from 'fs-extra';
import Logger from '../Logger';
@@ -61,11 +61,11 @@ describe('models/Setting', () => {
await Setting.reset();
await expectNotThrow(async () => Setting.load());
await expectThrow(async () => Setting.value('itsgone'));
await expectThrow(async () => Setting.value('itsgone'), 'unknown_key');
}));
it('should allow registering new settings dynamically', (async () => {
await expectThrow(async () => Setting.setValue('myCustom', '123'));
await expectThrow(async () => Setting.setValue('myCustom', '123'), 'unknown_key');
await Setting.registerSetting('myCustom', {
public: true,
@@ -297,12 +297,21 @@ describe('models/Setting', () => {
expect(Setting.isSet('spellChecker.languages')).toBe(false);
}));
it('should load sub-profile settings - 1', async () => {
it('should load sub-profile settings', async () => {
await Setting.reset();
await Setting.registerSetting('non_builtin', {
public: true,
storage: SettingStorage.File,
isGlobal: true,
type: SettingItemType.Bool,
value: false,
});
Setting.setValue('locale', 'fr_FR'); // Global setting
Setting.setValue('theme', Setting.THEME_DARK); // Global setting
Setting.setValue('sync.target', 9); // Local setting
Setting.setValue('non_builtin', true); // Local setting
await Setting.saveAll();
await switchToSubProfileSettings();
@@ -311,6 +320,9 @@ describe('models/Setting', () => {
expect(Setting.value('theme')).toBe(Setting.THEME_DARK); // Should come from the root profile
expect(Setting.value('sync.target')).toBe(0); // Should come from the local profile
// Non-built-in variables are not copied
expect(() => Setting.value('non_builtin')).toThrow();
// Also check that the special loadOne() function works as expected
expect((await Setting.loadOne('locale')).value).toBe('fr_FR');
@@ -318,7 +330,7 @@ describe('models/Setting', () => {
expect((await Setting.loadOne('sync.target')).value).toBe(undefined);
});
it('should save sub-profile settings - 2', async () => {
it('should save sub-profile settings', async () => {
await Setting.reset();
Setting.setValue('locale', 'fr_FR'); // Global setting
Setting.setValue('theme', Setting.THEME_DARK); // Global setting

View File

@@ -9,6 +9,7 @@ import FileHandler, { SettingValues } from './settings/FileHandler';
import Logger from '../Logger';
import mergeGlobalAndLocalSettings from '../services/profileConfig/mergeGlobalAndLocalSettings';
import splitGlobalAndLocalSettings from '../services/profileConfig/splitGlobalAndLocalSettings';
import JoplinError from '../JoplinError';
const { sprintf } = require('sprintf-js');
const ObjectUtils = require('../ObjectUtils');
const { toTitleCase } = require('../string-utils.js');
@@ -312,6 +313,7 @@ class Setting extends BaseModel {
private static fileHandler_: FileHandler = null;
private static rootFileHandler_: FileHandler = null;
private static settingFilename_: string = 'settings.json';
private static buildInMetadata_: SettingItems = null;
public static tableName() {
return 'settings';
@@ -406,7 +408,7 @@ class Setting extends BaseModel {
return output;
};
this.metadata_ = {
this.buildInMetadata_ = {
'clientId': {
value: '',
type: SettingItemType.String,
@@ -1696,6 +1698,8 @@ class Setting extends BaseModel {
};
this.metadata_ = { ...this.buildInMetadata_ };
this.metadata_ = Object.assign(this.metadata_, this.customMetadata_);
if (this.constants_.env === Env.Dev) this.validateMetadata(this.metadata_);
@@ -1709,6 +1713,10 @@ class Setting extends BaseModel {
}
}
public static isBuiltinKey(key: string): boolean {
return key in this.buildInMetadata_;
}
public static customCssFilePath(filename: string): string {
return `${this.value('rootProfileDir')}/${filename}`;
}
@@ -1810,7 +1818,7 @@ class Setting extends BaseModel {
public static settingMetadata(key: string): SettingItem {
const metadata = this.metadata();
if (!(key in metadata)) throw new Error(`Unknown key: ${key}`);
if (!(key in metadata)) throw new JoplinError(`Unknown key: ${key}`, 'unknown_key');
const output = Object.assign({}, metadata[key]);
output.key = key;
return output;
@@ -2255,7 +2263,7 @@ class Setting extends BaseModel {
public static enumOptions(key: string) {
const metadata = this.metadata();
if (!metadata[key]) throw new Error(`Unknown key: ${key}`);
if (!metadata[key]) throw new JoplinError(`Unknown key: ${key}`, 'unknown_key');
if (!metadata[key].options) throw new Error(`No options for: ${key}`);
return metadata[key].options();
}

View File

@@ -2,19 +2,29 @@ import shim from './shim';
import time from './time';
const ntpClient_ = require('./vendor/ntp-client');
const server = {
domain: 'pool.ntp.org',
port: 123,
};
interface NtpServer {
domain: string;
port: number;
}
function ntpClient() {
ntpClient_.dgram = shim.dgram();
return ntpClient_;
}
export async function getNetworkTime(): Promise<Date> {
const parseNtpServer = (ntpServer: string): NtpServer => {
const s = ntpServer.split(':');
if (s.length !== 2) throw new Error('NTP server URL must be in format `domain:port`');
return {
domain: s[0],
port: Number(s[1]),
};
};
export async function getNetworkTime(ntpServer: string): Promise<Date> {
return new Promise((resolve: Function, reject: Function) => {
ntpClient().getNetworkTime(server.domain, server.port, (error: any, date: Date) => {
const s = parseNtpServer(ntpServer);
ntpClient().getNetworkTime(s.domain, s.port, (error: any, date: Date) => {
if (error) {
reject(error);
return;
@@ -25,7 +35,7 @@ export async function getNetworkTime(): Promise<Date> {
});
}
export async function getDeviceTimeDrift(): Promise<number> {
export async function getDeviceTimeDrift(ntpServer: string): Promise<number> {
const maxTries = 3;
let tryCount = 0;
@@ -34,12 +44,12 @@ export async function getDeviceTimeDrift(): Promise<number> {
while (true) {
tryCount++;
try {
ntpTime = await getNetworkTime();
ntpTime = await getNetworkTime(ntpServer);
break;
} catch (error) {
if (tryCount >= maxTries) {
const newError = typeof error === 'string' ? new Error(error) : error;
newError.message = `Cannot retrieve the network time from ${server.domain}:${server.port}: ${newError.message}`;
newError.message = `Cannot retrieve the network time from ${ntpServer}: ${newError.message}`;
throw newError;
} else {
await time.msleep(tryCount * 1000);

View File

@@ -25,7 +25,7 @@
"@types/uuid": "^9.0.0",
"clean-html": "1.5.0",
"jest": "29.4.3",
"sharp": "0.32.0",
"sharp": "0.32.1",
"typescript": "4.9.4"
},
"dependencies": {
@@ -86,7 +86,7 @@
"sqlite3": "5.1.6",
"string-padding": "1.0.2",
"string-to-stream": "3.0.1",
"tar": "6.1.13",
"tar": "6.1.14",
"tcp-port-used": "1.0.2",
"uglifycss": "0.0.29",
"url-parse": "1.5.10",

View File

@@ -1,6 +1,6 @@
import { afterAllCleanUp, setupDatabaseAndSynchronizer, switchClient, encryptionService, expectNotThrow, expectThrow, kvStore } from '../../testing/test-utils';
import { afterAllCleanUp, setupDatabaseAndSynchronizer, switchClient, encryptionService, expectNotThrow, expectThrow, kvStore, msleep } from '../../testing/test-utils';
import MasterKey from '../../models/MasterKey';
import { migrateMasterPassword, resetMasterPassword, showMissingMasterKeyMessage, updateMasterPassword } from './utils';
import { activeMasterKeySanityCheck, migrateMasterPassword, resetMasterPassword, showMissingMasterKeyMessage, updateMasterPassword } from './utils';
import { localSyncInfo, masterKeyById, masterKeyEnabled, setActiveMasterKeyId, setMasterKeyEnabled, setPpk } from '../synchronizer/syncInfoUtils';
import Setting from '../../models/Setting';
import { generateKeyPair, ppkPasswordIsValid } from './ppk';
@@ -145,6 +145,43 @@ describe('e2ee/utils', () => {
expect(localSyncInfo().ppk.id).not.toBe(previousPpk.id);
expect(localSyncInfo().ppk.privateKey.ciphertext).not.toBe(previousPpk.privateKey.ciphertext);
expect(localSyncInfo().ppk.publicKey).not.toBe(previousPpk.publicKey);
// Also check that a new master key has been created, that it is active and enabled
expect(localSyncInfo().masterKeys.length).toBe(3);
expect(localSyncInfo().activeMasterKeyId).toBe(localSyncInfo().masterKeys[2].id);
expect(masterKeyEnabled(localSyncInfo().masterKeys[2])).toBe(true);
});
it('should fix active key selection issues - 1', async () => {
const masterPassword1 = '111111';
Setting.setValue('encryption.masterPassword', masterPassword1);
const mk1 = await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));
await msleep(1);
await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));
await msleep(1);
const mk3 = await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));
setActiveMasterKeyId(mk1.id);
setMasterKeyEnabled(mk1.id, false);
activeMasterKeySanityCheck();
const syncInfo = localSyncInfo();
expect(syncInfo.activeMasterKeyId).toBe(mk3.id);
});
it('should fix active key selection issues - 2', async () => {
// Should not do anything if the active key is already an enabled one.
const masterPassword1 = '111111';
Setting.setValue('encryption.masterPassword', masterPassword1);
const mk1 = await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));
await msleep(1);
await MasterKey.save(await encryptionService().generateMasterKey(masterPassword1));
setActiveMasterKeyId(mk1.id);
activeMasterKeySanityCheck();
const syncInfo = localSyncInfo();
expect(syncInfo.activeMasterKeyId).toBe(mk1.id);
});
});

View File

@@ -4,7 +4,7 @@ import MasterKey from '../../models/MasterKey';
import Setting from '../../models/Setting';
import { MasterKeyEntity } from './types';
import EncryptionService from './EncryptionService';
import { getActiveMasterKey, getActiveMasterKeyId, localSyncInfo, masterKeyEnabled, saveLocalSyncInfo, setEncryptionEnabled, SyncInfo } from '../synchronizer/syncInfoUtils';
import { getActiveMasterKey, getActiveMasterKeyId, localSyncInfo, masterKeyEnabled, saveLocalSyncInfo, setActiveMasterKeyId, setEncryptionEnabled, SyncInfo } from '../synchronizer/syncInfoUtils';
import JoplinError from '../../JoplinError';
import { generateKeyPair, pkReencryptPrivateKey, ppkPasswordIsValid } from './ppk';
import KvStore from '../KvStore';
@@ -131,6 +131,8 @@ export async function findMasterKeyPassword(service: EncryptionService, masterKe
}
export async function loadMasterKeysFromSettings(service: EncryptionService) {
activeMasterKeySanityCheck();
const masterKeys = await MasterKey.all();
const activeMasterKeyId = getActiveMasterKeyId();
@@ -153,6 +155,35 @@ export async function loadMasterKeysFromSettings(service: EncryptionService) {
logger.info(`Loaded master keys: ${service.loadedMasterKeysCount()}`);
}
// In some rare cases (normally should no longer be possible), a disabled master
// key end up being the active one (the one used to encrypt data). This sanity
// check resolves this by making an enabled key the active one.
export const activeMasterKeySanityCheck = () => {
const syncInfo = localSyncInfo();
const activeMasterKeyId = syncInfo.activeMasterKeyId;
const enabledMasterKeys = syncInfo.masterKeys.filter(mk => masterKeyEnabled(mk));
if (!enabledMasterKeys.length) return;
if (enabledMasterKeys.find(mk => mk.id === activeMasterKeyId)) {
logger.info('activeMasterKeySanityCheck: Active key is an enabled key - nothing to do');
return;
}
logger.info('activeMasterKeySanityCheck: Active key is **not** an enabled key - selecting a different key as the active key...');
const latestMasterKey = enabledMasterKeys.reduce((acc: MasterKeyEntity, current: MasterKeyEntity) => {
if (current.created_time > acc.created_time) {
return current;
} else {
return acc;
}
});
logger.info('activeMasterKeySanityCheck: Selected new active key:', latestMasterKey);
setActiveMasterKeyId(latestMasterKey.id);
};
export function showMissingMasterKeyMessage(syncInfo: SyncInfo, notLoadedMasterKeys: string[]) {
if (!syncInfo.masterKeys.length) return false;
@@ -280,6 +311,10 @@ export async function resetMasterPassword(encryptionService: EncryptionService,
}
Setting.setValue('encryption.masterPassword', newPassword);
const masterKey = await encryptionService.generateMasterKey(newPassword);
await MasterKey.save(masterKey);
await loadMasterKeysFromSettings(encryptionService);
}
export enum MasterPasswordStatus {

View File

@@ -1,20 +1,52 @@
import Logger from '../../Logger';
import Setting from '../../models/Setting';
const logger = Logger.create('mergeGlobalAndLocalSettings');
export default (rootSettings: Record<string, any>, subProfileSettings: Record<string, any>) => {
const output: Record<string, any> = { ...subProfileSettings };
for (const k of Object.keys(output)) {
const md = Setting.settingMetadata(k);
if (md.isGlobal) {
delete output[k];
if (k in rootSettings) output[k] = rootSettings[k];
try {
const md = Setting.settingMetadata(k);
if (md.isGlobal) {
delete output[k];
if (k in rootSettings) output[k] = rootSettings[k];
}
} catch (error) {
if (error.code === 'unknown_key') {
// The root settings may contain plugin parameters, but the
// sub-profile won't necessarily have these plugins. In that
// case, the app will throw an error, but we can ignore it since
// we don't need this particular setting.
// https://github.com/laurent22/joplin/issues/8143
logger.info(`Ignoring unknown key in root settings: ${k}`);
}
}
}
for (const k of Object.keys(rootSettings)) {
const md = Setting.settingMetadata(k);
if (md.isGlobal) {
output[k] = rootSettings[k];
// We only copy built-in key and not, for example, plugin keys, because
// those are plugin-specific
if (!Setting.isBuiltinKey(k)) {
logger.info(`Skipping non-built-in key: ${k}`);
continue;
}
try {
const md = Setting.settingMetadata(k);
if (md.isGlobal) {
output[k] = rootSettings[k];
}
} catch (error) {
if (error.code === 'unknown_key') {
// The root settings may contain plugin parameters, but the
// sub-profile won't necessarily have these plugins. In that
// case, the app will throw an error, but we can ignore it since
// we don't need this particular setting.
// https://github.com/laurent22/joplin/issues/8143
logger.info(`Ignoring unknown key in root settings: ${k}`);
}
}
}

View File

@@ -159,7 +159,7 @@ describe('synchronizer/ItemUploader', () => {
await itemUploader.preUploadItems(notes);
await expectNotThrow(async () => itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[0]), notes[0]));
await expectThrow(async () => itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[1]), notes[1]));
await expectThrow(async () => itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[1]), notes[1]), null);
await expectNotThrow(async () => itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[2]), notes[2]));
}));

View File

@@ -1,5 +1,6 @@
import { ModelType } from '../../BaseModel';
import { FileApi, MultiPutItem } from '../../file-api';
import JoplinError from '../../JoplinError';
import Logger from '../../Logger';
import BaseItem from '../../models/BaseItem';
import { BaseItemEntity } from '../database/types';
@@ -45,7 +46,8 @@ export default class ItemUploader {
// the regular upload.
logger.warn(`Pre-uploaded item updated_time has changed. It is going to be re-uploaded again: ${path} (From ${this.preUploadedItemUpdatedTimes_[path]} to ${local.updated_time})`);
} else {
if (preUploadItem.error) throw new Error(preUploadItem.error.message ? preUploadItem.error.message : 'Unknown pre-upload error');
const error = preUploadItem.error;
if (error) throw new JoplinError(error.message ? error.message : 'Unknown pre-upload error', error.code);
return;
}
}

View File

@@ -4,7 +4,7 @@ import { syncTargetName, afterAllCleanUp, synchronizerStart, setupDatabaseAndSyn
import Folder from '../../models/Folder';
import Note from '../../models/Note';
import BaseItem from '../../models/BaseItem';
const WelcomeUtils = require('../../WelcomeUtils');
import WelcomeUtils from '../../WelcomeUtils';
describe('Synchronizer.basics', () => {
@@ -332,12 +332,12 @@ describe('Synchronizer.basics', () => {
it('should create a new Welcome notebook on each client', (async () => {
// Create the Welcome items on two separate clients
await WelcomeUtils.createWelcomeItems();
await WelcomeUtils.createWelcomeItems('en_GB');
await synchronizerStart();
await switchClient(2);
await WelcomeUtils.createWelcomeItems();
await WelcomeUtils.createWelcomeItems('en_GB');
const beforeFolderCount = (await Folder.all()).length;
const beforeNoteCount = (await Note.all()).length;
expect(beforeFolderCount === 1).toBe(true);

View File

@@ -120,4 +120,38 @@ describe('syncInfoUtils', () => {
expect(mergeSyncInfos(syncInfo1, syncInfo2).activeMasterKeyId).toBe('2');
});
it('should merge sync target info, but should not make a disabled key the active one', async () => {
const syncInfo1 = new SyncInfo();
syncInfo1.masterKeys = [{
id: '1',
content: 'content1',
hasBeenUsed: true,
enabled: 0,
}];
syncInfo1.activeMasterKeyId = '1';
await msleep(1);
const syncInfo2 = new SyncInfo();
syncInfo2.masterKeys = [{
id: '2',
content: 'content2',
enabled: 1,
hasBeenUsed: false,
}];
syncInfo2.activeMasterKeyId = '2';
// Normally, if one master key has been used (1) and the other not (2),
// it should select the one that's been used regardless of timestamps.
// **However**, if the key 1 has been disabled by user, it should
// **not** be picked as the active one. Instead it should use key 2,
// because it's still enabled.
expect(mergeSyncInfos(syncInfo1, syncInfo2).activeMasterKeyId).toBe('2');
// If both key are disabled, we go back to the original logic, where we
// select the key that's been used.
syncInfo2.masterKeys[0].enabled = 0;
expect(mergeSyncInfos(syncInfo1, syncInfo2).activeMasterKeyId).toBe('1');
});
});

View File

@@ -115,13 +115,20 @@ export function localSyncInfoFromState(state: State): SyncInfo {
// has already been used to encrypt data. In this case, at the moment we compare
// local and remote sync info (before synchronising the data), key1.hasBeenUsed
// is true, but key2.hasBeenUsed is false.
//
// 2023-05-30: Additionally, if one key is enabled and the other is not, we
// always pick the enabled one regardless of usage.
const mergeActiveMasterKeys = (s1: SyncInfo, s2: SyncInfo, output: SyncInfo) => {
const activeMasterKey1 = getActiveMasterKey(s1);
const activeMasterKey2 = getActiveMasterKey(s2);
let doDefaultAction = false;
if (activeMasterKey1 && activeMasterKey2) {
if (activeMasterKey1.hasBeenUsed && !activeMasterKey2.hasBeenUsed) {
if (masterKeyEnabled(activeMasterKey1) && !masterKeyEnabled(activeMasterKey2)) {
output.setWithTimestamp(s1, 'activeMasterKeyId');
} else if (!masterKeyEnabled(activeMasterKey1) && masterKeyEnabled(activeMasterKey2)) {
output.setWithTimestamp(s2, 'activeMasterKeyId');
} else if (activeMasterKey1.hasBeenUsed && !activeMasterKey2.hasBeenUsed) {
output.setWithTimestamp(s1, 'activeMasterKeyId');
} else if (!activeMasterKey1.hasBeenUsed && activeMasterKey2.hasBeenUsed) {
output.setWithTimestamp(s2, 'activeMasterKeyId');

View File

@@ -139,7 +139,7 @@ function shimInit(options = null) {
};
shim.detectAndSetLocale = function(Setting) {
let locale = process.env.LANG;
let locale = shim.isElectron() ? shim.electronBridge().getLocale() : process.env.LANG;
if (!locale) locale = defaultLocale();
locale = locale.split('.');
locale = locale[0];

View File

@@ -24,9 +24,9 @@ describe('checkProviderIsSupported', () => {
expect(() => checkProviderIsSupported('https://api.pcloud.com')).toThrowError('The WebDAV implementation of pcloud is incompatible with Joplin, and as such is no longer supported. Please use a different sync method.');
expect(() => checkProviderIsSupported('https://api-pcloud-test.com')).toThrowError('The WebDAV implementation of pcloud is incompatible with Joplin, and as such is no longer supported. Please use a different sync method.');
// expect(() => checkProviderIsSupported('https://api-pcloud-test.com')).toThrowError('The WebDAV implementation of pcloud is incompatible with Joplin, and as such is no longer supported. Please use a different sync method.');
});
expect(() => checkProviderIsSupported('?param=pcloud')).toThrowError('The WebDAV implementation of pcloud is incompatible with Joplin, and as such is no longer supported. Please use a different sync method.');
// expect(() => checkProviderIsSupported('?param=pcloud')).toThrowError('The WebDAV implementation of pcloud is incompatible with Joplin, and as such is no longer supported. Please use a different sync method.');
});
describe('when an unsupported provider is already configured', () => {

View File

@@ -1,18 +1,30 @@
import { _ } from '../locale';
import Setting from '../models/Setting';
import { URL } from 'url';
const pathContainsUnsupportedProvider = (path: string, unsupportedProviders: string[]) => {
try {
const url = new URL(path.toLowerCase());
const splitted = url.host.split('.');
for (const s of splitted) {
if (unsupportedProviders.includes(s)) return true;
}
return false;
} catch (error) {
// The URL is probably invalid, but it's not here that we should handle
// this.
return false;
}
};
export const checkProviderIsSupported = (path: string): void => {
if (Setting.value('sync.allowUnsupportedProviders') === 1) return;
const unsupportedProviders = ['pcloud', 'jianguoyun'];
for (const p of unsupportedProviders) {
// For a provider named abc, this regex will match the provider's name if enclosed by either '/', '.', '-', '=' or end of string.
// E.g: https://abc.com, https://api.abc.com, https://api-abc-test.com, https://api/test?param=abc
//
// It will not match a provider which name happens to contain an unsupported provider (i.e a substring).
// E.g: https://fooabc.com
const pattern = `(?<=[-/.=])${p}(?=[-/.=]|$)`;
if (path.search(new RegExp(pattern)) !== -1) {
if (pathContainsUnsupportedProvider(path, unsupportedProviders)) {
throw new Error(_('The WebDAV implementation of %s is incompatible with Joplin, and as such is no longer supported. Please use a different sync method.', p));
}
}

File diff suppressed because one or more lines are too long

View File

@@ -44,6 +44,6 @@
"pdfjs-dist": "2.16.105",
"react": "18.2.0",
"react-dom": "18.2.0",
"styled-components": "5.3.9"
"styled-components": "5.3.10"
}
}

View File

@@ -25,7 +25,7 @@
"gh-release-assets": "2.0.1",
"node-fetch": "2.6.7",
"source-map-support": "0.5.21",
"yargs": "17.7.1"
"yargs": "17.7.2"
},
"devDependencies": {
"@types/fs-extra": "9.0.13",

View File

@@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.emekalites.react.alarm.notification">
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

View File

@@ -369,7 +369,7 @@ public class AlarmModel implements Serializable {
alarm.setActive(1);
alarm.setAutoCancel(bundle.getBoolean("auto_cancel", true));
alarm.setChannel(bundle.getString("channel", "my_channel_id"));
alarm.setColor(bundle.getString("color", "red"));
alarm.setColor(bundle.getString("color", ""));
Bundle data = bundle.getBundle("data");
alarm.setData(data);
@@ -380,7 +380,7 @@ public class AlarmModel implements Serializable {
alarm.setMessage(bundle.getString("message", "My Notification Message"));
alarm.setPlaySound(bundle.getBoolean("play_sound", true));
alarm.setScheduleType(bundle.getString("schedule_type", "once"));
alarm.setSmallIcon(bundle.getString("small_icon", "ic_launcher"));
alarm.setSmallIcon(bundle.getString("small_icon", "ic_notification"));
alarm.setSnoozeInterval((int) bundle.getDouble("snooze_interval", 1.0));
alarm.setSoundName(bundle.getString("sound_name", null));
alarm.setSoundNames(bundle.getString("sound_names", null));

View File

@@ -9,6 +9,7 @@ import android.util.Log;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import java.util.ArrayList;
import java.util.List;
public class AlarmReceiver extends BroadcastReceiver {
@@ -27,7 +28,7 @@ public class AlarmReceiver extends BroadcastReceiver {
alarmUtil.sendNotification(alarm);
alarmUtil.setBootReceiver();
ArrayList<AlarmModel> alarms = alarmDB.getAlarmList(1);
List<AlarmModel> alarms = alarmDB.getAlarmList(1);
Log.d(Constants.TAG, "alarm start: " + alarm.toString() + ", alarms left: " + alarms.size());
} catch (Exception e) {
Log.e(Constants.TAG, "Failed to add alarm", e);

Some files were not shown because too many files have changed in this diff Show More