You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-30 20:39:46 +02:00
Compare commits
124 Commits
publish_no
...
android-v2
Author | SHA1 | Date | |
---|---|---|---|
|
f4e6948065 | ||
|
cb6cf88471 | ||
|
7992fe5b63 | ||
|
3dd008ae9a | ||
|
99a61f1283 | ||
|
1cf121b52c | ||
|
3a7d6cd520 | ||
|
54085a960f | ||
|
3e8707d9ca | ||
|
83a1a42f92 | ||
|
996c98f0b3 | ||
|
56229d640b | ||
|
33b262cd22 | ||
|
fd445773ce | ||
|
3c24c4cd0b | ||
|
20c62e0353 | ||
|
3b9a730985 | ||
|
7183f79b28 | ||
|
8cb006cfd9 | ||
|
ae178016ab | ||
|
4779891154 | ||
|
db4c6eaa6d | ||
|
5b80fbc543 | ||
|
ea6b7caaf3 | ||
|
5d31c087b0 | ||
|
63afbb7346 | ||
|
f330e08a35 | ||
|
42a713288a | ||
|
a228f7ac58 | ||
|
6c647144e2 | ||
|
0c30198e8c | ||
|
150f0485c0 | ||
|
5324f39561 | ||
|
7129c0c14e | ||
|
1379c9c706 | ||
|
cb4cf92206 | ||
|
35ce87303f | ||
|
bf92ee7c44 | ||
|
1cfbefb76a | ||
|
8ac8d537c8 | ||
|
8ea6d89d49 | ||
|
59a7dd6673 | ||
|
c9ff0a3cd1 | ||
|
5cf83f5641 | ||
|
9bccf787a5 | ||
|
fa67b7193c | ||
|
0fe34d5178 | ||
|
bec97d6a42 | ||
|
1fe6910089 | ||
|
0d5f96f5bb | ||
|
5fd5be1e09 | ||
|
d3fa806d64 | ||
|
633c9acd49 | ||
|
c13c19b78c | ||
|
116e2fc92e | ||
|
713c00053e | ||
|
6a0700e335 | ||
|
7961acd06f | ||
|
e9f7f106f1 | ||
|
5e944df595 | ||
|
6f6f427356 | ||
|
cac10c4e29 | ||
|
9b348fdc29 | ||
|
ec97dd8c60 | ||
|
f28c1bc6ba | ||
|
e660fafb7a | ||
|
2c49270f38 | ||
|
13c1ae3d39 | ||
|
29550ade49 | ||
|
1b9f74f674 | ||
|
0b69ae371c | ||
|
37ebd21cb3 | ||
|
c996ddaf9d | ||
|
cea1aeac4b | ||
|
13ee1c89ea | ||
|
f01ec941b7 | ||
|
0853521bc9 | ||
|
e484671a08 | ||
|
50253d00e7 | ||
|
5364965a69 | ||
|
50baad3c04 | ||
|
cf219762c9 | ||
|
9e27b0881f | ||
|
44a96f347a | ||
|
cc6620a7e1 | ||
|
29f1abb666 | ||
|
9781a33419 | ||
|
0954794195 | ||
|
a996375b88 | ||
|
129ac1829d | ||
|
44e60bdda9 | ||
|
afc34b44c8 | ||
|
e08c74ae08 | ||
|
e5c669dc7a | ||
|
f4a7f5914e | ||
|
62eee4df56 | ||
|
c16445bc2f | ||
|
e05c5598a0 | ||
|
66c9ee0a1a | ||
|
d07788607c | ||
|
907dc7601b | ||
|
4b9adcde04 | ||
|
9f3a4e0d99 | ||
|
ea14488dc3 | ||
|
f59d29f1c5 | ||
|
0a9e919ac7 | ||
|
f11b6e8fa9 | ||
|
167560ff6f | ||
|
4b4e316bf0 | ||
|
7809228bd3 | ||
|
540fbbc22c | ||
|
2983d4f1a3 | ||
|
f6a8bf9ea2 | ||
|
e3ba02281b | ||
|
295b310079 | ||
|
62346575f8 | ||
|
0a590b7de9 | ||
|
dfd95f8385 | ||
|
6efe8c171a | ||
|
a7cdcaf25f | ||
|
6277958d6a | ||
|
24b4b879f2 | ||
|
86fbf82d36 | ||
|
96982849ce |
@@ -78,6 +78,12 @@ readme/
|
||||
packages/app-cli/app/LinkSelector.d.ts
|
||||
packages/app-cli/app/LinkSelector.js
|
||||
packages/app-cli/app/LinkSelector.js.map
|
||||
packages/app-cli/app/base-command.d.ts
|
||||
packages/app-cli/app/base-command.js
|
||||
packages/app-cli/app/base-command.js.map
|
||||
packages/app-cli/app/command-done.test.d.ts
|
||||
packages/app-cli/app/command-done.test.js
|
||||
packages/app-cli/app/command-done.test.js.map
|
||||
packages/app-cli/app/command-e2ee.d.ts
|
||||
packages/app-cli/app/command-e2ee.js
|
||||
packages/app-cli/app/command-e2ee.js.map
|
||||
@@ -93,6 +99,12 @@ packages/app-cli/app/command-testing.js.map
|
||||
packages/app-cli/app/services/plugins/PluginRunner.d.ts
|
||||
packages/app-cli/app/services/plugins/PluginRunner.js
|
||||
packages/app-cli/app/services/plugins/PluginRunner.js.map
|
||||
packages/app-cli/app/setupCommand.d.ts
|
||||
packages/app-cli/app/setupCommand.js
|
||||
packages/app-cli/app/setupCommand.js.map
|
||||
packages/app-cli/app/utils/testUtils.d.ts
|
||||
packages/app-cli/app/utils/testUtils.js
|
||||
packages/app-cli/app/utils/testUtils.js.map
|
||||
packages/app-cli/tests/HtmlToMd.d.ts
|
||||
packages/app-cli/tests/HtmlToMd.js
|
||||
packages/app-cli/tests/HtmlToMd.js.map
|
||||
@@ -252,6 +264,9 @@ packages/app-desktop/gui/EditFolderDialog/Dialog.js.map
|
||||
packages/app-desktop/gui/EditFolderDialog/IconSelector.d.ts
|
||||
packages/app-desktop/gui/EditFolderDialog/IconSelector.js
|
||||
packages/app-desktop/gui/EditFolderDialog/IconSelector.js.map
|
||||
packages/app-desktop/gui/EmojiBox.d.ts
|
||||
packages/app-desktop/gui/EmojiBox.js
|
||||
packages/app-desktop/gui/EmojiBox.js.map
|
||||
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.d.ts
|
||||
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js
|
||||
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js.map
|
||||
@@ -333,6 +348,9 @@ packages/app-desktop/gui/MainScreen/commands/openItem.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/openNote.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/openNote.js
|
||||
packages/app-desktop/gui/MainScreen/commands/openNote.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/openPdfViewer.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/openPdfViewer.js
|
||||
packages/app-desktop/gui/MainScreen/commands/openPdfViewer.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/openTag.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/openTag.js
|
||||
packages/app-desktop/gui/MainScreen/commands/openTag.js.map
|
||||
@@ -597,6 +615,9 @@ packages/app-desktop/gui/OneDriveLoginScreen.js.map
|
||||
packages/app-desktop/gui/PasswordInput/PasswordInput.d.ts
|
||||
packages/app-desktop/gui/PasswordInput/PasswordInput.js
|
||||
packages/app-desktop/gui/PasswordInput/PasswordInput.js.map
|
||||
packages/app-desktop/gui/PdfViewer.d.ts
|
||||
packages/app-desktop/gui/PdfViewer.js
|
||||
packages/app-desktop/gui/PdfViewer.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/MoveButtons.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/MoveButtons.js
|
||||
packages/app-desktop/gui/ResizableLayout/MoveButtons.js.map
|
||||
@@ -921,6 +942,9 @@ packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.js.map
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleOverflowButton.d.ts
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleOverflowButton.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleOverflowButton.js.map
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleSpaceButton.d.ts
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleSpaceButton.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleSpaceButton.js.map
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/Toolbar.d.ts
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/Toolbar.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/Toolbar.js.map
|
||||
@@ -972,6 +996,9 @@ packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
|
||||
packages/app-mobile/components/screens/encryption-config.d.ts
|
||||
packages/app-mobile/components/screens/encryption-config.js
|
||||
packages/app-mobile/components/screens/encryption-config.js.map
|
||||
packages/app-mobile/components/side-menu-content.d.ts
|
||||
packages/app-mobile/components/side-menu-content.js
|
||||
packages/app-mobile/components/side-menu-content.js.map
|
||||
packages/app-mobile/gulpfile.d.ts
|
||||
packages/app-mobile/gulpfile.js
|
||||
packages/app-mobile/gulpfile.js.map
|
||||
@@ -1005,6 +1032,9 @@ packages/app-mobile/utils/TlsUtils.js.map
|
||||
packages/app-mobile/utils/checkPermissions.d.ts
|
||||
packages/app-mobile/utils/checkPermissions.js
|
||||
packages/app-mobile/utils/checkPermissions.js.map
|
||||
packages/app-mobile/utils/debounce.d.ts
|
||||
packages/app-mobile/utils/debounce.js
|
||||
packages/app-mobile/utils/debounce.js.map
|
||||
packages/app-mobile/utils/fs-driver-rn.d.ts
|
||||
packages/app-mobile/utils/fs-driver-rn.js
|
||||
packages/app-mobile/utils/fs-driver-rn.js.map
|
||||
@@ -1014,6 +1044,9 @@ packages/app-mobile/utils/setupNotifications.js.map
|
||||
packages/app-mobile/utils/shareHandler.d.ts
|
||||
packages/app-mobile/utils/shareHandler.js
|
||||
packages/app-mobile/utils/shareHandler.js.map
|
||||
packages/app-mobile/utils/types.d.ts
|
||||
packages/app-mobile/utils/types.js
|
||||
packages/app-mobile/utils/types.js.map
|
||||
packages/fork-htmlparser2/src/CollectingHandler.d.ts
|
||||
packages/fork-htmlparser2/src/CollectingHandler.js
|
||||
packages/fork-htmlparser2/src/CollectingHandler.js.map
|
||||
@@ -2001,6 +2034,9 @@ packages/lib/uuid.js.map
|
||||
packages/lib/versionInfo.d.ts
|
||||
packages/lib/versionInfo.js
|
||||
packages/lib/versionInfo.js.map
|
||||
packages/pdf-viewer/FullViewer.d.ts
|
||||
packages/pdf-viewer/FullViewer.js
|
||||
packages/pdf-viewer/FullViewer.js.map
|
||||
packages/pdf-viewer/Page.d.ts
|
||||
packages/pdf-viewer/Page.js
|
||||
packages/pdf-viewer/Page.js.map
|
||||
@@ -2025,9 +2061,15 @@ packages/pdf-viewer/hooks/useScaledSize.js.map
|
||||
packages/pdf-viewer/hooks/useScrollSaver.d.ts
|
||||
packages/pdf-viewer/hooks/useScrollSaver.js
|
||||
packages/pdf-viewer/hooks/useScrollSaver.js.map
|
||||
packages/pdf-viewer/hooks/useVisibleOnSelect.d.ts
|
||||
packages/pdf-viewer/hooks/useVisibleOnSelect.js
|
||||
packages/pdf-viewer/hooks/useVisibleOnSelect.js.map
|
||||
packages/pdf-viewer/main.d.ts
|
||||
packages/pdf-viewer/main.js
|
||||
packages/pdf-viewer/main.js.map
|
||||
packages/pdf-viewer/messageService.d.ts
|
||||
packages/pdf-viewer/messageService.js
|
||||
packages/pdf-viewer/messageService.js.map
|
||||
packages/pdf-viewer/miniViewer.d.ts
|
||||
packages/pdf-viewer/miniViewer.js
|
||||
packages/pdf-viewer/miniViewer.js.map
|
||||
@@ -2037,6 +2079,9 @@ packages/pdf-viewer/pdfSource.test.js.map
|
||||
packages/pdf-viewer/types.d.ts
|
||||
packages/pdf-viewer/types.js
|
||||
packages/pdf-viewer/types.js.map
|
||||
packages/pdf-viewer/ui/GotoPage.d.ts
|
||||
packages/pdf-viewer/ui/GotoPage.js
|
||||
packages/pdf-viewer/ui/GotoPage.js.map
|
||||
packages/pdf-viewer/ui/IconButtons.d.ts
|
||||
packages/pdf-viewer/ui/IconButtons.js
|
||||
packages/pdf-viewer/ui/IconButtons.js.map
|
||||
|
@@ -90,6 +90,8 @@ module.exports = {
|
||||
// Disable because of this: https://github.com/facebook/react/issues/16265
|
||||
// "react-hooks/exhaustive-deps": "warn",
|
||||
|
||||
'promise/prefer-await-to-then': 'error',
|
||||
|
||||
// -------------------------------
|
||||
// Formatting
|
||||
// -------------------------------
|
||||
@@ -141,6 +143,7 @@ module.exports = {
|
||||
'@seiyab/eslint-plugin-react-hooks',
|
||||
// 'react-hooks',
|
||||
'import',
|
||||
'promise',
|
||||
],
|
||||
'overrides': [
|
||||
{
|
||||
|
4
.github/scripts/run_ci.sh
vendored
4
.github/scripts/run_ci.sh
vendored
@@ -175,8 +175,8 @@ cd "$ROOT_DIR/packages/app-desktop"
|
||||
|
||||
if [[ $GIT_TAG_NAME = v* ]]; then
|
||||
echo "Step: Building and publishing desktop application..."
|
||||
cd "$ROOT_DIR/packages/tools"
|
||||
node bundleDefaultPlugins.js
|
||||
# cd "$ROOT_DIR/packages/tools"
|
||||
# node bundleDefaultPlugins.js
|
||||
cd "$ROOT_DIR/packages/app-desktop"
|
||||
USE_HARD_LINKS=false yarn run dist
|
||||
elif [[ $IS_LINUX = 1 ]] && [[ $GIT_TAG_NAME = $SERVER_TAG_PREFIX-* ]]; then
|
||||
|
3
.github/workflows/github-actions-main.yml
vendored
3
.github/workflows/github-actions-main.yml
vendored
@@ -5,7 +5,6 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
# Removed windows-2016 for now - discontinued by GitHub
|
||||
os: [macos-latest, ubuntu-latest, windows-2019]
|
||||
steps:
|
||||
|
||||
@@ -76,6 +75,8 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
IS_CONTINUOUS_INTEGRATION: 1
|
||||
BUILD_SEQUENCIAL: 1
|
||||
SERVER_REPOSITORY: joplin/server
|
||||
SERVER_TAG_PREFIX: server
|
||||
run: |
|
||||
"${GITHUB_WORKSPACE}/.github/scripts/run_ci.sh"
|
||||
|
||||
|
45
.gitignore
vendored
45
.gitignore
vendored
@@ -66,6 +66,12 @@ docs/**/*.mustache
|
||||
packages/app-cli/app/LinkSelector.d.ts
|
||||
packages/app-cli/app/LinkSelector.js
|
||||
packages/app-cli/app/LinkSelector.js.map
|
||||
packages/app-cli/app/base-command.d.ts
|
||||
packages/app-cli/app/base-command.js
|
||||
packages/app-cli/app/base-command.js.map
|
||||
packages/app-cli/app/command-done.test.d.ts
|
||||
packages/app-cli/app/command-done.test.js
|
||||
packages/app-cli/app/command-done.test.js.map
|
||||
packages/app-cli/app/command-e2ee.d.ts
|
||||
packages/app-cli/app/command-e2ee.js
|
||||
packages/app-cli/app/command-e2ee.js.map
|
||||
@@ -81,6 +87,12 @@ packages/app-cli/app/command-testing.js.map
|
||||
packages/app-cli/app/services/plugins/PluginRunner.d.ts
|
||||
packages/app-cli/app/services/plugins/PluginRunner.js
|
||||
packages/app-cli/app/services/plugins/PluginRunner.js.map
|
||||
packages/app-cli/app/setupCommand.d.ts
|
||||
packages/app-cli/app/setupCommand.js
|
||||
packages/app-cli/app/setupCommand.js.map
|
||||
packages/app-cli/app/utils/testUtils.d.ts
|
||||
packages/app-cli/app/utils/testUtils.js
|
||||
packages/app-cli/app/utils/testUtils.js.map
|
||||
packages/app-cli/tests/HtmlToMd.d.ts
|
||||
packages/app-cli/tests/HtmlToMd.js
|
||||
packages/app-cli/tests/HtmlToMd.js.map
|
||||
@@ -240,6 +252,9 @@ packages/app-desktop/gui/EditFolderDialog/Dialog.js.map
|
||||
packages/app-desktop/gui/EditFolderDialog/IconSelector.d.ts
|
||||
packages/app-desktop/gui/EditFolderDialog/IconSelector.js
|
||||
packages/app-desktop/gui/EditFolderDialog/IconSelector.js.map
|
||||
packages/app-desktop/gui/EmojiBox.d.ts
|
||||
packages/app-desktop/gui/EmojiBox.js
|
||||
packages/app-desktop/gui/EmojiBox.js.map
|
||||
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.d.ts
|
||||
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js
|
||||
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js.map
|
||||
@@ -321,6 +336,9 @@ packages/app-desktop/gui/MainScreen/commands/openItem.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/openNote.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/openNote.js
|
||||
packages/app-desktop/gui/MainScreen/commands/openNote.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/openPdfViewer.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/openPdfViewer.js
|
||||
packages/app-desktop/gui/MainScreen/commands/openPdfViewer.js.map
|
||||
packages/app-desktop/gui/MainScreen/commands/openTag.d.ts
|
||||
packages/app-desktop/gui/MainScreen/commands/openTag.js
|
||||
packages/app-desktop/gui/MainScreen/commands/openTag.js.map
|
||||
@@ -585,6 +603,9 @@ packages/app-desktop/gui/OneDriveLoginScreen.js.map
|
||||
packages/app-desktop/gui/PasswordInput/PasswordInput.d.ts
|
||||
packages/app-desktop/gui/PasswordInput/PasswordInput.js
|
||||
packages/app-desktop/gui/PasswordInput/PasswordInput.js.map
|
||||
packages/app-desktop/gui/PdfViewer.d.ts
|
||||
packages/app-desktop/gui/PdfViewer.js
|
||||
packages/app-desktop/gui/PdfViewer.js.map
|
||||
packages/app-desktop/gui/ResizableLayout/MoveButtons.d.ts
|
||||
packages/app-desktop/gui/ResizableLayout/MoveButtons.js
|
||||
packages/app-desktop/gui/ResizableLayout/MoveButtons.js.map
|
||||
@@ -909,6 +930,9 @@ packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.js.map
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleOverflowButton.d.ts
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleOverflowButton.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleOverflowButton.js.map
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleSpaceButton.d.ts
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleSpaceButton.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleSpaceButton.js.map
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/Toolbar.d.ts
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/Toolbar.js
|
||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/Toolbar.js.map
|
||||
@@ -960,6 +984,9 @@ packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
|
||||
packages/app-mobile/components/screens/encryption-config.d.ts
|
||||
packages/app-mobile/components/screens/encryption-config.js
|
||||
packages/app-mobile/components/screens/encryption-config.js.map
|
||||
packages/app-mobile/components/side-menu-content.d.ts
|
||||
packages/app-mobile/components/side-menu-content.js
|
||||
packages/app-mobile/components/side-menu-content.js.map
|
||||
packages/app-mobile/gulpfile.d.ts
|
||||
packages/app-mobile/gulpfile.js
|
||||
packages/app-mobile/gulpfile.js.map
|
||||
@@ -993,6 +1020,9 @@ packages/app-mobile/utils/TlsUtils.js.map
|
||||
packages/app-mobile/utils/checkPermissions.d.ts
|
||||
packages/app-mobile/utils/checkPermissions.js
|
||||
packages/app-mobile/utils/checkPermissions.js.map
|
||||
packages/app-mobile/utils/debounce.d.ts
|
||||
packages/app-mobile/utils/debounce.js
|
||||
packages/app-mobile/utils/debounce.js.map
|
||||
packages/app-mobile/utils/fs-driver-rn.d.ts
|
||||
packages/app-mobile/utils/fs-driver-rn.js
|
||||
packages/app-mobile/utils/fs-driver-rn.js.map
|
||||
@@ -1002,6 +1032,9 @@ packages/app-mobile/utils/setupNotifications.js.map
|
||||
packages/app-mobile/utils/shareHandler.d.ts
|
||||
packages/app-mobile/utils/shareHandler.js
|
||||
packages/app-mobile/utils/shareHandler.js.map
|
||||
packages/app-mobile/utils/types.d.ts
|
||||
packages/app-mobile/utils/types.js
|
||||
packages/app-mobile/utils/types.js.map
|
||||
packages/fork-htmlparser2/src/CollectingHandler.d.ts
|
||||
packages/fork-htmlparser2/src/CollectingHandler.js
|
||||
packages/fork-htmlparser2/src/CollectingHandler.js.map
|
||||
@@ -1989,6 +2022,9 @@ packages/lib/uuid.js.map
|
||||
packages/lib/versionInfo.d.ts
|
||||
packages/lib/versionInfo.js
|
||||
packages/lib/versionInfo.js.map
|
||||
packages/pdf-viewer/FullViewer.d.ts
|
||||
packages/pdf-viewer/FullViewer.js
|
||||
packages/pdf-viewer/FullViewer.js.map
|
||||
packages/pdf-viewer/Page.d.ts
|
||||
packages/pdf-viewer/Page.js
|
||||
packages/pdf-viewer/Page.js.map
|
||||
@@ -2013,9 +2049,15 @@ packages/pdf-viewer/hooks/useScaledSize.js.map
|
||||
packages/pdf-viewer/hooks/useScrollSaver.d.ts
|
||||
packages/pdf-viewer/hooks/useScrollSaver.js
|
||||
packages/pdf-viewer/hooks/useScrollSaver.js.map
|
||||
packages/pdf-viewer/hooks/useVisibleOnSelect.d.ts
|
||||
packages/pdf-viewer/hooks/useVisibleOnSelect.js
|
||||
packages/pdf-viewer/hooks/useVisibleOnSelect.js.map
|
||||
packages/pdf-viewer/main.d.ts
|
||||
packages/pdf-viewer/main.js
|
||||
packages/pdf-viewer/main.js.map
|
||||
packages/pdf-viewer/messageService.d.ts
|
||||
packages/pdf-viewer/messageService.js
|
||||
packages/pdf-viewer/messageService.js.map
|
||||
packages/pdf-viewer/miniViewer.d.ts
|
||||
packages/pdf-viewer/miniViewer.js
|
||||
packages/pdf-viewer/miniViewer.js.map
|
||||
@@ -2025,6 +2067,9 @@ packages/pdf-viewer/pdfSource.test.js.map
|
||||
packages/pdf-viewer/types.d.ts
|
||||
packages/pdf-viewer/types.js
|
||||
packages/pdf-viewer/types.js.map
|
||||
packages/pdf-viewer/ui/GotoPage.d.ts
|
||||
packages/pdf-viewer/ui/GotoPage.js
|
||||
packages/pdf-viewer/ui/GotoPage.js.map
|
||||
packages/pdf-viewer/ui/IconButtons.d.ts
|
||||
packages/pdf-viewer/ui/IconButtons.js
|
||||
packages/pdf-viewer/ui/IconButtons.js.map
|
||||
|
@@ -1,4 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Mon, 08 Aug 2022 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 08 Aug 2022 00:00:00 GMT</pubDate><item><title><![CDATA[Joplin first meetup on 30 August!]]></title><description><![CDATA[<p>We are glad to announce <a href="https://www.meetup.com/joplin/events/287611873/">the first Joplin Meetup</a> that will take place on 30 August 2022 in London!</p>
|
||||
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Wed, 12 Oct 2022 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Wed, 12 Oct 2022 00:00:00 GMT</pubDate><item><title><![CDATA[Joplin Cloud is now part of the Joplin company]]></title><description><![CDATA[<p>As some of you may know Joplin Cloud so far has been operating under my own single-person limited company in the UK. This was mostly for convenience since it meant I could get things going quickly without having to setup a special structure for it.</p>
|
||||
<p>Now that Joplin Cloud is becoming more mature however a proper company, simply called Joplin, has been created. This company will be based in France, and will be used mainly to handle the commercial part of the project, which currently is mostly Joplin Cloud. I'm still heading the company so there won't be any major change to the way the project is managed.</p>
|
||||
<h2>What does it mean for Joplin Cloud?<a name="what-does-it-mean-for-joplin-cloud" href="#what-does-it-mean-for-joplin-cloud" class="heading-anchor">🔗</a></h2>
|
||||
<p>There will be no significant change - the website ownership simply moves from one company in the UK to one in France. The new company is still owned by myself so I will keep following the same roadmap.</p>
|
||||
<h2>What does it mean for the open source apps?<a name="what-does-it-mean-for-the-open-source-apps" href="#what-does-it-mean-for-the-open-source-apps" class="heading-anchor">🔗</a></h2>
|
||||
<p>On the short term, the only visible change will be moving the non-open source assets, such as logo or trademark from the UK company to the French one. So expect a few changes in copyright notices here and there.</p>
|
||||
<p>In the medium to long term, I would like to hire one or two software developers to help me with the Joplin Cloud development, because we reached a point where managing the whole project is difficult for a single person, so some help is needed. Some of their work might also touch the open source apps since both are quite related - but of course that work will remain open source too.</p>
|
||||
<p>As a general rule, there will be a permanent commitment to keep the apps open source and to derive value from Joplin Cloud/Server.</p>
|
||||
<p>Longer term I would like to create a non-profit organisation to handle the open source applications and to make decisions about the project, as well as to decide how to allocate any funding we receive (for example from GSoC).</p>
|
||||
<h2>Looking forward<a name="looking-forward" href="#looking-forward" class="heading-anchor">🔗</a></h2>
|
||||
<p>Those past 6 years of developing Joplin have been an exciting and rewarding experience, thank you to all of you of the friendly and vibrant Joplin community for your contribution toward making Joplin the software it is today, and looking forward to continuing the journey together!</p>
|
||||
]]></description><link>https://joplinapp.org/news/20221012-Joplin-Company/</link><guid isPermaLink="false">20221012-Joplin-Company</guid><pubDate>Wed, 12 Oct 2022 00:00:00 GMT</pubDate><twitter-text>Joplin Cloud is now operated by the Joplin company! More info on the announcement post.</twitter-text></item><item><title><![CDATA[Joplin interview on Website Planet]]></title><description><![CDATA[<p>Website Planet has recently conducted an interview about Joplin - it may give you some insight on the current status of the project, our priorities, and future plans! More on the article page - <a href="https://www.websiteplanet.com/blog/interview-joplin/">Organise Your Thoughts with Open Source Note-Taking App, Joplin</a></p>
|
||||
]]></description><link>https://joplinapp.org/news/20220906-interview-websiteplanet/</link><guid isPermaLink="false">20220906-interview-websiteplanet</guid><pubDate>Tue, 06 Sep 2022 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Joplin first meetup on 30 August!]]></title><description><![CDATA[<p>We are glad to announce <a href="https://www.meetup.com/joplin/events/287611873/">the first Joplin Meetup</a> that will take place on 30 August 2022 in London!</p>
|
||||
<p>This is an opportunity to meet other Joplin users as well as some of the main contributors, to discuss the apps, or to ask questions and exchange tips and tricks on how to use the app, develop plugins or contribute to the application. Everybody, technical or not, is welcome!</p>
|
||||
<p>We will meet at the Old Thameside Inn next to London Bridge. If the weather allows we will be on the terrace outside, if not inside.</p>
|
||||
<p>More information on the official Meetup page:</p>
|
||||
@@ -256,9 +268,4 @@
|
||||
</li>
|
||||
</ul>
|
||||
<p>Also many thanks to everyone who voted and contributed to the tagline discussion! It helped narrow down what the tagline should be, along with the equally important description below. If you have any question or notice any issue with the website let me know!</p>
|
||||
]]></description><link>https://joplinapp.org/news/20210711-095626/</link><guid isPermaLink="false">20210711-095626</guid><pubDate>Sun, 11 Jul 2021 09:56:26 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Poll: What should Joplin tagline be?]]></title><description><![CDATA[<p>Thanks everyone for your tagline suggestions - there were lots of good ideas in there. I've compiled a few of them and create a poll in the forum, so please cast your vote! And if you have any other suggestions on what would make a good tagline, feel free to post over there or here.</p>
|
||||
<p><a href="https://discourse.joplinapp.org/t/poll-what-should-joplin-tagline-be/18487">https://discourse.joplinapp.org/t/poll-what-should-joplin-tagline-be/18487</a></p>
|
||||
]]></description><link>https://joplinapp.org/news/20210706-140228/</link><guid isPermaLink="false">20210706-140228</guid><pubDate>Tue, 06 Jul 2021 14:02:28 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Any ideas for a Joplin tagline?]]></title><description><![CDATA[<p>I'm going to update the website front page to better showcase the application. I have most of the sections right, but the part I'm still not sure about is the top tagline, so I'm wondering if anyone had any suggestion about it?</p>
|
||||
<p>From what I can see on Google Keep or Evernote for example it should be something like "Use our app to get X or Y benefit", it should be a sentence that directly speaks to the user essentially.</p>
|
||||
<p>So far I have "Your notes, anywhere you are" but I'm not certain that's particularly inspiring. Any other idea about what tagline could be used?</p>
|
||||
]]></description><link>https://joplinapp.org/news/20210705-094247/</link><guid isPermaLink="false">20210705-094247</guid><pubDate>Mon, 05 Jul 2021 09:42:47 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
|
||||
]]></description><link>https://joplinapp.org/news/20210711-095626/</link><guid isPermaLink="false">20210711-095626</guid><pubDate>Sun, 11 Jul 2021 09:56:26 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
|
@@ -31,7 +31,7 @@ Joplin is available in multiple languages thanks to the help of its users. You c
|
||||
|
||||
If you want to start contributing to the project's code, please follow these guidelines before creating a pull request:
|
||||
|
||||
- Explain WHY you want to add this change. Explain it inside the pull request and you may link to an issue for additional information, but the PR should give a clear overview of why you want to add this.
|
||||
- The top post of the pull request should contain a full, self-contained explanation of the feature: what it does, how it does it, with examples of usage and screenshots. Also explain why you want to add this - what problem does it solve. Do not simply add a text `Implement feature #4345` or link to forum posts, because the information there will most likely be outdated or confusing (multiple discussions and opinions). The pull request needs to be self-contained.
|
||||
- Bug fixes are always welcome. Start by reviewing the [list of bugs](https://github.com/laurent22/joplin/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||
- A good way to easily start contributing is to pick and work on a [good first issue](https://github.com/laurent22/joplin/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). We try to make these issues as clear as possible and provide basic info on how the code should be changed, and if something is unclear feel free to ask for more information on the issue.
|
||||
- Before adding a new feature, ask about it in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue) or the [Joplin Forum](https://discourse.joplinapp.org/), or check if existing discussions exist to make sure the new functionality is desired.
|
||||
|
2
LICENSE
2
LICENSE
@@ -10,7 +10,7 @@ under that directory is licensed under the default license, which is MIT.
|
||||
|
||||
* * *
|
||||
|
||||
Joplin® is a trademark of Cozic Ltd registered in the European Union, with
|
||||
Joplin® is a trademark of JOPLIN SAS registered in the European Union, with
|
||||
filing number 018544315.
|
||||
|
||||
* * *
|
||||
|
96
README.md
96
README.md
@@ -85,11 +85,11 @@ A community maintained list of these distributions can be found here: [Unofficia
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/3061769?s=96&v=4"/></br>[c-nagy](https://github.com/c-nagy) | <img width="50" src="https://avatars2.githubusercontent.com/u/70780798?s=96&v=4"/></br>[cabottech](https://github.com/cabottech) | <img width="50" src="https://avatars2.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/4862947?s=96&v=4"/></br>[chrootlogin](https://github.com/chrootlogin) | <img width="50" src="https://avatars2.githubusercontent.com/u/82579431?s=96&v=4"/></br>[clmntsl](https://github.com/clmntsl) | <img width="50" src="https://avatars2.githubusercontent.com/u/808091?s=96&v=4"/></br>[cuongtransc](https://github.com/cuongtransc) | <img width="50" src="https://avatars2.githubusercontent.com/u/1307332?s=96&v=4"/></br>[dbrandonjohnson](https://github.com/dbrandonjohnson) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/1439535?s=96&v=4"/></br>[fbloise](https://github.com/fbloise) | <img width="50" src="https://avatars2.githubusercontent.com/u/49439044?s=96&v=4"/></br>[fourstepper](https://github.com/fourstepper) | <img width="50" src="https://avatars2.githubusercontent.com/u/38898566?s=96&v=4"/></br>[h4sh5](https://github.com/h4sh5) | <img width="50" src="https://avatars2.githubusercontent.com/u/3266447?s=96&v=4"/></br>[iamwillbar](https://github.com/iamwillbar) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/37297218?s=96&v=4"/></br>[Jesssullivan](https://github.com/Jesssullivan) | <img width="50" src="https://avatars2.githubusercontent.com/u/1248504?s=96&v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars2.githubusercontent.com/u/5588131?s=96&v=4"/></br>[kianenigma](https://github.com/kianenigma) | <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[konishi-t](https://github.com/konishi-t) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars2.githubusercontent.com/u/1168659?s=96&v=4"/></br>[nicholashead](https://github.com/nicholashead) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars2.githubusercontent.com/u/77214738?s=96&v=4"/></br>[Polymathic-Company](https://github.com/Polymathic-Company) | <img width="50" src="https://avatars2.githubusercontent.com/u/47742?s=96&v=4"/></br>[ravenscroftj](https://github.com/ravenscroftj) | <img width="50" src="https://avatars2.githubusercontent.com/u/327998?s=96&v=4"/></br>[sif](https://github.com/sif) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/54626606?s=96&v=4"/></br>[skyrunner15](https://github.com/skyrunner15) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) | <img width="50" src="https://avatars2.githubusercontent.com/u/73081837?s=96&v=4"/></br>[thismarty](https://github.com/thismarty) | <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[thomasbroussard](https://github.com/thomasbroussard) |
|
||||
| | | | |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/37297218?s=96&v=4"/></br>[Jesssullivan](https://github.com/Jesssullivan) | <img width="50" src="https://avatars2.githubusercontent.com/u/1310474?s=96&v=4"/></br>[jknowles](https://github.com/jknowles) | <img width="50" src="https://avatars2.githubusercontent.com/u/1248504?s=96&v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars2.githubusercontent.com/u/5588131?s=96&v=4"/></br>[kianenigma](https://github.com/kianenigma) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[konishi-t](https://github.com/konishi-t) | <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/1168659?s=96&v=4"/></br>[nicholashead](https://github.com/nicholashead) | <img width="50" src="https://avatars2.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars2.githubusercontent.com/u/77214738?s=96&v=4"/></br>[Polymathic-Company](https://github.com/Polymathic-Company) | <img width="50" src="https://avatars2.githubusercontent.com/u/47742?s=96&v=4"/></br>[ravenscroftj](https://github.com/ravenscroftj) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/327998?s=96&v=4"/></br>[sif](https://github.com/sif) | <img width="50" src="https://avatars2.githubusercontent.com/u/54626606?s=96&v=4"/></br>[skyrunner15](https://github.com/skyrunner15) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) | <img width="50" src="https://avatars2.githubusercontent.com/u/73081837?s=96&v=4"/></br>[thismarty](https://github.com/thismarty) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[thomasbroussard](https://github.com/thomasbroussard) | | | |
|
||||
<!-- SPONSORS-GITHUB -->
|
||||
|
||||
<!-- TOC -->
|
||||
@@ -301,7 +301,7 @@ As of Joplin 2.x.x, Joplin supports multiple S3 providers. We expose some option
|
||||
In the **desktop application** or **mobile application**, select "S3 (Beta)" as the synchronisation target in the [Configuration screen](https://github.com/laurent22/joplin/blob/dev/readme/config_screen.md).
|
||||
|
||||
- **S3 Bucket:** The name of your Bucket, such as `joplin-bucket`
|
||||
- **S3 URL:** Fully qualified URL; For AWS this should be `https://s3.amazonaws.com/`
|
||||
- **S3 URL:** Fully qualified URL; For AWS this should be `https://s3.<regionName>.amazonaws.com/`
|
||||
- **S3 Access Key & S3 Secret Key:** The User's programmatic access key. To create a new key & secret on AWS, visit [IAM Security Credentials](https://console.aws.amazon.com/iam/home#/security_credentials). For other providers follow their documentation.
|
||||
- **S3 Region:** Some providers require you to provide the region of your bucket. This is usually in the form of "eu-west1" or something similar depending on your region. For providers that do not require a region, you can leave it blank.
|
||||
- **Force Path Style**: This setting enables Joplin to talk to S3 providers using an older style S3 Path. Depending on your provider you may need to try with this on and off.
|
||||
@@ -341,17 +341,17 @@ All providers will require a bucket, Access Key, and Secret Key.
|
||||
If you provide a configuration and you receive "success!" on the "check config" then your S3 sync should work for your provider. If you do not receive success, you may need to adjust your settings, or save them, restart the app, and attempt a sync. This may reveal more clear error messaging that will help you deduce the problem.
|
||||
|
||||
### AWS
|
||||
- URL: https://s3.amazonaws.com
|
||||
- URL: `https://s3.<region>.amazonaws.com/` (fill in your region, a complete list of endpoint adresses can be found [here](https://docs.aws.amazon.com/general/latest/gr/s3.html))
|
||||
- Region: required
|
||||
- Force Path Style: unchecked
|
||||
|
||||
### Linode
|
||||
- URL: https://<region>.linodeobjects.com
|
||||
- Region: empty
|
||||
- URL: `https://<region>.linodeobjects.com` (region is in the URL provided by Linode; this URL is also the same as the URL provided by Linode with the bucket name removed)
|
||||
- Region: Anything you want to type, can't be left empty
|
||||
- Force Path Style: unchecked
|
||||
|
||||
### UpCloud
|
||||
- URL: https://<account>.<region>.upcloudobjects.com (They will provide you with multiple URLs, the one that follows this pattern should work.)
|
||||
- URL: `https://<account>.<region>.upcloudobjects.com` (They will provide you with multiple URLs, the one that follows this pattern should work.)
|
||||
- Region: required
|
||||
- Force Path Style: unchecked
|
||||
|
||||
@@ -540,47 +540,47 @@ Current translations:
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
| Language | Po File | Last translator | Percent done
|
||||
---|---|---|---|---
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 25%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 64%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 50%
|
||||
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 98%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 100%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 86%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 98%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pueblos_spatulas@aleeas.com) | 100%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 49%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 84%
|
||||
<img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 24%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 60%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 47%
|
||||
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 94%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 94%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 81%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 92%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pueblos_spatulas@aleeas.com) | 94%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 46%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/gb.png" width="16px"/> | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/us.png" width="16px"/> | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Mora](mailto:francisco.m.collao@gmail.com) | 98%
|
||||
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 28%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato0 | 98%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Mora](mailto:francisco.m.collao@gmail.com) | 92%
|
||||
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 27%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato0 | 94%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 100%
|
||||
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 32%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 88%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Albano Battistella](mailto:albano_battistella@hotmail.com) | 86%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 86%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 88%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MHolkamp](mailto:mholkamp@users.noreply.github.com) | 98%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 98%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 62%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 81%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 98%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 81%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 57%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 98%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 41%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 87%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 96%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 80%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 98%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 72%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [horaceyoung](mailto:paventyang@gmail.com) | 98%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [SiderealArt](mailto:nelson22768384@gmail.com) | 87%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 100%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 86%
|
||||
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 30%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Wisnu Adi Santoso](mailto:waditos@gmail.com) | 94%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Albano Battistella](mailto:albano_battistella@hotmail.com) | 81%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 81%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 82%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MHolkamp](mailto:mholkamp@users.noreply.github.com) | 92%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 92%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 58%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [X3NO](mailto:X3NO@disroot.org) | 94%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 92%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 76%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 53%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 84%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 94%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 38%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 82%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 94%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 76%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 92%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 84%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 68%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [KaneGreen](mailto:737445366KG@Gmail.com) | 94%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Kevin Hsu](mailto:kevin.hsu.hws@gmail.com) | 94%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 94%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 94%
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
|
||||
# Contributors
|
||||
|
@@ -9,3 +9,7 @@ Only the latest version is supported with security updates.
|
||||
Please [contact support](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/AdresseSupport.png) **with a proof of concept** that shows the security vulnerability. Please do not contact us without this proof of concept, as we cannot fix anything without this.
|
||||
|
||||
For general opinions on what makes an app more or less secure, please use the forum.
|
||||
|
||||
## Bounty
|
||||
|
||||
We **do not** offer a bounty for discovering vulnerabilities, please do not ask. We can however credit you and link to your website in the changelog and release announcement.
|
||||
|
@@ -69,6 +69,7 @@
|
||||
"eslint": "^8.22.0",
|
||||
"eslint-interactive": "^10.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.0.1",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"fs-extra": "^8.1.0",
|
||||
"glob": "^7.1.6",
|
||||
|
@@ -16,6 +16,7 @@ const { cliUtils } = require('./cli-utils.js');
|
||||
const Cache = require('@joplin/lib/Cache');
|
||||
const RevisionService = require('@joplin/lib/services/RevisionService').default;
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const setupCommand = require('./setupCommand').default;
|
||||
|
||||
class Application extends BaseApplication {
|
||||
constructor() {
|
||||
@@ -114,46 +115,12 @@ class Application extends BaseApplication {
|
||||
return [];
|
||||
}
|
||||
|
||||
stdout(text) {
|
||||
return this.gui().stdout(text);
|
||||
setupCommand(cmd) {
|
||||
return setupCommand(cmd, t => this.stdout(t), () => this.store(), () => this.gui());
|
||||
}
|
||||
|
||||
setupCommand(cmd) {
|
||||
cmd.setStdout(text => {
|
||||
return this.stdout(text);
|
||||
});
|
||||
|
||||
cmd.setDispatcher(action => {
|
||||
if (this.store()) {
|
||||
return this.store().dispatch(action);
|
||||
} else {
|
||||
return () => {};
|
||||
}
|
||||
});
|
||||
|
||||
cmd.setPrompt(async (message, options) => {
|
||||
if (!options) options = {};
|
||||
if (!options.type) options.type = 'boolean';
|
||||
if (!options.booleanAnswerDefault) options.booleanAnswerDefault = 'y';
|
||||
if (!options.answers) options.answers = options.booleanAnswerDefault === 'y' ? [_('Y'), _('n')] : [_('N'), _('y')];
|
||||
|
||||
if (options.type === 'boolean') {
|
||||
message += ` (${options.answers.join('/')})`;
|
||||
}
|
||||
|
||||
let answer = await this.gui().prompt('', `${message} `, options);
|
||||
|
||||
if (options.type === 'boolean') {
|
||||
if (answer === null) return false; // Pressed ESCAPE
|
||||
if (!answer) answer = options.answers[0];
|
||||
const positiveIndex = options.booleanAnswerDefault === 'y' ? 0 : 1;
|
||||
return answer.toLowerCase() === options.answers[positiveIndex].toLowerCase();
|
||||
} else {
|
||||
return answer;
|
||||
}
|
||||
});
|
||||
|
||||
return cmd;
|
||||
stdout(text) {
|
||||
return this.gui().stdout(text);
|
||||
}
|
||||
|
||||
async exit(code = 0) {
|
||||
@@ -180,6 +147,7 @@ class Application extends BaseApplication {
|
||||
if (!this.allCommandsLoaded_) {
|
||||
fs.readdirSync(__dirname).forEach(path => {
|
||||
if (path.indexOf('command-') !== 0) return;
|
||||
if (path.endsWith('.test.js')) return;
|
||||
const ext = fileExtension(path);
|
||||
if (ext !== 'js') return;
|
||||
|
||||
|
@@ -124,6 +124,7 @@ async function handleAutocompletionPromise(line) {
|
||||
return line;
|
||||
}
|
||||
function handleAutocompletion(str, callback) {
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
handleAutocompletionPromise(str).then(function(res) {
|
||||
callback(undefined, res);
|
||||
});
|
||||
|
@@ -1,97 +1,97 @@
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const locale_1 = require("@joplin/lib/locale");
|
||||
const registry_js_1 = require("@joplin/lib/registry.js");
|
||||
class BaseCommand {
|
||||
constructor() {
|
||||
this.stdout_ = null;
|
||||
this.prompt_ = null;
|
||||
}
|
||||
|
||||
usage() {
|
||||
throw new Error('Usage not defined');
|
||||
}
|
||||
|
||||
encryptionCheck(item) {
|
||||
if (item && item.encryption_applied) throw new Error(_('Cannot change encrypted item'));
|
||||
}
|
||||
|
||||
description() {
|
||||
throw new Error('Description not defined');
|
||||
}
|
||||
|
||||
async action() {
|
||||
throw new Error('Action not defined');
|
||||
}
|
||||
|
||||
compatibleUis() {
|
||||
return ['cli', 'gui'];
|
||||
}
|
||||
|
||||
supportsUi(ui) {
|
||||
return this.compatibleUis().indexOf(ui) >= 0;
|
||||
}
|
||||
|
||||
options() {
|
||||
return [];
|
||||
}
|
||||
|
||||
hidden() {
|
||||
return false;
|
||||
}
|
||||
|
||||
enabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
cancellable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
async cancel() {}
|
||||
|
||||
name() {
|
||||
const r = this.usage().split(' ');
|
||||
return r[0];
|
||||
}
|
||||
|
||||
setDispatcher(fn) {
|
||||
this.dispatcher_ = fn;
|
||||
}
|
||||
|
||||
dispatch(action) {
|
||||
if (!this.dispatcher_) throw new Error('Dispatcher not defined');
|
||||
return this.dispatcher_(action);
|
||||
}
|
||||
|
||||
setStdout(fn) {
|
||||
this.stdout_ = fn;
|
||||
}
|
||||
|
||||
stdout(text) {
|
||||
if (this.stdout_) this.stdout_(text);
|
||||
}
|
||||
|
||||
setPrompt(fn) {
|
||||
this.prompt_ = fn;
|
||||
}
|
||||
|
||||
async prompt(message, options = null) {
|
||||
if (!this.prompt_) throw new Error('Prompt is undefined');
|
||||
return await this.prompt_(message, options);
|
||||
}
|
||||
|
||||
metadata() {
|
||||
return {
|
||||
name: this.name(),
|
||||
usage: this.usage(),
|
||||
options: this.options(),
|
||||
hidden: this.hidden(),
|
||||
};
|
||||
}
|
||||
|
||||
logger() {
|
||||
return reg.logger();
|
||||
}
|
||||
constructor() {
|
||||
this.stdout_ = null;
|
||||
this.prompt_ = null;
|
||||
}
|
||||
usage() {
|
||||
throw new Error('Usage not defined');
|
||||
}
|
||||
encryptionCheck(item) {
|
||||
if (item && item.encryption_applied)
|
||||
throw new Error((0, locale_1._)('Cannot change encrypted item'));
|
||||
}
|
||||
description() {
|
||||
throw new Error('Description not defined');
|
||||
}
|
||||
action(_args) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
throw new Error('Action not defined');
|
||||
});
|
||||
}
|
||||
compatibleUis() {
|
||||
return ['cli', 'gui'];
|
||||
}
|
||||
supportsUi(ui) {
|
||||
return this.compatibleUis().indexOf(ui) >= 0;
|
||||
}
|
||||
options() {
|
||||
return [];
|
||||
}
|
||||
hidden() {
|
||||
return false;
|
||||
}
|
||||
enabled() {
|
||||
return true;
|
||||
}
|
||||
cancellable() {
|
||||
return false;
|
||||
}
|
||||
cancel() {
|
||||
return __awaiter(this, void 0, void 0, function* () { });
|
||||
}
|
||||
name() {
|
||||
const r = this.usage().split(' ');
|
||||
return r[0];
|
||||
}
|
||||
setDispatcher(fn) {
|
||||
this.dispatcher_ = fn;
|
||||
}
|
||||
dispatch(action) {
|
||||
if (!this.dispatcher_)
|
||||
throw new Error('Dispatcher not defined');
|
||||
return this.dispatcher_(action);
|
||||
}
|
||||
setStdout(fn) {
|
||||
this.stdout_ = fn;
|
||||
}
|
||||
stdout(text) {
|
||||
if (this.stdout_)
|
||||
this.stdout_(text);
|
||||
}
|
||||
setPrompt(fn) {
|
||||
this.prompt_ = fn;
|
||||
}
|
||||
prompt(message, options = null) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (!this.prompt_)
|
||||
throw new Error('Prompt is undefined');
|
||||
return yield this.prompt_(message, options);
|
||||
});
|
||||
}
|
||||
metadata() {
|
||||
return {
|
||||
name: this.name(),
|
||||
usage: this.usage(),
|
||||
options: this.options(),
|
||||
hidden: this.hidden(),
|
||||
};
|
||||
}
|
||||
logger() {
|
||||
return registry_js_1.reg.logger();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { BaseCommand };
|
||||
exports.default = BaseCommand;
|
||||
//# sourceMappingURL=base-command.js.map
|
95
packages/app-cli/app/base-command.ts
Normal file
95
packages/app-cli/app/base-command.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { reg } from '@joplin/lib/registry.js';
|
||||
|
||||
export default class BaseCommand {
|
||||
|
||||
protected stdout_: any = null;
|
||||
protected prompt_: any = null;
|
||||
protected dispatcher_: any;
|
||||
|
||||
usage(): string {
|
||||
throw new Error('Usage not defined');
|
||||
}
|
||||
|
||||
encryptionCheck(item: any) {
|
||||
if (item && item.encryption_applied) throw new Error(_('Cannot change encrypted item'));
|
||||
}
|
||||
|
||||
description() {
|
||||
throw new Error('Description not defined');
|
||||
}
|
||||
|
||||
async action(_args: any) {
|
||||
throw new Error('Action not defined');
|
||||
}
|
||||
|
||||
compatibleUis() {
|
||||
return ['cli', 'gui'];
|
||||
}
|
||||
|
||||
supportsUi(ui: string) {
|
||||
return this.compatibleUis().indexOf(ui) >= 0;
|
||||
}
|
||||
|
||||
options(): any[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
hidden() {
|
||||
return false;
|
||||
}
|
||||
|
||||
enabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
cancellable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
async cancel() {}
|
||||
|
||||
name() {
|
||||
const r = this.usage().split(' ');
|
||||
return r[0];
|
||||
}
|
||||
|
||||
setDispatcher(fn: Function) {
|
||||
this.dispatcher_ = fn;
|
||||
}
|
||||
|
||||
dispatch(action: any) {
|
||||
if (!this.dispatcher_) throw new Error('Dispatcher not defined');
|
||||
return this.dispatcher_(action);
|
||||
}
|
||||
|
||||
setStdout(fn: Function) {
|
||||
this.stdout_ = fn;
|
||||
}
|
||||
|
||||
stdout(text: string) {
|
||||
if (this.stdout_) this.stdout_(text);
|
||||
}
|
||||
|
||||
setPrompt(fn: Function) {
|
||||
this.prompt_ = fn;
|
||||
}
|
||||
|
||||
async prompt(message: string, options: any = null) {
|
||||
if (!this.prompt_) throw new Error('Prompt is undefined');
|
||||
return await this.prompt_(message, options);
|
||||
}
|
||||
|
||||
metadata() {
|
||||
return {
|
||||
name: this.name(),
|
||||
usage: this.usage(),
|
||||
options: this.options(),
|
||||
hidden: this.hidden(),
|
||||
};
|
||||
}
|
||||
|
||||
logger() {
|
||||
return reg.logger();
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const BaseItem = require('@joplin/lib/models/BaseItem').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const { toTitleCase } = require('@joplin/lib/string-utils.js');
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { _, setLocale } = require('@joplin/lib/locale');
|
||||
const { app } = require('./app.js');
|
||||
const fs = require('fs-extra');
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
|
27
packages/app-cli/app/command-done.test.ts
Normal file
27
packages/app-cli/app/command-done.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import { NoteEntity } from '@joplin/lib/services/database/types';
|
||||
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
|
||||
import { setupCommandForTesting, setupApplication } from './utils/testUtils';
|
||||
const Command = require('./command-done');
|
||||
|
||||
describe('command-done', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
await switchClient(1);
|
||||
await setupApplication();
|
||||
});
|
||||
|
||||
it('should make a note as "done"', async () => {
|
||||
const note = await Note.save({ title: 'hello', is_todo: 1, todo_completed: 0 });
|
||||
|
||||
const command = setupCommandForTesting(Command);
|
||||
|
||||
const now = Date.now();
|
||||
await command.action({ note: note.id });
|
||||
|
||||
const checkNote: NoteEntity = await Note.load(note.id);
|
||||
expect(checkNote.todo_completed).toBeGreaterThanOrEqual(now);
|
||||
});
|
||||
|
||||
});
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const Tag = require('@joplin/lib/models/Tag').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
|
||||
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
const fs = require('fs-extra');
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { splitCommandString } = require('@joplin/lib/string-utils.js');
|
||||
const uuid = require('@joplin/lib/uuid').default;
|
||||
const { app } = require('./app.js');
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const ReportService = require('@joplin/lib/services/ReportService').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const InteropService = require('@joplin/lib/services/interop/InteropService').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const { app } = require('./app.js');
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { renderCommandHelp } = require('./help-utils.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const InteropService = require('@joplin/lib/services/interop/InteropService').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const { cliUtils } = require('./cli-utils.js');
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
@@ -11,7 +11,7 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('Moves the given <item> (notes matching pattern in current notebook or one notebook) to [notebook]. If <item> is subnotebook and [notebook] is "root", will make <item> parent notebook');
|
||||
return _('Moves the given <item> to [notebook]');
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const Logger = require('@joplin/lib/Logger').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
|
@@ -2,7 +2,7 @@ import Setting, { SettingStorage } from '@joplin/lib/models/Setting';
|
||||
import { SettingItemType } from '@joplin/lib/services/plugins/api/types';
|
||||
import shim from '@joplin/lib/shim';
|
||||
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
|
||||
function settingTypeToSchemaType(type: SettingItemType): string {
|
||||
const map: Record<SettingItemType, string> = {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
|
@@ -6,7 +6,7 @@ import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
|
||||
import Synchronizer from '@joplin/lib/Synchronizer';
|
||||
import { masterKeysWithoutPassword } from '@joplin/lib/services/e2ee/utils';
|
||||
import { appTypeToLockType } from '@joplin/lib/services/synchronizer/LockHandler';
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { OneDriveApiNodeUtils } = require('@joplin/lib/onedrive-api-node-utils.js');
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const Tag = require('@joplin/lib/models/Tag').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import uuid from '@joplin/lib/uuid';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
|
||||
const CommandDone = require('./command-done.js');
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { app } = require('./app.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const { BaseCommand } = require('./base-command.js');
|
||||
const BaseCommand = require('./base-command').default;
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const versionInfo = require('@joplin/lib/versionInfo').default;
|
||||
|
||||
|
@@ -36,6 +36,7 @@ async function createClients() {
|
||||
const client = createClient(clientId);
|
||||
promises.push(fs.remove(client.profileDir));
|
||||
promises.push(
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
execCommand(client, 'config sync.target 2').then(() => {
|
||||
return execCommand(client, `config sync.2.path ${syncDir}`);
|
||||
})
|
||||
@@ -2324,10 +2325,12 @@ async function main() {
|
||||
clients[clientId].activeCommandCount++;
|
||||
|
||||
execRandomCommand(clients[clientId])
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
.catch(error => {
|
||||
logger.info(`Client ${clientId}:`);
|
||||
logger.error(error);
|
||||
})
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
.then(r => {
|
||||
if (r) {
|
||||
logger.info(`Client ${clientId}:\n${r.trim()}`);
|
||||
|
@@ -50,6 +50,7 @@ export default class PluginRunner extends BasePluginRunner {
|
||||
const callId = `${pluginId}::${path}::${uuid.createNano()}`;
|
||||
this.activeSandboxCalls_[callId] = true;
|
||||
const promise = executeSandboxCall(pluginId, sandbox, `joplin.${path}`, mapEventHandlersToIds(args, this.eventHandlers_), this.eventHandler);
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
promise.finally(() => {
|
||||
delete this.activeSandboxCalls_[callId];
|
||||
});
|
||||
|
39
packages/app-cli/app/setupCommand.ts
Normal file
39
packages/app-cli/app/setupCommand.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
|
||||
export default (cmd: any, stdout: Function, store: Function, gui: Function) => {
|
||||
cmd.setStdout((text: string) => {
|
||||
return stdout(text);
|
||||
});
|
||||
|
||||
cmd.setDispatcher((action: any) => {
|
||||
if (store()) {
|
||||
return store().dispatch(action);
|
||||
} else {
|
||||
return () => {};
|
||||
}
|
||||
});
|
||||
|
||||
cmd.setPrompt(async (message: string, options: any) => {
|
||||
if (!options) options = {};
|
||||
if (!options.type) options.type = 'boolean';
|
||||
if (!options.booleanAnswerDefault) options.booleanAnswerDefault = 'y';
|
||||
if (!options.answers) options.answers = options.booleanAnswerDefault === 'y' ? [_('Y'), _('n')] : [_('N'), _('y')];
|
||||
|
||||
if (options.type === 'boolean') {
|
||||
message += ` (${options.answers.join('/')})`;
|
||||
}
|
||||
|
||||
let answer = await gui().prompt('', `${message} `, options);
|
||||
|
||||
if (options.type === 'boolean') {
|
||||
if (answer === null) return false; // Pressed ESCAPE
|
||||
if (!answer) answer = options.answers[0];
|
||||
const positiveIndex = options.booleanAnswerDefault === 'y' ? 0 : 1;
|
||||
return answer.toLowerCase() === options.answers[positiveIndex].toLowerCase();
|
||||
} else {
|
||||
return answer;
|
||||
}
|
||||
});
|
||||
|
||||
return cmd;
|
||||
};
|
17
packages/app-cli/app/utils/testUtils.ts
Normal file
17
packages/app-cli/app/utils/testUtils.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
const { app } = require('../app');
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import BaseCommand from '../base-command';
|
||||
import setupCommand from '../setupCommand';
|
||||
|
||||
export const setupCommandForTesting = (CommandClass: any, stdout: Function = null): BaseCommand => {
|
||||
const command = new CommandClass();
|
||||
setupCommand(command, stdout, null, null);
|
||||
return command;
|
||||
};
|
||||
|
||||
export const setupApplication = async () => {
|
||||
// We create a notebook and set it as default since most commands require
|
||||
// such notebook.
|
||||
await Folder.save({ title: 'default' });
|
||||
await app().refreshCurrentFolder();
|
||||
};
|
@@ -27,6 +27,7 @@
|
||||
module.exports = {
|
||||
testMatch: [
|
||||
'**/tests/**/*.js',
|
||||
'**/*.test.js',
|
||||
],
|
||||
|
||||
testPathIgnorePatterns: [
|
||||
|
@@ -29,11 +29,12 @@
|
||||
2018,
|
||||
2019,
|
||||
2020,
|
||||
2021
|
||||
2021,
|
||||
2022
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "2.9.0",
|
||||
"version": "2.9.1",
|
||||
"bin": "./main.js",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { installDefaultPlugins, getDefaultPluginsInstallState, setSettingsForDefaultPlugins, checkPreInstalledDefaultPlugins } from '@joplin/lib/services/plugins/defaultPlugins/defaultPluginsUtils';
|
||||
import PluginRunner from '../../../app/services/plugins/PluginRunner';
|
||||
import { pathExists } from 'fs-extra';
|
||||
import { setupDatabaseAndSynchronizer, supportDir, switchClient } from '@joplin/lib/testing/test-utils';
|
||||
import { checkThrow, setupDatabaseAndSynchronizer, supportDir, switchClient } from '@joplin/lib/testing/test-utils';
|
||||
import PluginService, { defaultPluginSetting, DefaultPluginsInfo, PluginSettings } from '@joplin/lib/services/plugins/PluginService';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
@@ -213,4 +213,57 @@ describe('defaultPluginsUtils', function() {
|
||||
await service.destroy();
|
||||
});
|
||||
|
||||
it('should not throw error on missing setting key', async () => {
|
||||
|
||||
const service = newPluginService();
|
||||
|
||||
const pluginScript = `
|
||||
/* joplin-manifest:
|
||||
{
|
||||
"id": "io.github.jackgruber.backup",
|
||||
"manifest_version": 1,
|
||||
"app_min_version": "1.4",
|
||||
"name": "JS Bundle test",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
*/
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {
|
||||
await joplin.settings.registerSettings({
|
||||
path: {
|
||||
value: "initial-path",
|
||||
type: 2,
|
||||
section: "backupSection",
|
||||
public: true,
|
||||
label: "Backup path",
|
||||
},
|
||||
})
|
||||
},
|
||||
});`;
|
||||
|
||||
const plugin = await service.loadPluginFromJsBundle('', pluginScript);
|
||||
await service.runPlugin(plugin);
|
||||
|
||||
const defaultPluginsInfo: DefaultPluginsInfo = {
|
||||
'io.github.jackgruber.backup': {
|
||||
version: '1.0.2',
|
||||
settings: {
|
||||
'path': `${Setting.value('profileDir')}`,
|
||||
'missing-key1': 'someValue',
|
||||
},
|
||||
},
|
||||
'plugin.calebjohn.rich-markdown': {
|
||||
version: '0.8.3',
|
||||
settings: {
|
||||
'missing-key2': 'someValue',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Setting.setValue('installedDefaultPlugins', ['']);
|
||||
expect(checkThrow(() => setSettingsForDefaultPlugins(defaultPluginsInfo))).toBe(false);
|
||||
expect(Setting.value('plugin-io.github.jackgruber.backup.path')).toBe(`${Setting.value('profileDir')}`);
|
||||
await service.destroy();
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -278,8 +278,8 @@ class Application extends BaseApplication {
|
||||
checkPreInstalledDefaultPlugins(defaultPluginsId, pluginSettings);
|
||||
|
||||
try {
|
||||
const pluginsDir = path.join(bridge().buildDir(), 'defaultPlugins');
|
||||
pluginSettings = await installDefaultPlugins(service, pluginsDir, defaultPluginsId, pluginSettings);
|
||||
const defaultPluginsDir = path.join(bridge().buildDir(), 'defaultPlugins');
|
||||
pluginSettings = await installDefaultPlugins(service, defaultPluginsDir, defaultPluginsId, pluginSettings);
|
||||
if (await shim.fsDriver().exists(Setting.value('pluginDir'))) {
|
||||
await service.loadAndRunPlugins(Setting.value('pluginDir'), pluginSettings);
|
||||
}
|
||||
@@ -502,6 +502,7 @@ class Application extends BaseApplication {
|
||||
if (Setting.value('env') === 'dev') {
|
||||
void AlarmService.updateAllNotifications();
|
||||
} else {
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
void reg.scheduleSync(1000).then(() => {
|
||||
// Wait for the first sync before updating the notifications, since synchronisation
|
||||
// might change the notifications.
|
||||
|
@@ -246,7 +246,7 @@ export class Bridge {
|
||||
}
|
||||
|
||||
async openItem(fullPath: string) {
|
||||
return require('electron').shell.openPath(fullPath);
|
||||
return require('electron').shell.openPath(toSystemSlashes(fullPath));
|
||||
}
|
||||
|
||||
screen() {
|
||||
|
@@ -45,6 +45,7 @@ class ClipperConfigScreenComponent extends React.Component {
|
||||
if (confirm(_('Are you sure you want to renew the authorisation token?'))) {
|
||||
void EncryptionService.instance()
|
||||
.generateApiToken()
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
.then((token) => {
|
||||
Setting.setValue('api.token', token);
|
||||
});
|
||||
|
48
packages/app-desktop/gui/EmojiBox.tsx
Normal file
48
packages/app-desktop/gui/EmojiBox.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
|
||||
interface Props {
|
||||
width: number;
|
||||
height: number;
|
||||
emoji: string;
|
||||
}
|
||||
|
||||
const fontSizeCache_: Record<string, number> = {};
|
||||
|
||||
export default (props: Props) => {
|
||||
const containerRef = useRef(null);
|
||||
const [containerReady, setContainerReady] = useState(false);
|
||||
|
||||
const fontSize = useMemo(() => {
|
||||
if (!containerReady) return props.height;
|
||||
|
||||
const cacheKey = [props.width, props.height, props.emoji].join('-');
|
||||
if (fontSizeCache_[cacheKey]) {
|
||||
return fontSizeCache_[cacheKey];
|
||||
}
|
||||
|
||||
// Set the emoji font size so that it fits within the specified width
|
||||
// and height. In fact, currently it only looks at the height.
|
||||
|
||||
let spanFontSize = props.height;
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.innerText = props.emoji;
|
||||
span.style.fontSize = `${spanFontSize}px`;
|
||||
containerRef.current.appendChild(span);
|
||||
|
||||
let rect = span.getBoundingClientRect();
|
||||
|
||||
while (rect.height > props.height) {
|
||||
spanFontSize -= .5;
|
||||
span.style.fontSize = `${spanFontSize}px`;
|
||||
rect = span.getBoundingClientRect();
|
||||
}
|
||||
|
||||
span.remove();
|
||||
|
||||
fontSizeCache_[cacheKey] = spanFontSize;
|
||||
return spanFontSize;
|
||||
}, [props.width, props.height, props.emoji, containerReady, containerRef]);
|
||||
|
||||
return <div className="emoji-box" ref={el => { containerRef.current = el; setContainerReady(true); }} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: props.width, height: props.height, fontSize }}>{props.emoji}</div>;
|
||||
};
|
@@ -1,4 +1,5 @@
|
||||
import { FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
|
||||
import EmojiBox from './EmojiBox';
|
||||
|
||||
interface Props {
|
||||
folderIcon: FolderIcon;
|
||||
@@ -8,13 +9,15 @@ interface Props {
|
||||
export default function(props: Props) {
|
||||
const folderIcon = props.folderIcon;
|
||||
const opacity = 'opacity' in props ? props.opacity : 1;
|
||||
const width = 20;
|
||||
const height = 20;
|
||||
|
||||
if (folderIcon.type === FolderIconType.Emoji) {
|
||||
return <span style={{ fontSize: 20, opacity }}>{folderIcon.emoji}</span>;
|
||||
return <EmojiBox width={width} height={height} emoji={folderIcon.emoji}/>;
|
||||
} else if (folderIcon.type === FolderIconType.DataUrl) {
|
||||
return <img style={{ width: 20, height: 20, opacity }} src={folderIcon.dataUrl} />;
|
||||
return <img style={{ width, height, opacity }} src={folderIcon.dataUrl} />;
|
||||
} else if (folderIcon.type === FolderIconType.FontAwesome) {
|
||||
return <i style={{ fontSize: 18, width: 20, opacity }} className={folderIcon.name}></i>;
|
||||
return <i style={{ fontSize: 18, width, opacity }} className={folderIcon.name}></i>;
|
||||
} else {
|
||||
throw new Error(`Unsupported folder icon type: ${folderIcon.type}`);
|
||||
}
|
||||
|
@@ -56,7 +56,7 @@ export const KeymapConfigScreen = ({ themeId }: KeymapConfigScreenProps) => {
|
||||
filters: [{ name: 'Joplin Keymaps (keymap-desktop.json)', extensions: ['json'] }],
|
||||
});
|
||||
|
||||
if (filePath) {
|
||||
if (filePath && filePath.length !== 0) {
|
||||
const actualFilePath = filePath[0];
|
||||
try {
|
||||
const keymapFile = await shim.fsDriver().readFile(actualFilePath, 'utf-8');
|
||||
|
@@ -15,6 +15,7 @@ import * as openFolder from './openFolder';
|
||||
import * as openFolderDialog from './openFolderDialog';
|
||||
import * as openItem from './openItem';
|
||||
import * as openNote from './openNote';
|
||||
import * as openPdfViewer from './openPdfViewer';
|
||||
import * as openTag from './openTag';
|
||||
import * as print from './print';
|
||||
import * as renameFolder from './renameFolder';
|
||||
@@ -55,6 +56,7 @@ const index:any[] = [
|
||||
openFolderDialog,
|
||||
openItem,
|
||||
openNote,
|
||||
openPdfViewer,
|
||||
openTag,
|
||||
print,
|
||||
renameFolder,
|
||||
|
@@ -0,0 +1,28 @@
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Resource from '@joplin/lib/models/Resource';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'openPdfViewer',
|
||||
label: () => _('Open PDF viewer'),
|
||||
};
|
||||
|
||||
export const runtime = (): CommandRuntime => {
|
||||
return {
|
||||
execute: async (context: CommandContext, resourceId: string, pageNo: number) => {
|
||||
|
||||
const resource = await Resource.load(resourceId);
|
||||
if (!resource) throw new Error(`No such resource: ${resourceId}`);
|
||||
if (resource.mime !== 'application/pdf') throw new Error(`Not a PDF: ${resource.mime}`);
|
||||
console.log('Opening PDF', resource);
|
||||
context.dispatch({
|
||||
type: 'DIALOG_OPEN',
|
||||
name: 'pdfViewer',
|
||||
props: {
|
||||
resource,
|
||||
pageNo: pageNo,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
@@ -848,6 +848,8 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
|
||||
function renderEditor() {
|
||||
|
||||
const matchBracesOptions = Setting.value('editor.autoMatchingBraces') ? { override: true, pairs: '<>()[]{}\'\'""‘’“”()《》「」『』【】〔〕〖〗〘〙〚〛' } : false;
|
||||
|
||||
return (
|
||||
<div style={cellEditorStyle}>
|
||||
<Editor
|
||||
@@ -858,7 +860,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
codeMirrorTheme={styles.editor.codeMirrorTheme}
|
||||
style={styles.editor}
|
||||
readOnly={props.visiblePanes.indexOf('editor') < 0}
|
||||
autoMatchBraces={Setting.value('editor.autoMatchingBraces')}
|
||||
autoMatchBraces={matchBracesOptions}
|
||||
keyMap={props.keyboardMode}
|
||||
plugins={props.plugins}
|
||||
onChange={codeMirror_change}
|
||||
|
@@ -86,7 +86,7 @@ export interface EditorProps {
|
||||
style: any;
|
||||
codeMirrorTheme: any;
|
||||
readOnly: boolean;
|
||||
autoMatchBraces: boolean;
|
||||
autoMatchBraces: boolean | object;
|
||||
keyMap: string;
|
||||
plugins: PluginStates;
|
||||
onChange: any;
|
||||
|
@@ -7,22 +7,16 @@ import uuid from '@joplin/lib/uuid';
|
||||
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
|
||||
const loadedPluginIdSet = new Set<string>();
|
||||
|
||||
export default function useExternalPlugins(CodeMirror: any, plugins: PluginStates) {
|
||||
|
||||
const [options, setOptions] = useState({});
|
||||
useEffect(() => {
|
||||
let newOptions = {};
|
||||
|
||||
const contentScripts = contentScriptsToCodeMirrorPlugin(plugins);
|
||||
const addedElements: HTMLElement[] = [];
|
||||
|
||||
for (const contentScript of contentScripts) {
|
||||
try {
|
||||
if (loadedPluginIdSet.has(contentScript.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mod = contentScript.module;
|
||||
|
||||
if (mod.codeMirrorResources) {
|
||||
@@ -58,33 +52,37 @@ export default function useExternalPlugins(CodeMirror: any, plugins: PluginState
|
||||
if (asset.inline) {
|
||||
cssStrings.push(asset.text);
|
||||
} else {
|
||||
addScript(shim.fsDriver().resolveRelativePathWithinDir(contentScript.assetPath, asset.name), contentScript.id);
|
||||
addedElements.push(addScript(shim.fsDriver().resolveRelativePathWithinDir(contentScript.assetPath, asset.name), contentScript.id));
|
||||
}
|
||||
}
|
||||
|
||||
if (cssStrings.length > 0) {
|
||||
addInlineCss(cssStrings, contentScript.id);
|
||||
addedElements.push(addInlineCss(cssStrings, contentScript.id));
|
||||
}
|
||||
}
|
||||
|
||||
if (mod.plugin) {
|
||||
mod.plugin(CodeMirror);
|
||||
}
|
||||
|
||||
loadedPluginIdSet.add(contentScript.id);
|
||||
} catch (error) {
|
||||
reg.logger().error(error.toString());
|
||||
}
|
||||
}
|
||||
setOptions(newOptions);
|
||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||
}, [plugins]);
|
||||
|
||||
return () => {
|
||||
for (const element of addedElements) {
|
||||
element.remove();
|
||||
}
|
||||
};
|
||||
}, [plugins, CodeMirror]);
|
||||
|
||||
function addInlineCss(cssStrings: string[], id: string) {
|
||||
const element = document.createElement('style');
|
||||
element.setAttribute('id', `content-script-${id}-inline-${uuid.createNano()}`);
|
||||
document.head.appendChild(element);
|
||||
element.appendChild(document.createTextNode(cssStrings.join('\n')));
|
||||
return element;
|
||||
}
|
||||
|
||||
function addScript(path: string, id: string) {
|
||||
@@ -93,6 +91,7 @@ export default function useExternalPlugins(CodeMirror: any, plugins: PluginState
|
||||
element.setAttribute('rel', 'stylesheet');
|
||||
element.setAttribute('href', path);
|
||||
document.head.appendChild(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
return options;
|
||||
|
@@ -622,7 +622,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
});
|
||||
|
||||
editor.ui.registry.addButton('joplinInsertDateTime', {
|
||||
tooltip: _('Insert Date Time'),
|
||||
tooltip: _('Insert time'),
|
||||
icon: 'insert-time',
|
||||
onAction: function() {
|
||||
void CommandService.instance().execute('insertDateTime');
|
||||
@@ -1008,7 +1008,9 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, pastedText, markupRenderOptions({ bodyOnly: true }));
|
||||
editor.insertContent(result.html);
|
||||
} else { // Paste regular text
|
||||
const pastedHtml = event.clipboardData.getData('text/html');
|
||||
// event.clipboardData.getData('text/html') wraps the content with <html><body></body></html>,
|
||||
// which seems to be not supported in editor.insertContent().
|
||||
const pastedHtml = clipboard.readHTML();
|
||||
if (pastedHtml) { // Handles HTML
|
||||
const modifiedHtml = await processPastedHtml(pastedHtml);
|
||||
editor.insertContent(modifiedHtml);
|
||||
|
@@ -80,7 +80,7 @@ const declarations: CommandDeclaration[] = [
|
||||
},
|
||||
{
|
||||
name: 'insertDateTime',
|
||||
label: () => _('Insert Date Time'),
|
||||
label: () => _('Insert time'),
|
||||
iconName: 'icon-add-date',
|
||||
},
|
||||
{
|
||||
|
@@ -52,6 +52,8 @@ export default function useMessageHandler(scrollWhenReady: any, setScrollWhenRea
|
||||
void CommandService.instance().execute(commandName, ...commandArgs);
|
||||
} else if (msg === 'postMessageService.message') {
|
||||
void PostMessageService.instance().postMessage(arg0);
|
||||
} else if (msg === 'openPdfViewer') {
|
||||
await CommandService.instance().execute('openPdfViewer', arg0.resourceId, arg0.pageNo);
|
||||
} else {
|
||||
await CommandService.instance().execute('openItem', msg);
|
||||
// bridge().showErrorMessageBox(_('Unsupported link or message: %s', msg));
|
||||
|
101
packages/app-desktop/gui/PdfViewer.tsx
Normal file
101
packages/app-desktop/gui/PdfViewer.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import * as React from 'react';
|
||||
import { useCallback, useRef, useEffect } from 'react';
|
||||
import Resource from '@joplin/lib/models/Resource';
|
||||
import bridge from '../services/bridge';
|
||||
import contextMenu from './NoteEditor/utils/contextMenu';
|
||||
import { ContextMenuItemType, ContextMenuOptions } from './NoteEditor/utils/contextMenuUtils';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import styled from 'styled-components';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
|
||||
const Window = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
z-index: 999;
|
||||
background-color: ${(props: any) => props.theme.backgroundColor};
|
||||
color: ${(props: any) => props.theme.color};
|
||||
`;
|
||||
|
||||
const IFrame = styled.iframe`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: none;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
themeId: number;
|
||||
dispatch: Function;
|
||||
resource: any;
|
||||
pageNo: number;
|
||||
}
|
||||
|
||||
export default function PdfViewer(props: Props) {
|
||||
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
props.dispatch({
|
||||
type: 'DIALOG_CLOSE',
|
||||
name: 'pdfViewer',
|
||||
});
|
||||
}, [props.dispatch]);
|
||||
|
||||
const openExternalViewer = useCallback(async () => {
|
||||
await CommandService.instance().execute('openItem', `joplin://${props.resource.id}`);
|
||||
}, [props.resource.id]);
|
||||
|
||||
const textSelected = useCallback(async (text: string) => {
|
||||
if (!text) return;
|
||||
const itemType = ContextMenuItemType.Text;
|
||||
const menu = await contextMenu({
|
||||
itemType,
|
||||
resourceId: null,
|
||||
filename: null,
|
||||
mime: 'text/plain',
|
||||
textToCopy: text,
|
||||
linkToCopy: null,
|
||||
htmlToCopy: '',
|
||||
insertContent: () => { console.warn('insertContent() not implemented'); },
|
||||
} as ContextMenuOptions, props.dispatch);
|
||||
|
||||
menu.popup(bridge().window());
|
||||
}, [props.dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
const onMessage_ = async (event: any) =>{
|
||||
if (!event.data || !event.data.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data.name === 'close') {
|
||||
onClose();
|
||||
} else if (event.data.name === 'externalViewer') {
|
||||
await openExternalViewer();
|
||||
} else if (event.data.name === 'textSelected') {
|
||||
await textSelected(event.data.text);
|
||||
} else {
|
||||
console.error('Unknown event received', event.data.name);
|
||||
}
|
||||
};
|
||||
const iframe = iframeRef.current;
|
||||
iframe.contentWindow.addEventListener('message', onMessage_);
|
||||
return () => {
|
||||
iframe.contentWindow.removeEventListener('message', onMessage_);
|
||||
};
|
||||
}, [onClose, openExternalViewer, textSelected]);
|
||||
|
||||
const theme = themeStyle(props.themeId);
|
||||
|
||||
return (
|
||||
<Window theme={theme}>
|
||||
<IFrame src="./vendor/lib/@joplin/pdf-viewer/index.html" x-url={Resource.fullPath(props.resource)}
|
||||
x-appearance={theme.appearance} ref={iframeRef}
|
||||
x-title={props.resource.title}
|
||||
x-anchorpage={props.pageNo}
|
||||
x-type="full"></IFrame>
|
||||
</Window>
|
||||
);
|
||||
}
|
@@ -174,9 +174,11 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
||||
return;
|
||||
}
|
||||
Resource.delete(resource.id)
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
.catch((error: Error) => {
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
})
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
.finally(() => {
|
||||
void this.reloadResources(this.state.sorting);
|
||||
});
|
||||
|
@@ -22,6 +22,7 @@ import Dialog from './Dialog';
|
||||
import SyncWizardDialog from './SyncWizard/Dialog';
|
||||
import MasterPasswordDialog from './MasterPasswordDialog/Dialog';
|
||||
import EditFolderDialog from './EditFolderDialog/Dialog';
|
||||
import PdfViewer from './PdfViewer';
|
||||
import StyleSheetContainer from './StyleSheets/StyleSheetContainer';
|
||||
const { ImportScreen } = require('./ImportScreen.min.js');
|
||||
const { ResourceScreen } = require('./ResourceScreen.js');
|
||||
@@ -75,6 +76,11 @@ const registeredDialogs: Record<string, RegisteredDialog> = {
|
||||
return <EditFolderDialog key={props.key} dispatch={props.dispatch} themeId={props.themeId} {...customProps}/>;
|
||||
},
|
||||
},
|
||||
pdfViewer: {
|
||||
render: (props: RegisteredDialogProps, customProps: any) => {
|
||||
return <PdfViewer key={props.key} dispatch={props.dispatch} themeId={props.themeId} {...customProps}/>;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
|
@@ -86,10 +86,10 @@ const renderFolderIcon = (folderIcon: FolderIcon) => {
|
||||
name: 'far fa-folder',
|
||||
type: FolderIconType.FontAwesome,
|
||||
};
|
||||
return <div style={{ marginRight: 5, display: 'flex' }}><FolderIconBox opacity={0.7} folderIcon={defaultFolderIcon}/></div>;
|
||||
return <div style={{ marginRight: 7, display: 'flex' }}><FolderIconBox opacity={0.7} folderIcon={defaultFolderIcon}/></div>;
|
||||
}
|
||||
|
||||
return <div style={{ marginRight: 5, display: 'flex' }}><FolderIconBox folderIcon={folderIcon}/></div>;
|
||||
return <div style={{ marginRight: 7, display: 'flex' }}><FolderIconBox folderIcon={folderIcon}/></div>;
|
||||
};
|
||||
|
||||
function FolderItem(props: any) {
|
||||
@@ -152,7 +152,7 @@ const SidebarComponent = (props: Props) => {
|
||||
// visual alignment is correct for all folders, otherwise the folder tree
|
||||
// looks messy.
|
||||
const showFolderIcons = useMemo(() => {
|
||||
return !!props.folders.find((f: FolderEntity) => !!f.icon);
|
||||
return Folder.shouldShowFolderIcons(props.folders);
|
||||
}, [props.folders]);
|
||||
|
||||
const getSelectedItem = useCallback(() => {
|
||||
@@ -442,8 +442,8 @@ const SidebarComponent = (props: Props) => {
|
||||
}, [props.dispatch]);
|
||||
|
||||
const onHeaderClick_ = useCallback((key: string) => {
|
||||
const isExpanded = key === 'tag' ? props.tagHeaderIsExpanded : props.folderHeaderIsExpanded;
|
||||
Setting.setValue(key === 'tag' ? 'tagHeaderIsExpanded' : 'folderHeaderIsExpanded', !isExpanded);
|
||||
const isExpanded = key === 'tagHeader' ? props.tagHeaderIsExpanded : props.folderHeaderIsExpanded;
|
||||
Setting.setValue(key === 'tagHeader' ? 'tagHeaderIsExpanded' : 'folderHeaderIsExpanded', !isExpanded);
|
||||
}, [props.folderHeaderIsExpanded, props.tagHeaderIsExpanded]);
|
||||
|
||||
const onAllNotesClick_ = () => {
|
||||
|
@@ -579,6 +579,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
ipc.textSelected = function(event) {
|
||||
ipcProxySendToHost('contextMenu', {
|
||||
type: 'text',
|
||||
textToCopy: event.text,
|
||||
});
|
||||
}
|
||||
|
||||
ipc.openPdfViewer = function(event) {
|
||||
ipcProxySendToHost('openPdfViewer', { resourceId: event.resourceId, mime: 'application/pdf', pageNo: event.pageNo || 1 });
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', webviewLib.logEnabledEventHandler(e => {
|
||||
if (!window.location.hash) return;
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.9.4",
|
||||
"version": "2.9.12",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
@@ -117,7 +117,7 @@
|
||||
"app-builder-bin": "^1.9.11",
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"electron": "18.2.0",
|
||||
"electron": "19.0.10",
|
||||
"electron-builder": "^23.0.3",
|
||||
"electron-notarize": "^1.2.1",
|
||||
"electron-rebuild": "^3.2.7",
|
||||
@@ -147,7 +147,7 @@
|
||||
"compare-versions": "^3.2.1",
|
||||
"countable": "^3.0.1",
|
||||
"debounce": "^1.2.0",
|
||||
"electron-window-state": "^4.1.1",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"formatcoords": "^1.1.3",
|
||||
"fs-extra": "10.0.0",
|
||||
"highlight.js": "^10.2.1",
|
||||
|
@@ -3,6 +3,7 @@
|
||||
import SpellCheckerServiceDriverBase from '@joplin/lib/services/spellChecker/SpellCheckerServiceDriverBase';
|
||||
import bridge from '../bridge';
|
||||
import Logger from '@joplin/lib/Logger';
|
||||
import { languageCodeOnly, localesFromLanguageCode } from '@joplin/lib/locale';
|
||||
|
||||
const logger = Logger.create('SpellCheckerServiceDriverNative');
|
||||
|
||||
@@ -18,15 +19,45 @@ export default class SpellCheckerServiceDriverNative extends SpellCheckerService
|
||||
|
||||
// Language can be set to [] to disable spell-checking
|
||||
public setLanguages(v: string[]) {
|
||||
|
||||
// Note that in order to validate the language we need ot set it on the
|
||||
// session and check if Electron has thrown an exception or not. This is
|
||||
// fine because the actual languages will be set below after the calls
|
||||
// to this functions.
|
||||
const validateLanguage = (v: string) => {
|
||||
const languagesToTry = [
|
||||
v,
|
||||
languageCodeOnly(v),
|
||||
].concat(localesFromLanguageCode(languageCodeOnly(v), this.availableLanguages));
|
||||
|
||||
for (const toTry of languagesToTry) {
|
||||
try {
|
||||
this.session().setSpellCheckerLanguages([toTry]);
|
||||
return toTry;
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to set language to "${toTry}". Will try the next one in this list: ${JSON.stringify(languagesToTry)}`);
|
||||
logger.warn('Error was:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const effectiveLanguages: string[] = [];
|
||||
for (const language of v) {
|
||||
const effectiveLanguage = validateLanguage(language);
|
||||
if (effectiveLanguage) effectiveLanguages.push(effectiveLanguage);
|
||||
}
|
||||
|
||||
// If we pass an empty array, it disables spell checking
|
||||
// https://github.com/electron/electron/issues/25228
|
||||
if (v.length === 0) {
|
||||
if (effectiveLanguages.length === 0) {
|
||||
this.session().setSpellCheckerLanguages([]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.session().setSpellCheckerLanguages(v);
|
||||
logger.info(`Set effective languages to "${v}"`);
|
||||
this.session().setSpellCheckerLanguages(effectiveLanguages);
|
||||
logger.info(`Set effective languages to "${effectiveLanguages}"`);
|
||||
}
|
||||
|
||||
public get language(): string {
|
||||
|
@@ -146,8 +146,8 @@ android {
|
||||
applicationId "net.cozic.joplin"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 2097670
|
||||
versionName "2.9.2"
|
||||
versionCode 2097676
|
||||
versionName "2.9.8"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
}
|
||||
|
@@ -84,6 +84,7 @@
|
||||
android:configChanges="orientation"
|
||||
android:label="@string/app_name"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/AppTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
@@ -2,4 +2,5 @@
|
||||
<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"/>
|
||||
</adaptive-icon>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
@@ -2,4 +2,5 @@
|
||||
<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"/>
|
||||
</adaptive-icon>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
@@ -122,7 +122,7 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
||||
};
|
||||
|
||||
const itemRenderer = (item: DropdownListItem) => {
|
||||
const key = item.value.toString();
|
||||
const key = item.value ? item.value.toString() : '__null'; // The top item ("Move item to notebook...") has a null value.
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={itemWrapperStyle}
|
||||
|
@@ -36,6 +36,10 @@ type OnFileUpdateCallback = (event: SourceFileUpdateEvent)=> void;
|
||||
interface Props {
|
||||
themeId: number;
|
||||
|
||||
// A name to be associated with the WebView (e.g. NoteEditor)
|
||||
// This name should be unique.
|
||||
webviewInstanceId: string;
|
||||
|
||||
// If HTML is still being loaded, [html] should be an empty string.
|
||||
html: string;
|
||||
|
||||
@@ -47,6 +51,10 @@ interface Props {
|
||||
// Initial javascript. Must evaluate to true.
|
||||
injectedJavaScript: string;
|
||||
|
||||
// iOS only: Scroll the outer content of the view. Set this to `false` if
|
||||
// the main view container doesn't scroll.
|
||||
scrollEnabled?: boolean;
|
||||
|
||||
style?: StyleProp<ViewStyle>;
|
||||
onMessage: OnMessageCallback;
|
||||
onError: OnErrorCallback;
|
||||
@@ -81,7 +89,7 @@ const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
async function createHtmlFile() {
|
||||
const tempFile = `${Setting.value('resourceDir')}/NoteEditor.html`;
|
||||
const tempFile = `${Setting.value('resourceDir')}/${props.webviewInstanceId}.html`;
|
||||
await shim.fsDriver().writeFile(tempFile, props.html, 'utf8');
|
||||
if (cancelled) return;
|
||||
|
||||
@@ -110,12 +118,10 @@ const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [props.html, props.onFileUpdate]);
|
||||
}, [props.html, props.webviewInstanceId, props.onFileUpdate]);
|
||||
|
||||
// - `setSupportMultipleWindows` must be `true` for security reasons:
|
||||
// https://github.com/react-native-webview/react-native-webview/releases/tag/v11.0.0
|
||||
// - `scrollEnabled` prevents iOS from scrolling the document (has no effect on Android)
|
||||
// when an editable region (e.g. a the full-screen NoteEditor) is focused.
|
||||
return (
|
||||
<WebView
|
||||
style={{
|
||||
@@ -123,7 +129,7 @@ const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
|
||||
...(props.style as any),
|
||||
}}
|
||||
ref={webviewRef}
|
||||
scrollEnabled={false}
|
||||
scrollEnabled={props.scrollEnabled}
|
||||
useWebKit={true}
|
||||
source={source}
|
||||
setSupportMultipleWindows={true}
|
||||
|
@@ -89,6 +89,7 @@ export default function NoteBodyViewer(props: Props) {
|
||||
return (
|
||||
<View style={props.style}>
|
||||
<ExtendedWebView
|
||||
webviewInstanceId='NoteBodyViewer'
|
||||
themeId={props.themeId}
|
||||
style={webViewStyle}
|
||||
html={html}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
|
||||
// This contains the CodeMirror instance, which needs to be built into a bundle
|
||||
// using `npm run buildInjectedJs`. This bundle is then loaded from
|
||||
// using `yarn run buildInjectedJs`. This bundle is then loaded from
|
||||
// NoteEditor.tsx into the webview.
|
||||
//
|
||||
// In general, since this file is harder to debug due to the intermediate built
|
||||
@@ -21,7 +21,7 @@ import { GFM as GitHubFlavoredMarkdownExtension } from '@lezer/markdown';
|
||||
import { indentOnInput, indentUnit, syntaxTree } from '@codemirror/language';
|
||||
import {
|
||||
openSearchPanel, closeSearchPanel, SearchQuery, setSearchQuery, getSearchQuery,
|
||||
highlightSelectionMatches, search, findNext, findPrevious, replaceAll, replaceNext,
|
||||
/* highlightSelectionMatches, */ search, findNext, findPrevious, replaceAll, replaceNext,
|
||||
} from '@codemirror/search';
|
||||
|
||||
import {
|
||||
@@ -291,7 +291,7 @@ export function initCodeMirror(
|
||||
}),
|
||||
drawSelection(),
|
||||
highlightSpecialChars(),
|
||||
highlightSelectionMatches(),
|
||||
// highlightSelectionMatches(),
|
||||
indentOnInput(),
|
||||
|
||||
// By default, indent with four spaces
|
||||
|
@@ -89,10 +89,10 @@ const EditLinkDialog = (props: LinkDialogProps) => {
|
||||
// for more about creating accessible RN inputs.
|
||||
const linkTextInput = (
|
||||
<View style={styles.inputContainer} accessible>
|
||||
<Text style={styles.text}>{_('Link Text')}</Text>
|
||||
<Text style={styles.text}>{_('Link text')}</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder={_('Description of the link')}
|
||||
placeholder={_('Link description')}
|
||||
placeholderTextColor={placeholderColor}
|
||||
value={linkLabel}
|
||||
|
||||
@@ -138,7 +138,7 @@ const EditLinkDialog = (props: LinkDialogProps) => {
|
||||
props.editorControl.hideLinkDialog();
|
||||
}}>
|
||||
<View style={styles.modalContent}>
|
||||
<Text style={styles.header}>{_('Edit Link')}</Text>
|
||||
<Text style={styles.header}>{_('Edit link')}</Text>
|
||||
<View>
|
||||
{linkTextInput}
|
||||
{linkURLInput}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// A toolbar for the markdown editor.
|
||||
|
||||
const React = require('react');
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
import { Platform, StyleSheet } from 'react-native';
|
||||
import { useMemo, useState, useCallback } from 'react';
|
||||
|
||||
// See https://oblador.github.io/react-native-vector-icons/ for a list of
|
||||
@@ -20,6 +20,7 @@ import { ButtonSpec, StyleSheetData } from './types';
|
||||
import Toolbar from './Toolbar';
|
||||
import { buttonSize } from './ToolbarButton';
|
||||
import { Theme } from '@joplin/lib/themes/type';
|
||||
import ToggleSpaceButton from './ToggleSpaceButton';
|
||||
|
||||
type OnAttachCallback = ()=> void;
|
||||
|
||||
@@ -41,16 +42,10 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
const headerButtons: ButtonSpec[] = [];
|
||||
for (let level = 1; level <= 5; level++) {
|
||||
const active = selState.headerLevel === level;
|
||||
let label;
|
||||
if (!active) {
|
||||
label = _('Create header level %d', level);
|
||||
} else {
|
||||
label = _('Remove level %d header', level);
|
||||
}
|
||||
|
||||
headerButtons.push({
|
||||
icon: `H${level}`,
|
||||
description: label,
|
||||
description: _('Header %d', level),
|
||||
active,
|
||||
|
||||
// We only call addHeaderButton 5 times and in the same order, so
|
||||
@@ -71,8 +66,7 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
icon: (
|
||||
<FontAwesomeIcon name="list-ul" style={styles.text}/>
|
||||
),
|
||||
description:
|
||||
selState.inUnorderedList ? _('Remove unordered list') : _('Create unordered list'),
|
||||
description: _('Unordered list'),
|
||||
active: selState.inUnorderedList,
|
||||
onPress: useCallback(() => {
|
||||
editorControl.toggleList(ListType.UnorderedList);
|
||||
@@ -85,8 +79,7 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
icon: (
|
||||
<FontAwesomeIcon name="list-ol" style={styles.text}/>
|
||||
),
|
||||
description:
|
||||
selState.inOrderedList ? _('Remove ordered list') : _('Create ordered list'),
|
||||
description: _('Ordered list'),
|
||||
active: selState.inOrderedList,
|
||||
onPress: useCallback(() => {
|
||||
editorControl.toggleList(ListType.OrderedList);
|
||||
@@ -99,8 +92,7 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
icon: (
|
||||
<FontAwesomeIcon name="tasks" style={styles.text}/>
|
||||
),
|
||||
description:
|
||||
selState.inChecklist ? _('Remove task list') : _('Create task list'),
|
||||
description: _('Task list'),
|
||||
active: selState.inChecklist,
|
||||
onPress: useCallback(() => {
|
||||
editorControl.toggleList(ListType.CheckList);
|
||||
@@ -137,8 +129,7 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
icon: (
|
||||
<FontAwesomeIcon name="bold" style={styles.text}/>
|
||||
),
|
||||
description:
|
||||
selState.bolded ? _('Unbold') : _('Bold text'),
|
||||
description: _('Bold'),
|
||||
active: selState.bolded,
|
||||
onPress: editorControl.toggleBolded,
|
||||
|
||||
@@ -149,8 +140,7 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
icon: (
|
||||
<FontAwesomeIcon name="italic" style={styles.text}/>
|
||||
),
|
||||
description:
|
||||
selState.italicized ? _('Unitalicize') : _('Italicize'),
|
||||
description: _('Italic'),
|
||||
active: selState.italicized,
|
||||
onPress: editorControl.toggleItalicized,
|
||||
|
||||
@@ -159,8 +149,7 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
|
||||
inlineFormattingBtns.push({
|
||||
icon: '{;}',
|
||||
description:
|
||||
selState.inCode ? _('Remove code formatting') : _('Format as code'),
|
||||
description: _('Code'),
|
||||
active: selState.inCode,
|
||||
onPress: editorControl.toggleCode,
|
||||
|
||||
@@ -170,8 +159,7 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
if (props.editorSettings.katexEnabled) {
|
||||
inlineFormattingBtns.push({
|
||||
icon: '∑',
|
||||
description:
|
||||
selState.inMath ? _('Remove TeX region') : _('Create TeX region'),
|
||||
description: _('KaTeX'),
|
||||
active: selState.inMath,
|
||||
onPress: editorControl.toggleMath,
|
||||
|
||||
@@ -183,8 +171,7 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
icon: (
|
||||
<FontAwesomeIcon name="link" style={styles.text}/>
|
||||
),
|
||||
description:
|
||||
selState.inLink ? _('Edit link') : _('Create link'),
|
||||
description: _('Link'),
|
||||
active: selState.inLink,
|
||||
onPress: editorControl.showLinkDialog,
|
||||
|
||||
@@ -228,7 +215,7 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
<MaterialIcon name="search" style={styles.text}/>
|
||||
),
|
||||
description: (
|
||||
props.searchState.dialogVisible ? _('Close find and replace') : _('Find and replace')
|
||||
props.searchState.dialogVisible ? _('Close') : _('Find and replace')
|
||||
),
|
||||
active: props.searchState.dialogVisible,
|
||||
onPress: useCallback(() => {
|
||||
@@ -277,7 +264,11 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToggleSpaceButton
|
||||
spaceApplicable={ Platform.OS === 'ios' && keyboardVisible }
|
||||
themeId={props.editorSettings.themeId}
|
||||
style={styles.container}
|
||||
>
|
||||
<Toolbar
|
||||
styleSheet={styleData}
|
||||
buttons={[
|
||||
@@ -299,21 +290,16 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<View style={{
|
||||
// The keyboard on iOS can overlap the markdown toolbar.
|
||||
// Add additional padding to prevent this.
|
||||
height: (
|
||||
Platform.OS === 'ios' && keyboardVisible ? 16 : 0
|
||||
),
|
||||
}}/>
|
||||
</>
|
||||
</ToggleSpaceButton>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = (styleProps: any, theme: Theme) => {
|
||||
return useMemo(() => {
|
||||
return StyleSheet.create({
|
||||
container: {
|
||||
...styleProps,
|
||||
},
|
||||
button: {
|
||||
width: buttonSize,
|
||||
height: buttonSize,
|
||||
@@ -348,10 +334,8 @@ const useStyles = (styleProps: any, theme: Theme) => {
|
||||
|
||||
// Add a small amount of additional padding for button borders
|
||||
height: buttonSize + 6,
|
||||
...styleProps,
|
||||
},
|
||||
toolbarContainer: {
|
||||
maxHeight: '65%',
|
||||
flexShrink: 1,
|
||||
},
|
||||
toolbarContent: {
|
||||
|
@@ -0,0 +1,96 @@
|
||||
|
||||
// On some devices, the SafeAreaView conflicts with the KeyboardAvoidingView, creating
|
||||
// additional (or a lack of additional) space at the bottom of the screen. Because this
|
||||
// is different on different devices, this button allows toggling additional space a the bottom
|
||||
// of the screen to compensate.
|
||||
|
||||
// Works around https://github.com/facebook/react-native/issues/13393 by adding additional
|
||||
// space below the given component when the keyboard is visible unless a button is pressed.
|
||||
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { Theme } from '@joplin/lib/themes/type';
|
||||
|
||||
import * as React from 'react';
|
||||
import { ReactNode, useCallback, useState, useEffect } from 'react';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
import CustomButton from '../../CustomButton';
|
||||
|
||||
const AntIcon = require('react-native-vector-icons/AntDesign').default;
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
spaceApplicable: boolean;
|
||||
themeId: number;
|
||||
style?: ViewStyle;
|
||||
}
|
||||
|
||||
const ToggleSpaceButton = (props: Props) => {
|
||||
const [additionalSpace, setAdditionalSpace] = useState(0);
|
||||
const [decreaseSpaceBtnVisible, setDecreaseSpaceBtnVisible] = useState(true);
|
||||
|
||||
// Some devices need space added, others need space removed.
|
||||
const additionalPositiveSpace = 14;
|
||||
const additionalNegativeSpace = -14;
|
||||
|
||||
// Switch from adding +14px to -14px.
|
||||
const onDecreaseSpace = useCallback(() => {
|
||||
setAdditionalSpace(additionalNegativeSpace);
|
||||
setDecreaseSpaceBtnVisible(false);
|
||||
Setting.setValue('editor.mobile.removeSpaceBelowToolbar', true);
|
||||
}, [setAdditionalSpace, setDecreaseSpaceBtnVisible, additionalNegativeSpace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (Setting.value('editor.mobile.removeSpaceBelowToolbar')) {
|
||||
onDecreaseSpace();
|
||||
}
|
||||
}, [onDecreaseSpace]);
|
||||
|
||||
const theme: Theme = themeStyle(props.themeId);
|
||||
|
||||
const decreaseSpaceButton = (
|
||||
<>
|
||||
<View style={{
|
||||
height: additionalPositiveSpace,
|
||||
zIndex: -2,
|
||||
}} />
|
||||
<CustomButton
|
||||
themeId={props.themeId}
|
||||
description={'Move toolbar to bottom of screen'}
|
||||
style={{
|
||||
height: additionalPositiveSpace,
|
||||
width: '100%',
|
||||
|
||||
// Ensure that the icon is near the bottom of the screen,
|
||||
// and thus invisible on devices where it isn't necessary.
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
|
||||
// Don't show the button on top of views with content.
|
||||
zIndex: -1,
|
||||
|
||||
alignItems: 'center',
|
||||
}}
|
||||
onPress={onDecreaseSpace}
|
||||
>
|
||||
<AntIcon name='down' style={{
|
||||
color: theme.color,
|
||||
}}/>
|
||||
</CustomButton>
|
||||
</>
|
||||
);
|
||||
|
||||
const style: ViewStyle = {
|
||||
marginBottom: props.spaceApplicable ? additionalSpace : 0,
|
||||
...props.style,
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={style}>
|
||||
{props.children}
|
||||
{ decreaseSpaceBtnVisible && props.spaceApplicable ? decreaseSpaceButton : null }
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToggleSpaceButton;
|
@@ -1,8 +1,7 @@
|
||||
const React = require('react');
|
||||
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { ReactElement, useCallback, useState } from 'react';
|
||||
import { AccessibilityInfo, LayoutChangeEvent, ScrollView, View } from 'react-native';
|
||||
import { LayoutChangeEvent, ScrollView, View, ViewStyle } from 'react-native';
|
||||
import ToggleOverflowButton from './ToggleOverflowButton';
|
||||
import ToolbarButton, { buttonSize } from './ToolbarButton';
|
||||
import ToolbarOverflowRows from './ToolbarOverflowRows';
|
||||
@@ -11,6 +10,7 @@ import { ButtonGroup, ButtonSpec, StyleSheetData } from './types';
|
||||
interface ToolbarProps {
|
||||
buttons: ButtonGroup[];
|
||||
styleSheet: StyleSheetData;
|
||||
style?: ViewStyle;
|
||||
}
|
||||
|
||||
// Displays a list of buttons with an overflow menu.
|
||||
@@ -54,11 +54,6 @@ const Toolbar = (props: ToolbarProps) => {
|
||||
}, [allButtonSpecs.length]);
|
||||
|
||||
const onToggleOverflowVisible = useCallback(() => {
|
||||
AccessibilityInfo.announceForAccessibility(
|
||||
!overflowButtonsVisible
|
||||
? _('Opened toolbar overflow menu')
|
||||
: _('Closed toolbar overflow menu')
|
||||
);
|
||||
setOverflowPopupVisible(!overflowButtonsVisible);
|
||||
}, [overflowButtonsVisible]);
|
||||
|
||||
@@ -88,7 +83,7 @@ const Toolbar = (props: ToolbarProps) => {
|
||||
const styles = props.styleSheet.styles;
|
||||
const mainButtonRow = (
|
||||
<View style={styles.toolbarRow}>
|
||||
{!overflowButtonsVisible ? mainButtons : null }
|
||||
{ mainButtons }
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -101,6 +96,8 @@ const Toolbar = (props: ToolbarProps) => {
|
||||
// container. As such, we can't base the container's width on the
|
||||
// size of its content.
|
||||
width: '100%',
|
||||
|
||||
...props.style,
|
||||
}}
|
||||
onLayout={onContainerLayout}
|
||||
>
|
||||
@@ -111,8 +108,8 @@ const Toolbar = (props: ToolbarProps) => {
|
||||
visible={overflowButtonsVisible}
|
||||
onToggleOverflow={onToggleOverflowVisible}
|
||||
/>
|
||||
{ !overflowButtonsVisible ? mainButtonRow : null }
|
||||
</ScrollView>
|
||||
{ !overflowButtonsVisible ? mainButtonRow : null }
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@@ -111,6 +111,7 @@ const ToolbarOverflowRows = (props: OverflowPopupProps) => {
|
||||
style={{
|
||||
height: props.buttonGroups.length * buttonSize,
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
}}
|
||||
onLayout={onContainerLayout}
|
||||
>
|
||||
|
@@ -356,6 +356,8 @@ function NoteEditor(props: Props, ref: any) {
|
||||
console.error('NoteEditor: webview error');
|
||||
}, []);
|
||||
|
||||
// - `scrollEnabled` prevents iOS from scrolling the document (has no effect on Android)
|
||||
// when an editable region (e.g. a the full-screen NoteEditor) is focused.
|
||||
return (
|
||||
<View style={{
|
||||
...props.style,
|
||||
@@ -374,7 +376,9 @@ function NoteEditor(props: Props, ref: any) {
|
||||
...props.contentStyle,
|
||||
}}>
|
||||
<ExtendedWebView
|
||||
webviewInstanceId='NoteEditor'
|
||||
themeId={props.themeId}
|
||||
scrollEnabled={false}
|
||||
ref={webviewRef}
|
||||
html={html}
|
||||
injectedJavaScript={injectedJavaScript}
|
||||
|
@@ -195,7 +195,7 @@ export const SearchPanel = (props: SearchPanelProps) => {
|
||||
styles={styles}
|
||||
iconName="close"
|
||||
onPress={control.hideSearch}
|
||||
title={_('Close search')}
|
||||
title={_('Close')}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@@ -300,7 +300,6 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
disabled={disabled}
|
||||
|
||||
accessibilityLabel={_('Back')}
|
||||
accessibilityHint={_('Navigate to the previous view')}
|
||||
accessibilityRole="button">
|
||||
<View style={disabled ? styles.backButtonDisabled : styles.backButton}>
|
||||
<Icon
|
||||
@@ -326,7 +325,6 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
style={{ padding: 0 }}
|
||||
|
||||
accessibilityLabel={_('Save changes')}
|
||||
accessibilityHint={disabled ? _('Any changes have been saved') : null}
|
||||
accessibilityRole="button">
|
||||
<View style={disabled ? styles.saveButtonDisabled : styles.saveButton}>{icon}</View>
|
||||
</TouchableOpacity>
|
||||
|
@@ -22,7 +22,6 @@ const { themeStyle } = require('../global-style.js');
|
||||
const shared = require('@joplin/lib/components/shared/config-shared.js');
|
||||
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
||||
import { openDocumentTree } from '@joplin/react-native-saf-x';
|
||||
const RNFS = require('react-native-fs');
|
||||
|
||||
class ConfigScreenComponent extends BaseScreenComponent {
|
||||
static navigationOptions(): any {
|
||||
@@ -114,11 +113,18 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
const logItemCsv = service.csvCreate(logItemRows);
|
||||
|
||||
const itemListCsv = await service.basicItemList({ format: 'csv' });
|
||||
const filePath = `${RNFS.ExternalDirectoryPath}/syncReport-${new Date().getTime()}.txt`;
|
||||
|
||||
const externalDir = await shim.fsDriver().getExternalDirectoryPath();
|
||||
|
||||
if (!externalDir) {
|
||||
this.setState({ creatingReport: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const filePath = `${externalDir}/syncReport-${new Date().getTime()}.txt`;
|
||||
|
||||
const finalText = [logItemCsv, itemListCsv].join('\n================================================================================\n');
|
||||
|
||||
await RNFS.writeFile(filePath, finalText);
|
||||
await shim.fsDriver().writeFile(filePath, finalText, 'utf8');
|
||||
alert(`Debug report exported to ${filePath}`);
|
||||
this.setState({ creatingReport: false });
|
||||
};
|
||||
@@ -130,7 +136,12 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
};
|
||||
|
||||
this.exportProfileButtonPress_ = async () => {
|
||||
const p = this.state.profileExportPath ? this.state.profileExportPath : `${RNFS.ExternalStorageDirectoryPath}/JoplinProfileExport`;
|
||||
const externalDir = await shim.fsDriver().getExternalDirectoryPath();
|
||||
if (!externalDir) {
|
||||
return;
|
||||
}
|
||||
const p = this.state.profileExportPath ? this.state.profileExportPath : `${externalDir}/JoplinProfileExport`;
|
||||
|
||||
this.setState({
|
||||
profileExportStatus: 'prompt',
|
||||
profileExportPath: p,
|
||||
@@ -500,7 +511,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
</View>
|
||||
);
|
||||
} else if (md.type === Setting.TYPE_STRING) {
|
||||
if (md.key === 'sync.2.path' && Platform.OS === 'android' && Platform.Version > 28) {
|
||||
if (md.key === 'sync.2.path' && shim.fsDriver().isUsingAndroidSAF()) {
|
||||
return (
|
||||
<TouchableNativeFeedback key={key} onPress={this.selectDirectoryButtonPress} style={this.styles().settingContainer}>
|
||||
<View style={this.styles().settingContainer}>
|
||||
|
@@ -13,7 +13,6 @@ const React = require('react');
|
||||
const { Platform, Keyboard, View, TextInput, StyleSheet, Linking, Image, Share, PermissionsAndroid } = require('react-native');
|
||||
const { connect } = require('react-redux');
|
||||
// const { MarkdownEditor } = require('@joplin/lib/../MarkdownEditor/index.js');
|
||||
const RNFS = require('react-native-fs');
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import BaseItem from '@joplin/lib/models/BaseItem';
|
||||
import Resource from '@joplin/lib/models/Resource';
|
||||
@@ -37,10 +36,10 @@ const { BaseScreenComponent } = require('../base-screen.js');
|
||||
const { themeStyle, editorFont } = require('../global-style.js');
|
||||
const { dialogs } = require('../../utils/dialogs.js');
|
||||
const DialogBox = require('react-native-dialogbox').default;
|
||||
const DocumentPicker = require('react-native-document-picker').default;
|
||||
const ImageResizer = require('react-native-image-resizer').default;
|
||||
const shared = require('@joplin/lib/components/shared/note-screen-shared.js');
|
||||
const ImagePicker = require('react-native-image-picker').default;
|
||||
import { ImagePickerResponse } from 'react-native-image-picker';
|
||||
import SelectDateTimeDialog from '../SelectDateTimeDialog';
|
||||
import ShareExtension from '../../utils/ShareExtension.js';
|
||||
import CameraView from '../CameraView';
|
||||
@@ -541,18 +540,12 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
});
|
||||
}
|
||||
|
||||
async pickDocument() {
|
||||
try {
|
||||
const result = await DocumentPicker.pick();
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (DocumentPicker.isCancel(error)) {
|
||||
console.info('pickDocument: user has cancelled');
|
||||
return null;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
private async pickDocuments() {
|
||||
const result = await shim.fsDriver().pickDocument();
|
||||
if (!result) {
|
||||
console.info('pickDocuments: user has cancelled');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async imageDimensions(uri: string) {
|
||||
@@ -612,15 +605,15 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
reg.logger().info('Resized image ', resizedImagePath);
|
||||
reg.logger().info(`Moving ${resizedImagePath} => ${targetPath}`);
|
||||
|
||||
await RNFS.copyFile(resizedImagePath, targetPath);
|
||||
await shim.fsDriver().copy(resizedImagePath, targetPath);
|
||||
|
||||
try {
|
||||
await RNFS.unlink(resizedImagePath);
|
||||
await shim.fsDriver().unlink(resizedImagePath);
|
||||
} catch (error) {
|
||||
reg.logger().warn('Error when unlinking cached file: ', error);
|
||||
}
|
||||
} else {
|
||||
await RNFS.copyFile(localFilePath, targetPath);
|
||||
await shim.fsDriver().copy(localFilePath, targetPath);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -632,16 +625,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pickerResponse.error) {
|
||||
reg.logger().warn('Got error from picker', pickerResponse.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pickerResponse.didCancel) {
|
||||
reg.logger().info('User cancelled picker');
|
||||
return;
|
||||
}
|
||||
|
||||
const localFilePath = Platform.select({
|
||||
android: pickerResponse.uri,
|
||||
ios: decodeURI(pickerResponse.uri),
|
||||
@@ -686,11 +669,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return;
|
||||
} else {
|
||||
await shim.fsDriver().copy(localFilePath, targetPath);
|
||||
|
||||
const stat = await shim.fsDriver().stat(targetPath);
|
||||
if (stat.size >= 10000000) {
|
||||
|
||||
if (stat.size >= 200 * 1024 * 1024) {
|
||||
await shim.fsDriver().remove(targetPath);
|
||||
throw new Error('Resources larger than 10 MB are not currently supported as they may crash the mobile applications. The issue is being investigated and will be fixed at a later time.');
|
||||
throw new Error('Resources larger than 200 MB are not currently supported as they may crash the mobile applications. The issue is being investigated and will be fixed at a later time.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -735,9 +718,23 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
this.scheduleSave();
|
||||
}
|
||||
|
||||
async attachPhoto_onPress() {
|
||||
const response = await this.showImagePicker({ mediaType: 'photo', noData: true });
|
||||
await this.attachFile(response, 'image');
|
||||
private async attachPhoto_onPress() {
|
||||
// the selection Limit should be specfied. I think 200 is enough?
|
||||
const response: ImagePickerResponse = await this.showImagePicker({ mediaType: 'photo', includeBase64: false, selectionLimit: 200 });
|
||||
|
||||
if (response.errorCode) {
|
||||
reg.logger().warn('Got error from picker', response.errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.didCancel) {
|
||||
reg.logger().info('User cancelled picker');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const asset of response.assets) {
|
||||
await this.attachFile(asset, 'image');
|
||||
}
|
||||
}
|
||||
|
||||
takePhoto_onPress() {
|
||||
@@ -748,8 +745,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
void this.attachFile(
|
||||
{
|
||||
uri: data.uri,
|
||||
didCancel: false,
|
||||
error: null,
|
||||
type: 'image/jpg',
|
||||
},
|
||||
'image'
|
||||
@@ -762,9 +757,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
this.setState({ showCamera: false });
|
||||
}
|
||||
|
||||
async attachFile_onPress() {
|
||||
const response = await this.pickDocument();
|
||||
await this.attachFile(response, 'all');
|
||||
private async attachFile_onPress() {
|
||||
const response = await this.pickDocuments();
|
||||
for (const asset of response) {
|
||||
await this.attachFile(asset, 'all');
|
||||
}
|
||||
}
|
||||
|
||||
toggleIsTodo_onPress() {
|
||||
|
@@ -50,6 +50,7 @@ class FolderScreenComponent extends BaseScreenComponent {
|
||||
lastSavedFolder: Object.assign({}, folder),
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
Folder.load(this.props.folderId).then(folder => {
|
||||
this.setState({
|
||||
folder: folder,
|
||||
|
@@ -151,10 +151,12 @@ class NotesScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
|
||||
deleteFolder_onPress(folderId) {
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
dialogs.confirm(this, _('Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.')).then(ok => {
|
||||
if (!ok) return;
|
||||
|
||||
Folder.delete(folderId)
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
.then(() => {
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
@@ -162,6 +164,7 @@ class NotesScreenComponent extends BaseScreenComponent {
|
||||
smartFilterId: 'c3176726992c11e9ac940492261af972',
|
||||
});
|
||||
})
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
.catch(error => {
|
||||
alert(error.message);
|
||||
});
|
||||
|
@@ -1,440 +0,0 @@
|
||||
const React = require('react');
|
||||
const Component = React.Component;
|
||||
const { Easing, Animated, TouchableOpacity, Text, StyleSheet, ScrollView, View, Alert, Image } = require('react-native');
|
||||
const { connect } = require('react-redux');
|
||||
const Icon = require('react-native-vector-icons/Ionicons').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const Synchronizer = require('@joplin/lib/Synchronizer').default;
|
||||
const NavService = require('@joplin/lib/services/NavService').default;
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const { themeStyle } = require('./global-style.js');
|
||||
const shared = require('@joplin/lib/components/shared/side-menu-shared.js');
|
||||
|
||||
Icon.loadFont();
|
||||
|
||||
class SideMenuContentComponent extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
syncReportText: '',
|
||||
};
|
||||
this.styles_ = {};
|
||||
|
||||
this.tagButton_press = this.tagButton_press.bind(this);
|
||||
this.newFolderButton_press = this.newFolderButton_press.bind(this);
|
||||
this.synchronize_press = this.synchronize_press.bind(this);
|
||||
this.configButton_press = this.configButton_press.bind(this);
|
||||
this.allNotesButton_press = this.allNotesButton_press.bind(this);
|
||||
this.renderFolderItem = this.renderFolderItem.bind(this);
|
||||
|
||||
this.syncIconRotationValue = new Animated.Value(0);
|
||||
this.syncIconRotation = this.syncIconRotationValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['0deg', '360deg'],
|
||||
});
|
||||
}
|
||||
|
||||
styles() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
if (this.styles_[this.props.themeId]) return this.styles_[this.props.themeId];
|
||||
this.styles_ = {};
|
||||
|
||||
const styles = {
|
||||
menu: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
},
|
||||
button: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
height: 36,
|
||||
alignItems: 'center',
|
||||
paddingLeft: theme.marginLeft,
|
||||
paddingRight: theme.marginRight,
|
||||
},
|
||||
buttonText: {
|
||||
flex: 1,
|
||||
color: theme.color,
|
||||
paddingLeft: 10,
|
||||
fontSize: theme.fontSize,
|
||||
},
|
||||
syncStatus: {
|
||||
paddingLeft: theme.marginLeft,
|
||||
paddingRight: theme.marginRight,
|
||||
color: theme.colorFaded,
|
||||
fontSize: theme.fontSizeSmaller,
|
||||
flex: 0,
|
||||
},
|
||||
sidebarIcon: {
|
||||
fontSize: 22,
|
||||
color: theme.color,
|
||||
},
|
||||
};
|
||||
|
||||
styles.folderButton = Object.assign({}, styles.button);
|
||||
styles.folderButton.paddingLeft = 0;
|
||||
styles.folderButtonText = Object.assign({}, styles.buttonText, { paddingLeft: 0 });
|
||||
styles.folderButtonSelected = Object.assign({}, styles.folderButton);
|
||||
styles.folderButtonSelected.backgroundColor = theme.selectedColor;
|
||||
styles.folderIcon = Object.assign({}, theme.icon);
|
||||
styles.folderIcon.color = theme.colorFaded; // '#0072d5';
|
||||
styles.folderIcon.paddingTop = 3;
|
||||
|
||||
styles.sideButton = Object.assign({}, styles.button, { flex: 0 });
|
||||
styles.sideButtonSelected = Object.assign({}, styles.sideButton, { backgroundColor: theme.selectedColor });
|
||||
styles.sideButtonText = Object.assign({}, styles.buttonText);
|
||||
|
||||
this.styles_[this.props.themeId] = StyleSheet.create(styles);
|
||||
return this.styles_[this.props.themeId];
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.syncStarted !== prevProps.syncStarted) {
|
||||
if (this.props.syncStarted) {
|
||||
this.syncIconAnimation = Animated.loop(
|
||||
Animated.timing(this.syncIconRotationValue, {
|
||||
toValue: 1,
|
||||
duration: 3000,
|
||||
easing: Easing.linear,
|
||||
})
|
||||
);
|
||||
|
||||
this.syncIconAnimation.start();
|
||||
} else {
|
||||
if (this.syncIconAnimation) this.syncIconAnimation.stop();
|
||||
this.syncIconAnimation = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
folder_press(folder) {
|
||||
this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Notes',
|
||||
folderId: folder.id,
|
||||
});
|
||||
}
|
||||
|
||||
async folder_longPress(folder) {
|
||||
if (folder === 'all') return;
|
||||
|
||||
Alert.alert(
|
||||
'',
|
||||
_('Notebook: %s', folder.title),
|
||||
[
|
||||
{
|
||||
text: _('Rename'),
|
||||
onPress: () => {
|
||||
if (folder.encryption_applied) {
|
||||
alert(_('Encrypted notebooks cannot be renamed'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Folder',
|
||||
folderId: folder.id,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
text: _('Delete'),
|
||||
onPress: () => {
|
||||
Alert.alert('', _('Delete notebook "%s"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.', folder.title), [
|
||||
{
|
||||
text: _('OK'),
|
||||
onPress: () => {
|
||||
Folder.delete(folder.id);
|
||||
},
|
||||
},
|
||||
{
|
||||
text: _('Cancel'),
|
||||
onPress: () => {},
|
||||
style: 'cancel',
|
||||
},
|
||||
]);
|
||||
},
|
||||
style: 'destructive',
|
||||
},
|
||||
{
|
||||
text: _('Cancel'),
|
||||
onPress: () => {},
|
||||
style: 'cancel',
|
||||
},
|
||||
],
|
||||
{
|
||||
cancelable: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
folder_togglePress(folder) {
|
||||
this.props.dispatch({
|
||||
type: 'FOLDER_TOGGLE',
|
||||
id: folder.id,
|
||||
});
|
||||
}
|
||||
|
||||
tagButton_press() {
|
||||
this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Tags',
|
||||
});
|
||||
}
|
||||
|
||||
configButton_press() {
|
||||
this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||
NavService.go('Config');
|
||||
}
|
||||
|
||||
allNotesButton_press() {
|
||||
this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Notes',
|
||||
smartFilterId: 'c3176726992c11e9ac940492261af972',
|
||||
});
|
||||
}
|
||||
|
||||
newFolderButton_press() {
|
||||
this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Folder',
|
||||
folderId: null,
|
||||
});
|
||||
}
|
||||
|
||||
async synchronize_press() {
|
||||
const actionDone = await shared.synchronize_press(this);
|
||||
if (actionDone === 'auth') this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||
}
|
||||
|
||||
renderFolderIcon(theme, folderIcon) {
|
||||
if (!folderIcon) return null;
|
||||
|
||||
if (folderIcon.type === 1) { // FolderIconType.Emoji
|
||||
return <Text style={{ fontSize: theme.fontSize, marginRight: 4 }}>{folderIcon.emoji}</Text>;
|
||||
} else if (folderIcon.type === 2) { // FolderIconType.DataUrl
|
||||
return <Image style={{ width: 20, height: 20, marginRight: 4, resizeMode: 'contain' }} source={{ uri: folderIcon.dataUrl }}/>;
|
||||
} else {
|
||||
throw new Error(`Unsupported folder icon type: ${folderIcon.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
renderFolderItem(folder, selected, hasChildren, depth) {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const folderButtonStyle = {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
height: 36,
|
||||
alignItems: 'center',
|
||||
paddingRight: theme.marginRight,
|
||||
paddingLeft: 10,
|
||||
};
|
||||
if (selected) folderButtonStyle.backgroundColor = theme.selectedColor;
|
||||
folderButtonStyle.paddingLeft = depth * 10 + theme.marginLeft;
|
||||
|
||||
const iconWrapperStyle = { paddingLeft: 10, paddingRight: 10 };
|
||||
if (selected) iconWrapperStyle.backgroundColor = theme.selectedColor;
|
||||
|
||||
let iconWrapper = null;
|
||||
|
||||
const collapsed = this.props.collapsedFolderIds.indexOf(folder.id) >= 0;
|
||||
const iconName = collapsed ? 'chevron-down' : 'chevron-up';
|
||||
const iconComp = <Icon name={iconName} style={this.styles().folderIcon} />;
|
||||
|
||||
iconWrapper = !hasChildren ? null : (
|
||||
<TouchableOpacity
|
||||
style={iconWrapperStyle}
|
||||
folderid={folder.id}
|
||||
onPress={() => {
|
||||
if (hasChildren) this.folder_togglePress(folder);
|
||||
}}
|
||||
|
||||
accessibilityLabel={collapsed ? _('Expand folder') : _('Collapse folder')}
|
||||
accessibilityRole="togglebutton"
|
||||
>
|
||||
{iconComp}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
const folderIcon = Folder.unserializeIcon(folder.icon);
|
||||
|
||||
return (
|
||||
<View key={folder.id} style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<TouchableOpacity
|
||||
style={{ flex: 1 }}
|
||||
onPress={() => {
|
||||
this.folder_press(folder);
|
||||
}}
|
||||
onLongPress={() => {
|
||||
this.folder_longPress(folder);
|
||||
}}
|
||||
>
|
||||
<View style={folderButtonStyle}>
|
||||
{this.renderFolderIcon(theme, folderIcon)}
|
||||
<Text numberOfLines={1} style={this.styles().folderButtonText}>
|
||||
{Folder.displayTitle(folder)}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
{iconWrapper}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderSidebarButton(key, title, iconName, onPressHandler = null, selected = false) {
|
||||
let icon = <Icon name={iconName} style={this.styles().sidebarIcon} />;
|
||||
|
||||
if (key === 'synchronize_button') {
|
||||
icon = <Animated.View style={{ transform: [{ rotate: this.syncIconRotation }] }}>{icon}</Animated.View>;
|
||||
}
|
||||
|
||||
const content = (
|
||||
<View key={key} style={selected ? this.styles().sideButtonSelected : this.styles().sideButton}>
|
||||
{icon}
|
||||
<Text style={this.styles().sideButtonText}>{title}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
if (!onPressHandler) return content;
|
||||
|
||||
return (
|
||||
<TouchableOpacity key={key} onPress={onPressHandler}>
|
||||
{content}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
makeDivider(key) {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
return <View style={{ marginTop: 15, marginBottom: 15, flex: -1, borderBottomWidth: 1, borderBottomColor: theme.dividerColor }} key={key}></View>;
|
||||
}
|
||||
|
||||
renderBottomPanel() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const items = [];
|
||||
|
||||
items.push(this.makeDivider('divider_1'));
|
||||
|
||||
items.push(this.renderSidebarButton('newFolder_button', _('New Notebook'), 'md-folder-open', this.newFolderButton_press));
|
||||
|
||||
items.push(this.renderSidebarButton('tag_button', _('Tags'), 'md-pricetag', this.tagButton_press));
|
||||
|
||||
items.push(this.renderSidebarButton('config_button', _('Configuration'), 'md-settings', this.configButton_press));
|
||||
|
||||
items.push(this.makeDivider('divider_2'));
|
||||
|
||||
const lines = Synchronizer.reportToLines(this.props.syncReport);
|
||||
const syncReportText = lines.join('\n');
|
||||
|
||||
let decryptionReportText = '';
|
||||
if (this.props.decryptionWorker && this.props.decryptionWorker.state !== 'idle' && this.props.decryptionWorker.itemCount) {
|
||||
decryptionReportText = _('Decrypting items: %d/%d', this.props.decryptionWorker.itemIndex + 1, this.props.decryptionWorker.itemCount);
|
||||
}
|
||||
|
||||
let resourceFetcherText = '';
|
||||
if (this.props.resourceFetcher && this.props.resourceFetcher.toFetchCount) {
|
||||
resourceFetcherText = _('Fetching resources: %d/%d', this.props.resourceFetcher.fetchingCount, this.props.resourceFetcher.toFetchCount);
|
||||
}
|
||||
|
||||
const fullReport = [];
|
||||
if (syncReportText) fullReport.push(syncReportText);
|
||||
if (resourceFetcherText) fullReport.push(resourceFetcherText);
|
||||
if (decryptionReportText) fullReport.push(decryptionReportText);
|
||||
|
||||
items.push(this.renderSidebarButton('synchronize_button', !this.props.syncStarted ? _('Synchronise') : _('Cancel'), 'md-sync', this.synchronize_press));
|
||||
|
||||
if (fullReport.length) {
|
||||
items.push(
|
||||
<Text key="sync_report" style={this.styles().syncStatus}>
|
||||
{fullReport.join('\n')}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.syncOnlyOverWifi && this.props.isOnMobileData) {
|
||||
items.push(
|
||||
<Text key="net_info" style={this.styles().syncStatus}>
|
||||
{ _('Mobile data - auto-sync disabled') }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return <View style={{ flex: 0, flexDirection: 'column', paddingBottom: theme.marginBottom }}>{items}</View>;
|
||||
}
|
||||
|
||||
render() {
|
||||
let items = [];
|
||||
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
// HACK: inner height of ScrollView doesn't appear to be calculated correctly when
|
||||
// using padding. So instead creating blank elements for padding bottom and top.
|
||||
items.push(<View style={{ height: theme.marginTop }} key="bottom_top_hack" />);
|
||||
|
||||
items.push(this.renderSidebarButton('all_notes', _('All notes'), 'md-document', this.allNotesButton_press, this.props.notesParentType === 'SmartFilter'));
|
||||
|
||||
items.push(this.makeDivider('divider_all'));
|
||||
|
||||
items.push(this.renderSidebarButton('folder_header', _('Notebooks'), 'md-folder'));
|
||||
|
||||
if (this.props.folders.length) {
|
||||
const result = shared.renderFolders(this.props, this.renderFolderItem, false);
|
||||
const folderItems = result.items;
|
||||
items = items.concat(folderItems);
|
||||
}
|
||||
|
||||
const style = {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: theme.dividerColor,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={style}>
|
||||
<View style={{ flex: 1, opacity: this.props.opacity }}>
|
||||
<ScrollView scrollsToTop={false} style={this.styles().menu}>
|
||||
{items}
|
||||
</ScrollView>
|
||||
{this.renderBottomPanel()}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const SideMenuContent = connect(state => {
|
||||
return {
|
||||
folders: state.folders,
|
||||
syncStarted: state.syncStarted,
|
||||
syncReport: state.syncReport,
|
||||
selectedFolderId: state.selectedFolderId,
|
||||
selectedTagId: state.selectedTagId,
|
||||
notesParentType: state.notesParentType,
|
||||
locale: state.settings.locale,
|
||||
themeId: state.settings.theme,
|
||||
// Don't do the opacity animation as it means re-rendering the list multiple times
|
||||
// opacity: state.sideMenuOpenPercent,
|
||||
collapsedFolderIds: state.collapsedFolderIds,
|
||||
decryptionWorker: state.decryptionWorker,
|
||||
resourceFetcher: state.resourceFetcher,
|
||||
isOnMobileData: state.isOnMobileData,
|
||||
syncOnlyOverWifi: state.settings['sync.mobileWifiOnly'],
|
||||
};
|
||||
})(SideMenuContentComponent);
|
||||
|
||||
module.exports = { SideMenuContent };
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user