You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-12-14 23:26:58 +02:00
Compare commits
25 Commits
share_perm
...
jcgurango-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0f33b7349 | ||
|
|
1c5bb8f195 | ||
|
|
844f868aaa | ||
|
|
4ec8666368 | ||
|
|
ec5ead7b6f | ||
|
|
2f7f306399 | ||
|
|
4d6bbfa901 | ||
|
|
8e9d1dab2a | ||
|
|
fd733f9079 | ||
|
|
79cacc4d5a | ||
|
|
2c91af21e2 | ||
|
|
4c0eff38ff | ||
|
|
e49d9ebbf0 | ||
|
|
f73eee0d69 | ||
|
|
d5fc645fe6 | ||
|
|
0eded263b3 | ||
|
|
a0c923fab3 | ||
|
|
1e352ec0b1 | ||
|
|
9bf7864142 | ||
|
|
9e587c1b06 | ||
|
|
66755c5386 | ||
|
|
042a8e5d30 | ||
|
|
faade53cf9 | ||
|
|
8f526474ea | ||
|
|
3547211078 |
@@ -142,7 +142,6 @@ packages/app-desktop/gui/DialogButtonRow.js
|
||||
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js
|
||||
packages/app-desktop/gui/DialogTitle.js
|
||||
packages/app-desktop/gui/DropboxLoginScreen.js
|
||||
packages/app-desktop/gui/Dropdown/Dropdown.js
|
||||
packages/app-desktop/gui/EditFolderDialog/Dialog.js
|
||||
packages/app-desktop/gui/EditFolderDialog/IconSelector.js
|
||||
packages/app-desktop/gui/EmojiBox.js
|
||||
@@ -163,9 +162,6 @@ packages/app-desktop/gui/KeymapConfig/utils/useKeymap.js
|
||||
packages/app-desktop/gui/MainScreen/MainScreen.js
|
||||
packages/app-desktop/gui/MainScreen/commands/addProfile.js
|
||||
packages/app-desktop/gui/MainScreen/commands/commandPalette.js
|
||||
packages/app-desktop/gui/MainScreen/commands/deleteFolder.js
|
||||
packages/app-desktop/gui/MainScreen/commands/deleteNote.js
|
||||
packages/app-desktop/gui/MainScreen/commands/duplicateNote.js
|
||||
packages/app-desktop/gui/MainScreen/commands/editAlarm.js
|
||||
packages/app-desktop/gui/MainScreen/commands/exportPdf.js
|
||||
packages/app-desktop/gui/MainScreen/commands/gotoAnything.js
|
||||
@@ -200,7 +196,6 @@ packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteType.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderField.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderReverse.js
|
||||
packages/app-desktop/gui/MainScreen/commands/togglePerFolderSortOrder.js
|
||||
@@ -363,8 +358,6 @@ packages/app-desktop/services/sortOrder/notesSortOrderUtils.js
|
||||
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js
|
||||
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||
packages/app-desktop/tools/notarizeMacApp.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.test.js
|
||||
packages/app-desktop/utils/markupLanguageUtils.js
|
||||
packages/app-mobile/PluginAssetsLoader.js
|
||||
packages/app-mobile/components/ActionButton.js
|
||||
@@ -374,7 +367,6 @@ packages/app-mobile/components/CustomButton.js
|
||||
packages/app-mobile/components/Dropdown.js
|
||||
packages/app-mobile/components/ExtendedWebView.js
|
||||
packages/app-mobile/components/FolderPicker.js
|
||||
packages/app-mobile/components/Modal.js
|
||||
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
|
||||
packages/app-mobile/components/NoteBodyViewer/hooks/useOnMessage.js
|
||||
packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js
|
||||
@@ -415,6 +407,7 @@ packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js
|
||||
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
|
||||
packages/app-mobile/components/ScreenHeader.js
|
||||
packages/app-mobile/components/SelectDateTimeDialog.js
|
||||
packages/app-mobile/components/SideMenu.js
|
||||
packages/app-mobile/components/TextInput.js
|
||||
packages/app-mobile/components/app-nav.js
|
||||
packages/app-mobile/components/biometrics/BiometricPopup.js
|
||||
@@ -487,8 +480,6 @@ packages/lib/JoplinServerApi.js
|
||||
packages/lib/Logger.js
|
||||
packages/lib/ObjectUtils.js
|
||||
packages/lib/PoorManIntervals.js
|
||||
packages/lib/RotatingLogs.js
|
||||
packages/lib/RotatingLogs.test.js
|
||||
packages/lib/SyncTargetJoplinCloud.js
|
||||
packages/lib/SyncTargetJoplinServer.js
|
||||
packages/lib/SyncTargetNone.js
|
||||
@@ -507,14 +498,12 @@ packages/lib/commands/openMasterPasswordDialog.js
|
||||
packages/lib/commands/synchronize.js
|
||||
packages/lib/components/EncryptionConfigScreen/utils.js
|
||||
packages/lib/components/shared/note-screen-shared.js
|
||||
packages/lib/components/shared/reduxSharedMiddleware.js
|
||||
packages/lib/database-driver-better-sqlite.js
|
||||
packages/lib/database.js
|
||||
packages/lib/debug/DebugService.js
|
||||
packages/lib/dom.js
|
||||
packages/lib/dummy.test.js
|
||||
packages/lib/errorUtils.js
|
||||
packages/lib/errors.js
|
||||
packages/lib/eventManager.js
|
||||
packages/lib/file-api-driver-joplinServer.js
|
||||
packages/lib/file-api-driver-memory.js
|
||||
@@ -569,7 +558,6 @@ packages/lib/models/settings/FileHandler.js
|
||||
packages/lib/models/utils/itemCanBeEncrypted.js
|
||||
packages/lib/models/utils/paginatedFeed.js
|
||||
packages/lib/models/utils/paginationToSql.js
|
||||
packages/lib/models/utils/readOnly.js
|
||||
packages/lib/models/utils/types.js
|
||||
packages/lib/models/utils/userData.js
|
||||
packages/lib/models/utils/userData.test.js
|
||||
@@ -650,8 +638,6 @@ packages/lib/services/interop/InteropService_Importer_Md_frontmatter.js
|
||||
packages/lib/services/interop/InteropService_Importer_Md_frontmatter.test.js
|
||||
packages/lib/services/interop/InteropService_Importer_Raw.js
|
||||
packages/lib/services/interop/InteropService_Importer_Raw.test.js
|
||||
packages/lib/services/interop/Module.js
|
||||
packages/lib/services/interop/Module.test.js
|
||||
packages/lib/services/interop/types.js
|
||||
packages/lib/services/joplinServer/personalizedUserContentBaseUrl.js
|
||||
packages/lib/services/keychain/KeychainService.js
|
||||
@@ -773,10 +759,7 @@ packages/lib/services/synchronizer/syncInfoUtils.test.js
|
||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js
|
||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.js
|
||||
packages/lib/services/synchronizer/tools.js
|
||||
packages/lib/services/synchronizer/utils/handleConflictAction.js
|
||||
packages/lib/services/synchronizer/utils/handleSyncStartupOperation.js
|
||||
packages/lib/services/synchronizer/utils/resourceRemotePath.js
|
||||
packages/lib/services/synchronizer/utils/syncDeleteStep.js
|
||||
packages/lib/services/synchronizer/utils/types.js
|
||||
packages/lib/shim.js
|
||||
packages/lib/testing/syncTargetUtils.js
|
||||
@@ -874,7 +857,6 @@ packages/tools/buildServerDocker.js
|
||||
packages/tools/buildServerDocker.test.js
|
||||
packages/tools/bundleDefaultPlugins.js
|
||||
packages/tools/bundleDefaultPlugins.test.js
|
||||
packages/tools/checkIgnoredFiles.js
|
||||
packages/tools/checkLibPaths.js
|
||||
packages/tools/checkLibPaths.test.js
|
||||
packages/tools/convertThemesToCss.js
|
||||
@@ -894,7 +876,6 @@ packages/tools/spellcheck.js
|
||||
packages/tools/tagServerLatest.js
|
||||
packages/tools/tool-utils.js
|
||||
packages/tools/update-readme-download.js
|
||||
packages/tools/update-readme-download.test.js
|
||||
packages/tools/update-readme-sponsors.js
|
||||
packages/tools/updateMarkdownDoc.js
|
||||
packages/tools/utils/discourse.js
|
||||
|
||||
11
.eslintrc.js
11
.eslintrc.js
@@ -42,8 +42,6 @@ module.exports = {
|
||||
'zxcvbn': 'readonly',
|
||||
|
||||
'tinymce': 'readonly',
|
||||
|
||||
'JSX': 'readonly',
|
||||
},
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 2018,
|
||||
@@ -110,9 +108,6 @@ module.exports = {
|
||||
'semi': ['error', 'always'],
|
||||
'eol-last': ['error', 'always'],
|
||||
'quotes': ['error', 'single'],
|
||||
|
||||
// Note that "indent" only applies to JavaScript files. See
|
||||
// https://github.com/laurent22/joplin/issues/8360
|
||||
'indent': ['error', 'tab'],
|
||||
'comma-dangle': ['error', {
|
||||
'arrays': 'always-multiline',
|
||||
@@ -189,12 +184,6 @@ module.exports = {
|
||||
'project': './tsconfig.eslint.json',
|
||||
},
|
||||
'rules': {
|
||||
'@typescript-eslint/indent': ['error', 'tab', {
|
||||
'ignoredNodes': [
|
||||
// See https://github.com/typescript-eslint/typescript-eslint/issues/1824
|
||||
'TSUnionType',
|
||||
],
|
||||
}],
|
||||
'@typescript-eslint/ban-ts-comment': ['error'],
|
||||
'@typescript-eslint/ban-types': 'error',
|
||||
'@typescript-eslint/explicit-member-accessibility': ['error'],
|
||||
|
||||
35
.github/scripts/run_ci.sh
vendored
35
.github/scripts/run_ci.sh
vendored
@@ -153,24 +153,6 @@ if [ "$IS_PULL_REQUEST" == "1" ] || [ "$IS_DEV_BRANCH" = "1" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Check .gitignore and .eslintignore files - they should be updated when
|
||||
# new TypeScript files are added by running `yarn run updateIgnored`.
|
||||
# See coding_style.md
|
||||
# =============================================================================
|
||||
|
||||
if [ "$IS_PULL_REQUEST" == "1" ]; then
|
||||
if [ "$IS_LINUX" == "1" ]; then
|
||||
echo "Step: Checking for files that should have been ignored..."
|
||||
|
||||
node packages/tools/checkIgnoredFiles.js
|
||||
testResult=$?
|
||||
if [ $testResult -ne 0 ]; then
|
||||
exit $testResult
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Find out if we should run the build or not. Electron-builder gets stuck when
|
||||
# building PRs so we disable it in this case. The Linux build should provide
|
||||
@@ -212,12 +194,7 @@ if [[ $GIT_TAG_NAME = v* ]]; then
|
||||
# It can be removed once we upgrade to electron-builder@23, however we
|
||||
# cannot currently do this due to this error:
|
||||
# https://github.com/laurent22/joplin/issues/8149
|
||||
#
|
||||
# electron-builder@24, however, still expects the python binary to be named
|
||||
# "python" and seems to no longer respect the PYTHON_PATH environment variable.
|
||||
# We work around this by aliasing python.
|
||||
alias python=$(which python3)
|
||||
USE_HARD_LINKS=false yarn run dist
|
||||
PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn run dist
|
||||
else
|
||||
USE_HARD_LINKS=false yarn run dist
|
||||
fi
|
||||
@@ -230,15 +207,7 @@ else
|
||||
|
||||
if [ "$IS_MACOS" == "1" ]; then
|
||||
# See above why we need to specify Python
|
||||
alias python=$(which python3)
|
||||
|
||||
# We also want to disable signing the app in this case, because
|
||||
# it randomly fails and we don't even need it
|
||||
# https://www.electron.build/code-signing#how-to-disable-code-signing-during-the-build-process-on-macos
|
||||
export CSC_IDENTITY_AUTO_DISCOVERY=false
|
||||
npm pkg set 'build.mac.identity'=null --json
|
||||
|
||||
USE_HARD_LINKS=false yarn run dist --publish=never
|
||||
PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn run dist --publish=never
|
||||
else
|
||||
USE_HARD_LINKS=false yarn run dist --publish=never
|
||||
fi
|
||||
|
||||
69
.github/workflows/build-macos-m1.yml
vendored
69
.github/workflows/build-macos-m1.yml
vendored
@@ -1,69 +0,0 @@
|
||||
name: Build macOS M1
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
pre_job:
|
||||
if: github.repository == 'laurent22/joplin'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_check
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
concurrent_skipping: 'same_content_newer'
|
||||
|
||||
Main:
|
||||
needs: pre_job
|
||||
# We always process desktop release tags, because they also publish the release
|
||||
if: github.repository == 'laurent22/joplin' && (needs.pre_job.outputs.should_skip != 'true' || startsWith(github.ref, 'refs/tags/v'))
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: olegtarasov/get-tag@v2.1
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
# We need to pin the version to 18.15, because 18.16+ fails with this error:
|
||||
# https://github.com/facebook/react-native/issues/36440
|
||||
node-version: '18.15.0'
|
||||
|
||||
- name: Install Yarn
|
||||
run: |
|
||||
# https://yarnpkg.com/getting-started/install
|
||||
corepack enable
|
||||
|
||||
- name: Build macOS M1 app
|
||||
env:
|
||||
APPLE_ASC_PROVIDER: ${{ secrets.APPLE_ASC_PROVIDER }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CSC_KEY_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.APPLE_CSC_LINK }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
IS_CONTINUOUS_INTEGRATION: 1
|
||||
BUILD_SEQUENCIAL: 1
|
||||
run: |
|
||||
export npm_config_arch=arm64
|
||||
export npm_config_target_arch=arm64
|
||||
yarn install
|
||||
cd packages/app-desktop
|
||||
npm pkg set 'build.mac.artifactName'='${productName}-${version}-${arch}.${ext}'
|
||||
npm pkg set 'build.mac.target.target'='dmg'
|
||||
npm pkg set 'build.mac.target.arch[0]'='arm64'
|
||||
|
||||
if [[ $GIT_TAG_NAME = v* ]]; then
|
||||
echo "Building and publishing desktop application..."
|
||||
PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn run dist --mac --arm64
|
||||
else
|
||||
echo "Building but *not* publishing desktop application..."
|
||||
|
||||
# We also want to disable signing the app in this case, because
|
||||
# it doesn't work and we don't need it.
|
||||
# https://www.electron.build/code-signing#how-to-disable-code-signing-during-the-build-process-on-macos
|
||||
|
||||
export CSC_IDENTITY_AUTO_DISCOVERY=false
|
||||
npm pkg set 'build.mac.identity'=null --json
|
||||
|
||||
PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn run dist --publish=never
|
||||
fi
|
||||
21
.gitignore
vendored
21
.gitignore
vendored
@@ -127,7 +127,6 @@ packages/app-desktop/gui/DialogButtonRow.js
|
||||
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js
|
||||
packages/app-desktop/gui/DialogTitle.js
|
||||
packages/app-desktop/gui/DropboxLoginScreen.js
|
||||
packages/app-desktop/gui/Dropdown/Dropdown.js
|
||||
packages/app-desktop/gui/EditFolderDialog/Dialog.js
|
||||
packages/app-desktop/gui/EditFolderDialog/IconSelector.js
|
||||
packages/app-desktop/gui/EmojiBox.js
|
||||
@@ -148,9 +147,6 @@ packages/app-desktop/gui/KeymapConfig/utils/useKeymap.js
|
||||
packages/app-desktop/gui/MainScreen/MainScreen.js
|
||||
packages/app-desktop/gui/MainScreen/commands/addProfile.js
|
||||
packages/app-desktop/gui/MainScreen/commands/commandPalette.js
|
||||
packages/app-desktop/gui/MainScreen/commands/deleteFolder.js
|
||||
packages/app-desktop/gui/MainScreen/commands/deleteNote.js
|
||||
packages/app-desktop/gui/MainScreen/commands/duplicateNote.js
|
||||
packages/app-desktop/gui/MainScreen/commands/editAlarm.js
|
||||
packages/app-desktop/gui/MainScreen/commands/exportPdf.js
|
||||
packages/app-desktop/gui/MainScreen/commands/gotoAnything.js
|
||||
@@ -185,7 +181,6 @@ packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleEditors.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteType.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderField.js
|
||||
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderReverse.js
|
||||
packages/app-desktop/gui/MainScreen/commands/togglePerFolderSortOrder.js
|
||||
@@ -348,8 +343,6 @@ packages/app-desktop/services/sortOrder/notesSortOrderUtils.js
|
||||
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js
|
||||
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||
packages/app-desktop/tools/notarizeMacApp.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.js
|
||||
packages/app-desktop/utils/checkForUpdatesUtils.test.js
|
||||
packages/app-desktop/utils/markupLanguageUtils.js
|
||||
packages/app-mobile/PluginAssetsLoader.js
|
||||
packages/app-mobile/components/ActionButton.js
|
||||
@@ -359,7 +352,6 @@ packages/app-mobile/components/CustomButton.js
|
||||
packages/app-mobile/components/Dropdown.js
|
||||
packages/app-mobile/components/ExtendedWebView.js
|
||||
packages/app-mobile/components/FolderPicker.js
|
||||
packages/app-mobile/components/Modal.js
|
||||
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
|
||||
packages/app-mobile/components/NoteBodyViewer/hooks/useOnMessage.js
|
||||
packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js
|
||||
@@ -400,6 +392,7 @@ packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js
|
||||
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
|
||||
packages/app-mobile/components/ScreenHeader.js
|
||||
packages/app-mobile/components/SelectDateTimeDialog.js
|
||||
packages/app-mobile/components/SideMenu.js
|
||||
packages/app-mobile/components/TextInput.js
|
||||
packages/app-mobile/components/app-nav.js
|
||||
packages/app-mobile/components/biometrics/BiometricPopup.js
|
||||
@@ -472,8 +465,6 @@ packages/lib/JoplinServerApi.js
|
||||
packages/lib/Logger.js
|
||||
packages/lib/ObjectUtils.js
|
||||
packages/lib/PoorManIntervals.js
|
||||
packages/lib/RotatingLogs.js
|
||||
packages/lib/RotatingLogs.test.js
|
||||
packages/lib/SyncTargetJoplinCloud.js
|
||||
packages/lib/SyncTargetJoplinServer.js
|
||||
packages/lib/SyncTargetNone.js
|
||||
@@ -492,14 +483,12 @@ packages/lib/commands/openMasterPasswordDialog.js
|
||||
packages/lib/commands/synchronize.js
|
||||
packages/lib/components/EncryptionConfigScreen/utils.js
|
||||
packages/lib/components/shared/note-screen-shared.js
|
||||
packages/lib/components/shared/reduxSharedMiddleware.js
|
||||
packages/lib/database-driver-better-sqlite.js
|
||||
packages/lib/database.js
|
||||
packages/lib/debug/DebugService.js
|
||||
packages/lib/dom.js
|
||||
packages/lib/dummy.test.js
|
||||
packages/lib/errorUtils.js
|
||||
packages/lib/errors.js
|
||||
packages/lib/eventManager.js
|
||||
packages/lib/file-api-driver-joplinServer.js
|
||||
packages/lib/file-api-driver-memory.js
|
||||
@@ -554,7 +543,6 @@ packages/lib/models/settings/FileHandler.js
|
||||
packages/lib/models/utils/itemCanBeEncrypted.js
|
||||
packages/lib/models/utils/paginatedFeed.js
|
||||
packages/lib/models/utils/paginationToSql.js
|
||||
packages/lib/models/utils/readOnly.js
|
||||
packages/lib/models/utils/types.js
|
||||
packages/lib/models/utils/userData.js
|
||||
packages/lib/models/utils/userData.test.js
|
||||
@@ -635,8 +623,6 @@ packages/lib/services/interop/InteropService_Importer_Md_frontmatter.js
|
||||
packages/lib/services/interop/InteropService_Importer_Md_frontmatter.test.js
|
||||
packages/lib/services/interop/InteropService_Importer_Raw.js
|
||||
packages/lib/services/interop/InteropService_Importer_Raw.test.js
|
||||
packages/lib/services/interop/Module.js
|
||||
packages/lib/services/interop/Module.test.js
|
||||
packages/lib/services/interop/types.js
|
||||
packages/lib/services/joplinServer/personalizedUserContentBaseUrl.js
|
||||
packages/lib/services/keychain/KeychainService.js
|
||||
@@ -758,10 +744,7 @@ packages/lib/services/synchronizer/syncInfoUtils.test.js
|
||||
packages/lib/services/synchronizer/synchronizer_LockHandler.test.js
|
||||
packages/lib/services/synchronizer/synchronizer_MigrationHandler.test.js
|
||||
packages/lib/services/synchronizer/tools.js
|
||||
packages/lib/services/synchronizer/utils/handleConflictAction.js
|
||||
packages/lib/services/synchronizer/utils/handleSyncStartupOperation.js
|
||||
packages/lib/services/synchronizer/utils/resourceRemotePath.js
|
||||
packages/lib/services/synchronizer/utils/syncDeleteStep.js
|
||||
packages/lib/services/synchronizer/utils/types.js
|
||||
packages/lib/shim.js
|
||||
packages/lib/testing/syncTargetUtils.js
|
||||
@@ -859,7 +842,6 @@ packages/tools/buildServerDocker.js
|
||||
packages/tools/buildServerDocker.test.js
|
||||
packages/tools/bundleDefaultPlugins.js
|
||||
packages/tools/bundleDefaultPlugins.test.js
|
||||
packages/tools/checkIgnoredFiles.js
|
||||
packages/tools/checkLibPaths.js
|
||||
packages/tools/checkLibPaths.test.js
|
||||
packages/tools/convertThemesToCss.js
|
||||
@@ -879,7 +861,6 @@ packages/tools/spellcheck.js
|
||||
packages/tools/tagServerLatest.js
|
||||
packages/tools/tool-utils.js
|
||||
packages/tools/update-readme-download.js
|
||||
packages/tools/update-readme-download.test.js
|
||||
packages/tools/update-readme-sponsors.js
|
||||
packages/tools/updateMarkdownDoc.js
|
||||
packages/tools/utils/discourse.js
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
diff --git a/templates/nsis/include/allowOnlyOneInstallerInstance.nsh b/templates/nsis/include/allowOnlyOneInstallerInstance.nsh
|
||||
index a1fd1875d852ff69c087a3103eff827c20d37ca5..5222614ddad3276876857e7a9dde4017a6b9fc85 100644
|
||||
--- a/templates/nsis/include/allowOnlyOneInstallerInstance.nsh
|
||||
+++ b/templates/nsis/include/allowOnlyOneInstallerInstance.nsh
|
||||
@@ -42,7 +42,7 @@
|
||||
${nsProcess::FindProcess} "${_FILE}" ${_ERR}
|
||||
!else
|
||||
# find process owned by current user
|
||||
- nsExec::Exec `cmd /c tasklist /FI "USERNAME eq %USERNAME%" /FI "IMAGENAME eq ${_FILE}" | %SYSTEMROOT%\System32\find.exe "${_FILE}"`
|
||||
+ nsExec::Exec `cmd /c tasklist /FI "USERNAME eq %USERNAME%" /FI "PID ne $pid" /FI "IMAGENAME eq ${_FILE}" | %SYSTEMROOT%\System32\find.exe "${_FILE}"`
|
||||
Pop ${_ERR}
|
||||
!endif
|
||||
!macroend
|
||||
@@ -24,9 +24,3 @@ logFilters:
|
||||
# eg "string.prototype.matchall@npm:4.0.8 can't be found in the cache and will be fetched from the remote registry"
|
||||
- code: YN0013
|
||||
level: discard
|
||||
|
||||
# To avoid the error "The remote archive doesn't match the expected checksum".
|
||||
# Yarn offers no way to understand what the issue is when it happens, or what
|
||||
# needs to be done so it's just security theater that we have no choice but to
|
||||
# disable.
|
||||
checksumBehavior: update
|
||||
|
||||
@@ -11,17 +11,11 @@
|
||||
--variationPrimary-VaraintDark: #072445;
|
||||
--variationPrimary-VaraintWhite: #f8f9fa;
|
||||
--variationPrimary-VaraintDarkGray: #212529;
|
||||
--variationAccent-VaraintLightBlue: #F7FBFF;
|
||||
--variationAccent-VaraintLightBlue: #d9e8f8;
|
||||
}
|
||||
* {
|
||||
font-family: "Montserrat", sans-serif;
|
||||
}
|
||||
|
||||
h2 {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0.7em;
|
||||
}
|
||||
|
||||
#mobile_icon_logo{
|
||||
display: none;
|
||||
@@ -107,15 +101,14 @@
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
h1.main-logo {
|
||||
header .container nav h1.main-logo {
|
||||
|
||||
font-weight: 600;
|
||||
font-size: 48px;
|
||||
color: var(--primary-DarkBlue);
|
||||
color: var(--primary-White);
|
||||
text-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #ddd;
|
||||
line-height: 1.3em;
|
||||
padding-bottom: 0.5em;
|
||||
text-align: right;
|
||||
}
|
||||
@media screen and (max-width: 992px) {
|
||||
header .container nav img {
|
||||
@@ -214,7 +207,7 @@
|
||||
font-weight: 700;
|
||||
font-size: 40px;
|
||||
color: var(--primary-DarkBlue);
|
||||
/* margin-bottom: 45px; */
|
||||
margin-bottom: 45px;
|
||||
}
|
||||
@media screen and (max-width: 767px) {
|
||||
.joplin__logos h2.main-logo {
|
||||
@@ -230,20 +223,16 @@
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.joplin__logos > .wrapper {
|
||||
padding: 40px 0 45px 0;
|
||||
padding: 42px 59px 48px 66px;
|
||||
margin: 40px 45px;
|
||||
/* width: calc(100% - 6.5vw); */
|
||||
width: calc(100% - 6.5vw);
|
||||
border: 1px solid var(--variationPrimary-VaraintDarkGray);
|
||||
border-radius: 5px;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
@media screen and (max-width: 1440px) {
|
||||
.joplin__logos > .wrapper {
|
||||
padding: 40px 0 45px 0;
|
||||
margin: 40px auto;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.joplin__logos .wrapper .para-1 {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 94 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
@@ -1,34 +1,4 @@
|
||||
<?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 May 2023 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 08 May 2023 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 2.11]]></title><description><![CDATA[<h2>Desktop<a name="desktop" href="#desktop" class="heading-anchor">🔗</a></h2>
|
||||
<h3>Add support for plugin user data<a name="add-support-for-plugin-user-data" href="#add-support-for-plugin-user-data" class="heading-anchor">🔗</a></h3>
|
||||
<p>Developers of plugins now have the ability to associate additional information with notes, notebooks, and tags. This data is then synchronized across multiple devices. For instance, it is possible to envision attaching OCR data to an image or incorporating plugin-specific parameters to a note, which can subsequently be synchronized across all devices.</p>
|
||||
<h3>Improved end-to-end encryption<a name="improved-end-to-end-encryption" href="#improved-end-to-end-encryption" class="heading-anchor">🔗</a></h3>
|
||||
<p>End-to-end encryption (E2EE) plays a vital role in safeguarding your data privacy. Our aim is to make it effortlessly user-friendly, seamlessly integrated, and possibly enabled by default.</p>
|
||||
<p>To achieve this objective, we have implemented several enhancements in this version. These improvements encompass more streamlined management of encryption keys and simplified setup processes for both new and existing devices utilising end-to-end encryption.</p>
|
||||
<p>Moreover, we have upgraded the encryption algorithm from AES-128 to AES-256, ensuring heightened security for your data in the long run.</p>
|
||||
<h3>Export a Mermaid graph<a name="export-a-mermaid-graph" href="#export-a-mermaid-graph" class="heading-anchor">🔗</a></h3>
|
||||
<p>There is now a context menu on Mermaid graphs which you can use to export your graph as PNG, SVG, or by copying the vector data to the clipboard.</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230711-mermaid-export.png" alt=""></p>
|
||||
<h3>Auto-detect language on startup<a name="auto-detect-language-on-startup" href="#auto-detect-language-on-startup" class="heading-anchor">🔗</a></h3>
|
||||
<p>A small but useful change - the application language is now automatically detected startup. Of course, if needed you can later change it in the configuration screen.</p>
|
||||
<h2>Android<a name="android" href="#android" class="heading-anchor">🔗</a></h2>
|
||||
<p>As always most of these changes also apply to the mobile applications, such as the improved end-to-end encryption support, language auto-detection, and various bug fixes and improvements. But besides those, we also have several mobile-specific updates:</p>
|
||||
<h3>Support for offline voice typing<a name="support-for-offline-voice-typing" href="#support-for-offline-voice-typing" class="heading-anchor">🔗</a></h3>
|
||||
<p>The Android mobile app now support offline voice typing - it means your voice recording does not need to be uploaded to Google or Samsung server - it all takes place on your device for added security. To make use of it, open any note, tap on the three dots menu, then "Voice typing".</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230711-voice-typing-1.png" alt=""></p>
|
||||
<p>The first time, the app will need to download the language files to your device:</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230711-voice-typing-2.png" alt=""></p>
|
||||
<p>Once they are there, simply record your message and it will appear in the note:</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230711-voice-typing-3.png" alt=""></p>
|
||||
<h3>Create and move sub-notebooks<a name="create-and-move-sub-notebooks" href="#create-and-move-sub-notebooks" class="heading-anchor">🔗</a></h3>
|
||||
<p>@jcgurango implemented support for creating sub-notebooks, and moving notebooks around in the mobile app. To do so, long press on any notebook and press "Edit". You can now choose to move it under any other notebook using the provided dropdown list.</p>
|
||||
<h2>Full changelog<a name="full-changelog" href="#full-changelog" class="heading-anchor">🔗</a></h2>
|
||||
<p>This is just an overview of the main features. The full changelog is available there:</p>
|
||||
<ul>
|
||||
<li>Desktop: <a href="https://joplinapp.org/changelog">https://joplinapp.org/changelog</a></li>
|
||||
<li>Android: <a href="https://joplinapp.org/changelog_android">https://joplinapp.org/changelog_android</a></li>
|
||||
<li>iOS: <a href="https://joplinapp.org/changelog_ios">https://joplinapp.org/changelog_ios</a></li>
|
||||
</ul>
|
||||
]]></description><link>https://joplinapp.org/news/20230508-release-2-11/</link><guid isPermaLink="false">20230508-release-2-11</guid><pubDate>Mon, 08 May 2023 00:00:00 GMT</pubDate><twitter-text>What's new in Joplin 2.11</twitter-text></item><item><title><![CDATA[What's new in Joplin 2.10]]></title><description><![CDATA[<p>Great news! Joplin 2.10 is here and we've made some amazing improvements and bug fixes, with a focus on the mobile app.</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>Mon, 08 May 2023 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 08 May 2023 00:00:00 GMT</pubDate><item><title><![CDATA[What's new in Joplin 2.10]]></title><description><![CDATA[<p>Great news! Joplin 2.10 is here and we've made some amazing improvements and bug fixes, with a focus on the mobile app.</p>
|
||||
<h1>New design for "New note" and "New to-do" buttons<a name="new-design-for-new-note-and-new-to-do-buttons" href="#new-design-for-new-note-and-new-to-do-buttons" class="heading-anchor">🔗</a></h1>
|
||||
<p>We're excited to announce that we've made it even easier to create new notes and to-do lists with new designs for the "New note" and "New to-do" buttons.</p>
|
||||
<p>If there is enough space, the button labels will be shown in full:</p>
|
||||
@@ -334,4 +304,13 @@
|
||||
<p>As always the mobile apps (to be released soon) benefit from several of the above changes since they share the same codebase as the desktop app.</p>
|
||||
<p>Specific to the mobile version 2.5 are some improvements to the beta editor - in particular the layout has been cleaned up, and the first word of sentences is now automatically capitalised, which makes typing notes easier. If you haven't tried the beta editor yet, you can enable it from the Configuration screen.</p>
|
||||
<p>The full changelog is available there: <a href="https://joplinapp.org/changelog/">https://joplinapp.org/changelog/</a></p>
|
||||
]]></description><link>https://joplinapp.org/news/20211031-115215/</link><guid isPermaLink="false">20211031-115215</guid><pubDate>Sun, 31 Oct 2021 11:52:15 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
|
||||
]]></description><link>https://joplinapp.org/news/20211031-115215/</link><guid isPermaLink="false">20211031-115215</guid><pubDate>Sun, 31 Oct 2021 11:52:15 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA["Certificate has expired" error with Joplin Cloud, and workaround]]></title><description><![CDATA[<p>Some of you might be experiencing an error "Certificate has expired" when synchronising with Joplin Cloud (and possibly other services) when using the desktop application.</p>
|
||||
<p>This is due to Let's Encrypt root certificate that expired on 30 September, and the new method they are using is not compatible with the Joplin desktop application.</p>
|
||||
<p>This actually affects thousands of applications, not just Joplin, so various solutions are being considered right now and hopefully a fix will be available relatively soon.</p>
|
||||
<p>For now, as a workaround, you can simply check "<strong>Ignore TLS certificate errors</strong>" in <strong>Configuration > Synchronisation > Advanced Options</strong></p>
|
||||
<p>I will let you know as soon as a fix is available so that you can clear that option.</p>
|
||||
<p>More info:</p>
|
||||
<p>- <a href="https://community.letsencrypt.org/t/issues-with-electron-and-expired-root/160991">Issue with Electron and expired root</a> on Let's Encrypt</p>
|
||||
<p>- <a href="https://github.com/electron/electron/issues/31212">Let's Encrypt root CA isn't working properly</a> on Electron GitHub repository</p>
|
||||
<p><strong>Update:</strong> I have implemented a temporary fix on Joplin Cloud which should solve the issue for now. If you're still having some issues please let me know. An updated desktop app will be available later on with a more permanent fix.</p>
|
||||
]]></description><link>https://joplinapp.org/news/20210930-163458/</link><guid isPermaLink="false">20210930-163458</guid><pubDate>Thu, 30 Sep 2021 16:34:58 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
|
||||
@@ -1,7 +1,6 @@
|
||||
<!-- Logos Section -->
|
||||
<section class="joplin__logos">
|
||||
<div class="container">
|
||||
<h1 class="main-logo">Joplin brand guidelines</h1>
|
||||
<h2 class="main-logo">Logos</h2>
|
||||
<p class="joplin__para">
|
||||
All the variations of Joplin logo are all front facing. <br />
|
||||
@@ -15,10 +14,10 @@
|
||||
<div class="row align-items-center justify-content-tablet-end gy-5 p-row">
|
||||
<div class="col-xxl-4 col-xl-4 col-lg-6 col-md-6 col-sm-12">
|
||||
<div class="box">
|
||||
<h3 class="joplin__tagline1">Lockup logo</h3>
|
||||
<h3 class="joplin__tagline1">Lockup logo</h2>
|
||||
<p class="joplin__para">
|
||||
Joplin lockup is the main logo. It is used for
|
||||
products and marketing communications. To ensure
|
||||
Joplin lockup is the company's main logo. It is used for
|
||||
corporate materials and marketing communications. To ensure
|
||||
sufficient contrast and readability, two following versions of
|
||||
the lockup are provided.
|
||||
</p>
|
||||
@@ -57,7 +56,7 @@
|
||||
<div class="row align-items-center gy-5 p-row justify-between">
|
||||
<div class="col-xxl-6 col-xl-6 col-lg-12 col-md-12 col-sm-12">
|
||||
<div class="box">
|
||||
<h3 class="joplin__tagline1">Brandmark logo</h3>
|
||||
<h3 class="joplin__tagline1">Brandmark logo</h2>
|
||||
<div class="box-wrapper">
|
||||
<img
|
||||
src="{{imageBaseUrl}}/brand/J logo transparent background 1.png"
|
||||
@@ -67,7 +66,7 @@
|
||||
The classic brandmark icon is often used on a bright
|
||||
background to help increase the contrast. It is a reduced
|
||||
version of a logo which should be used in limited spacing or
|
||||
when using a full name is not necessary.
|
||||
when using a full company name is not necessary.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,6 +121,7 @@
|
||||
<div class="info">
|
||||
<p class="joplin__para">Dark blue</p>
|
||||
<p class="joplin__para">HEX - 043873</p>
|
||||
<p class="joplin__para">RGB - 4, 56, 115</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="circle-wrapper">
|
||||
@@ -129,6 +129,7 @@
|
||||
<div class="info">
|
||||
<p class="joplin__para">White</p>
|
||||
<p class="joplin__para">HEX - FFFFFF</p>
|
||||
<p class="joplin__para">RGB - 255, 255,</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="circle-wrapper">
|
||||
@@ -136,6 +137,7 @@
|
||||
<div class="info">
|
||||
<p class="joplin__para">Dark gray</p>
|
||||
<p class="joplin__para">HEX - 32363F</p>
|
||||
<p class="joplin__para">RGB - 50, 54, 63</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -149,6 +151,7 @@
|
||||
<div class="info">
|
||||
<p class="joplin__para">Light blue</p>
|
||||
<p class="joplin__para">HEX - 4F9CF9</p>
|
||||
<p class="joplin__para">RGB - 79, 156, 249</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="circle-wrapper">
|
||||
@@ -156,6 +159,7 @@
|
||||
<div class="info">
|
||||
<p class="joplin__para">Yellow</p>
|
||||
<p class="joplin__para">HEX - FFE492</p>
|
||||
<p class="joplin__para">RGB - 255, 228, 146</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -176,6 +180,7 @@
|
||||
<div class="info">
|
||||
<p class="joplin__para">Variant dark</p>
|
||||
<p class="joplin__para">HEX - 072445</p>
|
||||
<p class="joplin__para">RGB - 7, 36, 69</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="circle-wrapper">
|
||||
@@ -183,6 +188,7 @@
|
||||
<div class="info">
|
||||
<p class="joplin__para">Variant white</p>
|
||||
<p class="joplin__para">HEX - F8F9FA</p>
|
||||
<p class="joplin__para">RGB - 248, 249,</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="circle-wrapper">
|
||||
@@ -190,6 +196,7 @@
|
||||
<div class="info">
|
||||
<p class="joplin__para">Variant dark gray</p>
|
||||
<p class="joplin__para">HEX - 212529</p>
|
||||
<p class="joplin__para">RGB - 33, 37, 41</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -205,7 +212,8 @@
|
||||
<div class="circle variant-light-blue"></div>
|
||||
<div class="info">
|
||||
<p class="joplin__para">Variant light blue</p>
|
||||
<p class="joplin__para">HEX - F7FBFF</p>
|
||||
<p class="joplin__para">HEX - D9E8F8</p>
|
||||
<p class="joplin__para">RGB - 217, 232,</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -242,10 +250,10 @@
|
||||
<div class="box">
|
||||
<h2>Imagery</h2>
|
||||
<p class="joplin__para">
|
||||
We uses vector art illustration . It ensures high
|
||||
The company uses vector art illustration . It ensures high
|
||||
quality images in any scale, large or small. All the
|
||||
illustrations come from storyset.com and are personalised to
|
||||
match the Joplin's colours.
|
||||
match the company's colours.
|
||||
</p>
|
||||
</div>
|
||||
<div class="box">
|
||||
@@ -297,7 +305,7 @@
|
||||
<b>genuine</b> in engaging with our users and Joplin community.
|
||||
</p>
|
||||
<p class="joplin__para">
|
||||
As an open-source project we stay
|
||||
As a company providing open-source product we stay
|
||||
<b>open-minded</b> and <b>enthusiastic</b> about new ideas and
|
||||
solutions. At the same time we continue to be
|
||||
<b>professional</b> to honour the trust the users place in us.
|
||||
@@ -319,14 +327,13 @@ const ctx = document.getElementById("chart1");
|
||||
const myChart = new Chart(ctx, {
|
||||
type: "pie",
|
||||
data: {
|
||||
labels: ["Dark blue 40%", "Gray 20%", "White 20%", "Light blue 10%", "Yellow 7%", "Dark gray 3%"],
|
||||
labels: ["Dark blue 60%", "White 20%", "light blue 10%", "yellow 7%", "dark gray 3%"],
|
||||
datasets: [
|
||||
{
|
||||
label: "# of Votes",
|
||||
data: [40, 20, 20, 10, 7, 3],
|
||||
data: [60, 20, 10, 7, 3],
|
||||
backgroundColor: [
|
||||
"#043873",
|
||||
"#f7fbff",
|
||||
"#ffffff",
|
||||
"#4f9cf9",
|
||||
"#ffe492",
|
||||
@@ -337,11 +344,6 @@ const myChart = new Chart(ctx, {
|
||||
},
|
||||
options: {
|
||||
layout: {},
|
||||
elements: {
|
||||
arc: {
|
||||
borderColor: '#cccccc',
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
@@ -358,14 +360,13 @@ const ctx2 = document.getElementById("chart2");
|
||||
const myChart2 = new Chart(ctx2, {
|
||||
type: "pie",
|
||||
data: {
|
||||
labels: ["White 40%", "Gray 20%", "Dark blue 20%", "Light blue 10%", "Dark gray 7%", "Yellow 3%"],
|
||||
labels: ["White 60%", "Dark 20%", "light blue 10%", "dark gray 7%", "yellow 3%"],
|
||||
datasets: [
|
||||
{
|
||||
label: "# of Votes",
|
||||
data: [40, 20, 20, 10, 7, 3],
|
||||
data: [60, 20, 10, 7, 3],
|
||||
backgroundColor: [
|
||||
"#ffffff",
|
||||
"#f7fbff",
|
||||
"#043873",
|
||||
"#4f9cf9",
|
||||
"#212529",
|
||||
@@ -376,11 +377,6 @@ const myChart2 = new Chart(ctx2, {
|
||||
},
|
||||
options: {
|
||||
layout: {},
|
||||
elements: {
|
||||
arc: {
|
||||
borderColor: '#cccccc',
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
|
||||
@@ -96,8 +96,6 @@
|
||||
|
||||
console.info('Creating Stripe session for price:', priceId, 'Promotion code:', promotionCode);
|
||||
|
||||
const source = localStorage.getItem('source');
|
||||
|
||||
return fetch("{{{stripeConfig.webhookBaseUrl}}}/stripe/createCheckoutSession", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -107,7 +105,6 @@
|
||||
priceId,
|
||||
promotionCode,
|
||||
email: checkoutSessionUser ? checkoutSessionUser.email : '',
|
||||
source,
|
||||
})
|
||||
}).then(async function(result) {
|
||||
if (!result.ok) {
|
||||
|
||||
@@ -38,7 +38,7 @@ The install and update script supports the [following flags](https://github.com/
|
||||
|
||||
Operating System | Download | Alt. Download
|
||||
---|---|---
|
||||
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the [APK file](https://objects.joplinusercontent.com/v2.11.31/joplin-v2.11.31.apk?source=JoplinWebsite&type=New)
|
||||
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://objects.joplinusercontent.com/android-v2.9.8/joplin-v2.9.8.apk?source=JoplinWebsite&type=New) [32-bit](https://objects.joplinusercontent.com/android-v2.9.8/joplin-v2.9.8-32bit.apk?source=JoplinWebsite&type=New)
|
||||
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeIOS.png'/></a> | -
|
||||
|
||||
## Terminal application
|
||||
@@ -140,7 +140,6 @@ A community maintained list of these distributions can be found here: [Unofficia
|
||||
- [Server: File URL Format](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_file_url_format.md)
|
||||
- [Server: Delta Sync](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_delta_sync.md)
|
||||
- [Server: Sharing](https://github.com/laurent22/joplin/blob/dev/readme/spec/server_sharing.md)
|
||||
- [Read-only items](https://github.com/laurent22/joplin/blob/dev/readme/spec/read_only.md)
|
||||
|
||||
- Google Summer of Code 2022
|
||||
|
||||
|
||||
@@ -13,6 +13,5 @@ module.exports = {
|
||||
'*.{js,jsx,ts,tsx}': [
|
||||
'yarn run linter-precommit',
|
||||
'yarn run checkLibPaths',
|
||||
'node packages/tools/checkIgnoredFiles.js',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"husky": "3.1.0",
|
||||
"lerna": "3.22.1",
|
||||
"lint-staged": "13.2.2",
|
||||
"madge": "6.1.0",
|
||||
"madge": "6.0.0",
|
||||
"npm-package-json-lint": "6.4.0",
|
||||
"typescript": "5.0.2"
|
||||
},
|
||||
@@ -98,7 +98,6 @@
|
||||
"react-native-camera@4.2.1": "patch:react-native-camera@npm%3A4.2.1#./.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch",
|
||||
"react-native-vosk@0.1.12": "patch:react-native-vosk@npm%3A0.1.12#./.yarn/patches/react-native-vosk-npm-0.1.12-76b1caaae8.patch",
|
||||
"eslint@8.39.0": "patch:eslint@npm%3A8.39.0#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
|
||||
"eslint@^8.13.0": "patch:eslint@npm%3A8.39.0#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch",
|
||||
"app-builder-lib@24.4.0": "patch:app-builder-lib@npm%3A24.4.0#./.yarn/patches/app-builder-lib-npm-24.4.0-05322ff057.patch"
|
||||
"eslint@^8.13.0": "patch:eslint@npm%3A8.39.0#./.yarn/patches/eslint-npm-8.39.0-d92bace04d.patch"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,41 +252,4 @@ describe('MdToHtml', () => {
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
it('should attach source blocks to block KaTeX', async () => {
|
||||
const mdToHtml = newTestMdToHtml();
|
||||
|
||||
const katex = [
|
||||
'3 + 3',
|
||||
'\n\\int_0^1 x dx\n\n',
|
||||
'\n\\int_0^1 x dx\n3 + 3\n',
|
||||
'\n\t2^{3^4}\n\t3 + 3\n',
|
||||
'3\n4',
|
||||
];
|
||||
const surroundingTextChoices = [
|
||||
['', ''],
|
||||
['Test', ''],
|
||||
['Test', 'Test!'],
|
||||
['Test\n\n', '\n\nTest!'],
|
||||
];
|
||||
|
||||
const tests = [];
|
||||
for (const texSource of katex) {
|
||||
for (const [start, end] of surroundingTextChoices) {
|
||||
tests.push([texSource, `${start}\n$$${texSource}$$\n${end}`]);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [tex, input] of tests) {
|
||||
const html = await mdToHtml.render(input, null, { bodyOnly: true });
|
||||
|
||||
const opening = '<pre class="joplin-source" data-joplin-language="katex" data-joplin-source-open="$$ " data-joplin-source-close=" $$ ">';
|
||||
const closing = '</pre>';
|
||||
|
||||
// Remove any single leading and trailing newlines, those are included in data-joplin-source-open
|
||||
// and data-joplin-source-close.
|
||||
const trimmedTex = tex.replace(/^[\n]/, '').replace(/[\n]$/, '');
|
||||
expect(html.html).toContain(opening + trimmedTex + closing);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE en-export SYSTEM "http://xml.evernote.com/pub/evernote-export4.dtd">
|
||||
<en-export export-date="20230809T080314Z" application="Evernote" version="10.58.8">
|
||||
<note>
|
||||
<title>Here is a simple test</title>
|
||||
<created>20230709T080219Z</created>
|
||||
<updated>20230709T080302Z</updated>
|
||||
<note-attributes>
|
||||
</note-attributes>
|
||||
<content>
|
||||
<![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div>a</div><div><br/></div><div>b</div><div><br/></div><div>List 1</div><div style="--en-task-group:true; --en-id:9876cc26-ebd0-482d-bb36-603e2c0512d0;--en-content-hash:7e7703c4ce2d1805937a6024e5f7b426;-webkit-user-modify:read-only;-moz-user-modify:read-only;user-modify:read-only;border-radius:3px;border:1px solid rgba(182,182,182,0.09);background:rgba(174,174,174,0.09);overflow:hidden;color:#868686"><div style="background:rgba(182,182,182,0.09) no-repeat 6px 6px url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzg2ODY4NiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE5LjcyNTEgMjBDMjAuMTgxIDIwIDIwLjYwMTEgMTkuNzU2IDIwLjgyOSAxOS4zNkMyMS4wNTcgMTguOTY0IDIxLjA1NyAxOC40NzYgMjAuODI5IDE4LjA4TDEzLjEwMzEgNC42Mzk5OUMxMi44NzYgNC4yNDM5OSAxMi40NTUxIDQgMTIgNEMxMS41NDQ5IDQgMTEuMTI0IDQuMjQzOTkgMTAuODk2MSA0LjYzOTk5TDMuMTcwOTggMTguMDhDMi45NDMwMSAxOC40NzYgMi45NDMwMSAxOC45NjQgMy4xNzA5OCAxOS4zNkMzLjM5ODEgMTkuNzU2IDMuODE4OTMgMjAgNC4yNzQwOCAyMEgxOS43MjUxWk00Ljg2NDg5IDE4LjRMMTIgNS45ODc5OEwxOS4xMzUxIDE4LjRINC44NjQ4OVoiLz4KPHBhdGggZD0iTTEyLjc1IDEwLjU3NTJDMTIuNzUgMTAuMTYxIDEyLjQxNDIgOS44MjUyMyAxMiA5LjgyNTIzQzExLjU4NTggOS44MjUyMyAxMS4yNSAxMC4xNjEgMTEuMjUgMTAuNTc1MlYxMy4yOTc5QzExLjI1IDEzLjcxMjEgMTEuNTg1OCAxNC4wNDc5IDEyIDE0LjA0NzlDMTIuNDE0MiAxNC4wNDc5IDEyLjc1IDEzLjcxMjEgMTIuNzUgMTMuMjk3OVYxMC41NzUyWiIvPgo8cGF0aCBkPSJNMTIuODc1IDE1Ljk1MTFDMTIuODc1IDE2LjQzNDQgMTIuNDgzMiAxNi44MjYxIDEyIDE2LjgyNjFDMTEuNTE2OCAxNi44MjYxIDExLjEyNSAxNi40MzQ0IDExLjEyNSAxNS45NTExQzExLjEyNSAxNS40Njc5IDExLjUxNjggMTUuMDc2MSAxMiAxNS4wNzYxQzEyLjQ4MzIgMTUuMDc2MSAxMi44NzUgMTUuNDY3OSAxMi44NzUgMTUuOTUxMVoiLz4KPC9zdmc+Cg==");padding:8px 38px;font-weight:600">Content not supported</div><div style="padding:2px 6px;margin: 1em">This block is a placeholder for Tasks, which has been officially released on the newest version of Evernote and is no longer supported on this version. Deleting or moving this block may cause unexpected behavior in newer versions of Evernote.</div></div><div>List 2</div><div style="--en-task-group:true; --en-id:b89e9766-5afc-47b8-9c54-e42962d69421;--en-content-hash:85f6897ebdd0d3d12ba8fcc3ce6b8e56;-webkit-user-modify:read-only;-moz-user-modify:read-only;user-modify:read-only;border-radius:3px;border:1px solid rgba(182,182,182,0.09);background:rgba(174,174,174,0.09);overflow:hidden;color:#868686"><div style="background:rgba(182,182,182,0.09) no-repeat 6px 6px url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzg2ODY4NiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE5LjcyNTEgMjBDMjAuMTgxIDIwIDIwLjYwMTEgMTkuNzU2IDIwLjgyOSAxOS4zNkMyMS4wNTcgMTguOTY0IDIxLjA1NyAxOC40NzYgMjAuODI5IDE4LjA4TDEzLjEwMzEgNC42Mzk5OUMxMi44NzYgNC4yNDM5OSAxMi40NTUxIDQgMTIgNEMxMS41NDQ5IDQgMTEuMTI0IDQuMjQzOTkgMTAuODk2MSA0LjYzOTk5TDMuMTcwOTggMTguMDhDMi45NDMwMSAxOC40NzYgMi45NDMwMSAxOC45NjQgMy4xNzA5OCAxOS4zNkMzLjM5ODEgMTkuNzU2IDMuODE4OTMgMjAgNC4yNzQwOCAyMEgxOS43MjUxWk00Ljg2NDg5IDE4LjRMMTIgNS45ODc5OEwxOS4xMzUxIDE4LjRINC44NjQ4OVoiLz4KPHBhdGggZD0iTTEyLjc1IDEwLjU3NTJDMTIuNzUgMTAuMTYxIDEyLjQxNDIgOS44MjUyMyAxMiA5LjgyNTIzQzExLjU4NTggOS44MjUyMyAxMS4yNSAxMC4xNjEgMTEuMjUgMTAuNTc1MlYxMy4yOTc5QzExLjI1IDEzLjcxMjEgMTEuNTg1OCAxNC4wNDc5IDEyIDE0LjA0NzlDMTIuNDE0MiAxNC4wNDc5IDEyLjc1IDEzLjcxMjEgMTIuNzUgMTMuMjk3OVYxMC41NzUyWiIvPgo8cGF0aCBkPSJNMTIuODc1IDE1Ljk1MTFDMTIuODc1IDE2LjQzNDQgMTIuNDgzMiAxNi44MjYxIDEyIDE2LjgyNjFDMTEuNTE2OCAxNi44MjYxIDExLjEyNSAxNi40MzQ0IDExLjEyNSAxNS45NTExQzExLjEyNSAxNS40Njc5IDExLjUxNjggMTUuMDc2MSAxMiAxNS4wNzYxQzEyLjQ4MzIgMTUuMDc2MSAxMi44NzUgMTUuNDY3OSAxMi44NzUgMTUuOTUxMVoiLz4KPC9zdmc+Cg==");padding:8px 38px;font-weight:600">Content not supported</div><div style="padding:2px 6px;margin: 1em">This block is a placeholder for Tasks, which has been officially released on the newest version of Evernote and is no longer supported on this version. Deleting or moving this block may cause unexpected behavior in newer versions of Evernote.</div></div></en-note> ]]>
|
||||
</content>
|
||||
<task>
|
||||
<title>Clara</title>
|
||||
<created>20230709T080237Z</created>
|
||||
<updated>20230709T080300Z</updated>
|
||||
<taskStatus>completed</taskStatus>
|
||||
<taskFlag>false</taskFlag>
|
||||
<sortWeight>J</sortWeight>
|
||||
<noteLevelID>7ab2b90f-4aed-405f-8541-cb56132a2e1a</noteLevelID>
|
||||
<taskGroupNoteLevelID>9876cc26-ebd0-482d-bb36-603e2c0512d0</taskGroupNoteLevelID>
|
||||
<dueDate>2757600913T000000Z</dueDate>
|
||||
<statusUpdated>20230709T080237Z</statusUpdated>
|
||||
<creator>73671305</creator>
|
||||
<lastEditor>73671305</lastEditor>
|
||||
</task>
|
||||
<task>
|
||||
<title>Bob</title>
|
||||
<created>20230709T080237Z</created>
|
||||
<updated>20230709T080250Z</updated>
|
||||
<taskStatus>open</taskStatus>
|
||||
<taskFlag>false</taskFlag>
|
||||
<sortWeight>B</sortWeight>
|
||||
<noteLevelID>98185f99-1779-4af3-988b-4af054a2c8c0</noteLevelID>
|
||||
<taskGroupNoteLevelID>9876cc26-ebd0-482d-bb36-603e2c0512d0</taskGroupNoteLevelID>
|
||||
<dueDate>2757600913T000000Z</dueDate>
|
||||
<statusUpdated>20230709T080237Z</statusUpdated>
|
||||
<creator>73671305</creator>
|
||||
<lastEditor>73671305</lastEditor>
|
||||
</task>
|
||||
<task>
|
||||
<title>Jeff</title>
|
||||
<created>20230709T080300Z</created>
|
||||
<updated>20230709T080302Z</updated>
|
||||
<taskStatus>open</taskStatus>
|
||||
<taskFlag>false</taskFlag>
|
||||
<sortWeight>B</sortWeight>
|
||||
<noteLevelID>8458b06b-0007-4c3d-b61b-87abe15f55b8</noteLevelID>
|
||||
<taskGroupNoteLevelID>b89e9766-5afc-47b8-9c54-e42962d69421</taskGroupNoteLevelID>
|
||||
<dueDate>2757600913T000000Z</dueDate>
|
||||
<statusUpdated>20230709T080300Z</statusUpdated>
|
||||
<creator>73671305</creator>
|
||||
<lastEditor>73671305</lastEditor>
|
||||
</task>
|
||||
</note>
|
||||
</en-export>
|
||||
@@ -1,12 +0,0 @@
|
||||
a
|
||||
|
||||
b
|
||||
|
||||
List 1
|
||||
|
||||
- [x] Clara
|
||||
- [ ] Bob
|
||||
|
||||
List 2
|
||||
|
||||
- [ ] Jeff
|
||||
@@ -1,17 +0,0 @@
|
||||
<en-note>
|
||||
<div>a</div>
|
||||
<div><br/></div>
|
||||
<div>b</div>
|
||||
<div><br/></div>
|
||||
<div>List 1</div>
|
||||
<div style="--en-task-group:true; --en-id:9876cc26-ebd0-482d-bb36-603e2c0512d0;--en-content-hash:7e7703c4ce2d1805937a6024e5f7b426;-webkit-user-modify:read-only;-moz-user-modify:read-only;user-modify:read-only;border-radius:3px;border:1px solid rgba(182,182,182,0.09);background:rgba(174,174,174,0.09);overflow:hidden;color:#868686">
|
||||
<div style="background:rgba(182,182,182,0.09) no-repeat 6px 6px url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzg2ODY4NiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE5LjcyNTEgMjBDMjAuMTgxIDIwIDIwLjYwMTEgMTkuNzU2IDIwLjgyOSAxOS4zNkMyMS4wNTcgMTguOTY0IDIxLjA1NyAxOC40NzYgMjAuODI5IDE4LjA4TDEzLjEwMzEgNC42Mzk5OUMxMi44NzYgNC4yNDM5OSAxMi40NTUxIDQgMTIgNEMxMS41NDQ5IDQgMTEuMTI0IDQuMjQzOTkgMTAuODk2MSA0LjYzOTk5TDMuMTcwOTggMTguMDhDMi45NDMwMSAxOC40NzYgMi45NDMwMSAxOC45NjQgMy4xNzA5OCAxOS4zNkMzLjM5ODEgMTkuNzU2IDMuODE4OTMgMjAgNC4yNzQwOCAyMEgxOS43MjUxWk00Ljg2NDg5IDE4LjRMMTIgNS45ODc5OEwxOS4xMzUxIDE4LjRINC44NjQ4OVoiLz4KPHBhdGggZD0iTTEyLjc1IDEwLjU3NTJDMTIuNzUgMTAuMTYxIDEyLjQxNDIgOS44MjUyMyAxMiA5LjgyNTIzQzExLjU4NTggOS44MjUyMyAxMS4yNSAxMC4xNjEgMTEuMjUgMTAuNTc1MlYxMy4yOTc5QzExLjI1IDEzLjcxMjEgMTEuNTg1OCAxNC4wNDc5IDEyIDE0LjA0NzlDMTIuNDE0MiAxNC4wNDc5IDEyLjc1IDEzLjcxMjEgMTIuNzUgMTMuMjk3OVYxMC41NzUyWiIvPgo8cGF0aCBkPSJNMTIuODc1IDE1Ljk1MTFDMTIuODc1IDE2LjQzNDQgMTIuNDgzMiAxNi44MjYxIDEyIDE2LjgyNjFDMTEuNTE2OCAxNi44MjYxIDExLjEyNSAxNi40MzQ0IDExLjEyNSAxNS45NTExQzExLjEyNSAxNS40Njc5IDExLjUxNjggMTUuMDc2MSAxMiAxNS4wNzYxQzEyLjQ4MzIgMTUuMDc2MSAxMi44NzUgMTUuNDY3OSAxMi44NzUgMTUuOTUxMVoiLz4KPC9zdmc+Cg==");padding:8px 38px;font-weight:600">Content not supported</div>
|
||||
<div style="padding:2px 6px;margin: 1em">This block is a placeholder for Tasks, which has been officially released on the newest version of Evernote and is no longer supported on this version. Deleting or moving this block may cause unexpected behavior in newer versions of Evernote.</div>
|
||||
</div>
|
||||
<div>List 2</div>
|
||||
<div style="--en-task-group:true; --en-id:b89e9766-5afc-47b8-9c54-e42962d69421;--en-content-hash:85f6897ebdd0d3d12ba8fcc3ce6b8e56;-webkit-user-modify:read-only;-moz-user-modify:read-only;user-modify:read-only;border-radius:3px;border:1px solid rgba(182,182,182,0.09);background:rgba(174,174,174,0.09);overflow:hidden;color:#868686">
|
||||
<div style="background:rgba(182,182,182,0.09) no-repeat 6px 6px url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzg2ODY4NiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE5LjcyNTEgMjBDMjAuMTgxIDIwIDIwLjYwMTEgMTkuNzU2IDIwLjgyOSAxOS4zNkMyMS4wNTcgMTguOTY0IDIxLjA1NyAxOC40NzYgMjAuODI5IDE4LjA4TDEzLjEwMzEgNC42Mzk5OUMxMi44NzYgNC4yNDM5OSAxMi40NTUxIDQgMTIgNEMxMS41NDQ5IDQgMTEuMTI0IDQuMjQzOTkgMTAuODk2MSA0LjYzOTk5TDMuMTcwOTggMTguMDhDMi45NDMwMSAxOC40NzYgMi45NDMwMSAxOC45NjQgMy4xNzA5OCAxOS4zNkMzLjM5ODEgMTkuNzU2IDMuODE4OTMgMjAgNC4yNzQwOCAyMEgxOS43MjUxWk00Ljg2NDg5IDE4LjRMMTIgNS45ODc5OEwxOS4xMzUxIDE4LjRINC44NjQ4OVoiLz4KPHBhdGggZD0iTTEyLjc1IDEwLjU3NTJDMTIuNzUgMTAuMTYxIDEyLjQxNDIgOS44MjUyMyAxMiA5LjgyNTIzQzExLjU4NTggOS44MjUyMyAxMS4yNSAxMC4xNjEgMTEuMjUgMTAuNTc1MlYxMy4yOTc5QzExLjI1IDEzLjcxMjEgMTEuNTg1OCAxNC4wNDc5IDEyIDE0LjA0NzlDMTIuNDE0MiAxNC4wNDc5IDEyLjc1IDEzLjcxMjEgMTIuNzUgMTMuMjk3OVYxMC41NzUyWiIvPgo8cGF0aCBkPSJNMTIuODc1IDE1Ljk1MTFDMTIuODc1IDE2LjQzNDQgMTIuNDgzMiAxNi44MjYxIDEyIDE2LjgyNjFDMTEuNTE2OCAxNi44MjYxIDExLjEyNSAxNi40MzQ0IDExLjEyNSAxNS45NTExQzExLjEyNSAxNS40Njc5IDExLjUxNjggMTUuMDc2MSAxMiAxNS4wNzYxQzEyLjQ4MzIgMTUuMDc2MSAxMi44NzUgMTUuNDY3OSAxMi44NzUgMTUuOTUxMVoiLz4KPC9zdmc+Cg==");padding:8px 38px;font-weight:600">Content not supported</div>
|
||||
<div style="padding:2px 6px;margin: 1em">This block is a placeholder for Tasks, which has been officially released on the newest version of Evernote and is no longer supported on this version. Deleting or moving this block may cause unexpected behavior in newer versions of Evernote.</div>
|
||||
</div>
|
||||
</en-note>
|
||||
|
||||
@@ -1 +1 @@
|
||||
<img usemap="#test" src="https://github.com/Ry0taK.png" class="jop-noMdConv"/>
|
||||
<map name="test" class="jop-noMdConv"><area coords="0,0,1000,1000" href="#" class="jop-noMdConv"/></map><img usemap="#test" src="https://github.com/Ry0taK.png" class="jop-noMdConv"/>
|
||||
@@ -100,10 +100,7 @@ export default class ElectronAppWrapper {
|
||||
webviewTag: true,
|
||||
// We start with a hidden window, which is then made visible depending on the showTrayIcon setting
|
||||
// https://github.com/laurent22/joplin/issues/2031
|
||||
//
|
||||
// On Linux/GNOME, however, the window doesn't show correctly if show is false initially:
|
||||
// https://github.com/laurent22/joplin/issues/8256
|
||||
show: debugEarlyBugs || shim.isGNOME(),
|
||||
show: debugEarlyBugs,
|
||||
};
|
||||
|
||||
// Linux icon workaround for bug https://github.com/electron-userland/electron-builder/issues/2098
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import InteropService from '@joplin/lib/services/interop/InteropService';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import { ExportOptions, FileSystemItem } from '@joplin/lib/services/interop/types';
|
||||
import { ExportModule } from '@joplin/lib/services/interop/Module';
|
||||
import { ExportOptions, FileSystemItem, Module } from '@joplin/lib/services/interop/types';
|
||||
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
|
||||
@@ -153,7 +152,7 @@ export default class InteropServiceHelper {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
public static async export(_dispatch: Function, module: ExportModule, options: ExportNoteOptions = null) {
|
||||
public static async export(_dispatch: Function, module: Module, options: ExportNoteOptions = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
let path = null;
|
||||
|
||||
@@ -494,7 +494,7 @@ class Application extends BaseApplication {
|
||||
}, 1000 * 60 * 60);
|
||||
|
||||
if (Setting.value('startMinimized') && Setting.value('showTrayIcon')) {
|
||||
bridge().window().hide();
|
||||
// Keep it hidden
|
||||
} else {
|
||||
bridge().window().show();
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import Logger from '@joplin/lib/Logger';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import bridge from './services/bridge';
|
||||
import KvStore from '@joplin/lib/services/KvStore';
|
||||
const { fileExtension } = require('@joplin/lib/path-utils');
|
||||
import * as ArrayUtils from '@joplin/lib/ArrayUtils';
|
||||
import { CheckForUpdateOptions, extractVersionInfo, GitHubRelease } from './utils/checkForUpdatesUtils';
|
||||
const packageInfo = require('./packageInfo.js');
|
||||
const compareVersions = require('compare-versions');
|
||||
|
||||
@@ -13,6 +13,10 @@ const logger = Logger.create('checkForUpdates');
|
||||
let checkInBackground_ = false;
|
||||
let isCheckingForUpdate_ = false;
|
||||
|
||||
interface CheckForUpdateOptions {
|
||||
includePreReleases?: boolean;
|
||||
}
|
||||
|
||||
function onCheckStarted() {
|
||||
logger.info('Starting...');
|
||||
isCheckingForUpdate_ = true;
|
||||
@@ -23,7 +27,15 @@ function onCheckEnded() {
|
||||
isCheckingForUpdate_ = false;
|
||||
}
|
||||
|
||||
async function fetchLatestRelease() {
|
||||
function getMajorMinorTagName(tagName: string) {
|
||||
const s = tagName.split('.');
|
||||
s.pop();
|
||||
return s.join('.');
|
||||
}
|
||||
|
||||
async function fetchLatestRelease(options: CheckForUpdateOptions) {
|
||||
options = { includePreReleases: false, ...options };
|
||||
|
||||
const response = await shim.fetch('https://api.github.com/repos/laurent22/joplin/releases');
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -31,7 +43,89 @@ async function fetchLatestRelease() {
|
||||
throw new Error(`Cannot get latest release info: ${responseText.substr(0, 500)}`);
|
||||
}
|
||||
|
||||
return (await response.json()) as GitHubRelease[];
|
||||
const releases = await response.json();
|
||||
if (!releases.length) throw new Error('Cannot get latest release info (JSON)');
|
||||
|
||||
let release = null;
|
||||
|
||||
if (options.includePreReleases) {
|
||||
release = releases[0];
|
||||
} else {
|
||||
for (const r of releases) {
|
||||
if (!r.prerelease) {
|
||||
release = r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!release) throw new Error('Could not get tag name');
|
||||
|
||||
const version = release.tag_name.substr(1);
|
||||
|
||||
// We concatenate all the release notes of the major/minor versions
|
||||
// corresponding to the latest version. For example, if the latest version
|
||||
// is 1.8.3, we concatenate all the 1.8.x versions. This is so that no
|
||||
// matter from which version you upgrade, you always see the full changes,
|
||||
// with the latest changes being on top.
|
||||
|
||||
const fullReleaseNotes = [];
|
||||
const majorMinorTagName = getMajorMinorTagName(release.tag_name);
|
||||
|
||||
for (const release of releases) {
|
||||
if (getMajorMinorTagName(release.tag_name) === majorMinorTagName) {
|
||||
fullReleaseNotes.push(release.body.trim());
|
||||
}
|
||||
}
|
||||
|
||||
let downloadUrl = null;
|
||||
const platform = process.platform;
|
||||
for (let i = 0; i < release.assets.length; i++) {
|
||||
const asset = release.assets[i];
|
||||
let found = false;
|
||||
const ext = fileExtension(asset.name);
|
||||
if (platform === 'win32' && ext === 'exe') {
|
||||
if (shim.isPortable()) {
|
||||
found = asset.name === 'JoplinPortable.exe';
|
||||
} else {
|
||||
found = !!asset.name.match(/^Joplin-Setup-[\d.]+\.exe$/);
|
||||
}
|
||||
} else if (platform === 'darwin' && ext === 'dmg') {
|
||||
found = true;
|
||||
} else if (platform === 'linux' && ext === '.AppImage') {
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
downloadUrl = asset.browser_download_url.replace('github.com/laurent22/joplin/releases/download', 'objects.joplinusercontent.com');
|
||||
downloadUrl.concat('?source=DesktopApp&type=Update');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function cleanUpReleaseNotes(releaseNotes: string[]) {
|
||||
const lines = releaseNotes.join('\n\n* * *\n\n').split('\n');
|
||||
const output = [];
|
||||
for (const line of lines) {
|
||||
const r = line
|
||||
.replace(/\(#.* by .*\)/g, '') // Removes issue numbers and names - (#3157 by [@user](https://github.com/user))
|
||||
.replace(/\([0-9a-z]{7}\)/g, '') // Removes commits - "sync state or data (a6caa35)"
|
||||
.replace(/\(#[0-9]+\)/g, '') // Removes issue numbers - "(#4727)"
|
||||
.replace(/ {2}/g, ' ')
|
||||
.trim();
|
||||
|
||||
output.push(r);
|
||||
}
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
return {
|
||||
version: version,
|
||||
downloadUrl: downloadUrl,
|
||||
notes: cleanUpReleaseNotes(fullReleaseNotes),
|
||||
pageUrl: release.html_url,
|
||||
prerelease: release.prerelease,
|
||||
};
|
||||
}
|
||||
|
||||
function truncateText(text: string, length: number) {
|
||||
@@ -76,8 +170,7 @@ export default async function checkForUpdates(inBackground: boolean, parentWindo
|
||||
logger.info(`Checking with options ${JSON.stringify(options)}`);
|
||||
|
||||
try {
|
||||
const releases = await fetchLatestRelease();
|
||||
const release = extractVersionInfo(releases, process.platform, options);
|
||||
const release = await fetchLatestRelease(options);
|
||||
|
||||
logger.info(`Current version: ${packageInfo.version}`);
|
||||
logger.info(`Latest version: ${release.version}`);
|
||||
|
||||
@@ -23,6 +23,6 @@ export const runtime = (): CommandRuntime => {
|
||||
bridge().showErrorMessageBox(_('Error opening note in editor: %s', error.message));
|
||||
}
|
||||
},
|
||||
enabledCondition: 'oneNoteSelected && !noteIsReadOnly',
|
||||
enabledCondition: 'oneNoteSelected',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -15,6 +15,6 @@ export const runtime = (): CommandRuntime => {
|
||||
noteId = noteId || stateUtils.selectedNoteId(context.state);
|
||||
void ExternalEditWatcher.instance().stopWatching(noteId);
|
||||
},
|
||||
enabledCondition: 'oneNoteSelected && !noteIsReadOnly',
|
||||
enabledCondition: 'oneNoteSelected',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ import CommandService, { CommandRuntime, CommandDeclaration } from '@joplin/lib/
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { stateUtils } from '@joplin/lib/reducer';
|
||||
import { DesktopCommandContext } from '../services/commands/types';
|
||||
import { enabledCondition } from '../gui/NoteEditor/editorCommandDeclarations';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'toggleExternalEditing',
|
||||
@@ -23,7 +22,7 @@ export const runtime = (): CommandRuntime => {
|
||||
void CommandService.instance().execute('startExternalEditing', noteId);
|
||||
}
|
||||
},
|
||||
enabledCondition: enabledCondition(declaration.name),
|
||||
enabledCondition: 'oneNoteSelected',
|
||||
mapStateToTitle: (state: any) => {
|
||||
const noteId = stateUtils.selectedNoteId(state);
|
||||
return state.watchedNoteFiles.includes(noteId) ? _('Stop') : '';
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { useMemo, useCallback } from 'react';
|
||||
|
||||
export type DropdownOptions = Record<string, string>;
|
||||
export enum DropdownVariant {
|
||||
Default = 1,
|
||||
NoBorder,
|
||||
}
|
||||
|
||||
export interface ChangeEvent {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type ChangeEventHandler = (event: ChangeEvent)=> void;
|
||||
|
||||
interface Props {
|
||||
options: DropdownOptions;
|
||||
variant?: DropdownVariant;
|
||||
className?: string;
|
||||
onChange?: ChangeEventHandler;
|
||||
value?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const Dropdown = (props: Props) => {
|
||||
const renderOptions = () => {
|
||||
const optionComps = [];
|
||||
for (const [value, label] of Object.entries(props.options)) {
|
||||
optionComps.push(<option key={value} value={value}>{label}</option>);
|
||||
}
|
||||
return optionComps;
|
||||
};
|
||||
|
||||
const onChange = useCallback((event: any) => {
|
||||
props.onChange({ value: event.target.value });
|
||||
}, [props.onChange]);
|
||||
|
||||
const classNames = useMemo(() => {
|
||||
const variant = props.variant || DropdownVariant.Default;
|
||||
const output = [
|
||||
'dropdown-control',
|
||||
`-variant${variant}`,
|
||||
];
|
||||
if (props.className) output.push(props.className);
|
||||
return output.join(' ');
|
||||
}, [props.variant, props.className]);
|
||||
|
||||
return (
|
||||
<select disabled={props.disabled} className={classNames} onChange={onChange} value={props.value}>
|
||||
{renderOptions()}
|
||||
</select>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
.dropdown-control {
|
||||
border: 1px solid var(--joplin-border-color4);
|
||||
border-radius: 3px;
|
||||
font-size: var(--joplin-font-size)px;
|
||||
color: var(--joplin-color);
|
||||
padding: 0 8px;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--joplin-background-color4);
|
||||
min-height: 26px;
|
||||
|
||||
&.-variant2 {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
@@ -63,8 +63,6 @@ export default function(props: Props) {
|
||||
const folder: FolderEntity = {
|
||||
title: folderTitle,
|
||||
icon: Folder.serializeIcon(folderIcon),
|
||||
is_shared: 0,
|
||||
share_id: '',
|
||||
};
|
||||
|
||||
if (!isNew) folder.id = props.folderId;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import bridge from '../../../services/bridge';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
const { substrWithEllipsis } = require('@joplin/lib/string-utils');
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'deleteFolder',
|
||||
label: () => _('Delete notebook'),
|
||||
};
|
||||
|
||||
export const runtime = (): CommandRuntime => {
|
||||
return {
|
||||
execute: async (context: CommandContext, folderId: string = null) => {
|
||||
if (folderId === null) folderId = context.state.selectedFolderId;
|
||||
|
||||
const folder = await Folder.load(folderId);
|
||||
if (!folder) throw new Error(`No such folder: ${folderId}`);
|
||||
|
||||
const ok = bridge().showConfirmMessageBox(_('Delete notebook "%s"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.', substrWithEllipsis(folder.title, 0, 32)), {
|
||||
buttons: [_('Delete'), _('Cancel')],
|
||||
defaultId: 1,
|
||||
});
|
||||
if (!ok) return;
|
||||
|
||||
await Folder.delete(folderId);
|
||||
},
|
||||
enabledCondition: '!folderIsReadOnly',
|
||||
};
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import bridge from '../../../services/bridge';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'deleteNote',
|
||||
label: () => _('Delete note'),
|
||||
iconName: 'fa-times',
|
||||
};
|
||||
|
||||
export const runtime = (): CommandRuntime => {
|
||||
return {
|
||||
execute: async (context: CommandContext, noteIds: string[] = null) => {
|
||||
if (noteIds === null) noteIds = context.state.selectedNoteIds;
|
||||
|
||||
if (!noteIds.length) return;
|
||||
|
||||
const msg = await Note.deleteMessage(noteIds);
|
||||
if (!msg) return;
|
||||
|
||||
const ok = bridge().showConfirmMessageBox(msg, {
|
||||
buttons: [_('Delete'), _('Cancel')],
|
||||
defaultId: 1,
|
||||
});
|
||||
|
||||
if (!ok) return;
|
||||
await Note.batchDelete(noteIds);
|
||||
},
|
||||
enabledCondition: '!noteIsReadOnly',
|
||||
};
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'duplicateNote',
|
||||
label: () => _('Duplicate'),
|
||||
};
|
||||
|
||||
export const runtime = (): CommandRuntime => {
|
||||
return {
|
||||
execute: async (context: CommandContext, noteIds: string[] = null) => {
|
||||
if (noteIds === null) noteIds = context.state.selectedNoteIds;
|
||||
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
const note = await Note.load(noteIds[i]);
|
||||
await Note.duplicate(noteIds[i], {
|
||||
uniqueTitle: _('%s - Copy', note.title),
|
||||
});
|
||||
}
|
||||
},
|
||||
enabledCondition: '!noteIsReadOnly',
|
||||
};
|
||||
};
|
||||
@@ -1,9 +1,6 @@
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
import * as addProfile from './addProfile';
|
||||
import * as commandPalette from './commandPalette';
|
||||
import * as deleteFolder from './deleteFolder';
|
||||
import * as deleteNote from './deleteNote';
|
||||
import * as duplicateNote from './duplicateNote';
|
||||
import * as editAlarm from './editAlarm';
|
||||
import * as exportPdf from './exportPdf';
|
||||
import * as gotoAnything from './gotoAnything';
|
||||
@@ -37,7 +34,6 @@ import * as showSpellCheckerMenu from './showSpellCheckerMenu';
|
||||
import * as toggleEditors from './toggleEditors';
|
||||
import * as toggleLayoutMoveMode from './toggleLayoutMoveMode';
|
||||
import * as toggleNoteList from './toggleNoteList';
|
||||
import * as toggleNoteType from './toggleNoteType';
|
||||
import * as toggleNotesSortOrderField from './toggleNotesSortOrderField';
|
||||
import * as toggleNotesSortOrderReverse from './toggleNotesSortOrderReverse';
|
||||
import * as togglePerFolderSortOrder from './togglePerFolderSortOrder';
|
||||
@@ -47,9 +43,6 @@ import * as toggleVisiblePanes from './toggleVisiblePanes';
|
||||
const index:any[] = [
|
||||
addProfile,
|
||||
commandPalette,
|
||||
deleteFolder,
|
||||
deleteNote,
|
||||
duplicateNote,
|
||||
editAlarm,
|
||||
exportPdf,
|
||||
gotoAnything,
|
||||
@@ -83,7 +76,6 @@ const index:any[] = [
|
||||
toggleEditors,
|
||||
toggleLayoutMoveMode,
|
||||
toggleNoteList,
|
||||
toggleNoteType,
|
||||
toggleNotesSortOrderField,
|
||||
toggleNotesSortOrderReverse,
|
||||
togglePerFolderSortOrder,
|
||||
|
||||
@@ -44,6 +44,6 @@ export const runtime = (comp: any): CommandRuntime => {
|
||||
},
|
||||
});
|
||||
},
|
||||
enabledCondition: 'someNotesSelected && !noteIsReadOnly',
|
||||
enabledCondition: 'someNotesSelected',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -18,6 +18,5 @@ export const runtime = (): CommandRuntime => {
|
||||
|
||||
void CommandService.instance().execute('openFolderDialog', options);
|
||||
},
|
||||
enabledCondition: '!folderIsReadOnly',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -30,6 +30,6 @@ export const runtime = (): CommandRuntime => {
|
||||
id: newNote.id,
|
||||
});
|
||||
},
|
||||
enabledCondition: 'oneFolderSelected && !inConflictFolder && !folderIsReadOnly',
|
||||
enabledCondition: 'oneFolderSelected && !inConflictFolder',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,6 +13,5 @@ export const runtime = (): CommandRuntime => {
|
||||
parentId = parentId || context.state.selectedFolderId;
|
||||
return CommandService.instance().execute('newFolder', parentId);
|
||||
},
|
||||
enabledCondition: '!folderIsReadOnly',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -12,6 +12,6 @@ export const runtime = (): CommandRuntime => {
|
||||
execute: async (_context: CommandContext, body = '') => {
|
||||
return CommandService.instance().execute('newNote', body, true);
|
||||
},
|
||||
enabledCondition: 'oneFolderSelected && !inConflictFolder && !folderIsReadOnly',
|
||||
enabledCondition: 'oneFolderSelected && !inConflictFolder',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -33,6 +33,5 @@ export const runtime = (): CommandRuntime => {
|
||||
},
|
||||
});
|
||||
},
|
||||
enabledCondition: '!folderIsReadOnly',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import eventManager from '@joplin/lib/eventManager';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'toggleNoteType',
|
||||
label: () => _('Switch between note and to-do type'),
|
||||
};
|
||||
|
||||
export const runtime = (): CommandRuntime => {
|
||||
return {
|
||||
execute: async (context: CommandContext, noteIds: string[] = null) => {
|
||||
if (noteIds === null) noteIds = context.state.selectedNoteIds;
|
||||
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
const note = await Note.load(noteIds[i]);
|
||||
const newNote = await Note.save(Note.toggleIsTodo(note), { userSideValidation: true });
|
||||
const eventNote = {
|
||||
id: newNote.id,
|
||||
is_todo: newNote.is_todo,
|
||||
todo_due: newNote.todo_due,
|
||||
todo_completed: newNote.todo_completed,
|
||||
};
|
||||
eventManager.emit('noteTypeToggle', { noteId: note.id, note: eventNote });
|
||||
}
|
||||
},
|
||||
enabledCondition: '!noteIsReadOnly',
|
||||
};
|
||||
};
|
||||
@@ -9,7 +9,7 @@ import { PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins
|
||||
import shim from '@joplin/lib/shim';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import versionInfo from '@joplin/lib/versionInfo';
|
||||
import { ImportModule } from '@joplin/lib/services/interop/Module';
|
||||
import { Module } from '@joplin/lib/services/interop/types';
|
||||
import InteropServiceHelper from '../InteropServiceHelper';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { isContextMenuItemLocation, MenuItem, MenuItemLocation } from '@joplin/lib/services/plugins/api/types';
|
||||
@@ -230,7 +230,7 @@ function useMenu(props: Props) {
|
||||
void CommandService.instance().execute(commandName);
|
||||
}, []);
|
||||
|
||||
const onImportModuleClick = useCallback(async (module: ImportModule, moduleSource: string) => {
|
||||
const onImportModuleClick = useCallback(async (module: Module, moduleSource: string) => {
|
||||
let path = null;
|
||||
|
||||
if (moduleSource === 'file') {
|
||||
|
||||
@@ -893,7 +893,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
mode={props.contentMarkupLanguage === MarkupToHtml.MARKUP_LANGUAGE_HTML ? 'xml' : 'joplin-markdown'}
|
||||
codeMirrorTheme={styles.editor.codeMirrorTheme}
|
||||
style={styles.editor}
|
||||
readOnly={props.disabled || props.visiblePanes.indexOf('editor') < 0}
|
||||
readOnly={props.visiblePanes.indexOf('editor') < 0}
|
||||
autoMatchBraces={matchBracesOptions}
|
||||
keyMap={props.keyboardMode}
|
||||
plugins={props.plugins}
|
||||
@@ -927,7 +927,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
<ErrorBoundary message="The text editor encountered a fatal error and could not continue. The error might be due to a plugin, so please try to disable some of them and try again.">
|
||||
<div style={styles.root} ref={rootRef}>
|
||||
<div style={styles.rowToolbar}>
|
||||
<Toolbar themeId={props.themeId}/>
|
||||
<Toolbar themeId={props.themeId} />
|
||||
{props.noteToolbar}
|
||||
</div>
|
||||
<div style={styles.rowEditorViewer}>
|
||||
|
||||
@@ -167,7 +167,6 @@ function Editor(props: EditorProps, ref: any) {
|
||||
|
||||
const safeOptions: Record<string, any> = {
|
||||
value: props.value,
|
||||
readOnly: props.readOnly,
|
||||
};
|
||||
|
||||
const unsafeOptions: Record<string, any> = {
|
||||
|
||||
@@ -11,7 +11,6 @@ const { buildStyle } = require('@joplin/lib/theme');
|
||||
interface ToolbarProps {
|
||||
themeId: number;
|
||||
toolbarButtonInfos: ToolbarButtonInfo[];
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
function styles_(props: ToolbarProps) {
|
||||
@@ -29,7 +28,7 @@ const toolbarButtonUtils = new ToolbarButtonUtils(CommandService.instance());
|
||||
|
||||
function Toolbar(props: ToolbarProps) {
|
||||
const styles = styles_(props);
|
||||
return <ToolbarBase style={styles.root} items={props.toolbarButtonInfos} disabled={!!props.disabled} />;
|
||||
return <ToolbarBase style={styles.root} items={props.toolbarButtonInfos} />;
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
|
||||
@@ -31,7 +31,7 @@ import usePrevious from '../hooks/usePrevious';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
|
||||
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
|
||||
import { itemIsReadOnly } from '@joplin/lib/models/utils/readOnly';
|
||||
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const { substrWithEllipsis } = require('@joplin/lib/string-utils');
|
||||
import NoteSearchBar from '../NoteSearchBar';
|
||||
@@ -40,12 +40,6 @@ import Note from '@joplin/lib/models/Note';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
import NoteRevisionViewer from '../NoteRevisionViewer';
|
||||
import { readFromSettings } from '@joplin/lib/services/share/reducer';
|
||||
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
|
||||
import { ModelType } from '@joplin/lib/BaseModel';
|
||||
import BaseItem from '@joplin/lib/models/BaseItem';
|
||||
import { ErrorCode } from '@joplin/lib/errors';
|
||||
import ItemChange from '@joplin/lib/models/ItemChange';
|
||||
|
||||
const commands = [
|
||||
require('./commands/showRevisions'),
|
||||
@@ -57,7 +51,6 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
const [showRevisions, setShowRevisions] = useState(false);
|
||||
const [titleHasBeenManuallyChanged, setTitleHasBeenManuallyChanged] = useState(false);
|
||||
const [scrollWhenReady, setScrollWhenReady] = useState<ScrollOptions>(null);
|
||||
const [isReadOnly, setIsReadOnly] = useState<boolean>(false);
|
||||
|
||||
const editorRef = useRef<any>();
|
||||
const titleInputRef = useRef<any>();
|
||||
@@ -286,23 +279,6 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
// }
|
||||
// }, [props.dispatch]);
|
||||
|
||||
useAsyncEffect(async event => {
|
||||
if (!formNote.id) return;
|
||||
|
||||
try {
|
||||
const result = await itemIsReadOnly(BaseItem, ModelType.Note, ItemChange.SOURCE_UNSPECIFIED, formNote.id, props.syncUserId, props.shareCache);
|
||||
if (event.cancelled) return;
|
||||
setIsReadOnly(result);
|
||||
} catch (error) {
|
||||
if (error.code === ErrorCode.NotFound) {
|
||||
// Can happen if the note has been deleted but a render is
|
||||
// triggered anyway. It can be ignored.
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}, [formNote.id, props.syncUserId, props.shareCache]);
|
||||
|
||||
const onBodyWillChange = useCallback((event: any) => {
|
||||
handleProvisionalFlag();
|
||||
|
||||
@@ -430,7 +406,7 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
htmlToMarkdown: htmlToMarkdown,
|
||||
markupToHtml: markupToHtml,
|
||||
allAssets: allAssets,
|
||||
disabled: isReadOnly,
|
||||
disabled: false,
|
||||
themeId: props.themeId,
|
||||
dispatch: props.dispatch,
|
||||
noteToolbar: null,
|
||||
@@ -594,7 +570,6 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
noteTitle={formNote.title}
|
||||
noteUserUpdatedTime={formNote.user_updated_time}
|
||||
onTitleChange={onTitleChange}
|
||||
disabled={isReadOnly}
|
||||
/>
|
||||
{renderSearchInfo()}
|
||||
<div style={{ display: 'flex', flex: 1, paddingLeft: theme.editorPaddingLeft, maxHeight: '100%' }}>
|
||||
@@ -654,9 +629,7 @@ const mapStateToProps = (state: AppState) => {
|
||||
], whenClauseContext)[0],
|
||||
contentMaxWidth: state.settings['style.editor.contentMaxWidth'],
|
||||
isSafeMode: state.settings.isSafeMode,
|
||||
useCustomPdfViewer: false,
|
||||
syncUserId: state.settings['sync.userId'],
|
||||
shareCache: readFromSettings(state),
|
||||
useCustomPdfViewer: false, // state.settings.useCustomPdfViewer,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ interface Props {
|
||||
isProvisional: boolean;
|
||||
titleInputRef: any;
|
||||
onTitleChange(event: ChangeEvent<HTMLInputElement>): void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
function styles_(props: Props) {
|
||||
@@ -99,7 +98,6 @@ export default function NoteTitleBar(props: Props) {
|
||||
return <NoteToolbar
|
||||
themeId={props.themeId}
|
||||
style={styles.toolbarStyle}
|
||||
disabled={props.disabled}
|
||||
/>;
|
||||
}
|
||||
|
||||
@@ -111,7 +109,6 @@ export default function NoteTitleBar(props: Props) {
|
||||
ref={props.titleInputRef}
|
||||
placeholder={props.isProvisional ? _('Creating new %s...', props.noteIsTodo ? _('to-do') : _('note')) : ''}
|
||||
style={styles.titleInput}
|
||||
readOnly={props.disabled}
|
||||
onChange={props.onTitleChange}
|
||||
onKeyDown={onTitleKeydown}
|
||||
value={props.noteTitle}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { CommandDeclaration } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { joplinCommandToTinyMceCommands } from './NoteBody/TinyMCE/utils/joplinCommandToTinyMceCommands';
|
||||
|
||||
export const enabledCondition = (commandName: string) => {
|
||||
const markdownEditorOnly = !Object.keys(joplinCommandToTinyMceCommands).includes(commandName);
|
||||
return `(!modalDialogVisible || gotoAnythingVisible) ${markdownEditorOnly ? '&& markdownEditorPaneVisible' : ''} && oneNoteSelected && noteIsMarkdown && !noteIsReadOnly`;
|
||||
};
|
||||
|
||||
const declarations: CommandDeclaration[] = [
|
||||
{
|
||||
|
||||
@@ -7,13 +7,10 @@ const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
import Resource from '@joplin/lib/models/Resource';
|
||||
import BaseItem from '@joplin/lib/models/BaseItem';
|
||||
import BaseModel, { ModelType } from '@joplin/lib/BaseModel';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import { processPastedHtml } from './resourceHandling';
|
||||
import { NoteEntity, ResourceEntity } from '@joplin/lib/services/database/types';
|
||||
import { TinyMceEditorEvents } from '../NoteBody/TinyMCE/utils/types';
|
||||
import { itemIsReadOnlySync, ItemSlice } from '@joplin/lib/models/utils/readOnly';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import ItemChange from '@joplin/lib/models/ItemChange';
|
||||
const fs = require('fs-extra');
|
||||
const { writeFile } = require('fs-extra');
|
||||
const { clipboard } = require('electron');
|
||||
@@ -53,11 +50,7 @@ export async function openItemById(itemId: string, dispatch: Function, hash = ''
|
||||
}
|
||||
|
||||
try {
|
||||
if (itemIsReadOnlySync(ModelType.Resource, ItemChange.SOURCE_UNSPECIFIED, resource as ItemSlice, Setting.value('sync.userId'), BaseItem.syncShareCache)) {
|
||||
await ResourceEditWatcher.instance().openAsReadOnly(resource.id);
|
||||
} else {
|
||||
await ResourceEditWatcher.instance().openAndWatch(resource.id);
|
||||
}
|
||||
await ResourceEditWatcher.instance().openAndWatch(resource.id);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import AsyncActionQueue from '@joplin/lib/AsyncActionQueue';
|
||||
import { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
|
||||
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
|
||||
import { State as ShareState } from '@joplin/lib/services/share/reducer';
|
||||
import { MarkupLanguage } from '@joplin/renderer';
|
||||
import { RenderResult, RenderResultPluginAsset } from '@joplin/renderer/MarkupToHtml';
|
||||
import { MarkupToHtmlOptions } from './useMarkupToHtml';
|
||||
@@ -46,8 +45,6 @@ export interface NoteEditorProps {
|
||||
contentMaxWidth: number;
|
||||
isSafeMode: boolean;
|
||||
useCustomPdfViewer: boolean;
|
||||
shareCache: ShareState;
|
||||
syncUserId: string;
|
||||
}
|
||||
|
||||
export interface NoteBodyEditorProps {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useEffect } from 'react';
|
||||
import { FormNote, ScrollOptionTypes } from './types';
|
||||
import editorCommandDeclarations, { enabledCondition } from '../editorCommandDeclarations';
|
||||
import editorCommandDeclarations from '../editorCommandDeclarations';
|
||||
import CommandService, { CommandDeclaration, CommandRuntime, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import time from '@joplin/lib/time';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import { joplinCommandToTinyMceCommands } from '../NoteBody/TinyMCE/utils/joplinCommandToTinyMceCommands';
|
||||
|
||||
const commandsWithDependencies = [
|
||||
require('../commands/showLocalSearch'),
|
||||
@@ -29,6 +30,8 @@ interface HookDependencies {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
function editorCommandRuntime(declaration: CommandDeclaration, editorRef: any, setFormNote: Function): CommandRuntime {
|
||||
const markdownEditorOnly = !Object.keys(joplinCommandToTinyMceCommands).includes(declaration.name);
|
||||
|
||||
return {
|
||||
execute: async (_context: CommandContext, ...args: any[]) => {
|
||||
if (!editorRef.current) {
|
||||
@@ -70,7 +73,7 @@ function editorCommandRuntime(declaration: CommandDeclaration, editorRef: any, s
|
||||
// currently selected text.
|
||||
//
|
||||
// https://github.com/laurent22/joplin/issues/5707
|
||||
enabledCondition: enabledCondition(declaration.name),
|
||||
enabledCondition: `(!modalDialogVisible || gotoAnythingVisible) ${markdownEditorOnly ? '&& markdownEditorPaneVisible' : ''} && oneNoteSelected && noteIsMarkdown`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import eventManager from '@joplin/lib/eventManager';
|
||||
import NoteListUtils from '../utils/NoteListUtils';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import time from '@joplin/lib/time';
|
||||
import BaseModel, { ModelType } from '@joplin/lib/BaseModel';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import bridge from '../../services/bridge';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import NoteListItem from '../NoteListItem';
|
||||
@@ -19,9 +19,6 @@ import Note from '@joplin/lib/models/Note';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import { Props } from './types';
|
||||
import usePrevious from '../hooks/usePrevious';
|
||||
import { itemIsReadOnlySync, ItemSlice } from '@joplin/lib/models/utils/readOnly';
|
||||
import { FolderEntity } from '@joplin/lib/services/database/types';
|
||||
import ItemChange from '@joplin/lib/models/ItemChange';
|
||||
|
||||
const commands = [
|
||||
require('./commands/focusElementNoteList'),
|
||||
@@ -189,7 +186,7 @@ const NoteListComponent = (props: Props) => {
|
||||
setDragOverTargetNoteIndex(null);
|
||||
|
||||
const targetNoteIndex = dragTargetNoteIndex_(event);
|
||||
const noteIds: string[] = JSON.parse(dt.getData('text/x-jop-note-ids'));
|
||||
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
|
||||
|
||||
void Note.insertNotesAt(props.selectedFolderId, noteIds, targetNoteIndex, props.uncompletedTodosOnTop, props.showCompletedTodos);
|
||||
};
|
||||
@@ -226,9 +223,7 @@ const NoteListComponent = (props: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const noteItem_dragStart = useCallback((event: any) => {
|
||||
if (props.parentFolderIsReadOnly) return false;
|
||||
|
||||
const noteItem_dragStart = (event: any) => {
|
||||
let noteIds = [];
|
||||
|
||||
// Here there is two cases:
|
||||
@@ -241,14 +236,13 @@ const NoteListComponent = (props: Props) => {
|
||||
if (clickedNoteId) noteIds.push(clickedNoteId);
|
||||
}
|
||||
|
||||
if (!noteIds.length) return false;
|
||||
if (!noteIds.length) return;
|
||||
|
||||
event.dataTransfer.setDragImage(new Image(), 1, 1);
|
||||
event.dataTransfer.clearData();
|
||||
event.dataTransfer.setData('text/x-jop-note-ids', JSON.stringify(noteIds));
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
return true;
|
||||
}, [props.parentFolderIsReadOnly, props.selectedNoteIds]);
|
||||
};
|
||||
|
||||
const renderItem = useCallback((item: any, index: number) => {
|
||||
const highlightedWords = () => {
|
||||
@@ -284,7 +278,6 @@ const NoteListComponent = (props: Props) => {
|
||||
onNoteDragOver={noteItem_noteDragOver}
|
||||
onTitleClick={noteItem_titleClick}
|
||||
onContextMenu={itemContextMenu}
|
||||
draggable={!props.parentFolderIsReadOnly}
|
||||
/>;
|
||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||
}, [style, props.themeId, width, itemHeight, dragOverTargetNoteIndex, props.provisionalNoteIds, props.selectedNoteIds, props.watchedNoteFiles,
|
||||
@@ -293,7 +286,6 @@ const NoteListComponent = (props: Props) => {
|
||||
props.searches,
|
||||
props.selectedSearchId,
|
||||
props.highlightedWords,
|
||||
props.parentFolderIsReadOnly,
|
||||
]);
|
||||
|
||||
const previousSelectedNoteIds = usePrevious(props.selectedNoteIds, []);
|
||||
@@ -401,8 +393,7 @@ const NoteListComponent = (props: Props) => {
|
||||
if (noteIds.length && (keyCode === 46 || (keyCode === 8 && event.metaKey))) {
|
||||
// DELETE / CMD+Backspace
|
||||
event.preventDefault();
|
||||
void CommandService.instance().execute('deleteNote', noteIds);
|
||||
// await NoteListUtils.confirmDeleteNotes(noteIds);
|
||||
await NoteListUtils.confirmDeleteNotes(noteIds);
|
||||
}
|
||||
|
||||
if (noteIds.length && keyCode === 32) {
|
||||
@@ -550,9 +541,6 @@ const NoteListComponent = (props: Props) => {
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
const selectedFolder: FolderEntity = state.notesParentType === 'Folder' ? BaseModel.byId(state.folders, state.selectedFolderId) : null;
|
||||
const userId = state.settings['sync.userId'];
|
||||
|
||||
return {
|
||||
notes: state.notes,
|
||||
folders: state.folders,
|
||||
@@ -572,7 +560,6 @@ const mapStateToProps = (state: AppState) => {
|
||||
plugins: state.pluginService.plugins,
|
||||
customCss: state.customCss,
|
||||
focusedField: state.focusedField,
|
||||
parentFolderIsReadOnly: state.notesParentType === 'Folder' && selectedFolder ? itemIsReadOnlySync(ModelType.Folder, ItemChange.SOURCE_UNSPECIFIED, selectedFolder as ItemSlice, userId, state.shareService) : false,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -25,5 +25,4 @@ export interface Props {
|
||||
provisionalNoteIds: string[];
|
||||
visible: boolean;
|
||||
focusedField: string;
|
||||
parentFolderIsReadOnly: boolean;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ import Note from '@joplin/lib/models/Note';
|
||||
import { notesSortOrderNextField } from '../../services/sortOrder/notesSortOrderUtils';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
const { connect } = require('react-redux');
|
||||
import styled from 'styled-components';
|
||||
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
|
||||
const styled = require('styled-components').default;
|
||||
|
||||
enum BaseBreakpoint {
|
||||
Sm = 75,
|
||||
@@ -28,8 +27,6 @@ interface Props {
|
||||
height: number;
|
||||
width: number;
|
||||
onContentHeightChange: (sameRow: boolean)=> void;
|
||||
newNoteButtonEnabled: boolean;
|
||||
newTodoButtonEnabled: boolean;
|
||||
}
|
||||
|
||||
interface Breakpoints {
|
||||
@@ -258,7 +255,6 @@ function NoteListControls(props: Props) {
|
||||
level={ButtonLevel.Primary}
|
||||
size={ButtonSize.Small}
|
||||
onClick={onNewNoteButtonClick}
|
||||
disabled={!props.newNoteButtonEnabled}
|
||||
/>
|
||||
<StyledButton ref={newTodoRef}
|
||||
className="new-todo-button"
|
||||
@@ -268,7 +264,6 @@ function NoteListControls(props: Props) {
|
||||
level={ButtonLevel.Secondary}
|
||||
size={ButtonSize.Small}
|
||||
onClick={onNewTodoButtonClick}
|
||||
disabled={!props.newTodoButtonEnabled}
|
||||
/>
|
||||
</TopRow>
|
||||
);
|
||||
@@ -305,13 +300,9 @@ function NoteListControls(props: Props) {
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
const whenClauseContext = stateToWhenClauseContext(state);
|
||||
|
||||
return {
|
||||
// TODO: showNewNoteButtons and the logic associated is not needed anymore.
|
||||
showNewNoteButtons: true,
|
||||
newNoteButtonEnabled: CommandService.instance().isEnabled('newNote', whenClauseContext),
|
||||
newTodoButtonEnabled: CommandService.instance().isEnabled('newTodo', whenClauseContext),
|
||||
sortOrderButtonsVisible: state.settings['notes.sortOrder.buttonsVisible'],
|
||||
sortOrderField: state.settings['notes.sortOrder.field'],
|
||||
sortOrderReverse: state.settings['notes.sortOrder.reverse'],
|
||||
|
||||
@@ -58,7 +58,6 @@ interface NoteListItemProps {
|
||||
onNoteDragOver: any;
|
||||
onTitleClick: any;
|
||||
onContextMenu(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>): void;
|
||||
draggable: boolean;
|
||||
}
|
||||
|
||||
function NoteListItem(props: NoteListItemProps, ref: any) {
|
||||
@@ -186,7 +185,7 @@ function NoteListItem(props: NoteListItemProps, ref: any) {
|
||||
ref={anchorRef}
|
||||
onContextMenu={props.onContextMenu}
|
||||
href="#"
|
||||
draggable={props.draggable}
|
||||
draggable={true}
|
||||
style={listItemTitleStyle}
|
||||
onClick={onTitleClick}
|
||||
onDragStart={props.onDragStart}
|
||||
|
||||
@@ -11,7 +11,6 @@ interface NoteToolbarProps {
|
||||
themeId: number;
|
||||
style: any;
|
||||
toolbarButtonInfos: ToolbarButtonInfo[];
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
function styles_(props: NoteToolbarProps) {
|
||||
@@ -28,7 +27,7 @@ function styles_(props: NoteToolbarProps) {
|
||||
|
||||
function NoteToolbar(props: NoteToolbarProps) {
|
||||
const styles = styles_(props);
|
||||
return <ToolbarBase style={styles.root} items={props.toolbarButtonInfos} disabled={props.disabled}/>;
|
||||
return <ToolbarBase style={styles.root} items={props.toolbarButtonInfos} />;
|
||||
}
|
||||
|
||||
const toolbarButtonUtils = new ToolbarButtonUtils(CommandService.instance());
|
||||
|
||||
@@ -2,7 +2,7 @@ import Dialog from '../Dialog';
|
||||
import DialogButtonRow, { ClickEvent, ButtonSpec } from '../DialogButtonRow';
|
||||
import DialogTitle from '../DialogTitle';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FolderEntity } from '@joplin/lib/services/database/types';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import ShareService, { ApiShare } from '@joplin/lib/services/share/ShareService';
|
||||
@@ -12,12 +12,11 @@ import StyledInput from '../style/StyledInput';
|
||||
import Button, { ButtonSize } from '../Button/Button';
|
||||
import Logger from '@joplin/lib/Logger';
|
||||
import StyledMessage from '../style/StyledMessage';
|
||||
import { SharePermissions, ShareUserStatus, StateShare, StateShareUser } from '@joplin/lib/services/share/reducer';
|
||||
import { ShareUserStatus, StateShare, StateShareUser } from '@joplin/lib/services/share/reducer';
|
||||
import { State } from '@joplin/lib/reducer';
|
||||
import { connect } from 'react-redux';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||
import { ChangeEvent, Dropdown, DropdownOptions, DropdownVariant } from '../Dropdown/Dropdown';
|
||||
|
||||
const logger = Logger.create('ShareFolderDialog');
|
||||
|
||||
@@ -96,7 +95,6 @@ interface Props {
|
||||
onClose(): void;
|
||||
shares: StateShare[];
|
||||
shareUsers: Record<string, StateShareUser[]>;
|
||||
isJoplinCloud: boolean;
|
||||
}
|
||||
|
||||
interface RecipientDeleteEvent {
|
||||
@@ -110,20 +108,13 @@ enum ShareState {
|
||||
}
|
||||
|
||||
function ShareFolderDialog(props: Props) {
|
||||
const permissionOptions: DropdownOptions = {
|
||||
'can_read': _('Can view'),
|
||||
'can_read_and_write': _('Can view and edit'),
|
||||
};
|
||||
|
||||
const [folder, setFolder] = useState<FolderEntity>(null);
|
||||
const [recipientEmail, setRecipientEmail] = useState<string>('');
|
||||
const [recipientPermissions, setRecipientPermissions] = useState<string>('can_read_and_write');
|
||||
const [latestError, setLatestError] = useState<Error>(null);
|
||||
const [share, setShare] = useState<StateShare>(null);
|
||||
const [shareUsers, setShareUsers] = useState<StateShareUser[]>([]);
|
||||
const [shareState, setShareState] = useState<ShareState>(ShareState.Idle);
|
||||
const [customButtons, setCustomButtons] = useState<ButtonSpec[]>([]);
|
||||
const [recipientsBeingUpdated, setRecipientsBeingUpdated] = useState<Record<string, boolean>>({});
|
||||
|
||||
async function synchronize(event: AsyncEffectEvent = null) {
|
||||
setShareState(ShareState.Synchronizing);
|
||||
@@ -149,7 +140,8 @@ function ShareFolderDialog(props: Props) {
|
||||
useEffect(() => {
|
||||
const s = props.shares.find(s => s.folder_id === props.folderId);
|
||||
setShare(s);
|
||||
}, [props.shares, props.folderId]);
|
||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||
}, [props.shares]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!share) return;
|
||||
@@ -174,14 +166,7 @@ function ShareFolderDialog(props: Props) {
|
||||
void ShareService.instance().refreshShares();
|
||||
}, [props.folderId]);
|
||||
|
||||
const permissionsFromString = (p: string): SharePermissions => {
|
||||
return {
|
||||
can_read: 1,
|
||||
can_write: p === 'can_read_and_write' ? 1 : 0,
|
||||
};
|
||||
};
|
||||
|
||||
const shareRecipient_click = useCallback(async () => {
|
||||
async function shareRecipient_click() {
|
||||
setShareState(ShareState.Creating);
|
||||
setLatestError(null);
|
||||
|
||||
@@ -207,7 +192,7 @@ function ShareFolderDialog(props: Props) {
|
||||
}
|
||||
|
||||
try {
|
||||
await ShareService.instance().addShareRecipient(share.id, share.master_key_id, recipientEmail, permissionsFromString(recipientPermissions));
|
||||
await ShareService.instance().addShareRecipient(share.id, share.master_key_id, recipientEmail);
|
||||
} catch (error) {
|
||||
// Handle the error but continue the process because we need to at
|
||||
// least refresh the shares since one has been created above.
|
||||
@@ -227,7 +212,7 @@ function ShareFolderDialog(props: Props) {
|
||||
} finally {
|
||||
defer(null);
|
||||
}
|
||||
}, [recipientPermissions, props.folderId, recipientEmail]);
|
||||
}
|
||||
|
||||
function recipientEmail_change(event: any) {
|
||||
setRecipientEmail(event.target.value);
|
||||
@@ -254,43 +239,19 @@ function ShareFolderDialog(props: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const recipientPermissions_change = useCallback((event: ChangeEvent) => {
|
||||
setRecipientPermissions(event.value);
|
||||
}, []);
|
||||
|
||||
function renderAddRecipient() {
|
||||
const disabled = shareState !== ShareState.Idle;
|
||||
|
||||
const dropdown = !props.isJoplinCloud ? null : <Dropdown className="permission-dropdown" options={permissionOptions} value={recipientPermissions} onChange={recipientPermissions_change}/>;
|
||||
|
||||
return (
|
||||
<StyledAddRecipient>
|
||||
<StyledFormLabel>{_('Add recipient:')}</StyledFormLabel>
|
||||
<StyledRecipientControls>
|
||||
<StyledRecipientInput disabled={disabled} type="email" placeholder="example@domain.com" value={recipientEmail} onChange={recipientEmail_change} />
|
||||
{dropdown}
|
||||
<Button size={ButtonSize.Small} disabled={disabled} title={_('Share')} onClick={shareRecipient_click}></Button>
|
||||
</StyledRecipientControls>
|
||||
</StyledAddRecipient>
|
||||
);
|
||||
}
|
||||
|
||||
const recipient_permissionChange = useCallback(async (shareUserId: string, value: string) => {
|
||||
try {
|
||||
setRecipientsBeingUpdated(prev => {
|
||||
return { ...prev, [shareUserId]: true };
|
||||
});
|
||||
await ShareService.instance().setPermissions(share.id, shareUserId, permissionsFromString(value));
|
||||
} catch (error) {
|
||||
alert(`Could not set permissions: ${error.message}`);
|
||||
logger.error(error);
|
||||
} finally {
|
||||
setRecipientsBeingUpdated(prev => {
|
||||
return { ...prev, [shareUserId]: false };
|
||||
});
|
||||
}
|
||||
}, [share]);
|
||||
|
||||
function renderRecipient(index: number, shareUser: StateShareUser) {
|
||||
const statusToIcon = {
|
||||
[ShareUserStatus.Waiting]: 'fas fa-question',
|
||||
@@ -304,16 +265,11 @@ function ShareFolderDialog(props: Props) {
|
||||
[ShareUserStatus.Accepted]: _('Recipient has accepted the invitation'),
|
||||
};
|
||||
|
||||
const permission = shareUser.can_write ? 'can_read_and_write' : 'can_read';
|
||||
const enabled = !recipientsBeingUpdated[shareUser.id];
|
||||
const dropdown = !props.isJoplinCloud ? null : <Dropdown disabled={!enabled} className="permission-dropdown" value={permission} options={permissionOptions} variant={DropdownVariant.NoBorder} onChange={event => recipient_permissionChange(shareUser.id, event.value)}/>;
|
||||
|
||||
return (
|
||||
<StyledRecipient key={shareUser.user.email} index={index}>
|
||||
<StyledRecipientName>{shareUser.user.email}</StyledRecipientName>
|
||||
{dropdown}
|
||||
<StyledRecipientStatusIcon title={statusToMessage[shareUser.status]} className={statusToIcon[shareUser.status]}></StyledRecipientStatusIcon>
|
||||
<Button disabled={!enabled} size={ButtonSize.Small} iconName="far fa-times-circle" onClick={() => recipient_delete({ shareUserId: shareUser.id })}/>
|
||||
<Button size={ButtonSize.Small} iconName="far fa-times-circle" onClick={() => recipient_delete({ shareUserId: shareUser.id })}/>
|
||||
</StyledRecipient>
|
||||
);
|
||||
}
|
||||
@@ -379,7 +335,7 @@ function ShareFolderDialog(props: Props) {
|
||||
|
||||
function renderContent() {
|
||||
return (
|
||||
<StyledRoot className="share-folder-dialog">
|
||||
<StyledRoot>
|
||||
<DialogTitle title={_('Share Notebook')}/>
|
||||
{renderFolder()}
|
||||
{renderAddRecipient()}
|
||||
@@ -407,7 +363,6 @@ const mapStateToProps = (state: State) => {
|
||||
return {
|
||||
shares: state.shareService.shares,
|
||||
shareUsers: state.shareService.shareUsers,
|
||||
isJoplinCloud: state.settings['sync.target'] === 10,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
.share-folder-dialog {
|
||||
.permission-dropdown {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
@@ -296,9 +296,12 @@ const SidebarComponent = (props: Props) => {
|
||||
const state: AppState = store().getState();
|
||||
|
||||
let deleteMessage = '';
|
||||
const deleteButtonLabel = _('Remove');
|
||||
|
||||
if (itemType === BaseModel.TYPE_TAG) {
|
||||
let deleteButtonLabel = _('Remove');
|
||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
const folder = await Folder.load(itemId);
|
||||
deleteMessage = _('Delete notebook "%s"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.', substrWithEllipsis(folder.title, 0, 32));
|
||||
deleteButtonLabel = _('Delete');
|
||||
} else if (itemType === BaseModel.TYPE_TAG) {
|
||||
const tag = await Tag.load(itemId);
|
||||
deleteMessage = _('Remove tag "%s" from all notes?', substrWithEllipsis(tag.title, 0, 32));
|
||||
} else if (itemType === BaseModel.TYPE_SEARCH) {
|
||||
@@ -318,33 +321,29 @@ const SidebarComponent = (props: Props) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
menu.append(
|
||||
new MenuItem(menuUtils.commandToStatefulMenuItem('deleteFolder', itemId))
|
||||
);
|
||||
} else {
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: deleteButtonLabel,
|
||||
click: async () => {
|
||||
const ok = bridge().showConfirmMessageBox(deleteMessage, {
|
||||
buttons: [deleteButtonLabel, _('Cancel')],
|
||||
defaultId: 1,
|
||||
});
|
||||
if (!ok) return;
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: deleteButtonLabel,
|
||||
click: async () => {
|
||||
const ok = bridge().showConfirmMessageBox(deleteMessage, {
|
||||
buttons: [deleteButtonLabel, _('Cancel')],
|
||||
defaultId: 1,
|
||||
});
|
||||
if (!ok) return;
|
||||
|
||||
if (itemType === BaseModel.TYPE_TAG) {
|
||||
await Tag.untagAll(itemId);
|
||||
} else if (itemType === BaseModel.TYPE_SEARCH) {
|
||||
props.dispatch({
|
||||
type: 'SEARCH_DELETE',
|
||||
id: itemId,
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
await Folder.delete(itemId);
|
||||
} else if (itemType === BaseModel.TYPE_TAG) {
|
||||
await Tag.untagAll(itemId);
|
||||
} else if (itemType === BaseModel.TYPE_SEARCH) {
|
||||
props.dispatch({
|
||||
type: 'SEARCH_DELETE',
|
||||
id: itemId,
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
|
||||
menu.append(new MenuItem(menuUtils.commandToStatefulMenuItem('openFolderDialog', { folderId: itemId })));
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||
import themeToCss from '@joplin/lib/services/style/themeToCss';
|
||||
import { addExtraStyles, themeById } from '@joplin/lib/theme';
|
||||
import { themeById } from '@joplin/lib/theme';
|
||||
|
||||
interface Props {
|
||||
themeId: any;
|
||||
@@ -21,7 +21,7 @@ export default function(props: Props): any {
|
||||
const [styleSheetContent, setStyleSheetContent] = useState('');
|
||||
|
||||
useAsyncEffect(async (event: AsyncEffectEvent) => {
|
||||
const theme = addExtraStyles(themeById(props.themeId));
|
||||
const theme = themeById(props.themeId);
|
||||
const themeCss = themeToCss(theme);
|
||||
if (event.cancelled) return;
|
||||
setStyleSheetContent(themeCss);
|
||||
|
||||
@@ -9,7 +9,6 @@ interface Props {
|
||||
themeId: number;
|
||||
style: any;
|
||||
items: any[];
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class ToolbarBaseComponent extends React.Component<Props, any> {
|
||||
@@ -47,7 +46,6 @@ class ToolbarBaseComponent extends React.Component<Props, any> {
|
||||
const props = {
|
||||
key: key,
|
||||
themeId: this.props.themeId,
|
||||
disabled: this.props.disabled,
|
||||
...o,
|
||||
};
|
||||
|
||||
|
||||
@@ -53,7 +53,17 @@ export default class NoteListUtils {
|
||||
);
|
||||
|
||||
menu.append(
|
||||
new MenuItem(menuUtils.commandToStatefulMenuItem('duplicateNote', noteIds))
|
||||
new MenuItem({
|
||||
label: _('Duplicate'),
|
||||
click: async () => {
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
const note = await Note.load(noteIds[i]);
|
||||
await Note.duplicate(noteIds[i], {
|
||||
uniqueTitle: _('%s - Copy', note.title),
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
if (singleNoteId) {
|
||||
@@ -63,9 +73,22 @@ export default class NoteListUtils {
|
||||
|
||||
if (noteIds.length <= 1) {
|
||||
menu.append(
|
||||
new MenuItem(
|
||||
menuUtils.commandToStatefulMenuItem('toggleNoteType', noteIds)
|
||||
)
|
||||
new MenuItem({
|
||||
label: _('Switch between note and to-do type'),
|
||||
click: async () => {
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
const note = await Note.load(noteIds[i]);
|
||||
const newNote = await Note.save(Note.toggleIsTodo(note), { userSideValidation: true });
|
||||
const eventNote = {
|
||||
id: newNote.id,
|
||||
is_todo: newNote.is_todo,
|
||||
todo_due: newNote.todo_due,
|
||||
todo_completed: newNote.todo_completed,
|
||||
};
|
||||
eventManager.emit('noteTypeToggle', { noteId: note.id, note: eventNote });
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const switchNoteType = async (noteIds: string[], type: string) => {
|
||||
@@ -166,9 +189,12 @@ export default class NoteListUtils {
|
||||
}
|
||||
|
||||
menu.append(
|
||||
new MenuItem(
|
||||
menuUtils.commandToStatefulMenuItem('deleteNote', noteIds)
|
||||
)
|
||||
new MenuItem({
|
||||
label: _('Delete'),
|
||||
click: async () => {
|
||||
await this.confirmDeleteNotes(noteIds);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const pluginViewInfos = pluginUtils.viewInfosByType(props.plugins, 'menuItem');
|
||||
@@ -187,4 +213,19 @@ export default class NoteListUtils {
|
||||
return menu;
|
||||
}
|
||||
|
||||
public static async confirmDeleteNotes(noteIds: string[]) {
|
||||
if (!noteIds.length) return;
|
||||
|
||||
const msg = await Note.deleteMessage(noteIds);
|
||||
if (!msg) return;
|
||||
|
||||
const ok = bridge().showConfirmMessageBox(msg, {
|
||||
buttons: [_('Delete'), _('Cancel')],
|
||||
defaultId: 1,
|
||||
});
|
||||
|
||||
if (!ok) return;
|
||||
await Note.batchDelete(noteIds);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.12.8",
|
||||
"version": "2.12.0",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
@@ -78,6 +78,7 @@
|
||||
},
|
||||
"mac": {
|
||||
"icon": "../../Assets/macOs.icns",
|
||||
"target": "dmg",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "./build-mac/entitlements.mac.inherit.plist",
|
||||
"extendInfo": {
|
||||
@@ -107,7 +108,6 @@
|
||||
},
|
||||
"homepage": "https://github.com/laurent22/joplin#readme",
|
||||
"devDependencies": {
|
||||
"@electron/rebuild": "3.2.13",
|
||||
"@joplin/tools": "~2.12",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@types/jest": "29.5.1",
|
||||
@@ -115,8 +115,9 @@
|
||||
"@types/react": "16.14.41",
|
||||
"@types/react-redux": "7.1.25",
|
||||
"@types/styled-components": "5.1.26",
|
||||
"electron": "25.2.0",
|
||||
"electron-builder": "24.4.0",
|
||||
"electron": "19.1.4",
|
||||
"electron-builder": "22.11.7",
|
||||
"electron-rebuild": "3.2.9",
|
||||
"glob": "10.2.7",
|
||||
"gulp": "4.0.2",
|
||||
"jest": "29.5.0",
|
||||
|
||||
@@ -56,7 +56,6 @@ fi
|
||||
|
||||
COMMANDS=($(echo $2 | tr "," "\n"))
|
||||
PROFILE_DIR=~/.config/joplindev-desktop-$USER_PROFILE_NUM
|
||||
SYNC_TARGET=9
|
||||
|
||||
CMD_FILE="$SCRIPT_DIR/runForTestingCommands-$USER_PROFILE_NUM.txt"
|
||||
rm -f "$CMD_FILE"
|
||||
@@ -85,16 +84,18 @@ do
|
||||
USER_EMAIL="user$USER_NUM@example.com"
|
||||
rm -rf "$PROFILE_DIR"
|
||||
|
||||
# rm -rf "$HOME/Temp/SyncTestE2EE copy"
|
||||
# rsync -a "$HOME/Temp/SyncTestE2EE/" "$HOME/Temp/SyncTestE2EE copy/"
|
||||
|
||||
# echo "config sync.target 2" >> "$CMD_FILE"
|
||||
# echo "config sync.2.path \"$HOME/Temp/SyncTestE2EE copy/\"" >> "$CMD_FILE"
|
||||
|
||||
echo "config keychain.supported 0" >> "$CMD_FILE"
|
||||
echo "config sync.target $SYNC_TARGET" >> "$CMD_FILE"
|
||||
echo "config sync.$SYNC_TARGET.username $USER_EMAIL" >> "$CMD_FILE"
|
||||
echo "config sync.$SYNC_TARGET.password 111111" >> "$CMD_FILE"
|
||||
|
||||
if [[ $SYNC_TARGET = 9 ]]; then
|
||||
echo "config sync.$SYNC_TARGET.path http://api.joplincloud.local:22300" >> "$CMD_FILE"
|
||||
echo "config sync.$SYNC_TARGET.userContentPath http://joplinusercontent.local:22300" >> "$CMD_FILE"
|
||||
fi
|
||||
|
||||
echo "config sync.target 10" >> "$CMD_FILE"
|
||||
# echo "config sync.10.path http://api.joplincloud.local:22300" >> "$CMD_FILE"
|
||||
echo "config sync.10.username $USER_EMAIL" >> "$CMD_FILE"
|
||||
echo "config sync.10.password 111111" >> "$CMD_FILE"
|
||||
|
||||
elif [[ $CMD == "e2ee" ]]; then
|
||||
|
||||
echo "e2ee enable --password 111111" >> "$CMD_FILE"
|
||||
@@ -103,6 +104,11 @@ do
|
||||
|
||||
echo "sync --use-lock 0" >> "$CMD_FILE"
|
||||
|
||||
# elif [[ $CMD == "generatePpk" ]]; then
|
||||
|
||||
# echo "e2ee generate-ppk --password 111111" >> "$CMD_FILE"
|
||||
# echo "sync" >> "$CMD_FILE"
|
||||
|
||||
else
|
||||
|
||||
echo "Unknown command: $CMD"
|
||||
|
||||
@@ -2,10 +2,8 @@ import { useEffect } from 'react';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
export default function(frameWindow: any, onSubmit: Function, onDismiss: Function, loadedHtmlHash: string) {
|
||||
const document = frameWindow && frameWindow.document ? frameWindow.document : null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!document) return () => {};
|
||||
if (!frameWindow) return () => {};
|
||||
|
||||
function onFormSubmit(event: any) {
|
||||
event.preventDefault();
|
||||
@@ -22,18 +20,18 @@ export default function(frameWindow: any, onSubmit: Function, onDismiss: Functio
|
||||
// Disable enter key from submitting when a text area is in focus!
|
||||
// https://github.com/laurent22/joplin/issues/4766
|
||||
//
|
||||
if (document.activeElement.tagName !== 'TEXTAREA') {
|
||||
if (frameWindow.document.activeElement.tagName !== 'TEXTAREA') {
|
||||
if (onSubmit) onSubmit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('submit', onFormSubmit);
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
frameWindow.document.addEventListener('submit', onFormSubmit);
|
||||
frameWindow.document.addEventListener('keydown', onKeyDown);
|
||||
|
||||
return () => {
|
||||
if (document) document.removeEventListener('submit', onFormSubmit);
|
||||
if (document) document.removeEventListener('keydown', onKeyDown);
|
||||
if (frameWindow) frameWindow.document.removeEventListener('submit', onFormSubmit);
|
||||
if (frameWindow) frameWindow.document.removeEventListener('keydown', onKeyDown);
|
||||
};
|
||||
}, [document, loadedHtmlHash, onSubmit, onDismiss]);
|
||||
}, [frameWindow, loadedHtmlHash, onSubmit, onDismiss]);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,4 @@
|
||||
@use 'gui/EditFolderDialog/style.scss' as edit-folder-dialog;
|
||||
@use 'gui/EncryptionConfigScreen/style.scss' as encryption-config-screen;
|
||||
@use 'gui/PasswordInput/style.scss' as password-input;
|
||||
@use 'gui/Dropdown/style.scss' as dropdown-control;
|
||||
@use 'gui/ShareFolderDialog/style.scss' as share-folder-dialog;
|
||||
@use 'main.scss' as main;
|
||||
@@ -1,9 +1,5 @@
|
||||
const execCommand = require('./execCommand');
|
||||
|
||||
const isArm64 = () => {
|
||||
return process.platform === 'arm64';
|
||||
};
|
||||
|
||||
const isWindows = () => {
|
||||
return process && process.platform === 'win32';
|
||||
};
|
||||
@@ -25,20 +21,13 @@ async function main() {
|
||||
// wrong one. However it means it will have to be manually upgraded for each
|
||||
// new Electron release. Some ABI map there:
|
||||
// https://github.com/electron/node-abi/tree/master/test
|
||||
const forceAbiArgs = '--force-abi 116';
|
||||
const forceAbiArgs = '--force-abi 89';
|
||||
|
||||
if (isWindows()) {
|
||||
// Cannot run this in parallel, or the 64-bit version might end up
|
||||
// with 32-bit files and vice-versa
|
||||
console.info(await execCommand(['yarn', 'run', 'electron-rebuild', forceAbiArgs, '--arch ia32'].join(' ')));
|
||||
console.info(await execCommand(['yarn', 'run', 'electron-rebuild', forceAbiArgs, '--arch x64'].join(' ')));
|
||||
} else if (isArm64()) {
|
||||
// Keytar needs it's own electron-rebuild or else it will not fetch the
|
||||
// existing prebuilt binary, this will cause cross-compilation to fail.
|
||||
// E.g. for MacOS arm64 it will download:
|
||||
// https://github.com/atom/node-keytar/releases/download/v7.9.0/keytar-v7.9.0-napi-v3-darwin-arm64.tar.gz
|
||||
console.info(await execCommand(['yarn', 'run', 'electron-rebuild', forceAbiArgs, '--arch=arm64', '--only=keytar'].join(' ')));
|
||||
console.info(await execCommand(['yarn', 'run', 'electron-rebuild', forceAbiArgs].join(' ')));
|
||||
} else {
|
||||
console.info(await execCommand(['yarn', 'run', 'electron-rebuild', forceAbiArgs].join(' ')));
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,119 +0,0 @@
|
||||
import { fileExtension } from '@joplin/lib/path-utils';
|
||||
import shim from '@joplin/lib/shim';
|
||||
|
||||
export interface CheckForUpdateOptions {
|
||||
includePreReleases?: boolean;
|
||||
}
|
||||
|
||||
export interface GitHubRelease {
|
||||
tag_name: string;
|
||||
prerelease: boolean;
|
||||
body: string;
|
||||
assets: {
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
}[];
|
||||
html_url: string;
|
||||
}
|
||||
|
||||
interface Release {
|
||||
version: string;
|
||||
prerelease: boolean;
|
||||
downloadUrl: string;
|
||||
notes: string;
|
||||
pageUrl: string;
|
||||
}
|
||||
|
||||
function getMajorMinorTagName(tagName: string) {
|
||||
const s = tagName.split('.');
|
||||
s.pop();
|
||||
return s.join('.');
|
||||
}
|
||||
|
||||
export const extractVersionInfo = (releases: GitHubRelease[], platform: typeof process.platform, options: CheckForUpdateOptions) => {
|
||||
options = { includePreReleases: false, ...options };
|
||||
|
||||
if (!releases.length) throw new Error('Cannot get latest release info (JSON)');
|
||||
|
||||
let release = null;
|
||||
|
||||
if (options.includePreReleases) {
|
||||
release = releases[0];
|
||||
} else {
|
||||
for (const r of releases) {
|
||||
if (!r.prerelease) {
|
||||
release = r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!release) throw new Error('Could not get tag name');
|
||||
|
||||
const version = release.tag_name.substr(1);
|
||||
|
||||
// We concatenate all the release notes of the major/minor versions
|
||||
// corresponding to the latest version. For example, if the latest version
|
||||
// is 1.8.3, we concatenate all the 1.8.x versions. This is so that no
|
||||
// matter from which version you upgrade, you always see the full changes,
|
||||
// with the latest changes being on top.
|
||||
|
||||
const fullReleaseNotes = [];
|
||||
const majorMinorTagName = getMajorMinorTagName(release.tag_name);
|
||||
|
||||
for (const release of releases) {
|
||||
if (getMajorMinorTagName(release.tag_name) === majorMinorTagName) {
|
||||
fullReleaseNotes.push(release.body.trim());
|
||||
}
|
||||
}
|
||||
|
||||
let downloadUrl = null;
|
||||
for (let i = 0; i < release.assets.length; i++) {
|
||||
const asset = release.assets[i];
|
||||
let found = false;
|
||||
const ext = fileExtension(asset.name);
|
||||
if (platform === 'win32' && ext === 'exe') {
|
||||
if (shim.isPortable()) {
|
||||
found = asset.name === 'JoplinPortable.exe';
|
||||
} else {
|
||||
found = !!asset.name.match(/^Joplin-Setup-[\d.]+\.exe$/);
|
||||
}
|
||||
} else if (platform === 'darwin' && ext === 'dmg' && !asset.name.endsWith('arm64.dmg')) { // We don't return the arm64 version for now
|
||||
found = true;
|
||||
} else if (platform === 'linux' && ext === '.AppImage') {
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
downloadUrl = asset.browser_download_url.replace('github.com/laurent22/joplin/releases/download', 'objects.joplinusercontent.com');
|
||||
downloadUrl.concat('?source=DesktopApp&type=Update');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function cleanUpReleaseNotes(releaseNotes: string[]) {
|
||||
const lines = releaseNotes.join('\n\n* * *\n\n').split('\n');
|
||||
const output = [];
|
||||
for (const line of lines) {
|
||||
const r = line
|
||||
.replace(/\(#.* by .*\)/g, '') // Removes issue numbers and names - (#3157 by [@user](https://github.com/user))
|
||||
.replace(/\([0-9a-z]{7}\)/g, '') // Removes commits - "sync state or data (a6caa35)"
|
||||
.replace(/\(#[0-9]+\)/g, '') // Removes issue numbers - "(#4727)"
|
||||
.replace(/ {2}/g, ' ')
|
||||
.trim();
|
||||
|
||||
output.push(r);
|
||||
}
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
const output: Release = {
|
||||
version: version,
|
||||
downloadUrl: downloadUrl,
|
||||
notes: cleanUpReleaseNotes(fullReleaseNotes),
|
||||
pageUrl: release.html_url,
|
||||
prerelease: release.prerelease,
|
||||
};
|
||||
|
||||
return output;
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ['module:metro-react-native-babel-preset'],
|
||||
plugins: ['react-native-reanimated/plugin'],
|
||||
};
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { Modal, ModalProps, StyleSheet, View, ViewStyle } from 'react-native';
|
||||
import { hasNotch } from 'react-native-device-info';
|
||||
|
||||
interface ModalElementProps extends ModalProps {
|
||||
children: React.ReactNode;
|
||||
containerStyle?: ViewStyle;
|
||||
elevation?: number;
|
||||
}
|
||||
|
||||
const ModalElement: React.FC<ModalElementProps> = ({
|
||||
children,
|
||||
containerStyle,
|
||||
...modalProps
|
||||
}) => {
|
||||
return (
|
||||
<Modal {...modalProps}>
|
||||
<View style={[styleSheet.modalContainer, containerStyle ? containerStyle : null]}>
|
||||
{children}
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const styleSheet = StyleSheet.create({
|
||||
modalContainer: {
|
||||
marginTop: hasNotch() ? 65 : 15,
|
||||
marginBottom: hasNotch() ? 35 : 15,
|
||||
},
|
||||
});
|
||||
|
||||
export default ModalElement;
|
||||
@@ -1,10 +1,8 @@
|
||||
const React = require('react');
|
||||
const { Text, View, StyleSheet, Button } = require('react-native');
|
||||
const { Text, Modal, View, StyleSheet, Button } = require('react-native');
|
||||
const { themeStyle } = require('./global-style.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
|
||||
import Modal from './Modal';
|
||||
|
||||
class ModalDialog extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
@@ -32,7 +30,6 @@ class ModalDialog extends React.Component {
|
||||
margin: 20,
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
elevation: 10,
|
||||
},
|
||||
modalContentWrapper2: {
|
||||
flex: 1,
|
||||
@@ -59,15 +56,17 @@ class ModalDialog extends React.Component {
|
||||
|
||||
return (
|
||||
<View style={this.styles().modalWrapper}>
|
||||
<Modal transparent={true} visible={true} onRequestClose={() => {}} containerStyle={this.styles().modalContentWrapper}>
|
||||
<Text style={this.styles().title}>{this.props.title}</Text>
|
||||
<View style={this.styles().modalContentWrapper2}>{ContentComponent}</View>
|
||||
<View style={this.styles().buttonRow}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Button disabled={!buttonBarEnabled} title={_('OK')} onPress={this.props.onOkPress}></Button>
|
||||
</View>
|
||||
<View style={{ flex: 1, marginLeft: 5 }}>
|
||||
<Button disabled={!buttonBarEnabled} title={_('Cancel')} onPress={this.props.onCancelPress}></Button>
|
||||
<Modal transparent={true} visible={true} onRequestClose={() => {}}>
|
||||
<View elevation={10} style={this.styles().modalContentWrapper}>
|
||||
<Text style={this.styles().title}>{this.props.title}</Text>
|
||||
<View style={this.styles().modalContentWrapper2}>{ContentComponent}</View>
|
||||
<View style={this.styles().buttonRow}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Button disabled={!buttonBarEnabled} title={_('OK')} onPress={this.props.onOkPress}></Button>
|
||||
</View>
|
||||
<View style={{ flex: 1, marginLeft: 5 }}>
|
||||
<Button disabled={!buttonBarEnabled} title={_('Cancel')} onPress={this.props.onCancelPress}></Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
|
||||
@@ -342,8 +342,6 @@ export function initCodeMirror(
|
||||
|
||||
...defaultKeymap, ...historyKeymap, indentWithTab, ...searchKeymap,
|
||||
]),
|
||||
|
||||
EditorState.readOnly.of(settings.readOnly),
|
||||
],
|
||||
doc: initialText,
|
||||
}),
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
const React = require('react');
|
||||
const { useState, useEffect, useMemo, useRef } = require('react');
|
||||
const { StyleSheet } = require('react-native');
|
||||
const { View, Text, TextInput, Button } = require('react-native');
|
||||
const { View, Modal, Text, TextInput, Button } = require('react-native');
|
||||
|
||||
import Modal from '../Modal';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { EditorControl } from './types';
|
||||
@@ -44,6 +43,7 @@ const EditLinkDialog = (props: LinkDialogProps) => {
|
||||
margin: 15,
|
||||
padding: 30,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
|
||||
elevation: 5,
|
||||
shadowOffset: {
|
||||
width: 1,
|
||||
@@ -132,22 +132,23 @@ const EditLinkDialog = (props: LinkDialogProps) => {
|
||||
return (
|
||||
<Modal
|
||||
animationType="slide"
|
||||
containerStyle={styles.modalContent}
|
||||
transparent={true}
|
||||
visible={props.visible}
|
||||
onRequestClose={() => {
|
||||
props.editorControl.hideLinkDialog();
|
||||
}}>
|
||||
<Text style={styles.header}>{_('Edit link')}</Text>
|
||||
<View>
|
||||
{linkTextInput}
|
||||
{linkURLInput}
|
||||
<View style={styles.modalContent}>
|
||||
<Text style={styles.header}>{_('Edit link')}</Text>
|
||||
<View>
|
||||
{linkTextInput}
|
||||
{linkURLInput}
|
||||
</View>
|
||||
<Button
|
||||
style={styles.button}
|
||||
onPress={onSubmit}
|
||||
title={_('Done')}
|
||||
/>
|
||||
</View>
|
||||
<Button
|
||||
style={styles.button}
|
||||
onPress={onSubmit}
|
||||
title={_('Done')}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -31,7 +31,6 @@ interface MarkdownToolbarProps {
|
||||
editorSettings: EditorSettings;
|
||||
onAttach: OnAttachCallback;
|
||||
style?: ViewStyle;
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
@@ -39,7 +38,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
const styles = useStyles(props.style, themeData);
|
||||
const selState = props.selectionState;
|
||||
const editorControl = props.editorControl;
|
||||
const readOnly = props.readOnly;
|
||||
|
||||
const headerButtons: ButtonSpec[] = [];
|
||||
for (let level = 1; level <= 5; level++) {
|
||||
@@ -60,7 +58,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
// Make it likely for the first three header buttons to show, less likely for
|
||||
// the others.
|
||||
priority: level < 3 ? 2 : 0,
|
||||
disabled: readOnly,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,7 +73,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
}, [editorControl]),
|
||||
|
||||
priority: -2,
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
listButtons.push({
|
||||
@@ -90,7 +86,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
}, [editorControl]),
|
||||
|
||||
priority: -2,
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
listButtons.push({
|
||||
@@ -104,7 +99,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
}, [editorControl]),
|
||||
|
||||
priority: -2,
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
|
||||
@@ -116,7 +110,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
onPress: editorControl.decreaseIndent,
|
||||
|
||||
priority: -1,
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
listButtons.push({
|
||||
@@ -127,7 +120,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
onPress: editorControl.increaseIndent,
|
||||
|
||||
priority: -1,
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
|
||||
@@ -142,7 +134,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
onPress: editorControl.toggleBolded,
|
||||
|
||||
priority: 3,
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
inlineFormattingBtns.push({
|
||||
@@ -154,7 +145,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
onPress: editorControl.toggleItalicized,
|
||||
|
||||
priority: 2,
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
inlineFormattingBtns.push({
|
||||
@@ -164,7 +154,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
onPress: editorControl.toggleCode,
|
||||
|
||||
priority: 2,
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
if (props.editorSettings.katexEnabled) {
|
||||
@@ -175,7 +164,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
onPress: editorControl.toggleMath,
|
||||
|
||||
priority: 1,
|
||||
disabled: readOnly,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -188,7 +176,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
onPress: editorControl.showLinkDialog,
|
||||
|
||||
priority: -3,
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
|
||||
@@ -202,7 +189,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
onPress: useCallback(() => {
|
||||
editorControl.insertText(time.formatDateToLocal(new Date()));
|
||||
}, [editorControl]),
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
const onDismissKeyboard = useCallback(() => {
|
||||
@@ -222,7 +208,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
onDismissKeyboard();
|
||||
props.onAttach();
|
||||
}, [props.onAttach, onDismissKeyboard]),
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
actionButtons.push({
|
||||
@@ -242,7 +227,6 @@ const MarkdownToolbar = (props: MarkdownToolbarProps) => {
|
||||
}, [editorControl, props.searchState.dialogVisible]),
|
||||
|
||||
priority: -3,
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
const [keyboardVisible, setKeyboardVisible] = useState(false);
|
||||
|
||||
@@ -31,7 +31,6 @@ interface Props {
|
||||
style: ViewStyle;
|
||||
contentStyle?: ViewStyle;
|
||||
toolbarEnabled: boolean;
|
||||
readOnly: boolean;
|
||||
|
||||
onChange: ChangeEventHandler;
|
||||
onSelectionChange: SelectionChangeEventHandler;
|
||||
@@ -227,7 +226,6 @@ function NoteEditor(props: Props, ref: any) {
|
||||
themeData: editorTheme(props.themeId),
|
||||
katexEnabled: Setting.value('markdown.plugin.katex'),
|
||||
spellcheckEnabled: Setting.value('editor.mobile.spellcheckEnabled'),
|
||||
readOnly: props.readOnly,
|
||||
};
|
||||
|
||||
const injectedJavaScript = `
|
||||
@@ -379,7 +377,6 @@ function NoteEditor(props: Props, ref: any) {
|
||||
selectionState={selectionState}
|
||||
searchState={searchState}
|
||||
onAttach={props.onAttach}
|
||||
readOnly={props.readOnly}
|
||||
/>;
|
||||
|
||||
// - `scrollEnabled` prevents iOS from scrolling the document (has no effect on Android)
|
||||
|
||||
@@ -25,9 +25,9 @@ export const defaultSearchState: SearchState = {
|
||||
};
|
||||
|
||||
export interface SearchPanelProps {
|
||||
searchControl: SearchControl;
|
||||
searchState: SearchState;
|
||||
editorSettings: EditorSettings;
|
||||
searchControl: SearchControl;
|
||||
searchState: SearchState;
|
||||
editorSettings: EditorSettings;
|
||||
}
|
||||
|
||||
interface ActionButtonProps {
|
||||
|
||||
@@ -15,11 +15,10 @@ export interface EditorSettings {
|
||||
// [themeStyle(themeId: number)] doesn't work. As such, we need both
|
||||
// the [themeId] and [themeData].
|
||||
themeId: number;
|
||||
themeData: Theme;
|
||||
themeData: Theme;
|
||||
|
||||
katexEnabled: boolean;
|
||||
katexEnabled: boolean;
|
||||
spellcheckEnabled: boolean;
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
export interface ChangeEvent {
|
||||
@@ -42,14 +41,14 @@ export interface SelectionChangeEvent {
|
||||
}
|
||||
|
||||
export interface SearchControl {
|
||||
findNext(): void;
|
||||
findPrevious(): void;
|
||||
replaceCurrent(): void;
|
||||
replaceAll(): void;
|
||||
findNext(): void;
|
||||
findPrevious(): void;
|
||||
replaceCurrent(): void;
|
||||
replaceAll(): void;
|
||||
setSearchState(state: SearchState): void;
|
||||
|
||||
showSearch(): void;
|
||||
hideSearch(): void;
|
||||
showSearch(): void;
|
||||
hideSearch(): void;
|
||||
}
|
||||
|
||||
export interface SearchState {
|
||||
@@ -58,7 +57,7 @@ export interface SearchState {
|
||||
|
||||
searchText: string;
|
||||
replaceText: string;
|
||||
dialogVisible: boolean;
|
||||
dialogVisible: boolean;
|
||||
}
|
||||
|
||||
// Possible types of lists in the editor
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
const React = require('react');
|
||||
|
||||
import { Component } from 'react';
|
||||
import { Component, FunctionComponent } from 'react';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { FlatList, Text, StyleSheet, Button, View } from 'react-native';
|
||||
import { FlatList, Text, StyleSheet, Button, View, ViewStyle, TextStyle, ImageStyle, PanResponder } from 'react-native';
|
||||
import { FolderEntity, NoteEntity } from '@joplin/lib/services/database/types';
|
||||
import { AppState } from '../utils/types';
|
||||
import DraggableFlatList, { ScaleDecorator } from 'react-native-draggable-flatlist';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const { NoteItem } = require('./note-item.js');
|
||||
const { themeStyle } = require('./global-style.js');
|
||||
const { dialogs } = require('../utils/dialogs.js');
|
||||
const DialogBox = require('react-native-dialogbox').default;
|
||||
|
||||
interface NoteListProps {
|
||||
themeId: string;
|
||||
@@ -19,17 +23,91 @@ interface NoteListProps {
|
||||
folders: FolderEntity[];
|
||||
noteSelectionEnabled?: boolean;
|
||||
selectedFolderId?: string;
|
||||
onSorted: (sortedId: string, newIndex: number)=> void;
|
||||
}
|
||||
|
||||
class NoteListComponent extends Component<NoteListProps> {
|
||||
interface NoteListState {
|
||||
items: NoteEntity[];
|
||||
selectedItemIds: string[];
|
||||
}
|
||||
|
||||
interface NoteItemWrapperProps {
|
||||
note: NoteEntity;
|
||||
dialogbox: any;
|
||||
drag: ()=> void;
|
||||
isActive: boolean;
|
||||
style: ViewStyle | TextStyle | ImageStyle;
|
||||
noteSelectionEnabled?: boolean;
|
||||
dispatch: (payload: any)=> void;
|
||||
}
|
||||
|
||||
const NoteItemWrapper: FunctionComponent<NoteItemWrapperProps> = ({
|
||||
note,
|
||||
drag,
|
||||
isActive,
|
||||
style,
|
||||
dialogbox,
|
||||
noteSelectionEnabled,
|
||||
dispatch,
|
||||
}) => {
|
||||
if (Setting.value('notes.sortOrder.field') !== 'order') {
|
||||
drag = async () => {
|
||||
const doIt = await dialogs.confirmRef(dialogbox, `${_('To manually sort the notes, the sort order must be changed to "%s" in the menu "%s" > "%s"', _('Custom order'), _('View'), _('Sort notes by'))}\n\n${_('Do you want to do this')}`);
|
||||
|
||||
if (doIt) {
|
||||
Setting.setValue('notes.sortOrder.field', 'order');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const panResponder = React.useRef(
|
||||
PanResponder.create({
|
||||
// Ask to be the responder:
|
||||
onStartShouldSetPanResponder: () => false,
|
||||
onStartShouldSetPanResponderCapture: () => false,
|
||||
onMoveShouldSetPanResponder: (_) => true,
|
||||
onMoveShouldSetPanResponderCapture: (_) => true,
|
||||
onPanResponderGrant: () => {
|
||||
void drag();
|
||||
},
|
||||
onShouldBlockNativeResponder: () => false,
|
||||
})
|
||||
).current;
|
||||
|
||||
return (
|
||||
<View style={style} {...panResponder.panHandlers}>
|
||||
<NoteItem
|
||||
note={note}
|
||||
onLongPress={() => {
|
||||
dispatch({
|
||||
type: noteSelectionEnabled ? 'NOTE_SELECTION_TOGGLE' : 'NOTE_SELECTION_START',
|
||||
id: note.id,
|
||||
});
|
||||
}}
|
||||
disabled={isActive}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const ConnectedNoteItemWrapper = connect((state: AppState) => {
|
||||
return {
|
||||
noteSelectionEnabled: state.noteSelectionEnabled,
|
||||
};
|
||||
})(NoteItemWrapper);
|
||||
|
||||
class NoteListComponent extends Component<NoteListProps, NoteListState> {
|
||||
private rootRef_: FlatList;
|
||||
private styles_: Record<string, StyleSheet.NamedStyles<any>>;
|
||||
|
||||
/** DialogBox isn't a type, so we can't use it here */
|
||||
private dialogbox: any;
|
||||
|
||||
public constructor(props: NoteListProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
items: [],
|
||||
items: props.items || [],
|
||||
selectedItemIds: [],
|
||||
};
|
||||
this.rootRef_ = null;
|
||||
@@ -45,7 +123,7 @@ class NoteListComponent extends Component<NoteListProps> {
|
||||
if (this.styles_[themeId]) return this.styles_[themeId];
|
||||
this.styles_ = {};
|
||||
|
||||
const styles = {
|
||||
const styles: Record<string, ViewStyle | TextStyle | ImageStyle> = {
|
||||
noItemMessage: {
|
||||
paddingLeft: theme.marginLeft,
|
||||
paddingRight: theme.marginRight,
|
||||
@@ -55,8 +133,22 @@ class NoteListComponent extends Component<NoteListProps> {
|
||||
color: theme.color,
|
||||
textAlign: 'center',
|
||||
},
|
||||
selectAction: {
|
||||
flex: 1,
|
||||
paddingLeft: theme.marginLeft,
|
||||
// Reverse the color 4 to use for e.x. white text over blue bg
|
||||
backgroundColor: theme.color4,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
actionText: {
|
||||
// Reverse the color 4 to use for e.x. white text over blue bg
|
||||
color: theme.backgroundColor4,
|
||||
fontSize: theme.fontSize,
|
||||
},
|
||||
noteContainer: {
|
||||
backgroundColor: theme.backgroundColor,
|
||||
},
|
||||
noNotebookView: {
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
@@ -77,18 +169,51 @@ class NoteListComponent extends Component<NoteListProps> {
|
||||
if (this.rootRef_ && newProps.notesSource !== this.props.notesSource) {
|
||||
this.rootRef_.scrollToOffset({ offset: 0, animated: false });
|
||||
}
|
||||
|
||||
this.setState({
|
||||
items: newProps.items || [],
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
public renderMainContent() {
|
||||
// `enableEmptySections` is to fix this warning: https://github.com/FaridSafi/react-native-gifted-listview/issues/39
|
||||
|
||||
if (this.props.items.length) {
|
||||
return <FlatList
|
||||
ref={ref => (this.rootRef_ = ref)}
|
||||
data={this.props.items}
|
||||
renderItem={({ item }) => <NoteItem note={item} />}
|
||||
keyExtractor={item => item.id}
|
||||
/>;
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<DraggableFlatList
|
||||
ref={(ref: any) => (this.rootRef_ = ref)}
|
||||
data={this.state.items}
|
||||
renderItem={({ item, drag, isActive }) => (
|
||||
<ScaleDecorator>
|
||||
<ConnectedNoteItemWrapper
|
||||
note={item}
|
||||
drag={drag}
|
||||
isActive={isActive}
|
||||
style={this.styles().noteContainer}
|
||||
dialogbox={this.dialogbox}
|
||||
/>
|
||||
</ScaleDecorator>
|
||||
)}
|
||||
keyExtractor={item => item.id}
|
||||
onDragEnd={async ({ data, to, from }) => {
|
||||
if (this.props.selectedFolderId) {
|
||||
this.setState({ items: data });
|
||||
|
||||
if (this.props.onSorted) {
|
||||
let newIndex = to;
|
||||
|
||||
if (to > from) {
|
||||
newIndex++;
|
||||
}
|
||||
|
||||
this.props.onSorted(data[to].id, newIndex);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
if (!this.props.folders.length) {
|
||||
const noItemMessage = _('You currently have no notebooks.');
|
||||
@@ -104,15 +229,27 @@ class NoteListComponent extends Component<NoteListProps> {
|
||||
}
|
||||
}
|
||||
}
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
{this.renderMainContent()}
|
||||
<DialogBox
|
||||
ref={(dialogbox: any) => {
|
||||
this.dialogbox = dialogbox;
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const NoteList = connect((state: AppState) => {
|
||||
return {
|
||||
items: state.notes,
|
||||
folders: state.folders,
|
||||
selectedFolderId: state.selectedFolderId,
|
||||
notesSource: state.notesSource,
|
||||
themeId: state.settings.theme,
|
||||
noteSelectionEnabled: state.noteSelectionEnabled,
|
||||
};
|
||||
})(NoteListComponent);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ const Icon = require('react-native-vector-icons/Ionicons').default;
|
||||
const { BackButtonService } = require('../services/back-button.js');
|
||||
import NavService from '@joplin/lib/services/NavService';
|
||||
import { Menu, MenuOptions, MenuOption, MenuTrigger } from 'react-native-popup-menu';
|
||||
import { _, _n } from '@joplin/lib/locale';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
@@ -40,11 +40,10 @@ interface NavButtonPressEvent {
|
||||
screen: string;
|
||||
}
|
||||
|
||||
export interface MenuOptionType {
|
||||
interface MenuOptionType {
|
||||
onPress: OnPressCallback;
|
||||
isDivider?: boolean;
|
||||
title: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
type DispatchCommandType=(event: { type: string })=> void;
|
||||
@@ -202,11 +201,6 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
},
|
||||
};
|
||||
|
||||
styleObject.contextMenuItemTextDisabled = {
|
||||
...styleObject.contextMenuItemText,
|
||||
opacity: 0.5,
|
||||
};
|
||||
|
||||
styleObject.topIcon = { ...theme.icon };
|
||||
styleObject.topIcon.flex = 1;
|
||||
styleObject.topIcon.textAlignVertical = 'center';
|
||||
@@ -246,15 +240,11 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
private async duplicateButton_press() {
|
||||
const noteIds = this.props.selectedNoteIds;
|
||||
|
||||
this.props.dispatch({ type: 'NOTE_SELECTION_END' });
|
||||
// Duplicate all selected notes. ensureUniqueTitle is set to true to use the
|
||||
// original note's name as a root for the new unique identifier.
|
||||
await Note.duplicateMultipleNotes(noteIds, { ensureUniqueTitle: true });
|
||||
|
||||
try {
|
||||
// Duplicate all selected notes. ensureUniqueTitle is set to true to use the
|
||||
// original note's name as a root for the new unique identifier.
|
||||
await Note.duplicateMultipleNotes(noteIds, { ensureUniqueTitle: true });
|
||||
} catch (error) {
|
||||
alert(_n('This note could not be duplicated: %s', 'These notes could not be duplicated: %s', noteIds.length, error.message));
|
||||
}
|
||||
this.props.dispatch({ type: 'NOTE_SELECTION_END' });
|
||||
}
|
||||
|
||||
private async deleteButton_press() {
|
||||
@@ -269,12 +259,7 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
if (!ok) return;
|
||||
|
||||
this.props.dispatch({ type: 'NOTE_SELECTION_END' });
|
||||
|
||||
try {
|
||||
await Note.batchDelete(noteIds);
|
||||
} catch (error) {
|
||||
alert(_n('This note could not be deleted: %s', 'These notes could not be deleted: %s', noteIds.length, error.message));
|
||||
}
|
||||
await Note.batchDelete(noteIds);
|
||||
}
|
||||
|
||||
private menu_select(value: OnSelectCallbackType) {
|
||||
@@ -485,8 +470,8 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
menuOptionComponents.push(<View key={`menuOption_${key++}`} style={this.styles().divider} />);
|
||||
} else {
|
||||
menuOptionComponents.push(
|
||||
<MenuOption value={o.onPress} key={`menuOption_${key++}`} style={this.styles().contextMenuItem} disabled={!!o.disabled}>
|
||||
<Text style={o.disabled ? this.styles().contextMenuItemTextDisabled : this.styles().contextMenuItemText}>{o.title}</Text>
|
||||
<MenuOption value={o.onPress} key={`menuOption_${key++}`} style={this.styles().contextMenuItem}>
|
||||
<Text style={this.styles().contextMenuItemText}>{o.title}</Text>
|
||||
</MenuOption>
|
||||
);
|
||||
}
|
||||
@@ -538,13 +523,8 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
if (!ok) return;
|
||||
|
||||
this.props.dispatch({ type: 'NOTE_SELECTION_END' });
|
||||
|
||||
try {
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
await Note.moveToFolder(noteIds[i], folderId);
|
||||
}
|
||||
} catch (error) {
|
||||
alert(_n('This note could not be moved: %s', 'These notes could not be moved: %s', noteIds.length, error.message));
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
await Note.moveToFolder(noteIds[i], folderId);
|
||||
}
|
||||
}}
|
||||
mustSelect={!!folderPickerOptions.mustSelect}
|
||||
|
||||
22
packages/app-mobile/components/SideMenu.ts
Normal file
22
packages/app-mobile/components/SideMenu.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
const { connect } = require('react-redux');
|
||||
const SideMenu_ = require('react-native-side-menu-updated').default;
|
||||
import { Dimensions } from 'react-native';
|
||||
import { State } from '@joplin/lib/reducer';
|
||||
|
||||
class SideMenuComponent extends SideMenu_ {
|
||||
public onLayoutChange(e: any) {
|
||||
const { width, height } = e.nativeEvent.layout;
|
||||
const openMenuOffsetPercentage = this.props.openMenuOffset / Dimensions.get('window').width;
|
||||
const openMenuOffset = width * openMenuOffsetPercentage;
|
||||
const hiddenMenuOffset = width * this.state.hiddenMenuOffsetPercentage;
|
||||
this.setState({ width, height, openMenuOffset, hiddenMenuOffset });
|
||||
}
|
||||
}
|
||||
|
||||
const SideMenu = connect((state: State) => {
|
||||
return {
|
||||
isOpen: state.showSideMenu,
|
||||
};
|
||||
})(SideMenuComponent);
|
||||
|
||||
export default SideMenu;
|
||||
@@ -17,15 +17,15 @@ import { Dimensions } from 'react-native';
|
||||
|
||||
export interface ValueMap {
|
||||
// Value to use on small-width displays
|
||||
sm?: number;
|
||||
sm?: number;
|
||||
// Value to use on medium-width displays
|
||||
md?: number;
|
||||
md?: number;
|
||||
// Value to use on large-width displays
|
||||
lg?: number;
|
||||
lg?: number;
|
||||
// Value to use on extra-large width displays
|
||||
xl?: number;
|
||||
xl?: number;
|
||||
// Value to use on extra-extra-large width displays
|
||||
xxl?: number;
|
||||
xxl?: number;
|
||||
}
|
||||
|
||||
export default function getResponsiveValue(valueMap: ValueMap): number {
|
||||
|
||||
@@ -98,10 +98,9 @@ class NoteItemComponent extends Component {
|
||||
onLongPress() {
|
||||
if (!this.props.note) return;
|
||||
|
||||
this.props.dispatch({
|
||||
type: this.props.noteSelectionEnabled ? 'NOTE_SELECTION_TOGGLE' : 'NOTE_SELECTION_START',
|
||||
id: this.props.note.id,
|
||||
});
|
||||
if (this.props.onLongPress) {
|
||||
this.props.onLongPress();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -132,7 +131,7 @@ class NoteItemComponent extends Component {
|
||||
const noteTitle = Note.displayTitle(note);
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={() => this.onPress()} onLongPress={() => this.onLongPress()} activeOpacity={0.5}>
|
||||
<TouchableOpacity onPress={() => this.onPress()} onLongPress={() => this.onLongPress()} disabled={this.props.disabled} activeOpacity={0.5}>
|
||||
<View style={selectionWrapperStyle}>
|
||||
<View style={opacityStyle}>
|
||||
<View style={listItemStyle}>
|
||||
|
||||
@@ -25,7 +25,7 @@ import BaseModel from '@joplin/lib/BaseModel';
|
||||
import ActionButton from '../ActionButton';
|
||||
const { fileExtension, safeFileExtension } = require('@joplin/lib/path-utils');
|
||||
const mimeUtils = require('@joplin/lib/mime-utils.js').mime;
|
||||
import ScreenHeader, { MenuOptionType } from '../ScreenHeader';
|
||||
import ScreenHeader from '../ScreenHeader';
|
||||
const NoteTagsDialog = require('./NoteTagsDialog');
|
||||
import time from '@joplin/lib/time';
|
||||
const { Checkbox } = require('../checkbox.js');
|
||||
@@ -960,14 +960,13 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
const note = this.state.note;
|
||||
const isTodo = note && !!note.is_todo;
|
||||
const isSaved = note && note.id;
|
||||
const readOnly = this.state.readOnly;
|
||||
|
||||
const cacheKey = md5([isTodo, isSaved].join('_'));
|
||||
if (!this.menuOptionsCache_) this.menuOptionsCache_ = {};
|
||||
|
||||
if (this.menuOptionsCache_[cacheKey]) return this.menuOptionsCache_[cacheKey];
|
||||
|
||||
const output: MenuOptionType[] = [];
|
||||
const output = [];
|
||||
|
||||
// The file attachement modules only work in Android >= 5 (Version 21)
|
||||
// https://github.com/react-community/react-native-image-picker/issues/606
|
||||
@@ -982,7 +981,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
output.push({
|
||||
title: _('Attach...'),
|
||||
onPress: () => this.showAttachMenu(),
|
||||
disabled: readOnly,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -992,7 +990,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
onPress: () => {
|
||||
this.setState({ alarmDialogShown: true });
|
||||
},
|
||||
disabled: readOnly,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1001,7 +998,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
onPress: () => {
|
||||
void this.share_onPress();
|
||||
},
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
// Voice typing is enabled only for French language and on Android for now
|
||||
@@ -1012,7 +1008,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
// this.voiceRecording_onPress();
|
||||
this.setState({ voiceTypingDialogShown: true });
|
||||
},
|
||||
disabled: readOnly,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1029,7 +1024,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
onPress: () => {
|
||||
this.toggleIsTodo_onPress();
|
||||
},
|
||||
disabled: readOnly,
|
||||
});
|
||||
if (isSaved) {
|
||||
output.push({
|
||||
@@ -1050,7 +1044,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
onPress: () => {
|
||||
void this.deleteNote_onPress();
|
||||
},
|
||||
disabled: readOnly,
|
||||
});
|
||||
|
||||
this.menuOptionsCache_ = {};
|
||||
@@ -1113,7 +1106,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
|
||||
public folderPickerOptions() {
|
||||
const options = {
|
||||
enabled: !this.state.readOnly,
|
||||
enabled: true,
|
||||
selectedFolderId: this.state.folder ? this.state.folder.id : null,
|
||||
onValueChange: this.folderPickerOptions_valueChanged,
|
||||
};
|
||||
@@ -1251,7 +1244,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
onSelectionChange={this.body_selectionChange}
|
||||
onUndoRedoDepthChange={this.onUndoRedoDepthChange}
|
||||
onAttach={() => this.showAttachMenu()}
|
||||
readOnly={this.state.readOnly}
|
||||
style={{
|
||||
...editorStyle,
|
||||
paddingLeft: 0,
|
||||
@@ -1308,7 +1300,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
keyboardAppearance={theme.keyboardAppearance}
|
||||
placeholder={_('Add title')}
|
||||
placeholderTextColor={theme.colorFaded}
|
||||
editable={!this.state.readOnly}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -1335,7 +1326,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
undoButtonDisabled={!this.state.undoRedoButtonState.canUndo && this.state.undoRedoButtonState.canRedo}
|
||||
onUndoButtonPress={this.screenHeader_undoButtonPress}
|
||||
onRedoButtonPress={this.screenHeader_redoButtonPress}
|
||||
title={this.state.folder ? this.state.folder.title : ''}
|
||||
/>
|
||||
{titleComp}
|
||||
{bodyComponent}
|
||||
|
||||
@@ -69,6 +69,8 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
Setting.setValue(r.name, r.value);
|
||||
};
|
||||
|
||||
this.noteList_onSort = this.noteList_onSort.bind(this);
|
||||
|
||||
this.backHandler = () => {
|
||||
if (this.dialogbox && this.dialogbox.state && this.dialogbox.state.isVisible) {
|
||||
this.dialogbox.close();
|
||||
@@ -113,7 +115,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
}
|
||||
}
|
||||
|
||||
public async refreshNotes(props: any = null) {
|
||||
public async refreshNotes(props: any = null, force?: boolean) {
|
||||
if (props === null) props = this.props;
|
||||
|
||||
const options = {
|
||||
@@ -131,7 +133,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
parentId: parent.id,
|
||||
});
|
||||
|
||||
if (source === props.notesSource) return;
|
||||
if (source === props.notesSource && !force) return;
|
||||
|
||||
let notes = [];
|
||||
if (props.notesParentType === 'Folder') {
|
||||
@@ -149,21 +151,29 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
});
|
||||
}
|
||||
|
||||
public newNoteNavigate = async (folderId: string, isTodo: boolean) => {
|
||||
try {
|
||||
const newNote = await Note.save({
|
||||
parent_id: folderId,
|
||||
is_todo: isTodo ? 1 : 0,
|
||||
}, { provisional: true });
|
||||
private async noteList_onSort(sortedId: string, newIndex: number) {
|
||||
await Note.insertNotesAt(
|
||||
this.props.selectedFolderId,
|
||||
[sortedId],
|
||||
newIndex,
|
||||
this.props.uncompletedTodosOnTop,
|
||||
this.props.showCompletedTodos
|
||||
);
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Note',
|
||||
noteId: newNote.id,
|
||||
});
|
||||
} catch (error) {
|
||||
alert(_('Cannot create a new note: %s', error.message));
|
||||
}
|
||||
await this.refreshNotes(null, true);
|
||||
}
|
||||
|
||||
public newNoteNavigate = async (folderId: string, isTodo: boolean) => {
|
||||
const newNote = await Note.save({
|
||||
parent_id: folderId,
|
||||
is_todo: isTodo ? 1 : 0,
|
||||
}, { provisional: true });
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Note',
|
||||
noteId: newNote.id,
|
||||
});
|
||||
};
|
||||
|
||||
public parentItem(props: any = null) {
|
||||
@@ -258,7 +268,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
return (
|
||||
<View style={rootStyle}>
|
||||
<ScreenHeader title={iconString + title} showBackButton={false} parentComponent={thisComp} sortButton_press={this.sortButton_press} folderPickerOptions={this.folderPickerOptions()} showSearchButton={true} showSideMenuButton={true} />
|
||||
<NoteList />
|
||||
<NoteList onSorted={this.noteList_onSort} />
|
||||
{actionButtonComp}
|
||||
<DialogBox
|
||||
ref={(dialogbox: any) => {
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
import 'react-native-get-random-values';
|
||||
import 'react-native-url-polyfill/auto';
|
||||
|
||||
// Set up required for react-native-drawer-layout (See: https://reactnavigation.org/docs/drawer-layout/ v6.x)
|
||||
import 'react-native-gesture-handler';
|
||||
|
||||
import { LogBox, AppRegistry } from 'react-native';
|
||||
const Root = require('./root').default;
|
||||
|
||||
|
||||
@@ -343,7 +343,7 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-camera/RN (4.2.1):
|
||||
- React-Core
|
||||
- react-native-document-picker (8.2.1):
|
||||
- react-native-document-picker (8.2.0):
|
||||
- React-Core
|
||||
- react-native-fingerprint-scanner (6.0.0):
|
||||
- React
|
||||
@@ -361,7 +361,7 @@ PODS:
|
||||
- React
|
||||
- react-native-saf-x (2.12.0):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.5.5):
|
||||
- react-native-safe-area-context (4.5.3):
|
||||
- RCT-Folly
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@@ -467,17 +467,15 @@ PODS:
|
||||
- React-Core
|
||||
- RNDateTimePicker (7.1.0):
|
||||
- React-Core
|
||||
- RNDeviceInfo (10.6.0):
|
||||
- React-Core
|
||||
- RNExitApp (1.1.0):
|
||||
- React
|
||||
- RNFileViewer (2.1.5):
|
||||
- React-Core
|
||||
- RNFS (2.20.0):
|
||||
- React-Core
|
||||
- RNGestureHandler (2.10.2):
|
||||
- RNGestureHandler (2.12.0):
|
||||
- React-Core
|
||||
- RNLocalize (3.0.1):
|
||||
- RNLocalize (3.0.0):
|
||||
- React-Core
|
||||
- RNQuickAction (0.3.13):
|
||||
- React
|
||||
@@ -610,7 +608,6 @@ DEPENDENCIES:
|
||||
- "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)"
|
||||
- "RNCPushNotificationIOS (from `../node_modules/@react-native-community/push-notification-ios`)"
|
||||
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
|
||||
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
|
||||
- RNExitApp (from `../node_modules/react-native-exit-app`)
|
||||
- RNFileViewer (from `../node_modules/react-native-file-viewer`)
|
||||
- RNFS (from `../node_modules/react-native-fs`)
|
||||
@@ -754,8 +751,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/@react-native-community/push-notification-ios"
|
||||
RNDateTimePicker:
|
||||
:path: "../node_modules/@react-native-community/datetimepicker"
|
||||
RNDeviceInfo:
|
||||
:path: "../node_modules/react-native-device-info"
|
||||
RNExitApp:
|
||||
:path: "../node_modules/react-native-exit-app"
|
||||
RNFileViewer:
|
||||
@@ -782,9 +777,9 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
boost: a7c83b31436843459a1961bfd74b96033dc77234
|
||||
boost: 57d2868c099736d80fcd648bf211b4431e51a558
|
||||
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
|
||||
DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
FBLazyVector: ddb55c55295ea51ed98aa7e2e08add2f826309d5
|
||||
FBReactNativeSpec: 90fc1a90b4b7a171e0a7c20ea426c1bf6ce4399c
|
||||
Flipper: 26fc4b7382499f1281eb8cb921e5c3ad6de91fe0
|
||||
@@ -797,13 +792,13 @@ SPEC CHECKSUMS:
|
||||
Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541
|
||||
FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86
|
||||
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
||||
glog: 5337263514dd6f09803962437687240c5dc39aa4
|
||||
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
|
||||
hermes-engine: d27603b55a48402501ad1928c05411dae9cd6b85
|
||||
JoplinCommonShareExtension: a8b60b02704d85a7305627912c0240e94af78db7
|
||||
JoplinRNShareExtension: 485f3e6dad83b7b77f1572eabc249f869ee55c02
|
||||
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
||||
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
|
||||
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
|
||||
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
|
||||
RCTRequired: 8ef706f91e2b643cd32c26a57700b5f24fab0585
|
||||
RCTTypeSafety: 5fbddd8eb9242b91ac0d901c01da3673f358b1b7
|
||||
React: e5d2d559e89d256a1d6da64d51adaecda9c8ddae
|
||||
@@ -819,7 +814,7 @@ SPEC CHECKSUMS:
|
||||
React-logger: ef2269b3afa6ba868da90496c3e17a4ec4f4cee0
|
||||
react-native-alarm-notification: 0732f97be04975a23ba60e675bdb961a0aaf6aa6
|
||||
react-native-camera: 3eae183c1d111103963f3dd913b65d01aef8110f
|
||||
react-native-document-picker: 69ca2094d8780cfc1e7e613894d15290fdc54bba
|
||||
react-native-document-picker: 495c444c0c773c6e83a5d91165890ecb1c0a399a
|
||||
react-native-fingerprint-scanner: ac6656f18c8e45a7459302b84da41a44ad96dbbe
|
||||
react-native-geolocation: 0f7fe8a4c2de477e278b0365cce27d089a8c5903
|
||||
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
|
||||
@@ -828,7 +823,7 @@ SPEC CHECKSUMS:
|
||||
react-native-netinfo: ccbe1085dffd16592791d550189772e13bf479e2
|
||||
react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a
|
||||
react-native-saf-x: 129cd2ddf120a1f6164c724b2846d172666b33de
|
||||
react-native-safe-area-context: 33e71d7408dffe148b08968d97a524009634ccc8
|
||||
react-native-safe-area-context: b8979f5eda6ed5903d4dbc885be3846ea3daa753
|
||||
react-native-slider: 33b8d190b59d4f67a541061bb91775d53d617d9d
|
||||
react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261
|
||||
react-native-version-info: a106f23009ac0db4ee00de39574eb546682579b9
|
||||
@@ -850,12 +845,11 @@ SPEC CHECKSUMS:
|
||||
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
||||
RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8
|
||||
RNDateTimePicker: 7ecd54a97fc3749f38c3c89a171f6cbd52f3c142
|
||||
RNDeviceInfo: 475a4c447168d0ad4c807e48ef5e0963a0f4eb1b
|
||||
RNExitApp: c4e052df2568b43bec8a37c7cd61194d4cfee2c3
|
||||
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
|
||||
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
|
||||
RNGestureHandler: f75d81410b40aaa99e71ae8f8bb7a88620c95042
|
||||
RNLocalize: 6dd9226886fa61bf0cefc7644e3f9620770b1a31
|
||||
RNGestureHandler: dec4645026e7401a0899f2846d864403478ff6a5
|
||||
RNLocalize: 5944c97d2fe8150913a51ddd5eab4e23a82bd80d
|
||||
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
|
||||
RNReanimated: 9976fbaaeb8a188c36026154c844bf374b3b7eeb
|
||||
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef
|
||||
|
||||
@@ -43,29 +43,30 @@
|
||||
"react-native": "0.71.10",
|
||||
"react-native-action-button": "2.8.5",
|
||||
"react-native-camera": "4.2.1",
|
||||
"react-native-device-info": "10.6.0",
|
||||
"react-native-dialogbox": "0.6.10",
|
||||
"react-native-document-picker": "8.2.1",
|
||||
"react-native-drawer-layout": "3.2.1",
|
||||
"react-native-document-picker": "8.2.0",
|
||||
"react-native-draggable-flatlist": "4.0.1",
|
||||
"react-native-drawer-layout": "3.2.0",
|
||||
"react-native-dropdownalert": "4.5.1",
|
||||
"react-native-exit-app": "1.1.0",
|
||||
"react-native-file-viewer": "2.1.5",
|
||||
"react-native-fingerprint-scanner": "6.0.0",
|
||||
"react-native-fs": "2.20.0",
|
||||
"react-native-gesture-handler": "2.10.2",
|
||||
"react-native-gesture-handler": "2.12.0",
|
||||
"react-native-get-random-values": "1.9.0",
|
||||
"react-native-image-picker": "5.4.2",
|
||||
"react-native-image-resizer": "1.4.5",
|
||||
"react-native-localize": "3.0.1",
|
||||
"react-native-localize": "3.0.0",
|
||||
"react-native-modal-datetime-picker": "15.0.1",
|
||||
"react-native-paper": "5.8.0",
|
||||
"react-native-popup-menu": "0.16.1",
|
||||
"react-native-quick-actions": "0.3.13",
|
||||
"react-native-reanimated": "3.3.0",
|
||||
"react-native-rsa-native": "2.0.5",
|
||||
"react-native-safe-area-context": "4.5.5",
|
||||
"react-native-safe-area-context": "4.5.3",
|
||||
"react-native-securerandom": "1.0.1",
|
||||
"react-native-share": "8.2.2",
|
||||
"react-native-side-menu-updated": "1.3.2",
|
||||
"react-native-sqlite-storage": "6.0.1",
|
||||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-vector-icons": "9.2.0",
|
||||
|
||||
@@ -28,7 +28,7 @@ import SyncTargetJoplinCloud from '@joplin/lib/SyncTargetJoplinCloud';
|
||||
import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive';
|
||||
import initProfile from '@joplin/lib/services/profileConfig/initProfile';
|
||||
const VersionInfo = require('react-native-version-info').default;
|
||||
const { Keyboard, BackHandler, View, StatusBar, Platform, Dimensions } = require('react-native');
|
||||
const { Keyboard, BackHandler, Animated, View, StatusBar, Platform, Dimensions } = require('react-native');
|
||||
import { AppState as RNAppState, EmitterSubscription, Linking, NativeEventSubscription } from 'react-native';
|
||||
import getResponsiveValue from './components/getResponsiveValue';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
@@ -67,7 +67,7 @@ const { OneDriveLoginScreen } = require('./components/screens/onedrive-login.js'
|
||||
import EncryptionConfigScreen from './components/screens/encryption-config';
|
||||
const { DropboxLoginScreen } = require('./components/screens/dropbox-login.js');
|
||||
const { MenuContext } = require('react-native-popup-menu');
|
||||
import { Drawer } from 'react-native-drawer-layout';
|
||||
import SideMenu from './components/SideMenu';
|
||||
import SideMenuContent from './components/side-menu-content';
|
||||
const { SideMenuContentNote } = require('./components/side-menu-content-note.js');
|
||||
const { DatabaseDriverReactNative } = require('./utils/database-driver-react-native');
|
||||
@@ -116,10 +116,7 @@ import ProfileEditor from './components/ProfileSwitcher/ProfileEditor';
|
||||
import sensorInfo, { SensorInfo } from './components/biometrics/sensorInfo';
|
||||
import { getCurrentProfile } from '@joplin/lib/services/profileConfig';
|
||||
import { getDatabaseName, getProfilesRootDir, getResourceDir, setDispatch } from './services/profiles';
|
||||
import { ReactNode } from 'react';
|
||||
import { parseShareCache } from '@joplin/lib/services/share/reducer';
|
||||
|
||||
type SideMenuPosition = 'left' | 'right';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
|
||||
const logger = Logger.create('root');
|
||||
|
||||
@@ -517,7 +514,6 @@ async function initialize(dispatch: Function) {
|
||||
if (!Setting.value('clientId')) Setting.setValue('clientId', uuid.create());
|
||||
reg.logger().info(`Client ID: ${Setting.value('clientId')}`);
|
||||
|
||||
BaseItem.syncShareCache = parseShareCache(Setting.value('sync.shareCache'));
|
||||
|
||||
if (Setting.value('firstStart')) {
|
||||
const detectedLocale = shim.detectAndSetLocale(Setting);
|
||||
@@ -717,6 +713,7 @@ class AppComponent extends React.Component {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
sideMenuContentOpacity: new Animated.Value(0),
|
||||
sideMenuWidth: this.getSideMenuWidth(),
|
||||
sensorInfo: null,
|
||||
};
|
||||
@@ -960,8 +957,8 @@ class AppComponent extends React.Component {
|
||||
if (this.props.appState !== 'ready') return null;
|
||||
const theme: Theme = themeStyle(this.props.themeId);
|
||||
|
||||
let sideMenuContent: ReactNode = null;
|
||||
let menuPosition: SideMenuPosition = 'left';
|
||||
let sideMenuContent = null;
|
||||
let menuPosition = 'left';
|
||||
|
||||
if (this.props.routeName === 'Note') {
|
||||
sideMenuContent = <SafeAreaView style={{ flex: 1, backgroundColor: theme.backgroundColor }}><SideMenuContentNote options={this.props.noteSideMenuOptions}/></SafeAreaView>;
|
||||
@@ -1000,20 +997,18 @@ class AppComponent extends React.Component {
|
||||
|
||||
const mainContent = (
|
||||
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
|
||||
<Drawer
|
||||
// Need to reset the key here based on menu position, otherwise
|
||||
// the drawer will flash open on screen and close every time the
|
||||
// drawer position switches (i.e. when opening or closing a note)
|
||||
key={`main-drawer-${menuPosition}`}
|
||||
open={this.props.showSideMenu}
|
||||
onOpen={() => this.sideMenu_change(true)}
|
||||
onClose={() => this.sideMenu_change(false)}
|
||||
drawerPosition={menuPosition}
|
||||
swipeEdgeWidth={15}
|
||||
drawerStyle={{
|
||||
width: this.state.sideMenuWidth,
|
||||
<SideMenu
|
||||
menu={sideMenuContent}
|
||||
edgeHitWidth={5}
|
||||
openMenuOffset={this.state.sideMenuWidth}
|
||||
menuPosition={menuPosition}
|
||||
onChange={(isOpen: boolean) => this.sideMenu_change(isOpen)}
|
||||
onSliding={(percent: number) => {
|
||||
this.props.dispatch({
|
||||
type: 'SIDE_MENU_OPEN_PERCENT',
|
||||
value: percent,
|
||||
});
|
||||
}}
|
||||
renderDrawerContent={() => sideMenuContent}
|
||||
>
|
||||
<StatusBar barStyle={statusBarStyle} />
|
||||
<MenuContext style={{ flex: 1 }}>
|
||||
@@ -1030,7 +1025,7 @@ class AppComponent extends React.Component {
|
||||
/> }
|
||||
</SafeAreaView>
|
||||
</MenuContext>
|
||||
</Drawer>
|
||||
</SideMenu>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1039,20 +1034,22 @@ class AppComponent extends React.Component {
|
||||
|
||||
// Wrap everything in a PaperProvider -- this allows using components from react-native-paper
|
||||
return (
|
||||
<PaperProvider theme={{
|
||||
...paperTheme,
|
||||
version: 3,
|
||||
colors: {
|
||||
...paperTheme.colors,
|
||||
onPrimaryContainer: theme.color5,
|
||||
primaryContainer: theme.backgroundColor5,
|
||||
surfaceVariant: theme.backgroundColor,
|
||||
onSurfaceVariant: theme.color,
|
||||
primary: theme.color,
|
||||
},
|
||||
}}>
|
||||
{mainContent}
|
||||
</PaperProvider>
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<PaperProvider theme={{
|
||||
...paperTheme,
|
||||
version: 3,
|
||||
colors: {
|
||||
...paperTheme.colors,
|
||||
onPrimaryContainer: theme.color5,
|
||||
primaryContainer: theme.backgroundColor5,
|
||||
surfaceVariant: theme.backgroundColor,
|
||||
onSurfaceVariant: theme.color,
|
||||
primary: theme.color,
|
||||
},
|
||||
}}>
|
||||
{mainContent}
|
||||
</PaperProvider>
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import { Dispatch } from 'redux';
|
||||
const { NativeEventEmitter, NativeModules, Platform } = require('react-native');
|
||||
|
||||
interface NotificationData {
|
||||
joplinNotificationId: string;
|
||||
noteId: string;
|
||||
joplinNotificationId: string;
|
||||
noteId: string;
|
||||
}
|
||||
|
||||
export default async (dispatch: Dispatch) => {
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
"README.md"
|
||||
],
|
||||
"devDependencies": {
|
||||
"standard": "17.1.0",
|
||||
"tap": "16.3.7"
|
||||
"standard": "17.0.0",
|
||||
"tap": "16.3.6"
|
||||
},
|
||||
"gitHead": "eb4b0e64eab40a51b0895d3a40a9d8c3cb7b1b14"
|
||||
}
|
||||
|
||||
@@ -58,9 +58,6 @@ import RSA from './services/e2ee/RSA.node';
|
||||
import Resource from './models/Resource';
|
||||
import { ProfileConfig } from './services/profileConfig/types';
|
||||
import initProfile from './services/profileConfig/initProfile';
|
||||
import { parseShareCache } from './services/share/reducer';
|
||||
|
||||
import RotatingLogs from './RotatingLogs';
|
||||
|
||||
const appLogger: LoggerWrapper = Logger.create('App');
|
||||
|
||||
@@ -88,8 +85,6 @@ export default class BaseApplication {
|
||||
|
||||
protected store_: Store<any> = null;
|
||||
|
||||
private rotatingLogs: RotatingLogs;
|
||||
|
||||
public constructor() {
|
||||
this.eventEmitter_ = new EventEmitter();
|
||||
this.decryptionWorker_resourceMetadataButNotBlobDecrypted = this.decryptionWorker_resourceMetadataButNotBlobDecrypted.bind(this);
|
||||
@@ -547,7 +542,7 @@ export default class BaseApplication {
|
||||
const newState = store.getState();
|
||||
|
||||
if (this.hasGui() && ['NOTE_UPDATE_ONE', 'NOTE_DELETE', 'FOLDER_UPDATE_ONE', 'FOLDER_DELETE'].indexOf(action.type) >= 0) {
|
||||
if (!(await reg.syncTarget().syncStarted())) void reg.scheduleSync(1000, { syncSteps: ['update_remote', 'delete_remote'] });
|
||||
if (!(await reg.syncTarget().syncStarted())) void reg.scheduleSync(30 * 1000, { syncSteps: ['update_remote', 'delete_remote'] });
|
||||
SearchEngine.instance().scheduleSyncTables();
|
||||
}
|
||||
|
||||
@@ -841,8 +836,6 @@ export default class BaseApplication {
|
||||
|
||||
appLogger.info(`Client ID: ${Setting.value('clientId')}`);
|
||||
|
||||
BaseItem.syncShareCache = parseShareCache(Setting.value('sync.shareCache'));
|
||||
|
||||
if (initArgs?.isSafeMode) {
|
||||
Setting.setValue('isSafeMode', true);
|
||||
}
|
||||
@@ -934,18 +927,6 @@ export default class BaseApplication {
|
||||
|
||||
await MigrationService.instance().run();
|
||||
|
||||
this.rotatingLogs = new RotatingLogs(profileDir);
|
||||
const processLogs = async () => {
|
||||
try {
|
||||
await this.rotatingLogs.cleanActiveLogFile();
|
||||
await this.rotatingLogs.deleteNonActiveLogFiles();
|
||||
} catch (error) {
|
||||
appLogger.error(error);
|
||||
}
|
||||
};
|
||||
shim.setTimeout(() => { void processLogs(); }, 60000);
|
||||
shim.setInterval(() => { void processLogs(); }, 24 * 60 * 60 * 1000);
|
||||
|
||||
return argv;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import Database from './database';
|
||||
import uuid from './uuid';
|
||||
import time from './time';
|
||||
import JoplinDatabase, { TableField } from './JoplinDatabase';
|
||||
import { LoadOptions, SaveOptions } from './models/utils/types';
|
||||
import { LoadOptions } from './models/utils/types';
|
||||
const Mutex = require('async-mutex').Mutex;
|
||||
|
||||
// New code should make use of this enum
|
||||
@@ -535,7 +535,7 @@ class BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
public static async save(o: any, options: SaveOptions = null) {
|
||||
public static async save(o: any, options: any = null) {
|
||||
// When saving, there's a mutex per model ID. This is because the model returned from this function
|
||||
// is basically its input `o` (instead of being read from the database, for performance reasons).
|
||||
// This works well in general except if that model is saved simultaneously in two places. In that
|
||||
@@ -546,8 +546,7 @@ class BaseModel {
|
||||
const mutexRelease = await this.saveMutex(o).acquire();
|
||||
|
||||
options = this.modOptions(options);
|
||||
const isNew = this.isNew(o, options);
|
||||
options.isNew = isNew;
|
||||
options.isNew = this.isNew(o, options);
|
||||
|
||||
// Diff saving is an optimisation which takes a new version of the item and an old one,
|
||||
// do a diff and save only this diff. IMPORTANT: When using this make sure that both
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import EventDispatcher from './EventDispatcher';
|
||||
|
||||
enum TestKey {
|
||||
FooEvent,
|
||||
BarEvent,
|
||||
BazEvent,
|
||||
FooEvent,
|
||||
BarEvent,
|
||||
BazEvent,
|
||||
}
|
||||
|
||||
describe('EventDispatcher', () => {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
"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 shim_1 = require("./shim");
|
||||
class RotatingLogs {
|
||||
constructor(logFilesDir, maxFileSize = null, inactiveMaxAge = null) {
|
||||
this.maxFileSize = 1024 * 1024 * 100;
|
||||
this.inactiveMaxAge = 90 * 24 * 60 * 60 * 1000;
|
||||
this.logFilesDir = logFilesDir;
|
||||
if (maxFileSize)
|
||||
this.maxFileSize = maxFileSize;
|
||||
if (inactiveMaxAge)
|
||||
this.inactiveMaxAge = inactiveMaxAge;
|
||||
}
|
||||
cleanActiveLogFile() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const stats = yield this.fsDriver().stat(this.logFileFullpath());
|
||||
if (stats.size >= this.maxFileSize) {
|
||||
const newLogFile = this.logFileFullpath(this.getNameToNonActiveLogFile());
|
||||
yield this.fsDriver().move(this.logFileFullpath(), newLogFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
getNameToNonActiveLogFile() {
|
||||
return `log-${Date.now()}.txt`;
|
||||
}
|
||||
deleteNonActiveLogFiles() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const files = yield this.fsDriver().readDirStats(this.logFilesDir);
|
||||
for (const file of files) {
|
||||
if (!file.path.match(/^log-[0-9]+.txt$/gi))
|
||||
continue;
|
||||
const ageOfTheFile = Date.now() - file.birthtime;
|
||||
if (ageOfTheFile >= this.inactiveMaxAge) {
|
||||
yield this.fsDriver().remove(this.logFileFullpath(file.path));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
logFileFullpath(fileName = 'log.txt') {
|
||||
return `${this.logFilesDir}/${fileName}`;
|
||||
}
|
||||
fsDriver() {
|
||||
return shim_1.default.fsDriver();
|
||||
}
|
||||
}
|
||||
exports.default = RotatingLogs;
|
||||
//# sourceMappingURL=RotatingLogs.js.map
|
||||
@@ -1,56 +0,0 @@
|
||||
"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 fs_extra_1 = require("fs-extra");
|
||||
const test_utils_1 = require("./testing/test-utils");
|
||||
const RotatingLogs_1 = require("./RotatingLogs");
|
||||
const createTestLogFile = (dir) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
yield (0, fs_extra_1.writeFile)(`${dir}/log.txt`, 'some content');
|
||||
});
|
||||
describe('RotatingLogs', () => {
|
||||
test('should rename log.txt to log-TIMESTAMP.txt', () => __awaiter(void 0, void 0, void 0, function* () {
|
||||
let dir;
|
||||
try {
|
||||
dir = yield (0, test_utils_1.createTempDir)();
|
||||
yield createTestLogFile(dir);
|
||||
let files = yield (0, fs_extra_1.readdir)(dir);
|
||||
expect(files.find(file => file.match(/^log.txt$/gi))).toBeTruthy();
|
||||
expect(files.length).toBe(1);
|
||||
const rotatingLogs = new RotatingLogs_1.default(dir, 1, 1);
|
||||
yield rotatingLogs.cleanActiveLogFile();
|
||||
files = yield (0, fs_extra_1.readdir)(dir);
|
||||
expect(files.find(file => file.match(/^log.txt$/gi))).toBeFalsy();
|
||||
expect(files.find(file => file.match(/^log-[0-9]+.txt$/gi))).toBeTruthy();
|
||||
expect(files.length).toBe(1);
|
||||
}
|
||||
finally {
|
||||
yield (0, fs_extra_1.remove)(dir);
|
||||
}
|
||||
}));
|
||||
test('should delete inative log file after 1ms', () => __awaiter(void 0, void 0, void 0, function* () {
|
||||
let dir;
|
||||
try {
|
||||
dir = yield (0, test_utils_1.createTempDir)();
|
||||
yield createTestLogFile(dir);
|
||||
const rotatingLogs = new RotatingLogs_1.default(dir, 1, 1);
|
||||
yield rotatingLogs.cleanActiveLogFile();
|
||||
yield (0, test_utils_1.msleep)(1);
|
||||
yield rotatingLogs.deleteNonActiveLogFiles();
|
||||
const files = yield (0, fs_extra_1.readdir)(dir);
|
||||
expect(files.find(file => file.match(/^log-[0-9]+.txt$/gi))).toBeFalsy();
|
||||
expect(files.length).toBe(0);
|
||||
}
|
||||
finally {
|
||||
yield (0, fs_extra_1.remove)(dir);
|
||||
}
|
||||
}));
|
||||
});
|
||||
//# sourceMappingURL=RotatingLogs.test.js.map
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user