You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-12-14 23:26:58 +02:00
Compare commits
209 Commits
android-v2
...
mac-binary
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
357277cda0 | ||
|
|
8c1568ae81 | ||
|
|
66e479d13d | ||
|
|
700c1410fa | ||
|
|
3873e15a21 | ||
|
|
72ec2d3b1e | ||
|
|
7eddbc7002 | ||
|
|
d593ed130e | ||
|
|
b2eabb3836 | ||
|
|
5e1c789926 | ||
|
|
4f552b396c | ||
|
|
132bcbd477 | ||
|
|
8f6192464b | ||
|
|
897addec4e | ||
|
|
f3eea43d24 | ||
|
|
8babaddbcb | ||
|
|
13cdaabb17 | ||
|
|
a94aa21088 | ||
|
|
6116bed4e3 | ||
|
|
fabd0b4dda | ||
|
|
6b72f86e7b | ||
|
|
02cf546124 | ||
|
|
eecb012d64 | ||
|
|
04e9b40769 | ||
|
|
efdbaeb397 | ||
|
|
46425b920c | ||
|
|
f5be43c2ac | ||
|
|
51e865c467 | ||
|
|
080541a2fe | ||
|
|
ac8b3ae0cc | ||
|
|
0b3919fd62 | ||
|
|
7dc638edf4 | ||
|
|
db69125e8f | ||
|
|
3b686194d8 | ||
|
|
ccabc778f6 | ||
|
|
5c2640f88f | ||
|
|
eca0f92dff | ||
|
|
260fa6c038 | ||
|
|
8ec6bc9138 | ||
|
|
93fa92369b | ||
|
|
56b9c9fbc9 | ||
|
|
bc6c5ab7a7 | ||
|
|
c68d196baa | ||
|
|
1826625e4f | ||
|
|
20b8fb2719 | ||
|
|
f813e71b29 | ||
|
|
02422a6e31 | ||
|
|
69a34e87f3 | ||
|
|
cbeaa16b61 | ||
|
|
05917ac142 | ||
|
|
897fd0f727 | ||
|
|
9edb402b18 | ||
|
|
36ae58ffe1 | ||
|
|
5ef8b86957 | ||
|
|
c7228cfcd6 | ||
|
|
7a791dbf98 | ||
|
|
a543588527 | ||
|
|
998ad142a6 | ||
|
|
b6a53f96f7 | ||
|
|
841a9c6e09 | ||
|
|
d25c078ef0 | ||
|
|
0c8de68b80 | ||
|
|
44d93d52d3 | ||
|
|
073bec9e8c | ||
|
|
e6a8c2bea5 | ||
|
|
81c316cd2c | ||
|
|
659c851960 | ||
|
|
572701d9a0 | ||
|
|
ff69ac17be | ||
|
|
b44945b3a0 | ||
|
|
2584224026 | ||
|
|
46136871bf | ||
|
|
66ef37bd4e | ||
|
|
9ddf75604d | ||
|
|
3ed7e1d7e8 | ||
|
|
b2b412105a | ||
|
|
60a3c4f65e | ||
|
|
9645414c17 | ||
|
|
af0136ef39 | ||
|
|
b76586c4fd | ||
|
|
376e4ebde0 | ||
|
|
1439b8787f | ||
|
|
b8854a99be | ||
|
|
6cf02173dc | ||
|
|
4d8a53d8c9 | ||
|
|
7f43718e1d | ||
|
|
690ce637b1 | ||
|
|
4d023e679e | ||
|
|
6e220a978f | ||
|
|
39757cd90e | ||
|
|
5ccbbea757 | ||
|
|
309222c082 | ||
|
|
50f5fe2c91 | ||
|
|
eacae83182 | ||
|
|
403d770b1d | ||
|
|
a481bf1b53 | ||
|
|
0d32570c9e | ||
|
|
f017e99b02 | ||
|
|
a89d64d435 | ||
|
|
3a27086534 | ||
|
|
413c1e41b5 | ||
|
|
8b879464b8 | ||
|
|
97c9bbc1fe | ||
|
|
e5bebef7b2 | ||
|
|
73752c4b3f | ||
|
|
dcf7c9838d | ||
|
|
f325e7694b | ||
|
|
75d204c9ca | ||
|
|
cf4008951d | ||
|
|
d67818d096 | ||
|
|
6aaea8ad4f | ||
|
|
de41278096 | ||
|
|
f01ab70907 | ||
|
|
bbdb221a67 | ||
|
|
7d053f8c79 | ||
|
|
fcad0bf3ca | ||
|
|
58f929f6b5 | ||
|
|
943198c56e | ||
|
|
2112ad4004 | ||
|
|
5995dc81f3 | ||
|
|
104e752634 | ||
|
|
fc335cd15d | ||
|
|
45923ba0d3 | ||
|
|
8fefa99d81 | ||
|
|
85d652cd67 | ||
|
|
88e41e9c7d | ||
|
|
26750488d0 | ||
|
|
e115ef4259 | ||
|
|
bcec699124 | ||
|
|
d23c728a1a | ||
|
|
0a2d507dec | ||
|
|
0c08617606 | ||
|
|
29fba45c33 | ||
|
|
1071a455b6 | ||
|
|
57e4b36fd7 | ||
|
|
f08fa92294 | ||
|
|
3a8d87d292 | ||
|
|
53302c9e90 | ||
|
|
28a24d8c03 | ||
|
|
3e52411bc4 | ||
|
|
1548ea18e1 | ||
|
|
f8cd1ba8e5 | ||
|
|
d18a4be31f | ||
|
|
c56f270ed6 | ||
|
|
2bca3d1032 | ||
|
|
9f81d69c5e | ||
|
|
815419260d | ||
|
|
6729a3d51f | ||
|
|
6d8ce280dd | ||
|
|
9e5b455065 | ||
|
|
7754048b80 | ||
|
|
ffeeff260f | ||
|
|
71ea74d273 | ||
|
|
d9ba27a1ec | ||
|
|
0a3540049c | ||
|
|
ab50ca9bbd | ||
|
|
0bee793ab8 | ||
|
|
89fc5e19d9 | ||
|
|
6a3bf51084 | ||
|
|
df1e298c84 | ||
|
|
b9c706324b | ||
|
|
ab7e2de1d9 | ||
|
|
7ef8753b94 | ||
|
|
d48a5efa03 | ||
|
|
0804b62ffb | ||
|
|
489d6778db | ||
|
|
538a1413d9 | ||
|
|
3c9b755045 | ||
|
|
fd7b345efa | ||
|
|
f6d1a27f51 | ||
|
|
80a1500634 | ||
|
|
bcb578c933 | ||
|
|
75ad454971 | ||
|
|
e90b7f2d81 | ||
|
|
ffca11ca8a | ||
|
|
bd98951d32 | ||
|
|
5c6e17bc89 | ||
|
|
d871b3c7d6 | ||
|
|
99c6c9b411 | ||
|
|
3eca4ada5a | ||
|
|
19431abc73 | ||
|
|
3e299f1ab9 | ||
|
|
42873d3829 | ||
|
|
4983327f90 | ||
|
|
fddbe6cf6c | ||
|
|
57f00c612d | ||
|
|
235288e903 | ||
|
|
38be744c3e | ||
|
|
2384ec8792 | ||
|
|
85f7caa0eb | ||
|
|
437320f90c | ||
|
|
c1db7182ac | ||
|
|
aa4af69afc | ||
|
|
21a39af97b | ||
|
|
0812cc5944 | ||
|
|
1fb5d2c6c5 | ||
|
|
1b9901d232 | ||
|
|
f15d2793cc | ||
|
|
ad4d71dbe1 | ||
|
|
4bee6ffc90 | ||
|
|
01f63b3d97 | ||
|
|
b19b590efc | ||
|
|
6b7577f94d | ||
|
|
4d09b14522 | ||
|
|
9f1e95324d | ||
|
|
f98314346d | ||
|
|
fa659b615a | ||
|
|
e4aafa7edb | ||
|
|
811c40b074 |
@@ -66,6 +66,7 @@ packages/lib/welcomeAssets.js
|
|||||||
packages/plugins/**/api
|
packages/plugins/**/api
|
||||||
packages/plugins/**/dist
|
packages/plugins/**/dist
|
||||||
packages/server/dist/
|
packages/server/dist/
|
||||||
|
packages/utils/dist/
|
||||||
packages/tools/node_modules
|
packages/tools/node_modules
|
||||||
packages/tools/PortableAppsLauncher
|
packages/tools/PortableAppsLauncher
|
||||||
packages/turndown-plugin-gfm/
|
packages/turndown-plugin-gfm/
|
||||||
@@ -329,6 +330,7 @@ packages/app-desktop/gui/style/StyledTextInput.js
|
|||||||
packages/app-desktop/gui/utils/NoteListUtils.js
|
packages/app-desktop/gui/utils/NoteListUtils.js
|
||||||
packages/app-desktop/gui/utils/convertToScreenCoordinates.js
|
packages/app-desktop/gui/utils/convertToScreenCoordinates.js
|
||||||
packages/app-desktop/gui/utils/loadScript.js
|
packages/app-desktop/gui/utils/loadScript.js
|
||||||
|
packages/app-desktop/gulpfile.js
|
||||||
packages/app-desktop/plugins/GotoAnything.js
|
packages/app-desktop/plugins/GotoAnything.js
|
||||||
packages/app-desktop/services/bridge.js
|
packages/app-desktop/services/bridge.js
|
||||||
packages/app-desktop/services/commands/stateToWhenClauseContext.js
|
packages/app-desktop/services/commands/stateToWhenClauseContext.js
|
||||||
@@ -404,6 +406,7 @@ packages/app-mobile/components/SideMenu.js
|
|||||||
packages/app-mobile/components/TextInput.js
|
packages/app-mobile/components/TextInput.js
|
||||||
packages/app-mobile/components/app-nav.js
|
packages/app-mobile/components/app-nav.js
|
||||||
packages/app-mobile/components/biometrics/BiometricPopup.js
|
packages/app-mobile/components/biometrics/BiometricPopup.js
|
||||||
|
packages/app-mobile/components/biometrics/biometricAuthenticate.js
|
||||||
packages/app-mobile/components/biometrics/sensorInfo.js
|
packages/app-mobile/components/biometrics/sensorInfo.js
|
||||||
packages/app-mobile/components/getResponsiveValue.js
|
packages/app-mobile/components/getResponsiveValue.js
|
||||||
packages/app-mobile/components/getResponsiveValue.test.js
|
packages/app-mobile/components/getResponsiveValue.test.js
|
||||||
@@ -835,6 +838,7 @@ packages/renderer/index.js
|
|||||||
packages/renderer/noteStyle.js
|
packages/renderer/noteStyle.js
|
||||||
packages/renderer/pathUtils.js
|
packages/renderer/pathUtils.js
|
||||||
packages/renderer/utils.js
|
packages/renderer/utils.js
|
||||||
|
packages/tools/build-release-stats.js
|
||||||
packages/tools/buildServerDocker.js
|
packages/tools/buildServerDocker.js
|
||||||
packages/tools/buildServerDocker.test.js
|
packages/tools/buildServerDocker.test.js
|
||||||
packages/tools/bundleDefaultPlugins.js
|
packages/tools/bundleDefaultPlugins.js
|
||||||
@@ -845,6 +849,7 @@ packages/tools/convertThemesToCss.js
|
|||||||
packages/tools/generate-database-types.js
|
packages/tools/generate-database-types.js
|
||||||
packages/tools/generate-images.js
|
packages/tools/generate-images.js
|
||||||
packages/tools/git-changelog.js
|
packages/tools/git-changelog.js
|
||||||
|
packages/tools/git-changelog.test.js
|
||||||
packages/tools/licenseChecker.js
|
packages/tools/licenseChecker.js
|
||||||
packages/tools/release-android.js
|
packages/tools/release-android.js
|
||||||
packages/tools/release-cli.js
|
packages/tools/release-cli.js
|
||||||
@@ -860,6 +865,7 @@ packages/tools/update-readme-download.js
|
|||||||
packages/tools/update-readme-sponsors.js
|
packages/tools/update-readme-sponsors.js
|
||||||
packages/tools/updateMarkdownDoc.js
|
packages/tools/updateMarkdownDoc.js
|
||||||
packages/tools/utils/discourse.js
|
packages/tools/utils/discourse.js
|
||||||
|
packages/tools/utils/loadSponsors.js
|
||||||
packages/tools/utils/translation.js
|
packages/tools/utils/translation.js
|
||||||
packages/tools/website/build.js
|
packages/tools/website/build.js
|
||||||
packages/tools/website/buildTranslations.js
|
packages/tools/website/buildTranslations.js
|
||||||
|
|||||||
@@ -91,6 +91,10 @@ module.exports = {
|
|||||||
// Disable because of this: https://github.com/facebook/react/issues/16265
|
// Disable because of this: https://github.com/facebook/react/issues/16265
|
||||||
// "react-hooks/exhaustive-deps": "warn",
|
// "react-hooks/exhaustive-deps": "warn",
|
||||||
|
|
||||||
|
'jest/require-top-level-describe': ['error', { 'maxNumberOfTopLevelDescribes': 1 }],
|
||||||
|
'jest/no-identical-title': ['error'],
|
||||||
|
'jest/prefer-lowercase-title': ['error', { 'ignoreTopLevelDescribe': true }],
|
||||||
|
|
||||||
'promise/prefer-await-to-then': 'error',
|
'promise/prefer-await-to-then': 'error',
|
||||||
'no-unneeded-ternary': 'error',
|
'no-unneeded-ternary': 'error',
|
||||||
|
|
||||||
@@ -155,6 +159,7 @@ module.exports = {
|
|||||||
// 'react-hooks',
|
// 'react-hooks',
|
||||||
'import',
|
'import',
|
||||||
'promise',
|
'promise',
|
||||||
|
'jest',
|
||||||
],
|
],
|
||||||
'overrides': [
|
'overrides': [
|
||||||
{
|
{
|
||||||
@@ -175,9 +180,7 @@ module.exports = {
|
|||||||
'project': './tsconfig.eslint.json',
|
'project': './tsconfig.eslint.json',
|
||||||
},
|
},
|
||||||
'rules': {
|
'rules': {
|
||||||
// Warn only because it would make it difficult to convert JS classes to TypeScript, unless we
|
'@typescript-eslint/explicit-member-accessibility': ['error'],
|
||||||
// make everything public which is not great. New code however should specify member accessibility.
|
|
||||||
'@typescript-eslint/explicit-member-accessibility': ['warn'],
|
|
||||||
'@typescript-eslint/type-annotation-spacing': ['error', { 'before': false, 'after': true }],
|
'@typescript-eslint/type-annotation-spacing': ['error', { 'before': false, 'after': true }],
|
||||||
'@typescript-eslint/no-inferrable-types': ['error', { 'ignoreParameters': true, 'ignoreProperties': true }],
|
'@typescript-eslint/no-inferrable-types': ['error', { 'ignoreParameters': true, 'ignoreProperties': true }],
|
||||||
'@typescript-eslint/comma-dangle': ['error', {
|
'@typescript-eslint/comma-dangle': ['error', {
|
||||||
|
|||||||
3
.github/scripts/run_ci.sh
vendored
3
.github/scripts/run_ci.sh
vendored
@@ -180,9 +180,6 @@ cd "$ROOT_DIR/packages/app-desktop"
|
|||||||
|
|
||||||
if [[ $GIT_TAG_NAME = v* ]]; then
|
if [[ $GIT_TAG_NAME = v* ]]; then
|
||||||
echo "Step: Building and publishing desktop application..."
|
echo "Step: Building and publishing desktop application..."
|
||||||
# cd "$ROOT_DIR/packages/tools"
|
|
||||||
# node bundleDefaultPlugins.js
|
|
||||||
cd "$ROOT_DIR/packages/app-desktop"
|
|
||||||
USE_HARD_LINKS=false yarn run dist
|
USE_HARD_LINKS=false yarn run dist
|
||||||
elif [[ $IS_LINUX = 1 ]] && [[ $GIT_TAG_NAME = $SERVER_TAG_PREFIX-* ]]; then
|
elif [[ $IS_LINUX = 1 ]] && [[ $GIT_TAG_NAME = $SERVER_TAG_PREFIX-* ]]; then
|
||||||
echo "Step: Building Docker Image..."
|
echo "Step: Building Docker Image..."
|
||||||
|
|||||||
2
.github/workflows/build-android.yml
vendored
2
.github/workflows/build-android.yml
vendored
@@ -6,6 +6,7 @@ on: [push, pull_request]
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre_job:
|
pre_job:
|
||||||
|
if: github.repository == 'laurent22/joplin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||||
@@ -16,6 +17,7 @@ jobs:
|
|||||||
concurrent_skipping: 'same_content_newer'
|
concurrent_skipping: 'same_content_newer'
|
||||||
|
|
||||||
BuildAndroidDebug:
|
BuildAndroidDebug:
|
||||||
|
if: github.repository == 'laurent22/joplin'
|
||||||
needs: pre_job
|
needs: pre_job
|
||||||
if: needs.pre_job.outputs.should_skip != 'true'
|
if: needs.pre_job.outputs.should_skip != 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
3
.github/workflows/cla.yml
vendored
3
.github/workflows/cla.yml
vendored
@@ -7,12 +7,13 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
CLAAssistant:
|
CLAAssistant:
|
||||||
|
if: github.repository == 'laurent22/joplin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: "CLA Assistant"
|
- name: "CLA Assistant"
|
||||||
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
||||||
# Beta Release
|
# Beta Release
|
||||||
uses: contributor-assistant/github-action@v2.2.1
|
uses: contributor-assistant/github-action@v2.3.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
# the below token should have repo scope and must be manually added by you in the repository's secret
|
# the below token should have repo scope and must be manually added by you in the repository's secret
|
||||||
|
|||||||
1
.github/workflows/close-stale-issues.yml
vendored
1
.github/workflows/close-stale-issues.yml
vendored
@@ -6,6 +6,7 @@ permissions:
|
|||||||
issues: write
|
issues: write
|
||||||
jobs:
|
jobs:
|
||||||
ProcessStaleIssues:
|
ProcessStaleIssues:
|
||||||
|
if: github.repository == 'laurent22/joplin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v4
|
- uses: actions/stale@v4
|
||||||
|
|||||||
6
.github/workflows/github-actions-main.yml
vendored
6
.github/workflows/github-actions-main.yml
vendored
@@ -2,6 +2,7 @@ name: Joplin Continuous Integration
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
pre_job:
|
pre_job:
|
||||||
|
if: github.repository == 'laurent22/joplin'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||||
@@ -14,7 +15,7 @@ jobs:
|
|||||||
Main:
|
Main:
|
||||||
needs: pre_job
|
needs: pre_job
|
||||||
# We always process server or desktop release tags, because they also publish the release
|
# We always process server or desktop release tags, because they also publish the release
|
||||||
if: needs.pre_job.outputs.should_skip != 'true' || startsWith(github.ref, 'refs/tags/server-v') || startsWith(github.ref, 'refs/tags/v')
|
if: github.repository == 'laurent22/joplin' && (needs.pre_job.outputs.should_skip != 'true' || startsWith(github.ref, 'refs/tags/server-v') || startsWith(github.ref, 'refs/tags/v'))
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -92,6 +93,7 @@ jobs:
|
|||||||
APPLE_ASC_PROVIDER: ${{ secrets.APPLE_ASC_PROVIDER }}
|
APPLE_ASC_PROVIDER: ${{ secrets.APPLE_ASC_PROVIDER }}
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
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_KEY_PASSWORD: ${{ secrets.APPLE_CSC_KEY_PASSWORD }}
|
||||||
CSC_LINK: ${{ secrets.APPLE_CSC_LINK }}
|
CSC_LINK: ${{ secrets.APPLE_CSC_LINK }}
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
@@ -129,7 +131,7 @@ jobs:
|
|||||||
|
|
||||||
ServerDockerImage:
|
ServerDockerImage:
|
||||||
needs: pre_job
|
needs: pre_job
|
||||||
if: needs.pre_job.outputs.should_skip != 'true'
|
if: github.repository == 'laurent22/joplin' && needs.pre_job.outputs.should_skip != 'true'
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -317,6 +317,7 @@ packages/app-desktop/gui/style/StyledTextInput.js
|
|||||||
packages/app-desktop/gui/utils/NoteListUtils.js
|
packages/app-desktop/gui/utils/NoteListUtils.js
|
||||||
packages/app-desktop/gui/utils/convertToScreenCoordinates.js
|
packages/app-desktop/gui/utils/convertToScreenCoordinates.js
|
||||||
packages/app-desktop/gui/utils/loadScript.js
|
packages/app-desktop/gui/utils/loadScript.js
|
||||||
|
packages/app-desktop/gulpfile.js
|
||||||
packages/app-desktop/plugins/GotoAnything.js
|
packages/app-desktop/plugins/GotoAnything.js
|
||||||
packages/app-desktop/services/bridge.js
|
packages/app-desktop/services/bridge.js
|
||||||
packages/app-desktop/services/commands/stateToWhenClauseContext.js
|
packages/app-desktop/services/commands/stateToWhenClauseContext.js
|
||||||
@@ -392,6 +393,7 @@ packages/app-mobile/components/SideMenu.js
|
|||||||
packages/app-mobile/components/TextInput.js
|
packages/app-mobile/components/TextInput.js
|
||||||
packages/app-mobile/components/app-nav.js
|
packages/app-mobile/components/app-nav.js
|
||||||
packages/app-mobile/components/biometrics/BiometricPopup.js
|
packages/app-mobile/components/biometrics/BiometricPopup.js
|
||||||
|
packages/app-mobile/components/biometrics/biometricAuthenticate.js
|
||||||
packages/app-mobile/components/biometrics/sensorInfo.js
|
packages/app-mobile/components/biometrics/sensorInfo.js
|
||||||
packages/app-mobile/components/getResponsiveValue.js
|
packages/app-mobile/components/getResponsiveValue.js
|
||||||
packages/app-mobile/components/getResponsiveValue.test.js
|
packages/app-mobile/components/getResponsiveValue.test.js
|
||||||
@@ -823,6 +825,7 @@ packages/renderer/index.js
|
|||||||
packages/renderer/noteStyle.js
|
packages/renderer/noteStyle.js
|
||||||
packages/renderer/pathUtils.js
|
packages/renderer/pathUtils.js
|
||||||
packages/renderer/utils.js
|
packages/renderer/utils.js
|
||||||
|
packages/tools/build-release-stats.js
|
||||||
packages/tools/buildServerDocker.js
|
packages/tools/buildServerDocker.js
|
||||||
packages/tools/buildServerDocker.test.js
|
packages/tools/buildServerDocker.test.js
|
||||||
packages/tools/bundleDefaultPlugins.js
|
packages/tools/bundleDefaultPlugins.js
|
||||||
@@ -833,6 +836,7 @@ packages/tools/convertThemesToCss.js
|
|||||||
packages/tools/generate-database-types.js
|
packages/tools/generate-database-types.js
|
||||||
packages/tools/generate-images.js
|
packages/tools/generate-images.js
|
||||||
packages/tools/git-changelog.js
|
packages/tools/git-changelog.js
|
||||||
|
packages/tools/git-changelog.test.js
|
||||||
packages/tools/licenseChecker.js
|
packages/tools/licenseChecker.js
|
||||||
packages/tools/release-android.js
|
packages/tools/release-android.js
|
||||||
packages/tools/release-cli.js
|
packages/tools/release-cli.js
|
||||||
@@ -848,6 +852,7 @@ packages/tools/update-readme-download.js
|
|||||||
packages/tools/update-readme-sponsors.js
|
packages/tools/update-readme-sponsors.js
|
||||||
packages/tools/updateMarkdownDoc.js
|
packages/tools/updateMarkdownDoc.js
|
||||||
packages/tools/utils/discourse.js
|
packages/tools/utils/discourse.js
|
||||||
|
packages/tools/utils/loadSponsors.js
|
||||||
packages/tools/utils/translation.js
|
packages/tools/utils/translation.js
|
||||||
packages/tools/website/build.js
|
packages/tools/website/build.js
|
||||||
packages/tools/website/buildTranslations.js
|
packages/tools/website/buildTranslations.js
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
"@joplin/turndown-plugin-gfm",
|
"@joplin/turndown-plugin-gfm",
|
||||||
"@joplin/tools",
|
"@joplin/tools",
|
||||||
"@joplin/react-native-saf-x",
|
"@joplin/react-native-saf-x",
|
||||||
"@joplin/react-native-alarm-notification"
|
"@joplin/react-native-alarm-notification",
|
||||||
|
"@joplin/utils"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -780,6 +780,7 @@ footer .bottom-links-row p {
|
|||||||
|
|
||||||
#menu-mobile .social-links .social-link-mastodon,
|
#menu-mobile .social-links .social-link-mastodon,
|
||||||
#menu-mobile .social-links .social-link-reddit,
|
#menu-mobile .social-links .social-link-reddit,
|
||||||
|
#menu-mobile .social-links .social-link-linkedin,
|
||||||
#menu-mobile .social-links .social-link-patreon {
|
#menu-mobile .social-links .social-link-patreon {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -947,6 +948,41 @@ footer .bottom-links-row p {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
MORE NARROW VIEW
|
||||||
|
eg for Galaxy S9
|
||||||
|
*****************************************************************/
|
||||||
|
|
||||||
|
@media (max-width: 580px) {
|
||||||
|
|
||||||
|
#nav-section .plans-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
MORE NARROW VIEW
|
||||||
|
eg for Galaxy S9
|
||||||
|
*****************************************************************/
|
||||||
|
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
|
||||||
|
#nav-section .navbar-mobile-content a.sponsor-button .sponsor-button-label {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-section .navbar-mobile-content a.sponsor-button {
|
||||||
|
padding: 2px 6px;
|
||||||
|
margin-right: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-section a {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/*****************************************************************
|
/*****************************************************************
|
||||||
VERY NARROW VIEW
|
VERY NARROW VIEW
|
||||||
eg for Galaxy Fold
|
eg for Galaxy Fold
|
||||||
@@ -968,6 +1004,15 @@ footer .bottom-links-row p {
|
|||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.navbar-mobile-content a.sponsor-button {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-section .button-link {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*****************************************************************
|
/*****************************************************************
|
||||||
|
|||||||
BIN
Assets/WebsiteAssets/images/news/20230202-jdll.jpg
Normal file
BIN
Assets/WebsiteAssets/images/news/20230202-jdll.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 238 KiB |
@@ -1,4 +1,7 @@
|
|||||||
<?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, 16 Jan 2023 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 16 Jan 2023 00:00:00 GMT</pubDate><item><title><![CDATA[Introducing the "GitHub Actions Raw Log Viewer" extension for Chrome]]></title><description><![CDATA[<p>If you've ever used GitHub Actions, you will find that they provide by default a nice coloured output for the log. It looks good and it's even interactive! (You can click to collapse/expand blocks of text) But unfortunately it doesn't scale to large workflows, like we have for Joplin - the log can freeze and it will take forever to search for something. Indeed searching is done in "real time"... which mostly means it will freeze for a minute or two for each letter you type in the search box. Not great.</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>Thu, 02 Mar 2023 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Thu, 02 Mar 2023 00:00:00 GMT</pubDate><item><title><![CDATA[Joplin will participate in JdLL 2023!]]></title><description><![CDATA[<p>On 1 and 2 April 2023, we will have a stand for Joplin at the <a href="https://www.jdll.org/">Journées du Logiciel Libre</a> in Lyon, France. The JdLL has been taking place in Lyon for 24 years and is a popular open source conference in France. We had a stand in 2020 and 2021 but that was cancelled due to Covid, so this year is a first for Joplin!</p>
|
||||||
|
<p>Admission is free, so don't hesitate to come and meet us, exchange ideas and learn more about Joplin!</p>
|
||||||
|
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230202-jdll.jpg" alt="Joplin at JdLL 2023"></p>
|
||||||
|
]]></description><link>https://joplinapp.org/news/20230302-jdll-2023/</link><guid isPermaLink="false">20230302-jdll-2023</guid><pubDate>Thu, 02 Mar 2023 00:00:00 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Introducing the "GitHub Actions Raw Log Viewer" extension for Chrome]]></title><description><![CDATA[<p>If you've ever used GitHub Actions, you will find that they provide by default a nice coloured output for the log. It looks good and it's even interactive! (You can click to collapse/expand blocks of text) But unfortunately it doesn't scale to large workflows, like we have for Joplin - the log can freeze and it will take forever to search for something. Indeed searching is done in "real time"... which mostly means it will freeze for a minute or two for each letter you type in the search box. Not great.</p>
|
||||||
<p>Thankfully GitHub provides an alternative access: the raw logs. This is much better because they will open as plain text, without any styling or JS magic, which means you can use the browser native search and it will be fast.</p>
|
<p>Thankfully GitHub provides an alternative access: the raw logs. This is much better because they will open as plain text, without any styling or JS magic, which means you can use the browser native search and it will be fast.</p>
|
||||||
<p>But now the problem is that raw logs look like this:</p>
|
<p>But now the problem is that raw logs look like this:</p>
|
||||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230116-ga-raw-log.png" alt="Raw log without extension"></p>
|
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230116-ga-raw-log.png" alt="Raw log without extension"></p>
|
||||||
@@ -294,9 +297,4 @@
|
|||||||
<p>This release also includes about 30 various bug fixes and improvements.</p>
|
<p>This release also includes about 30 various bug fixes and improvements.</p>
|
||||||
<p>A notable one is a fix for GotoAnything, which recently wasn't working on first try.</p>
|
<p>A notable one is a fix for GotoAnything, which recently wasn't working on first try.</p>
|
||||||
<p>The plugin screen has also been improved so that search works even when GitHub is down or blocked, as it is in China in particular.</p>
|
<p>The plugin screen has also been improved so that search works even when GitHub is down or blocked, as it is in China in particular.</p>
|
||||||
]]></description><link>https://joplinapp.org/news/20210929-144036/</link><guid isPermaLink="false">20210929-144036</guid><pubDate>Wed, 29 Sep 2021 14:40:36 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Introducing recommended plugins in the next Joplin version]]></title><description><![CDATA[<p>A common request from new users is how to know which plugin is safe to install or not. In fact probably all of them are safe but as a new user that's not necessarily easy to know. So to help with this, the next version of Joplin will support recommended plugins - those will be plugins that meet our standards of quality and performance, and they will be indicated by a small crown tag inside the plugin box. Recommended plugins will also appear on top when searching.</p>
|
]]></description><link>https://joplinapp.org/news/20210929-144036/</link><guid isPermaLink="false">20210929-144036</guid><pubDate>Wed, 29 Sep 2021 14:40:36 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
|
||||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20210901-113415_0.png" alt=""></p>
|
|
||||||
<p>For now, since we don't have a review process, the recommended plugins are those developed by the Joplin team and frequent contributors, because we know those are safe to use.</p>
|
|
||||||
<p>Later we might have a review process and add more recommended plugins. That being said, in the meantime even if a plugin is not marked as recommended, there's a good chance it is still safe and have good performance too. Often you can search for it on the forum and if it's active with many users commenting, you're most likely good to go.</p>
|
|
||||||
<p>But if there's any doubt, the recommended tag is a good way to be sure.</p>
|
|
||||||
]]></description><link>https://joplinapp.org/news/20210901-113415/</link><guid isPermaLink="false">20210901-113415</guid><pubDate>Wed, 01 Sep 2021 11:34:15 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
|
|
||||||
@@ -24,7 +24,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-9 text-right d-block d-md-none navbar-mobile-content">
|
<div class="col-9 text-right d-block d-md-none navbar-mobile-content">
|
||||||
{{> twitterLink}}
|
{{> twitterLink}}
|
||||||
<a href="{{baseUrl}}/cn/" class="fw500">中文</a>
|
<a href="{{baseUrl}}/cn/" class="fw500 chinese-page-link">中文</a>
|
||||||
|
{{> joplinCloudButton}}
|
||||||
{{> supportButton}}
|
{{> supportButton}}
|
||||||
|
|
||||||
<span class="pointer"
|
<span class="pointer"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<a class="social-link-mastodon" href="https://mastodon.social/@joplinapp" title="Joplin Mastodon feed"><i class="fab fa-mastodon"></i></a>
|
<a class="social-link-mastodon" href="https://mastodon.social/@joplinapp" title="Joplin Mastodon feed"><i class="fab fa-mastodon"></i></a>
|
||||||
<a class="social-link-patreon" href="https://www.patreon.com/joplin" title="Joplin Patreon"><i class="fab fa-patreon"></i></a>
|
<a class="social-link-patreon" href="https://www.patreon.com/joplin" title="Joplin Patreon"><i class="fab fa-patreon"></i></a>
|
||||||
<a class="social-link-discord" href="https://discord.gg/VSj7AFHvpq" title="Joplin Discord chat"><i class="fab fa-discord"></i></a>
|
<a class="social-link-discord" href="https://discord.gg/VSj7AFHvpq" title="Joplin Discord chat"><i class="fab fa-discord"></i></a>
|
||||||
|
<a class="social-link-linkedin" href="https://www.linkedin.com/company/joplin" title="Joplin LinkedIn Feed"><i class="fab fa-linkedin"></i></a>
|
||||||
<a class="social-link-reddit" href="https://www.reddit.com/r/joplinapp/" title="Joplin Subreddit"><i class="fab fa-reddit"></i></a>
|
<a class="social-link-reddit" href="https://www.reddit.com/r/joplinapp/" title="Joplin Subreddit"><i class="fab fa-reddit"></i></a>
|
||||||
<a class="social-link-github" href="https://github.com/laurent22/joplin/" title="Joplin GitHub repository"><i class="fab fa-github"></i></a>
|
<a class="social-link-github" href="https://github.com/laurent22/joplin/" title="Joplin GitHub repository"><i class="fab fa-github"></i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<a href="https://twitter.com/joplinapp" title="Joplin Twitter feed" class="fw500"><i class="fab fa-twitter"></i></a>
|
<a href="https://twitter.com/joplinapp" title="Joplin Twitter feed" class="fw500 twitter-link"><i class="fab fa-twitter"></i></a>
|
||||||
@@ -30,6 +30,7 @@ COPY packages/fork-uslug ./packages/fork-uslug
|
|||||||
COPY packages/htmlpack ./packages/htmlpack
|
COPY packages/htmlpack ./packages/htmlpack
|
||||||
COPY packages/renderer ./packages/renderer
|
COPY packages/renderer ./packages/renderer
|
||||||
COPY packages/tools ./packages/tools
|
COPY packages/tools ./packages/tools
|
||||||
|
COPY packages/utils ./packages/utils
|
||||||
COPY packages/lib ./packages/lib
|
COPY packages/lib ./packages/lib
|
||||||
COPY packages/server ./packages/server
|
COPY packages/server ./packages/server
|
||||||
|
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -64,7 +64,7 @@ A community maintained list of these distributions can be found here: [Unofficia
|
|||||||
# Sponsors
|
# Sponsors
|
||||||
|
|
||||||
<!-- SPONSORS-ORG -->
|
<!-- SPONSORS-ORG -->
|
||||||
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://usrigging.com/"><img title="U.S. Ringing Supply" width="256" src="https://joplinapp.org/images/sponsors/RingingSupply.svg"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&mtm_kwd=joplinapp&mtm_source=joplinapp-github&mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://residence-greece.com/"><img title="Greece Golden Visa" width="256" src="https://joplinapp.org/images/sponsors/ResidenceGreece.jpg"/></a> <a href="https://grundstueckspreise.info/"><img title="SP Software GmbH" width="256" src="https://joplinapp.org/images/sponsors/Grundstueckspreise.png"/></a>
|
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://www.hosting.de/nextcloud/?mtm_campaign=managed-nextcloud&mtm_kwd=joplinapp&mtm_source=joplinapp-webseite&mtm_medium=banner"><img title="Hosting.de" width="256" src="https://joplinapp.org/images/sponsors/HostingDe.png"/></a> <a href="https://residence-greece.com/"><img title="Greece Golden Visa" width="256" src="https://joplinapp.org/images/sponsors/ResidenceGreece.jpg"/></a> <a href="https://grundstueckspreise.info/"><img title="SP Software GmbH" width="256" src="https://joplinapp.org/images/sponsors/Grundstueckspreise.png"/></a>
|
||||||
<!-- SPONSORS-ORG -->
|
<!-- SPONSORS-ORG -->
|
||||||
|
|
||||||
* * *
|
* * *
|
||||||
@@ -72,14 +72,11 @@ A community maintained list of these distributions can be found here: [Unofficia
|
|||||||
<!-- SPONSORS-GITHUB -->
|
<!-- SPONSORS-GITHUB -->
|
||||||
| | | | |
|
| | | | |
|
||||||
| :---: | :---: | :---: | :---: |
|
| :---: | :---: | :---: | :---: |
|
||||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/3061769?s=96&v=4"/></br>[c-nagy](https://github.com/c-nagy) | <img width="50" src="https://avatars2.githubusercontent.com/u/70780798?s=96&v=4"/></br>[cabottech](https://github.com/cabottech) | <img width="50" src="https://avatars2.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) |
|
| <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) | <img width="50" src="https://avatars2.githubusercontent.com/u/2793530?s=96&v=4"/></br>[CyberXZT](https://github.com/CyberXZT) | <img width="50" src="https://avatars2.githubusercontent.com/u/1307332?s=96&v=4"/></br>[dbrandonjohnson](https://github.com/dbrandonjohnson) |
|
||||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/4862947?s=96&v=4"/></br>[chrootlogin](https://github.com/chrootlogin) | <img width="50" src="https://avatars2.githubusercontent.com/u/82579431?s=96&v=4"/></br>[clmntsl](https://github.com/clmntsl) | <img width="50" src="https://avatars2.githubusercontent.com/u/808091?s=96&v=4"/></br>[cuongtransc](https://github.com/cuongtransc) | <img width="50" src="https://avatars2.githubusercontent.com/u/1307332?s=96&v=4"/></br>[dbrandonjohnson](https://github.com/dbrandonjohnson) |
|
| <img width="50" src="https://avatars2.githubusercontent.com/u/49439044?s=96&v=4"/></br>[fourstepper](https://github.com/fourstepper) | <img width="50" src="https://avatars2.githubusercontent.com/u/64712218?s=96&v=4"/></br>[Hegghammer](https://github.com/Hegghammer) | <img width="50" src="https://avatars2.githubusercontent.com/u/3266447?s=96&v=4"/></br>[iamwillbar](https://github.com/iamwillbar) | <img width="50" src="https://avatars2.githubusercontent.com/u/1310474?s=96&v=4"/></br>[jknowles](https://github.com/jknowles) |
|
||||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/1439535?s=96&v=4"/></br>[fbloise](https://github.com/fbloise) | <img width="50" src="https://avatars2.githubusercontent.com/u/49439044?s=96&v=4"/></br>[fourstepper](https://github.com/fourstepper) | <img width="50" src="https://avatars2.githubusercontent.com/u/38898566?s=96&v=4"/></br>[h4sh5](https://github.com/h4sh5) | <img width="50" src="https://avatars2.githubusercontent.com/u/3266447?s=96&v=4"/></br>[iamwillbar](https://github.com/iamwillbar) |
|
| <img width="50" src="https://avatars2.githubusercontent.com/u/11947658?s=96&v=4"/></br>[KentBrockman](https://github.com/KentBrockman) | <img width="50" src="https://avatars2.githubusercontent.com/u/5588131?s=96&v=4"/></br>[kianenigma](https://github.com/kianenigma) | <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[konishi-t](https://github.com/konishi-t) | <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) |
|
||||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/37297218?s=96&v=4"/></br>[Jesssullivan](https://github.com/Jesssullivan) | <img width="50" src="https://avatars2.githubusercontent.com/u/1310474?s=96&v=4"/></br>[jknowles](https://github.com/jknowles) | <img width="50" src="https://avatars2.githubusercontent.com/u/1248504?s=96&v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars2.githubusercontent.com/u/5588131?s=96&v=4"/></br>[kianenigma](https://github.com/kianenigma) |
|
| <img width="50" src="https://avatars2.githubusercontent.com/u/126279083?s=96&v=4"/></br>[matmoly](https://github.com/matmoly) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars2.githubusercontent.com/u/31054972?s=96&v=4"/></br>[saarantras](https://github.com/saarantras) |
|
||||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[konishi-t](https://github.com/konishi-t) | <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) |
|
| <img width="50" src="https://avatars2.githubusercontent.com/u/327998?s=96&v=4"/></br>[sif](https://github.com/sif) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) | <img width="50" src="https://avatars2.githubusercontent.com/u/333944?s=96&v=4"/></br>[tateisu](https://github.com/tateisu) | |
|
||||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/1168659?s=96&v=4"/></br>[nicholashead](https://github.com/nicholashead) | <img width="50" src="https://avatars2.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars2.githubusercontent.com/u/77214738?s=96&v=4"/></br>[Polymathic-Company](https://github.com/Polymathic-Company) | <img width="50" src="https://avatars2.githubusercontent.com/u/47742?s=96&v=4"/></br>[ravenscroftj](https://github.com/ravenscroftj) |
|
|
||||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/327998?s=96&v=4"/></br>[sif](https://github.com/sif) | <img width="50" src="https://avatars2.githubusercontent.com/u/54626606?s=96&v=4"/></br>[skyrunner15](https://github.com/skyrunner15) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) | <img width="50" src="https://avatars2.githubusercontent.com/u/73081837?s=96&v=4"/></br>[thismarty](https://github.com/thismarty) |
|
|
||||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[thomasbroussard](https://github.com/thomasbroussard) | | | |
|
|
||||||
<!-- SPONSORS-GITHUB -->
|
<!-- SPONSORS-GITHUB -->
|
||||||
|
|
||||||
<!-- TOC -->
|
<!-- TOC -->
|
||||||
@@ -129,6 +126,7 @@ A community maintained list of these distributions can be found here: [Unofficia
|
|||||||
- [Writing a technical spec](https://github.com/laurent22/joplin/blob/dev/readme/technical_spec.md)
|
- [Writing a technical spec](https://github.com/laurent22/joplin/blob/dev/readme/technical_spec.md)
|
||||||
- [Desktop application styling](https://github.com/laurent22/joplin/blob/dev/readme/spec/desktop_styling.md)
|
- [Desktop application styling](https://github.com/laurent22/joplin/blob/dev/readme/spec/desktop_styling.md)
|
||||||
- [Note History spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/history.md)
|
- [Note History spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/history.md)
|
||||||
|
- [Synchronisation spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync.md)
|
||||||
- [Sync Lock spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_lock.md)
|
- [Sync Lock spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_lock.md)
|
||||||
- [Synchronous Scroll spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_scroll.md)
|
- [Synchronous Scroll spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_scroll.md)
|
||||||
- [Plugin Architecture spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/plugins.md)
|
- [Plugin Architecture spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/plugins.md)
|
||||||
@@ -505,6 +503,7 @@ Name | Description
|
|||||||
[Mastodon feed](https://mastodon.social/@joplinapp) | Follow us on Mastodon
|
[Mastodon feed](https://mastodon.social/@joplinapp) | Follow us on Mastodon
|
||||||
[Patreon page](https://www.patreon.com/joplin) |The latest news are often posted there
|
[Patreon page](https://www.patreon.com/joplin) |The latest news are often posted there
|
||||||
[Discord server](https://discord.gg/VSj7AFHvpq) | Our chat server
|
[Discord server](https://discord.gg/VSj7AFHvpq) | Our chat server
|
||||||
|
[LinkedIn](https://www.linkedin.com/company/joplin) | Our LinkedIn page
|
||||||
[Sub-reddit](https://www.reddit.com/r/joplinapp/) | Also a good place to get help
|
[Sub-reddit](https://www.reddit.com/r/joplinapp/) | Also a good place to get help
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|||||||
54
gulpfile.js
54
gulpfile.js
@@ -1,26 +1,52 @@
|
|||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
const utils = require('./packages/tools/gulp/utils');
|
const execa = require('execa');
|
||||||
|
const { stdout } = require('process');
|
||||||
|
|
||||||
|
const execCommand = async (executableName, args, options = null) => {
|
||||||
|
options = {
|
||||||
|
showInput: true,
|
||||||
|
showStdout: true,
|
||||||
|
showStderr: true,
|
||||||
|
quiet: false,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.quiet) {
|
||||||
|
options.showInput = false;
|
||||||
|
options.showStdout = false;
|
||||||
|
options.showStderr = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.showInput) {
|
||||||
|
stdout.write(`> ${executableName} ${args.join(' ')}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise = execa(executableName, args);
|
||||||
|
if (options.showStdout && promise.stdout) promise.stdout.pipe(process.stdout);
|
||||||
|
if (options.showStderr && promise.stderr) promise.stderr.pipe(process.stderr);
|
||||||
|
const result = await promise;
|
||||||
|
return result.stdout.trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const tasks = {
|
const tasks = {
|
||||||
updateIgnoredTypeScriptBuild: require('./packages/tools/gulp/tasks/updateIgnoredTypeScriptBuild'),
|
|
||||||
buildCommandIndex: require('./packages/tools/gulp/tasks/buildCommandIndex'),
|
|
||||||
completePublishAll: {
|
completePublishAll: {
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
await utils.execCommandVerbose('git', ['add', '-A']);
|
await execCommand('git', ['add', '-A']);
|
||||||
await utils.execCommandVerbose('git', ['commit', '-m', 'Releasing sub-packages']);
|
await execCommand('git', ['commit', '-m', 'Releasing sub-packages']);
|
||||||
|
|
||||||
// Lerna does some unnecessary auth check that doesn't work with
|
// Lerna does some unnecessary auth check that doesn't work with
|
||||||
// automation tokens, thus the --no-verify-access. Automation token
|
// automation tokens, thus the --no-verify-access. Automation token
|
||||||
// is still used for access when publishing even with this flag
|
// is still used for access when publishing even with this flag
|
||||||
// (publishing would fail otherwise).
|
// (publishing would fail otherwise).
|
||||||
// https://github.com/lerna/lerna/issues/2788
|
// https://github.com/lerna/lerna/issues/2788
|
||||||
await utils.execCommandVerbose('lerna', ['publish', 'from-package', '-y', '--no-verify-access']);
|
await execCommand('lerna', ['publish', 'from-package', '-y', '--no-verify-access']);
|
||||||
|
|
||||||
await utils.execCommandVerbose('yarn', ['install']);
|
await execCommand('yarn', ['install']);
|
||||||
await utils.execCommandVerbose('git', ['add', '-A']);
|
await execCommand('git', ['add', '-A']);
|
||||||
await utils.execCommandVerbose('git', ['commit', '-m', 'Lock file']);
|
await execCommand('git', ['commit', '-m', 'Lock file']);
|
||||||
|
|
||||||
await utils.execCommandVerbose('git', ['push']);
|
await execCommand('git', ['push']);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
@@ -33,12 +59,14 @@ const tasks = {
|
|||||||
// faster, especially when having to rebuild after adding a
|
// faster, especially when having to rebuild after adding a
|
||||||
// dependency.
|
// dependency.
|
||||||
if (process.env.BUILD_SEQUENCIAL === '1') {
|
if (process.env.BUILD_SEQUENCIAL === '1') {
|
||||||
await utils.execCommandVerbose('yarn', ['run', 'buildSequential']);
|
await execCommand('yarn', ['run', 'buildSequential']);
|
||||||
} else {
|
} else {
|
||||||
await utils.execCommandVerbose('yarn', ['run', 'buildParallel']);
|
await execCommand('yarn', ['run', 'buildParallel']);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
utils.registerGulpTasks(gulp, tasks);
|
for (const taskName in tasks) {
|
||||||
|
gulp.task(taskName, tasks[taskName].fn);
|
||||||
|
}
|
||||||
|
|||||||
@@ -329,6 +329,7 @@
|
|||||||
"packages/renderer/MdToHtml/rules/sanitize_html.js": true,
|
"packages/renderer/MdToHtml/rules/sanitize_html.js": true,
|
||||||
"packages/server/db-*.sqlite": true,
|
"packages/server/db-*.sqlite": true,
|
||||||
"packages/server/dist/": true,
|
"packages/server/dist/": true,
|
||||||
|
"packages/utils/dist/": true,
|
||||||
"packages/server/temp": true,
|
"packages/server/temp": true,
|
||||||
"packages/server/test.pid": true,
|
"packages/server/test.pid": true,
|
||||||
"phpunit.xml": true,
|
"phpunit.xml": true,
|
||||||
|
|||||||
15
package.json
15
package.json
@@ -13,9 +13,9 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"buildParallel": "yarn workspaces foreach --verbose --interlaced --parallel --jobs 2 --topological run build && yarn run tsc",
|
"buildParallel": "yarn workspaces foreach --verbose --interlaced --parallel --jobs 2 --topological run build && yarn run tsc",
|
||||||
"buildSequential": "yarn workspaces foreach --verbose --interlaced --topological run build && yarn run tsc",
|
"buildSequential": "yarn run tsc && yarn workspaces foreach --verbose --interlaced --topological-dev run build",
|
||||||
"buildApiDoc": "yarn workspace joplin start apidoc ../../readme/api/references/rest_api.md",
|
"buildApiDoc": "yarn workspace joplin start apidoc ../../readme/api/references/rest_api.md",
|
||||||
"buildCommandIndex": "gulp buildCommandIndex",
|
"buildCommandIndex": "node packages/tools/gulp/tasks/buildCommandIndexRun.js",
|
||||||
"buildPluginDoc": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme './Assets/PluginDocTheme/' --readme './Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out ../joplin-website/docs/api/references/plugin_api packages/lib/services/plugins/api/",
|
"buildPluginDoc": "typedoc --name 'Joplin Plugin API Documentation' --mode file -theme './Assets/PluginDocTheme/' --readme './Assets/PluginDocTheme/index.md' --excludeNotExported --excludeExternals --excludePrivate --excludeProtected --out ../joplin-website/docs/api/references/plugin_api packages/lib/services/plugins/api/",
|
||||||
"updateMarkdownDoc": "node ./packages/tools/updateMarkdownDoc",
|
"updateMarkdownDoc": "node ./packages/tools/updateMarkdownDoc",
|
||||||
"updateNews": "node ./packages/tools/website/updateNews",
|
"updateNews": "node ./packages/tools/website/updateNews",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"test-ci": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 2 run test-ci",
|
"test-ci": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 2 run test-ci",
|
||||||
"test": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 2 run test",
|
"test": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 2 run test",
|
||||||
"tsc": "yarn workspaces foreach --parallel --verbose --interlaced run tsc",
|
"tsc": "yarn workspaces foreach --parallel --verbose --interlaced run tsc",
|
||||||
"updateIgnored": "gulp updateIgnoredTypeScriptBuild",
|
"updateIgnored": "node packages/tools/gulp/tasks/updateIgnoredTypeScriptBuildRun.js",
|
||||||
"updatePluginTypes": "./packages/generator-joplin/updateTypes.sh",
|
"updatePluginTypes": "./packages/generator-joplin/updateTypes.sh",
|
||||||
"watch": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 999 run watch",
|
"watch": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 999 run watch",
|
||||||
"watchWebsite": "nodemon --verbose --watch Assets/WebsiteAssets --watch packages/tools/website --watch packages/tools/website/utils --ext md,ts,js,mustache,css,tsx,gif,png,svg --exec \"node packages/tools/website/build.js && http-server --port 8077 ../joplin-website/docs -a localhost\""
|
"watchWebsite": "nodemon --verbose --watch Assets/WebsiteAssets --watch packages/tools/website --watch packages/tools/website/utils --ext md,ts,js,mustache,css,tsx,gif,png,svg --exec \"node packages/tools/website/build.js && http-server --port 8077 ../joplin-website/docs -a localhost\""
|
||||||
@@ -64,6 +64,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@joplin/utils": "~2.11",
|
||||||
"@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0",
|
"@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0",
|
||||||
"@typescript-eslint/eslint-plugin": "5.48.2",
|
"@typescript-eslint/eslint-plugin": "5.48.2",
|
||||||
"@typescript-eslint/parser": "5.48.2",
|
"@typescript-eslint/parser": "5.48.2",
|
||||||
@@ -71,14 +72,16 @@
|
|||||||
"eslint": "8.31.0",
|
"eslint": "8.31.0",
|
||||||
"eslint-interactive": "10.3.0",
|
"eslint-interactive": "10.3.0",
|
||||||
"eslint-plugin-import": "2.27.4",
|
"eslint-plugin-import": "2.27.4",
|
||||||
|
"eslint-plugin-jest": "27.2.1",
|
||||||
"eslint-plugin-promise": "6.1.1",
|
"eslint-plugin-promise": "6.1.1",
|
||||||
"eslint-plugin-react": "7.32.0",
|
"eslint-plugin-react": "7.32.0",
|
||||||
"fs-extra": "11.1.0",
|
"execa": "5.1.1",
|
||||||
|
"fs-extra": "11.1.1",
|
||||||
"glob": "8.1.0",
|
"glob": "8.1.0",
|
||||||
"gulp": "4.0.2",
|
"gulp": "4.0.2",
|
||||||
"husky": "3.1.0",
|
"husky": "3.1.0",
|
||||||
"lerna": "3.22.1",
|
"lerna": "3.22.1",
|
||||||
"lint-staged": "13.1.2",
|
"lint-staged": "13.2.0",
|
||||||
"madge": "6.0.0",
|
"madge": "6.0.0",
|
||||||
"npm-package-json-lint": "6.4.0",
|
"npm-package-json-lint": "6.4.0",
|
||||||
"typedoc": "0.17.8",
|
"typedoc": "0.17.8",
|
||||||
@@ -88,7 +91,7 @@
|
|||||||
"@types/fs-extra": "9.0.13",
|
"@types/fs-extra": "9.0.13",
|
||||||
"http-server": "14.1.1",
|
"http-server": "14.1.1",
|
||||||
"node-gyp": "9.3.1",
|
"node-gyp": "9.3.1",
|
||||||
"nodemon": "2.0.20"
|
"nodemon": "2.0.22"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@3.3.1",
|
"packageManager": "yarn@3.3.1",
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ interface LinkStoreEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LinkSelector {
|
class LinkSelector {
|
||||||
noteId_: string;
|
private noteId_: string;
|
||||||
scrollTop_: number;
|
private scrollTop_: number;
|
||||||
renderedText_: string;
|
private renderedText_: string;
|
||||||
currentLinkIndex_: number;
|
private currentLinkIndex_: number;
|
||||||
linkStore_: LinkStoreEntry[];
|
private linkStore_: LinkStoreEntry[];
|
||||||
linkRegex_: RegExp;
|
private linkRegex_: RegExp;
|
||||||
|
|
||||||
constructor() {
|
public constructor() {
|
||||||
this.noteId_ = null;
|
this.noteId_ = null;
|
||||||
this.scrollTop_ = null; // used so 'o' won't open unhighlighted link after scrolling
|
this.scrollTop_ = null; // used so 'o' won't open unhighlighted link after scrolling
|
||||||
this.renderedText_ = null;
|
this.renderedText_ = null;
|
||||||
@@ -22,22 +22,22 @@ class LinkSelector {
|
|||||||
this.linkRegex_ = /http:\/\/[0-9.]+:[0-9]+\/[0-9]+/g;
|
this.linkRegex_ = /http:\/\/[0-9.]+:[0-9]+\/[0-9]+/g;
|
||||||
}
|
}
|
||||||
|
|
||||||
get link(): string | null {
|
public get link(): string | null {
|
||||||
if (this.currentLinkIndex_ === null) return null;
|
if (this.currentLinkIndex_ === null) return null;
|
||||||
return this.linkStore_[this.currentLinkIndex_].link;
|
return this.linkStore_[this.currentLinkIndex_].link;
|
||||||
}
|
}
|
||||||
|
|
||||||
get noteX(): number | null {
|
public get noteX(): number | null {
|
||||||
if (this.currentLinkIndex_ === null) return null;
|
if (this.currentLinkIndex_ === null) return null;
|
||||||
return this.linkStore_[this.currentLinkIndex_].noteX;
|
return this.linkStore_[this.currentLinkIndex_].noteX;
|
||||||
}
|
}
|
||||||
|
|
||||||
get noteY(): number | null {
|
public get noteY(): number | null {
|
||||||
if (this.currentLinkIndex_ === null) return null;
|
if (this.currentLinkIndex_ === null) return null;
|
||||||
return this.linkStore_[this.currentLinkIndex_].noteY;
|
return this.linkStore_[this.currentLinkIndex_].noteY;
|
||||||
}
|
}
|
||||||
|
|
||||||
findLinks(renderedText: string): LinkStoreEntry[] {
|
public findLinks(renderedText: string): LinkStoreEntry[] {
|
||||||
const newLinkStore: LinkStoreEntry[] = [];
|
const newLinkStore: LinkStoreEntry[] = [];
|
||||||
const lines: string[] = renderedText.split('\n');
|
const lines: string[] = renderedText.split('\n');
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
@@ -56,19 +56,19 @@ class LinkSelector {
|
|||||||
return newLinkStore;
|
return newLinkStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateText(renderedText: string): void {
|
public updateText(renderedText: string): void {
|
||||||
this.currentLinkIndex_ = null;
|
this.currentLinkIndex_ = null;
|
||||||
this.renderedText_ = renderedText;
|
this.renderedText_ = renderedText;
|
||||||
this.linkStore_ = this.findLinks(this.renderedText_);
|
this.linkStore_ = this.findLinks(this.renderedText_);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNote(textWidget: any): void {
|
public updateNote(textWidget: any): void {
|
||||||
this.noteId_ = textWidget.noteId;
|
this.noteId_ = textWidget.noteId;
|
||||||
this.scrollTop_ = textWidget.scrollTop_;
|
this.scrollTop_ = textWidget.scrollTop_;
|
||||||
this.updateText(textWidget.renderedText_);
|
this.updateText(textWidget.renderedText_);
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollWidget(textWidget: any): void {
|
public scrollWidget(textWidget: any): void {
|
||||||
if (this.currentLinkIndex_ === null) return;
|
if (this.currentLinkIndex_ === null) return;
|
||||||
|
|
||||||
const noteY = this.linkStore_[this.currentLinkIndex_].noteY;
|
const noteY = this.linkStore_[this.currentLinkIndex_].noteY;
|
||||||
@@ -93,7 +93,7 @@ class LinkSelector {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
changeLink(textWidget: any, offset: number): void | null {
|
public changeLink(textWidget: any, offset: number): void | null {
|
||||||
if (textWidget.noteId !== this.noteId_) {
|
if (textWidget.noteId !== this.noteId_) {
|
||||||
this.updateNote(textWidget);
|
this.updateNote(textWidget);
|
||||||
this.changeLink(textWidget, offset);
|
this.changeLink(textWidget, offset);
|
||||||
@@ -123,7 +123,7 @@ class LinkSelector {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
openLink(textWidget: any): void {
|
public openLink(textWidget: any): void {
|
||||||
if (textWidget.noteId !== this.noteId_) return;
|
if (textWidget.noteId !== this.noteId_) return;
|
||||||
if (textWidget.renderedText_ !== this.renderedText_) return;
|
if (textWidget.renderedText_ !== this.renderedText_) return;
|
||||||
if (textWidget.scrollTop_ !== this.scrollTop_) return;
|
if (textWidget.scrollTop_ !== this.scrollTop_) return;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const Resource = require('@joplin/lib/models/Resource').default;
|
|||||||
const Setting = require('@joplin/lib/models/Setting').default;
|
const Setting = require('@joplin/lib/models/Setting').default;
|
||||||
const reducer = require('@joplin/lib/reducer').default;
|
const reducer = require('@joplin/lib/reducer').default;
|
||||||
const { defaultState } = require('@joplin/lib/reducer');
|
const { defaultState } = require('@joplin/lib/reducer');
|
||||||
const { splitCommandString } = require('@joplin/lib/string-utils.js');
|
const { splitCommandString } = require('@joplin/utils');
|
||||||
const { reg } = require('@joplin/lib/registry.js');
|
const { reg } = require('@joplin/lib/registry.js');
|
||||||
const { _ } = require('@joplin/lib/locale');
|
const { _ } = require('@joplin/lib/locale');
|
||||||
const shim = require('@joplin/lib/shim').default;
|
const shim = require('@joplin/lib/shim').default;
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ const Tag = require('@joplin/lib/models/Tag').default;
|
|||||||
const Setting = require('@joplin/lib/models/Setting').default;
|
const Setting = require('@joplin/lib/models/Setting').default;
|
||||||
const { reg } = require('@joplin/lib/registry.js');
|
const { reg } = require('@joplin/lib/registry.js');
|
||||||
const { fileExtension } = require('@joplin/lib/path-utils');
|
const { fileExtension } = require('@joplin/lib/path-utils');
|
||||||
const { splitCommandString, splitCommandBatch } = require('@joplin/lib/string-utils');
|
const { splitCommandString } = require('@joplin/utils');
|
||||||
|
const { splitCommandBatch } = require('@joplin/lib/string-utils');
|
||||||
const { _ } = require('@joplin/lib/locale');
|
const { _ } = require('@joplin/lib/locale');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { cliUtils } = require('./cli-utils.js');
|
const { cliUtils } = require('./cli-utils.js');
|
||||||
|
|||||||
@@ -7,80 +7,80 @@ export default class BaseCommand {
|
|||||||
protected prompt_: any = null;
|
protected prompt_: any = null;
|
||||||
protected dispatcher_: any;
|
protected dispatcher_: any;
|
||||||
|
|
||||||
usage(): string {
|
public usage(): string {
|
||||||
throw new Error('Usage not defined');
|
throw new Error('Usage not defined');
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptionCheck(item: any) {
|
public encryptionCheck(item: any) {
|
||||||
if (item && item.encryption_applied) throw new Error(_('Cannot change encrypted item'));
|
if (item && item.encryption_applied) throw new Error(_('Cannot change encrypted item'));
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
public description() {
|
||||||
throw new Error('Description not defined');
|
throw new Error('Description not defined');
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(_args: any) {
|
public async action(_args: any) {
|
||||||
throw new Error('Action not defined');
|
throw new Error('Action not defined');
|
||||||
}
|
}
|
||||||
|
|
||||||
compatibleUis() {
|
public compatibleUis() {
|
||||||
return ['cli', 'gui'];
|
return ['cli', 'gui'];
|
||||||
}
|
}
|
||||||
|
|
||||||
supportsUi(ui: string) {
|
public supportsUi(ui: string) {
|
||||||
return this.compatibleUis().indexOf(ui) >= 0;
|
return this.compatibleUis().indexOf(ui) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
options(): any[] {
|
public options(): any[] {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
hidden() {
|
public hidden() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled() {
|
public enabled() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancellable() {
|
public cancellable() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancel() {}
|
public async cancel() {}
|
||||||
|
|
||||||
name() {
|
public name() {
|
||||||
const r = this.usage().split(' ');
|
const r = this.usage().split(' ');
|
||||||
return r[0];
|
return r[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
setDispatcher(fn: Function) {
|
public setDispatcher(fn: Function) {
|
||||||
this.dispatcher_ = fn;
|
this.dispatcher_ = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(action: any) {
|
public dispatch(action: any) {
|
||||||
if (!this.dispatcher_) throw new Error('Dispatcher not defined');
|
if (!this.dispatcher_) throw new Error('Dispatcher not defined');
|
||||||
return this.dispatcher_(action);
|
return this.dispatcher_(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
setStdout(fn: Function) {
|
public setStdout(fn: Function) {
|
||||||
this.stdout_ = fn;
|
this.stdout_ = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout(text: string) {
|
public stdout(text: string) {
|
||||||
if (this.stdout_) this.stdout_(text);
|
if (this.stdout_) this.stdout_(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
setPrompt(fn: Function) {
|
public setPrompt(fn: Function) {
|
||||||
this.prompt_ = fn;
|
this.prompt_ = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
async prompt(message: string, options: any = null) {
|
public async prompt(message: string, options: any = null) {
|
||||||
if (!this.prompt_) throw new Error('Prompt is undefined');
|
if (!this.prompt_) throw new Error('Prompt is undefined');
|
||||||
return await this.prompt_(message, options);
|
return await this.prompt_(message, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata() {
|
public metadata() {
|
||||||
return {
|
return {
|
||||||
name: this.name(),
|
name: this.name(),
|
||||||
usage: this.usage(),
|
usage: this.usage(),
|
||||||
@@ -89,7 +89,7 @@ export default class BaseCommand {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
logger() {
|
public logger() {
|
||||||
return reg.logger();
|
return reg.logger();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,15 @@ const imageType = require('image-type');
|
|||||||
const readChunk = require('read-chunk');
|
const readChunk = require('read-chunk');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
usage() {
|
public usage() {
|
||||||
return 'e2ee <command> [path]';
|
return 'e2ee <command> [path]';
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
public description() {
|
||||||
return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, and `target-status`.'); // `generate-ppk`
|
return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, and `target-status`.'); // `generate-ppk`
|
||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
public options() {
|
||||||
return [
|
return [
|
||||||
// This is here mostly for testing - shouldn't be used
|
// This is here mostly for testing - shouldn't be used
|
||||||
['-p, --password <password>', 'Use this password as master password (For security reasons, it is not recommended to use this option).'],
|
['-p, --password <password>', 'Use this password as master password (For security reasons, it is not recommended to use this option).'],
|
||||||
@@ -30,7 +30,7 @@ class Command extends BaseCommand {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args: any) {
|
public async action(args: any) {
|
||||||
const options = args.options;
|
const options = args.options;
|
||||||
|
|
||||||
const askForMasterKey = async (error: any) => {
|
const askForMasterKey = async (error: any) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const BaseCommand = require('./base-command').default;
|
const BaseCommand = require('./base-command').default;
|
||||||
const { splitCommandString } = require('@joplin/lib/string-utils.js');
|
const { splitCommandString } = require('@joplin/utils');
|
||||||
const uuid = require('@joplin/lib/uuid').default;
|
const uuid = require('@joplin/lib/uuid').default;
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('@joplin/lib/locale');
|
const { _ } = require('@joplin/lib/locale');
|
||||||
|
|||||||
@@ -6,22 +6,22 @@ import Folder from '@joplin/lib/models/Folder';
|
|||||||
import { FolderEntity } from '@joplin/lib/services/database/types';
|
import { FolderEntity } from '@joplin/lib/services/database/types';
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
usage() {
|
public usage() {
|
||||||
return 'mkbook <new-notebook>';
|
return 'mkbook <new-notebook>';
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
public description() {
|
||||||
return _('Creates a new notebook.');
|
return _('Creates a new notebook.');
|
||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
public options() {
|
||||||
return [
|
return [
|
||||||
['-p, --parent <parent-notebook>', _('Create a new notebook under a parent notebook.')],
|
['-p, --parent <parent-notebook>', _('Create a new notebook under a parent notebook.')],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// validDestinationFolder check for presents and ambiguous folders
|
// validDestinationFolder check for presents and ambiguous folders
|
||||||
async validDestinationFolder(targetFolder: string) {
|
public async validDestinationFolder(targetFolder: string) {
|
||||||
|
|
||||||
const destinationFolder = await app().loadItem(BaseModel.TYPE_FOLDER, targetFolder);
|
const destinationFolder = await app().loadItem(BaseModel.TYPE_FOLDER, targetFolder);
|
||||||
if (!destinationFolder) {
|
if (!destinationFolder) {
|
||||||
@@ -36,14 +36,14 @@ class Command extends BaseCommand {
|
|||||||
return destinationFolder;
|
return destinationFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveAndSwitchFolder(newFolder: FolderEntity) {
|
public async saveAndSwitchFolder(newFolder: FolderEntity) {
|
||||||
|
|
||||||
const folder = await Folder.save(newFolder, { userSideValidation: true });
|
const folder = await Folder.save(newFolder, { userSideValidation: true });
|
||||||
app().switchCurrentFolder(folder);
|
app().switchCurrentFolder(folder);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args: any) {
|
public async action(args: any) {
|
||||||
const targetFolder = args.options.parent;
|
const targetFolder = args.options.parent;
|
||||||
|
|
||||||
const newFolder: FolderEntity = {
|
const newFolder: FolderEntity = {
|
||||||
|
|||||||
@@ -23,19 +23,19 @@ function settingTypeToSchemaType(type: SettingItemType): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
usage() {
|
public usage() {
|
||||||
return 'settingschema <file>';
|
return 'settingschema <file>';
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
public description() {
|
||||||
return 'Build the setting schema file';
|
return 'Build the setting schema file';
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled() {
|
public enabled() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args: any) {
|
public async action(args: any) {
|
||||||
const schema: Record<string, any> = {
|
const schema: Record<string, any> = {
|
||||||
title: 'JSON schema for Joplin setting files',
|
title: 'JSON schema for Joplin setting files',
|
||||||
'$id': Setting.schemaUrl,
|
'$id': Setting.schemaUrl,
|
||||||
|
|||||||
@@ -21,15 +21,15 @@ class Command extends BaseCommand {
|
|||||||
private releaseLockFn_: Function = null;
|
private releaseLockFn_: Function = null;
|
||||||
private oneDriveApiUtils_: any = null;
|
private oneDriveApiUtils_: any = null;
|
||||||
|
|
||||||
usage() {
|
public usage() {
|
||||||
return 'sync';
|
return 'sync';
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
public description() {
|
||||||
return _('Synchronises with remote storage.');
|
return _('Synchronises with remote storage.');
|
||||||
}
|
}
|
||||||
|
|
||||||
options() {
|
public options() {
|
||||||
return [
|
return [
|
||||||
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
|
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
|
||||||
['--upgrade', _('Upgrade the sync target to the latest version.')],
|
['--upgrade', _('Upgrade the sync target to the latest version.')],
|
||||||
@@ -45,7 +45,7 @@ class Command extends BaseCommand {
|
|||||||
return locker.check(filePath);
|
return locker.check(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async doAuth() {
|
public async doAuth() {
|
||||||
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
const syncTarget = reg.syncTarget(this.syncTargetId_);
|
||||||
const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_);
|
const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_);
|
||||||
|
|
||||||
@@ -89,18 +89,18 @@ class Command extends BaseCommand {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelAuth() {
|
public cancelAuth() {
|
||||||
if (this.oneDriveApiUtils_) {
|
if (this.oneDriveApiUtils_) {
|
||||||
this.oneDriveApiUtils_.cancelOAuthDance();
|
this.oneDriveApiUtils_.cancelOAuthDance();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doingAuth() {
|
public doingAuth() {
|
||||||
return !!this.oneDriveApiUtils_;
|
return !!this.oneDriveApiUtils_;
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args: any) {
|
public async action(args: any) {
|
||||||
this.releaseLockFn_ = null;
|
this.releaseLockFn_ = null;
|
||||||
|
|
||||||
// Lock is unique per profile/database
|
// Lock is unique per profile/database
|
||||||
@@ -238,7 +238,7 @@ class Command extends BaseCommand {
|
|||||||
cleanUp();
|
cleanUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancel() {
|
public async cancel() {
|
||||||
if (this.doingAuth()) {
|
if (this.doingAuth()) {
|
||||||
this.cancelAuth();
|
this.cancelAuth();
|
||||||
return;
|
return;
|
||||||
@@ -263,7 +263,7 @@ class Command extends BaseCommand {
|
|||||||
this.syncTargetId_ = null;
|
this.syncTargetId_ = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancellable() {
|
public cancellable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,19 +18,19 @@ function itemCount(args: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
usage() {
|
public usage() {
|
||||||
return 'testing <command> [arg0]';
|
return 'testing <command> [arg0]';
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
public description() {
|
||||||
return 'testing';
|
return 'testing';
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled() {
|
public enabled() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
options(): any[] {
|
public options(): any[] {
|
||||||
return [
|
return [
|
||||||
['--folder-count <count>', 'Folders to create'],
|
['--folder-count <count>', 'Folders to create'],
|
||||||
['--note-count <count>', 'Notes to create'],
|
['--note-count <count>', 'Notes to create'],
|
||||||
@@ -40,7 +40,7 @@ class Command extends BaseCommand {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args: any) {
|
public async action(args: any) {
|
||||||
const { command, options } = args;
|
const { command, options } = args;
|
||||||
|
|
||||||
if (command === 'populate') {
|
if (command === 'populate') {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const stripAnsi = require('strip-ansi');
|
|||||||
const { handleAutocompletion } = require('../autocompletion.js');
|
const { handleAutocompletion } = require('../autocompletion.js');
|
||||||
|
|
||||||
export default class StatusBarWidget extends BaseWidget {
|
export default class StatusBarWidget extends BaseWidget {
|
||||||
constructor() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.promptState_ = null;
|
this.promptState_ = null;
|
||||||
@@ -14,20 +14,20 @@ export default class StatusBarWidget extends BaseWidget {
|
|||||||
this.items_ = [];
|
this.items_ = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
public get name() {
|
||||||
return 'statusBar';
|
return 'statusBar';
|
||||||
}
|
}
|
||||||
|
|
||||||
get canHaveFocus() {
|
public get canHaveFocus() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setItemAt(index: number, text: string) {
|
public setItemAt(index: number, text: string) {
|
||||||
this.items_[index] = stripAnsi(text).trim();
|
this.items_[index] = stripAnsi(text).trim();
|
||||||
this.invalidate();
|
this.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
async prompt(initialText = '', promptString: any = null, options: any = null) {
|
public async prompt(initialText = '', promptString: any = null, options: any = null) {
|
||||||
if (this.promptState_) throw new Error('Another prompt already active');
|
if (this.promptState_) throw new Error('Another prompt already active');
|
||||||
if (promptString === null) promptString = ':';
|
if (promptString === null) promptString = ':';
|
||||||
if (options === null) options = {};
|
if (options === null) options = {};
|
||||||
@@ -53,15 +53,15 @@ export default class StatusBarWidget extends BaseWidget {
|
|||||||
return this.promptState_.promise;
|
return this.promptState_.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
get promptActive() {
|
public get promptActive() {
|
||||||
return !!this.promptState_;
|
return !!this.promptState_;
|
||||||
}
|
}
|
||||||
|
|
||||||
get history() {
|
public get history() {
|
||||||
return this.history_;
|
return this.history_;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetCursor() {
|
public resetCursor() {
|
||||||
if (!this.promptActive) return;
|
if (!this.promptActive) return;
|
||||||
if (!this.inputEventEmitter_) return;
|
if (!this.inputEventEmitter_) return;
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ export default class StatusBarWidget extends BaseWidget {
|
|||||||
this.term.moveTo(this.absoluteInnerX + termutils.textLength(this.promptState_.promptString) + this.inputEventEmitter_.getInput().length, this.absoluteInnerY);
|
this.term.moveTo(this.absoluteInnerX + termutils.textLength(this.promptState_.promptString) + this.inputEventEmitter_.getInput().length, this.absoluteInnerY);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
super.render();
|
super.render();
|
||||||
|
|
||||||
const doSaveCursor = !this.promptActive;
|
const doSaveCursor = !this.promptActive;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default class PluginRunner extends BasePluginRunner {
|
|||||||
private eventHandlers_: EventHandlers = {};
|
private eventHandlers_: EventHandlers = {};
|
||||||
private activeSandboxCalls_: any = {};
|
private activeSandboxCalls_: any = {};
|
||||||
|
|
||||||
constructor() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.eventHandler = this.eventHandler.bind(this);
|
this.eventHandler = this.eventHandler.bind(this);
|
||||||
@@ -64,7 +64,7 @@ export default class PluginRunner extends BasePluginRunner {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(plugin: Plugin, sandbox: Global): Promise<void> {
|
public async run(plugin: Plugin, sandbox: Global): Promise<void> {
|
||||||
return new Promise((resolve: Function, reject: Function) => {
|
return new Promise((resolve: Function, reject: Function) => {
|
||||||
const onStarted = () => {
|
const onStarted = () => {
|
||||||
plugin.off('started', onStarted);
|
plugin.off('started', onStarted);
|
||||||
|
|||||||
@@ -30,34 +30,36 @@
|
|||||||
2019,
|
2019,
|
||||||
2020,
|
2020,
|
||||||
2021,
|
2021,
|
||||||
2022
|
2022,
|
||||||
|
2023
|
||||||
],
|
],
|
||||||
"owner": "Laurent Cozic"
|
"owner": "Laurent Cozic"
|
||||||
},
|
},
|
||||||
"version": "2.10.3",
|
"version": "2.11.0",
|
||||||
"bin": "./main.js",
|
"bin": "./main.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@joplin/lib": "~2.10",
|
"@joplin/lib": "~2.11",
|
||||||
"@joplin/renderer": "~2.10",
|
"@joplin/renderer": "~2.11",
|
||||||
|
"@joplin/utils": "~2.11",
|
||||||
"aws-sdk": "2.1290.0",
|
"aws-sdk": "2.1290.0",
|
||||||
"chalk": "4.1.2",
|
"chalk": "4.1.2",
|
||||||
"compare-version": "0.1.2",
|
"compare-version": "0.1.2",
|
||||||
"fs-extra": "11.1.0",
|
"fs-extra": "11.1.1",
|
||||||
"html-entities": "1.4.0",
|
"html-entities": "1.4.0",
|
||||||
"image-type": "3.1.0",
|
"image-type": "3.1.0",
|
||||||
"keytar": "7.9.0",
|
"keytar": "7.9.0",
|
||||||
"md5": "2.3.0",
|
"md5": "2.3.0",
|
||||||
"node-rsa": "1.1.1",
|
"node-rsa": "1.1.1",
|
||||||
"open": "8.4.1",
|
"open": "8.4.2",
|
||||||
"proper-lockfile": "4.1.2",
|
"proper-lockfile": "4.1.2",
|
||||||
"read-chunk": "2.1.0",
|
"read-chunk": "2.1.0",
|
||||||
"server-destroy": "1.0.1",
|
"server-destroy": "1.0.1",
|
||||||
"sharp": "0.31.3",
|
"sharp": "0.31.3",
|
||||||
"sprintf-js": "1.1.2",
|
"sprintf-js": "1.1.2",
|
||||||
"sqlite3": "5.1.4",
|
"sqlite3": "5.1.6",
|
||||||
"string-padding": "1.0.2",
|
"string-padding": "1.0.2",
|
||||||
"strip-ansi": "6.0.1",
|
"strip-ansi": "6.0.1",
|
||||||
"tcp-port-used": "1.0.2",
|
"tcp-port-used": "1.0.2",
|
||||||
@@ -68,7 +70,7 @@
|
|||||||
"yargs-parser": "21.1.1"
|
"yargs-parser": "21.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@joplin/tools": "~2.10",
|
"@joplin/tools": "~2.11",
|
||||||
"@types/fs-extra": "9.0.13",
|
"@types/fs-extra": "9.0.13",
|
||||||
"@types/jest": "29.2.6",
|
"@types/jest": "29.2.6",
|
||||||
"@types/node": "18.11.18",
|
"@types/node": "18.11.18",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable jest/require-top-level-describe */
|
||||||
|
|
||||||
import KeychainService from '@joplin/lib/services/keychain/KeychainService';
|
import KeychainService from '@joplin/lib/services/keychain/KeychainService';
|
||||||
import shim from '@joplin/lib/shim';
|
import shim from '@joplin/lib/shim';
|
||||||
import Setting from '@joplin/lib/models/Setting';
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
|
|||||||
@@ -47,9 +47,11 @@ describe('services_plugins_RepositoryApi', () => {
|
|||||||
|
|
||||||
it('should tell if a plugin can be updated', (async () => {
|
it('should tell if a plugin can be updated', (async () => {
|
||||||
const api = await newRepoApi();
|
const api = await newRepoApi();
|
||||||
expect(await api.pluginCanBeUpdated('org.joplinapp.plugins.ToggleSidebars', '1.0.0')).toBe(true);
|
|
||||||
expect(await api.pluginCanBeUpdated('org.joplinapp.plugins.ToggleSidebars', '1.0.2')).toBe(false);
|
expect(await api.pluginCanBeUpdated('org.joplinapp.plugins.ToggleSidebars', '1.0.0', '3.0.0')).toBe(true);
|
||||||
expect(await api.pluginCanBeUpdated('does.not.exist', '1.0.0')).toBe(false);
|
expect(await api.pluginCanBeUpdated('org.joplinapp.plugins.ToggleSidebars', '1.0.0', '1.0.0')).toBe(false);
|
||||||
|
expect(await api.pluginCanBeUpdated('org.joplinapp.plugins.ToggleSidebars', '1.0.2', '3.0.0')).toBe(false);
|
||||||
|
expect(await api.pluginCanBeUpdated('does.not.exist', '1.0.0', '3.0.0')).toBe(false);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
import * as fs from 'fs-extra';
|
import * as fs from 'fs-extra';
|
||||||
import { homedir } from 'os';
|
import { homedir } from 'os';
|
||||||
import { execCommand2 } from '@joplin/tools/tool-utils';
|
import { execCommand } from '@joplin/utils';
|
||||||
import { chdir } from 'process';
|
import { chdir } from 'process';
|
||||||
|
|
||||||
const minUserNum = 1;
|
const minUserNum = 1;
|
||||||
@@ -66,7 +66,7 @@ const processUser = async (userNum: number) => {
|
|||||||
|
|
||||||
await chdir(cliDir);
|
await chdir(cliDir);
|
||||||
|
|
||||||
await execCommand2(['yarn', 'run', 'start-no-build', '--', '--profile', profileDir, 'batch', commandFile]);
|
await execCommand(['yarn', 'run', 'start-no-build', '--', '--profile', profileDir, 'batch', commandFile]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Could not process user ${userNum}:`, error);
|
console.error(`Could not process user ${userNum}:`, error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -90,7 +90,7 @@ const main = async () => {
|
|||||||
|
|
||||||
// Build the app once before starting, because we'll use start-no-build to
|
// Build the app once before starting, because we'll use start-no-build to
|
||||||
// run the scripts (faster)
|
// run the scripts (faster)
|
||||||
await execCommand2(['yarn', 'run', 'build']);
|
await execCommand(['yarn', 'run', 'build']);
|
||||||
|
|
||||||
const focusUserNum = 0;
|
const focusUserNum = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Joplin Web Clipper [DEV]",
|
"name": "Joplin Web Clipper [DEV]",
|
||||||
"version": "2.10.0",
|
"version": "2.11.2",
|
||||||
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
|
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
|
||||||
"homepage_url": "https://joplinapp.org",
|
"homepage_url": "https://joplinapp.org",
|
||||||
"content_security_policy": "script-src 'self'; object-src 'self'",
|
"content_security_policy": "script-src 'self'; object-src 'self'",
|
||||||
|
|||||||
@@ -126,4 +126,4 @@
|
|||||||
"react-app"
|
"react-app"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default class ElectronAppWrapper {
|
|||||||
private pluginWindows_: PluginWindows = {};
|
private pluginWindows_: PluginWindows = {};
|
||||||
private initialCallbackUrl_: string = null;
|
private initialCallbackUrl_: string = null;
|
||||||
|
|
||||||
constructor(electronApp: any, env: string, profilePath: string, isDebugMode: boolean, initialCallbackUrl: string) {
|
public constructor(electronApp: any, env: string, profilePath: string, isDebugMode: boolean, initialCallbackUrl: string) {
|
||||||
this.electronApp_ = electronApp;
|
this.electronApp_ = electronApp;
|
||||||
this.env_ = env;
|
this.env_ = env;
|
||||||
this.isDebugMode_ = isDebugMode;
|
this.isDebugMode_ = isDebugMode;
|
||||||
@@ -41,31 +41,31 @@ export default class ElectronAppWrapper {
|
|||||||
this.initialCallbackUrl_ = initialCallbackUrl;
|
this.initialCallbackUrl_ = initialCallbackUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
electronApp() {
|
public electronApp() {
|
||||||
return this.electronApp_;
|
return this.electronApp_;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLogger(v: Logger) {
|
public setLogger(v: Logger) {
|
||||||
this.logger_ = v;
|
this.logger_ = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger() {
|
public logger() {
|
||||||
return this.logger_;
|
return this.logger_;
|
||||||
}
|
}
|
||||||
|
|
||||||
window() {
|
public window() {
|
||||||
return this.win_;
|
return this.win_;
|
||||||
}
|
}
|
||||||
|
|
||||||
env() {
|
public env() {
|
||||||
return this.env_;
|
return this.env_;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialCallbackUrl() {
|
public initialCallbackUrl() {
|
||||||
return this.initialCallbackUrl_;
|
return this.initialCallbackUrl_;
|
||||||
}
|
}
|
||||||
|
|
||||||
createWindow() {
|
public createWindow() {
|
||||||
// Set to true to view errors if the application does not start
|
// Set to true to view errors if the application does not start
|
||||||
const debugEarlyBugs = this.env_ === 'dev' || this.isDebugMode_;
|
const debugEarlyBugs = this.env_ === 'dev' || this.isDebugMode_;
|
||||||
|
|
||||||
@@ -236,11 +236,11 @@ export default class ElectronAppWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerPluginWindow(pluginId: string, window: any) {
|
public registerPluginWindow(pluginId: string, window: any) {
|
||||||
this.pluginWindows_[pluginId] = window;
|
this.pluginWindows_[pluginId] = window;
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForElectronAppReady() {
|
public async waitForElectronAppReady() {
|
||||||
if (this.electronApp().isReady()) return Promise.resolve();
|
if (this.electronApp().isReady()) return Promise.resolve();
|
||||||
|
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
@@ -253,25 +253,25 @@ export default class ElectronAppWrapper {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
quit() {
|
public quit() {
|
||||||
this.electronApp_.quit();
|
this.electronApp_.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
exit(errorCode = 0) {
|
public exit(errorCode = 0) {
|
||||||
this.electronApp_.exit(errorCode);
|
this.electronApp_.exit(errorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
trayShown() {
|
public trayShown() {
|
||||||
return !!this.tray_;
|
return !!this.tray_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method is used in macOS only to hide the whole app (and not just the main window)
|
// This method is used in macOS only to hide the whole app (and not just the main window)
|
||||||
// including the menu bar. This follows the macOS way of hiding an app.
|
// including the menu bar. This follows the macOS way of hiding an app.
|
||||||
hide() {
|
public hide() {
|
||||||
this.electronApp_.hide();
|
this.electronApp_.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
buildDir() {
|
public buildDir() {
|
||||||
if (this.buildDir_) return this.buildDir_;
|
if (this.buildDir_) return this.buildDir_;
|
||||||
let dir = `${__dirname}/build`;
|
let dir = `${__dirname}/build`;
|
||||||
if (!fs.pathExistsSync(dir)) {
|
if (!fs.pathExistsSync(dir)) {
|
||||||
@@ -283,7 +283,7 @@ export default class ElectronAppWrapper {
|
|||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
trayIconFilename_() {
|
private trayIconFilename_() {
|
||||||
let output = '';
|
let output = '';
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
@@ -298,7 +298,7 @@ export default class ElectronAppWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Note: this must be called only after the "ready" event of the app has been dispatched
|
// Note: this must be called only after the "ready" event of the app has been dispatched
|
||||||
createTray(contextMenu: any) {
|
public createTray(contextMenu: any) {
|
||||||
try {
|
try {
|
||||||
this.tray_ = new Tray(`${this.buildDir()}/icons/${this.trayIconFilename_()}`);
|
this.tray_ = new Tray(`${this.buildDir()}/icons/${this.trayIconFilename_()}`);
|
||||||
this.tray_.setToolTip(this.electronApp_.name);
|
this.tray_.setToolTip(this.electronApp_.name);
|
||||||
@@ -312,13 +312,13 @@ export default class ElectronAppWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyTray() {
|
public destroyTray() {
|
||||||
if (!this.tray_) return;
|
if (!this.tray_) return;
|
||||||
this.tray_.destroy();
|
this.tray_.destroy();
|
||||||
this.tray_ = null;
|
this.tray_ = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureSingleInstance() {
|
public ensureSingleInstance() {
|
||||||
if (this.env_ === 'dev') return false;
|
if (this.env_ === 'dev') return false;
|
||||||
|
|
||||||
const gotTheLock = this.electronApp_.requestSingleInstanceLock();
|
const gotTheLock = this.electronApp_.requestSingleInstanceLock();
|
||||||
@@ -347,7 +347,7 @@ export default class ElectronAppWrapper {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
public async start() {
|
||||||
// Since we are doing other async things before creating the window, we might miss
|
// Since we are doing other async things before creating the window, we might miss
|
||||||
// the "ready" event. So we use the function below to make sure that the app is ready.
|
// the "ready" event. So we use the function below to make sure that the app is ready.
|
||||||
await this.waitForElectronAppReady();
|
await this.waitForElectronAppReady();
|
||||||
@@ -375,7 +375,7 @@ export default class ElectronAppWrapper {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async openCallbackUrl(url: string) {
|
public async openCallbackUrl(url: string) {
|
||||||
this.win_.webContents.send('asynchronous-message', 'openCallbackUrl', {
|
this.win_.webContents.send('asynchronous-message', 'openCallbackUrl', {
|
||||||
url: url,
|
url: url,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import appReducer, { createAppDefaultState } from './app.reducer';
|
|||||||
|
|
||||||
describe('app.reducer', () => {
|
describe('app.reducer', () => {
|
||||||
|
|
||||||
it('DIALOG_OPEN', async () => {
|
it('should handle DIALOG_OPEN', async () => {
|
||||||
const state: AppState = createAppDefaultState({}, {});
|
const state: AppState = createAppDefaultState({}, {});
|
||||||
|
|
||||||
let newState = appReducer(state, {
|
let newState = appReducer(state, {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export class Bridge {
|
|||||||
private electronWrapper_: ElectronAppWrapper;
|
private electronWrapper_: ElectronAppWrapper;
|
||||||
private lastSelectedPaths_: LastSelectedPath;
|
private lastSelectedPaths_: LastSelectedPath;
|
||||||
|
|
||||||
constructor(electronWrapper: ElectronAppWrapper) {
|
public constructor(electronWrapper: ElectronAppWrapper) {
|
||||||
this.electronWrapper_ = electronWrapper;
|
this.electronWrapper_ = electronWrapper;
|
||||||
this.lastSelectedPaths_ = {
|
this.lastSelectedPaths_ = {
|
||||||
file: null,
|
file: null,
|
||||||
@@ -29,11 +29,11 @@ export class Bridge {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
electronApp() {
|
public electronApp() {
|
||||||
return this.electronWrapper_;
|
return this.electronWrapper_;
|
||||||
}
|
}
|
||||||
|
|
||||||
electronIsDev() {
|
public electronIsDev() {
|
||||||
return !this.electronApp().electronApp().isPackaged;
|
return !this.electronApp().electronApp().isPackaged;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,11 +60,11 @@ export class Bridge {
|
|||||||
return `${__dirname}/vendor`;
|
return `${__dirname}/vendor`;
|
||||||
}
|
}
|
||||||
|
|
||||||
env() {
|
public env() {
|
||||||
return this.electronWrapper_.env();
|
return this.electronWrapper_.env();
|
||||||
}
|
}
|
||||||
|
|
||||||
processArgv() {
|
public processArgv() {
|
||||||
return process.argv;
|
return process.argv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,44 +114,44 @@ export class Bridge {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window() {
|
public window() {
|
||||||
return this.electronWrapper_.window();
|
return this.electronWrapper_.window();
|
||||||
}
|
}
|
||||||
|
|
||||||
showItemInFolder(fullPath: string) {
|
public showItemInFolder(fullPath: string) {
|
||||||
return require('electron').shell.showItemInFolder(toSystemSlashes(fullPath));
|
return require('electron').shell.showItemInFolder(toSystemSlashes(fullPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
newBrowserWindow(options: any) {
|
public newBrowserWindow(options: any) {
|
||||||
return new BrowserWindow(options);
|
return new BrowserWindow(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
windowContentSize() {
|
public windowContentSize() {
|
||||||
if (!this.window()) return { width: 0, height: 0 };
|
if (!this.window()) return { width: 0, height: 0 };
|
||||||
const s = this.window().getContentSize();
|
const s = this.window().getContentSize();
|
||||||
return { width: s[0], height: s[1] };
|
return { width: s[0], height: s[1] };
|
||||||
}
|
}
|
||||||
|
|
||||||
windowSize() {
|
public windowSize() {
|
||||||
if (!this.window()) return { width: 0, height: 0 };
|
if (!this.window()) return { width: 0, height: 0 };
|
||||||
const s = this.window().getSize();
|
const s = this.window().getSize();
|
||||||
return { width: s[0], height: s[1] };
|
return { width: s[0], height: s[1] };
|
||||||
}
|
}
|
||||||
|
|
||||||
windowSetSize(width: number, height: number) {
|
public windowSetSize(width: number, height: number) {
|
||||||
if (!this.window()) return;
|
if (!this.window()) return;
|
||||||
return this.window().setSize(width, height);
|
return this.window().setSize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
openDevTools() {
|
public openDevTools() {
|
||||||
return this.window().webContents.openDevTools();
|
return this.window().webContents.openDevTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
closeDevTools() {
|
public closeDevTools() {
|
||||||
return this.window().webContents.closeDevTools();
|
return this.window().webContents.closeDevTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
async showSaveDialog(options: any) {
|
public async showSaveDialog(options: any) {
|
||||||
const { dialog } = require('electron');
|
const { dialog } = require('electron');
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
if (!('defaultPath' in options) && this.lastSelectedPaths_.file) options.defaultPath = this.lastSelectedPaths_.file;
|
if (!('defaultPath' in options) && this.lastSelectedPaths_.file) options.defaultPath = this.lastSelectedPaths_.file;
|
||||||
@@ -162,7 +162,7 @@ export class Bridge {
|
|||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
async showOpenDialog(options: OpenDialogOptions = null) {
|
public async showOpenDialog(options: OpenDialogOptions = null) {
|
||||||
const { dialog } = require('electron');
|
const { dialog } = require('electron');
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
let fileType = 'file';
|
let fileType = 'file';
|
||||||
@@ -177,13 +177,13 @@ export class Bridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't use this directly - call one of the showXxxxxxxMessageBox() instead
|
// Don't use this directly - call one of the showXxxxxxxMessageBox() instead
|
||||||
showMessageBox_(window: any, options: any): number {
|
private showMessageBox_(window: any, options: any): number {
|
||||||
const { dialog } = require('electron');
|
const { dialog } = require('electron');
|
||||||
if (!window) window = this.window();
|
if (!window) window = this.window();
|
||||||
return dialog.showMessageBoxSync(window, options);
|
return dialog.showMessageBoxSync(window, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
showErrorMessageBox(message: string) {
|
public showErrorMessageBox(message: string) {
|
||||||
return this.showMessageBox_(this.window(), {
|
return this.showMessageBox_(this.window(), {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: message,
|
message: message,
|
||||||
@@ -191,7 +191,7 @@ export class Bridge {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
showConfirmMessageBox(message: string, options: any = null) {
|
public showConfirmMessageBox(message: string, options: any = null) {
|
||||||
options = {
|
options = {
|
||||||
buttons: [_('OK'), _('Cancel')],
|
buttons: [_('OK'), _('Cancel')],
|
||||||
...options,
|
...options,
|
||||||
@@ -208,7 +208,7 @@ export class Bridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* returns the index of the clicked button */
|
/* returns the index of the clicked button */
|
||||||
showMessageBox(message: string, options: any = null) {
|
public showMessageBox(message: string, options: any = null) {
|
||||||
if (options === null) options = {};
|
if (options === null) options = {};
|
||||||
|
|
||||||
const result = this.showMessageBox_(this.window(), Object.assign({}, {
|
const result = this.showMessageBox_(this.window(), Object.assign({}, {
|
||||||
@@ -220,7 +220,7 @@ export class Bridge {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
showInfoMessageBox(message: string, options: any = {}) {
|
public showInfoMessageBox(message: string, options: any = {}) {
|
||||||
const result = this.showMessageBox_(this.window(), Object.assign({}, {
|
const result = this.showMessageBox_(this.window(), Object.assign({}, {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
message: message,
|
message: message,
|
||||||
@@ -229,35 +229,35 @@ export class Bridge {
|
|||||||
return result === 0;
|
return result === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocale(locale: string) {
|
public setLocale(locale: string) {
|
||||||
setLocale(locale);
|
setLocale(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
get Menu() {
|
public get Menu() {
|
||||||
return require('electron').Menu;
|
return require('electron').Menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
get MenuItem() {
|
public get MenuItem() {
|
||||||
return require('electron').MenuItem;
|
return require('electron').MenuItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
openExternal(url: string) {
|
public openExternal(url: string) {
|
||||||
return require('electron').shell.openExternal(url);
|
return require('electron').shell.openExternal(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
async openItem(fullPath: string) {
|
public async openItem(fullPath: string) {
|
||||||
return require('electron').shell.openPath(toSystemSlashes(fullPath));
|
return require('electron').shell.openPath(toSystemSlashes(fullPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
screen() {
|
public screen() {
|
||||||
return require('electron').screen;
|
return require('electron').screen;
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldUseDarkColors() {
|
public shouldUseDarkColors() {
|
||||||
return nativeTheme.shouldUseDarkColors;
|
return nativeTheme.shouldUseDarkColors;
|
||||||
}
|
}
|
||||||
|
|
||||||
addEventListener(name: string, fn: Function) {
|
public addEventListener(name: string, fn: Function) {
|
||||||
if (name === 'nativeThemeUpdated') {
|
if (name === 'nativeThemeUpdated') {
|
||||||
nativeTheme.on('updated', fn);
|
nativeTheme.on('updated', fn);
|
||||||
} else {
|
} else {
|
||||||
@@ -265,7 +265,7 @@ export class Bridge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
restart(linuxSafeRestart = true) {
|
public restart(linuxSafeRestart = true) {
|
||||||
// Note that in this case we are not sending the "appClose" event
|
// Note that in this case we are not sending the "appClose" event
|
||||||
// to notify services and component that the app is about to close
|
// to notify services and component that the app is about to close
|
||||||
// but for the current use-case it's not really needed.
|
// but for the current use-case it's not really needed.
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ const React = require('react');
|
|||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { clipboard } = require('electron');
|
const { clipboard } = require('electron');
|
||||||
import ExtensionBadge from './ExtensionBadge';
|
import ExtensionBadge from './ExtensionBadge';
|
||||||
import bridge from '../services/bridge';
|
|
||||||
import { themeStyle } from '@joplin/lib/theme';
|
import { themeStyle } from '@joplin/lib/theme';
|
||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
import ClipperServer from '@joplin/lib/ClipperServer';
|
import ClipperServer from '@joplin/lib/ClipperServer';
|
||||||
@@ -11,37 +10,29 @@ import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
|
|||||||
import { AppState } from '../app.reducer';
|
import { AppState } from '../app.reducer';
|
||||||
|
|
||||||
class ClipperConfigScreenComponent extends React.Component {
|
class ClipperConfigScreenComponent extends React.Component {
|
||||||
constructor() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.copyToken_click = this.copyToken_click.bind(this);
|
this.copyToken_click = this.copyToken_click.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
disableClipperServer_click() {
|
private disableClipperServer_click() {
|
||||||
Setting.setValue('clipperServer.autoStart', false);
|
Setting.setValue('clipperServer.autoStart', false);
|
||||||
void ClipperServer.instance().stop();
|
void ClipperServer.instance().stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
enableClipperServer_click() {
|
private enableClipperServer_click() {
|
||||||
Setting.setValue('clipperServer.autoStart', true);
|
Setting.setValue('clipperServer.autoStart', true);
|
||||||
void ClipperServer.instance().start();
|
void ClipperServer.instance().start();
|
||||||
}
|
}
|
||||||
|
|
||||||
chromeButton_click() {
|
private copyToken_click() {
|
||||||
void bridge().openExternal('https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek');
|
|
||||||
}
|
|
||||||
|
|
||||||
firefoxButton_click() {
|
|
||||||
void bridge().openExternal('https://addons.mozilla.org/en-US/firefox/addon/joplin-web-clipper/');
|
|
||||||
}
|
|
||||||
|
|
||||||
copyToken_click() {
|
|
||||||
clipboard.writeText(this.props.apiToken);
|
clipboard.writeText(this.props.apiToken);
|
||||||
|
|
||||||
alert(_('Token has been copied to the clipboard!'));
|
alert(_('Token has been copied to the clipboard!'));
|
||||||
}
|
}
|
||||||
|
|
||||||
renewToken_click() {
|
private renewToken_click() {
|
||||||
if (confirm(_('Are you sure you want to renew the authorisation token?'))) {
|
if (confirm(_('Are you sure you want to renew the authorisation token?'))) {
|
||||||
void EncryptionService.instance()
|
void EncryptionService.instance()
|
||||||
.generateApiToken()
|
.generateApiToken()
|
||||||
@@ -52,7 +43,7 @@ class ClipperConfigScreenComponent extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const containerStyle = Object.assign({}, theme.containerStyle, {
|
const containerStyle = Object.assign({}, theme.containerStyle, {
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ const settingKeyToControl: any = {
|
|||||||
|
|
||||||
class ConfigScreenComponent extends React.Component<any, any> {
|
class ConfigScreenComponent extends React.Component<any, any> {
|
||||||
|
|
||||||
rowStyle_: any = null;
|
private rowStyle_: any = null;
|
||||||
|
|
||||||
constructor(props: any) {
|
public constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
shared.init(this, reg);
|
shared.init(this, reg);
|
||||||
@@ -55,15 +55,15 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
|||||||
this.handleSettingButton = this.handleSettingButton.bind(this);
|
this.handleSettingButton = this.handleSettingButton.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkSyncConfig_() {
|
private async checkSyncConfig_() {
|
||||||
await shared.checkSyncConfig(this, this.state.settings);
|
await shared.checkSyncConfig(this, this.state.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
public UNSAFE_componentWillMount() {
|
||||||
this.setState({ settings: this.props.settings });
|
this.setState({ settings: this.props.settings });
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount() {
|
||||||
if (this.props.defaultSection) {
|
if (this.props.defaultSection) {
|
||||||
this.setState({ selectedSectionName: this.props.defaultSection }, () => {
|
this.setState({ selectedSectionName: this.props.defaultSection }, () => {
|
||||||
this.switchSection(this.props.defaultSection);
|
this.switchSection(this.props.defaultSection);
|
||||||
@@ -93,7 +93,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sectionByName(name: string) {
|
public sectionByName(name: string) {
|
||||||
const sections = shared.settingsSections({ device: 'desktop', settings: this.state.settings });
|
const sections = shared.settingsSections({ device: 'desktop', settings: this.state.settings });
|
||||||
for (const section of sections) {
|
for (const section of sections) {
|
||||||
if (section.name === name) return section;
|
if (section.name === name) return section;
|
||||||
@@ -102,7 +102,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
|||||||
throw new Error(`Invalid section name: ${name}`);
|
throw new Error(`Invalid section name: ${name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
screenFromName(screenName: string) {
|
public screenFromName(screenName: string) {
|
||||||
if (screenName === 'encryption') return <EncryptionConfigScreen/>;
|
if (screenName === 'encryption') return <EncryptionConfigScreen/>;
|
||||||
if (screenName === 'server') return <ClipperConfigScreen themeId={this.props.themeId}/>;
|
if (screenName === 'server') return <ClipperConfigScreen themeId={this.props.themeId}/>;
|
||||||
if (screenName === 'keymap') return <KeymapConfigScreen themeId={this.props.themeId}/>;
|
if (screenName === 'keymap') return <KeymapConfigScreen themeId={this.props.themeId}/>;
|
||||||
@@ -110,7 +110,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
|||||||
throw new Error(`Invalid screen name: ${screenName}`);
|
throw new Error(`Invalid screen name: ${screenName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
switchSection(name: string) {
|
public switchSection(name: string) {
|
||||||
const section = this.sectionByName(name);
|
const section = this.sectionByName(name);
|
||||||
let screenName = '';
|
let screenName = '';
|
||||||
if (section.isScreen) {
|
if (section.isScreen) {
|
||||||
@@ -125,11 +125,11 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
|||||||
this.setState({ selectedSectionName: section.name, screenName: screenName });
|
this.setState({ selectedSectionName: section.name, screenName: screenName });
|
||||||
}
|
}
|
||||||
|
|
||||||
sidebar_selectionChange(event: any) {
|
private sidebar_selectionChange(event: any) {
|
||||||
this.switchSection(event.section.name);
|
this.switchSection(event.section.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSectionDescription(section: any) {
|
public renderSectionDescription(section: any) {
|
||||||
const description = Setting.sectionDescription(section.name);
|
const description = Setting.sectionDescription(section.name);
|
||||||
if (!description) return null;
|
if (!description) return null;
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sectionToComponent(key: string, section: any, settings: any, selected: boolean) {
|
public sectionToComponent(key: string, section: any, settings: any, selected: boolean) {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const createSettingComponents = (advanced: boolean) => {
|
const createSettingComponents = (advanced: boolean) => {
|
||||||
@@ -284,7 +284,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
|||||||
return description ? <div style={this.descriptionStyle(themeId)}>{description}</div> : null;
|
return description ? <div style={this.descriptionStyle(themeId)}>{description}</div> : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
settingToComponent(key: string, value: any) {
|
public settingToComponent(key: string, value: any) {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const output: any = null;
|
const output: any = null;
|
||||||
@@ -657,26 +657,26 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onApplyClick() {
|
public async onApplyClick() {
|
||||||
shared.saveSettings(this);
|
shared.saveSettings(this);
|
||||||
await this.checkNeedRestart();
|
await this.checkNeedRestart();
|
||||||
}
|
}
|
||||||
|
|
||||||
async onSaveClick() {
|
public async onSaveClick() {
|
||||||
shared.saveSettings(this);
|
shared.saveSettings(this);
|
||||||
await this.checkNeedRestart();
|
await this.checkNeedRestart();
|
||||||
this.props.dispatch({ type: 'NAV_BACK' });
|
this.props.dispatch({ type: 'NAV_BACK' });
|
||||||
}
|
}
|
||||||
|
|
||||||
onCancelClick() {
|
public onCancelClick() {
|
||||||
this.props.dispatch({ type: 'NAV_BACK' });
|
this.props.dispatch({ type: 'NAV_BACK' });
|
||||||
}
|
}
|
||||||
|
|
||||||
hasChanges() {
|
public hasChanges() {
|
||||||
return !!this.state.changedSettingKeys.length;
|
return !!this.state.changedSettingKeys.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const style = Object.assign({},
|
const style = Object.assign({},
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export default function(props: Props) {
|
|||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
||||||
async function fetchPluginIds() {
|
async function fetchPluginIds() {
|
||||||
const pluginIds = await repoApi().canBeUpdatedPlugins(pluginItems.map(p => p.manifest));
|
const pluginIds = await repoApi().canBeUpdatedPlugins(pluginItems.map(p => p.manifest), pluginService.appVersion);
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
const conv: Record<string, boolean> = {};
|
const conv: Record<string, boolean> = {};
|
||||||
pluginIds.forEach(id => conv[id] = true);
|
pluginIds.forEach(id => conv[id] = true);
|
||||||
@@ -155,7 +155,7 @@ export default function(props: Props) {
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [manifestsLoaded, pluginItems]);
|
}, [manifestsLoaded, pluginItems, pluginService.appVersion]);
|
||||||
|
|
||||||
const onDelete = useCallback(async (event: ItemEvent) => {
|
const onDelete = useCallback(async (event: ItemEvent) => {
|
||||||
const item = event.item;
|
const item = event.item;
|
||||||
|
|||||||
@@ -13,19 +13,19 @@ interface Props {
|
|||||||
|
|
||||||
class DropboxLoginScreenComponent extends React.Component<any, any> {
|
class DropboxLoginScreenComponent extends React.Component<any, any> {
|
||||||
|
|
||||||
shared_: any;
|
private shared_: any;
|
||||||
|
|
||||||
constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.shared_ = new Shared(this, (msg: string) => bridge().showInfoMessageBox(msg), (msg: string) => bridge().showErrorMessageBox(msg));
|
this.shared_ = new Shared(this, (msg: string) => bridge().showInfoMessageBox(msg), (msg: string) => bridge().showErrorMessageBox(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
public UNSAFE_componentWillMount() {
|
||||||
this.shared_.refreshUrl();
|
this.shared_.refreshUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const style = this.props.style;
|
const style = this.props.style;
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
|
|||||||
|
|
||||||
public state: State = { error: null, errorInfo: null, pluginInfos: [], plugins: {} };
|
public state: State = { error: null, errorInfo: null, pluginInfos: [], plugins: {} };
|
||||||
|
|
||||||
componentDidCatch(error: any, errorInfo: ErrorInfo) {
|
public componentDidCatch(error: any, errorInfo: ErrorInfo) {
|
||||||
if (typeof error === 'string') error = { message: error };
|
if (typeof error === 'string') error = { message: error };
|
||||||
|
|
||||||
const pluginInfos: PluginInfo[] = [];
|
const pluginInfos: PluginInfo[] = [];
|
||||||
@@ -58,7 +58,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
|
|||||||
this.setState({ error, errorInfo, pluginInfos, plugins });
|
this.setState({ error, errorInfo, pluginInfos, plugins });
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount() {
|
||||||
const onAppClose = () => {
|
const onAppClose = () => {
|
||||||
ipcRenderer.send('asynchronous-message', 'appCloseReply', {
|
ipcRenderer.send('asynchronous-message', 'appCloseReply', {
|
||||||
canClose: true,
|
canClose: true,
|
||||||
@@ -68,12 +68,12 @@ export default class ErrorBoundary extends React.Component<Props, State> {
|
|||||||
ipcRenderer.on('appClose', onAppClose);
|
ipcRenderer.on('appClose', onAppClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMessage() {
|
public renderMessage() {
|
||||||
const message = this.props.message || 'Joplin encountered a fatal error and could not continue.';
|
const message = this.props.message || 'Joplin encountered a fatal error and could not continue.';
|
||||||
return <p>{message}</p>;
|
return <p>{message}</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
const safeMode_click = async () => {
|
const safeMode_click = async () => {
|
||||||
Setting.setValue('isSafeMode', true);
|
Setting.setValue('isSafeMode', true);
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class HelpButtonComponent extends React.Component<Props> {
|
class HelpButtonComponent extends React.Component<Props> {
|
||||||
constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.onClick = this.onClick.bind(this);
|
this.onClick = this.onClick.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick() {
|
public onClick() {
|
||||||
if (this.props.onClick) this.props.onClick();
|
if (this.props.onClick) this.props.onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const style = Object.assign({}, this.props.style, { color: theme.color, textDecoration: 'none' });
|
const style = Object.assign({}, this.props.style, { color: theme.color, textDecoration: 'none' });
|
||||||
const helpIconStyle = { flex: 0, width: 16, height: 16, marginLeft: 10 };
|
const helpIconStyle = { flex: 0, width: 16, height: 16, marginLeft: 10 };
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class IconButton extends React.Component<Props> {
|
class IconButton extends React.Component<Props> {
|
||||||
render() {
|
public render() {
|
||||||
const style = this.props.style;
|
const style = this.props.style;
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const iconStyle = {
|
const iconStyle = {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ImportScreenComponent extends React.Component<Props, State> {
|
class ImportScreenComponent extends React.Component<Props, State> {
|
||||||
UNSAFE_componentWillMount() {
|
public UNSAFE_componentWillMount() {
|
||||||
this.setState({
|
this.setState({
|
||||||
doImport: true,
|
doImport: true,
|
||||||
filePath: this.props.filePath,
|
filePath: this.props.filePath,
|
||||||
@@ -32,7 +32,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(newProps: Props) {
|
public UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||||
if (newProps.filePath) {
|
if (newProps.filePath) {
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
@@ -47,13 +47,13 @@ class ImportScreenComponent extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount() {
|
||||||
if (this.state.filePath && this.state.doImport) {
|
if (this.state.filePath && this.state.doImport) {
|
||||||
void this.doImport();
|
void this.doImport();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addMessage(key: string, text: string) {
|
public addMessage(key: string, text: string) {
|
||||||
const messages = this.state.messages.slice();
|
const messages = this.state.messages.slice();
|
||||||
|
|
||||||
messages.push({ key: key, text: text });
|
messages.push({ key: key, text: text });
|
||||||
@@ -61,7 +61,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
|
|||||||
this.setState({ messages: messages });
|
this.setState({ messages: messages });
|
||||||
}
|
}
|
||||||
|
|
||||||
uniqueMessages() {
|
public uniqueMessages() {
|
||||||
const output = [];
|
const output = [];
|
||||||
const messages = this.state.messages.slice();
|
const messages = this.state.messages.slice();
|
||||||
const foundKeys = [];
|
const foundKeys = [];
|
||||||
@@ -74,7 +74,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
async doImport() {
|
public async doImport() {
|
||||||
const filePath = this.props.filePath;
|
const filePath = this.props.filePath;
|
||||||
const folderTitle = await Folder.findUniqueItemTitle(filename(filePath));
|
const folderTitle = await Folder.findUniqueItemTitle(filename(filePath));
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
|
|||||||
this.setState({ doImport: false });
|
this.setState({ doImport: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const messages = this.uniqueMessages();
|
const messages = this.uniqueMessages();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import Logger from '@joplin/lib/Logger';
|
||||||
|
|
||||||
|
const logger = Logger.create('ItemList');
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
style: any;
|
style: any;
|
||||||
@@ -21,7 +24,7 @@ class ItemList extends React.Component<Props, State> {
|
|||||||
private scrollTop_: number;
|
private scrollTop_: number;
|
||||||
private listRef: any;
|
private listRef: any;
|
||||||
|
|
||||||
constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.scrollTop_ = 0;
|
this.scrollTop_ = 0;
|
||||||
@@ -33,12 +36,12 @@ class ItemList extends React.Component<Props, State> {
|
|||||||
this.onDrop = this.onDrop.bind(this);
|
this.onDrop = this.onDrop.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
visibleItemCount(props: Props = undefined) {
|
public visibleItemCount(props: Props = undefined) {
|
||||||
if (typeof props === 'undefined') props = this.props;
|
if (typeof props === 'undefined') props = this.props;
|
||||||
return Math.ceil(props.style.height / props.itemHeight);
|
return Math.ceil(props.style.height / props.itemHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStateItemIndexes(props: Props = undefined) {
|
public updateStateItemIndexes(props: Props = undefined) {
|
||||||
if (typeof props === 'undefined') props = this.props;
|
if (typeof props === 'undefined') props = this.props;
|
||||||
|
|
||||||
const topItemIndex = Math.floor(this.scrollTop_ / props.itemHeight);
|
const topItemIndex = Math.floor(this.scrollTop_ / props.itemHeight);
|
||||||
@@ -47,42 +50,66 @@ class ItemList extends React.Component<Props, State> {
|
|||||||
let bottomItemIndex = topItemIndex + (visibleItemCount - 1);
|
let bottomItemIndex = topItemIndex + (visibleItemCount - 1);
|
||||||
if (bottomItemIndex >= props.items.length) bottomItemIndex = props.items.length - 1;
|
if (bottomItemIndex >= props.items.length) bottomItemIndex = props.items.length - 1;
|
||||||
|
|
||||||
|
// EDGE CASE:
|
||||||
|
// ref: https://github.com/laurent22/joplin/issues/4124
|
||||||
|
// when the note list is hidden, visibleItemCount is negative, and scroll top is positive when a note is selected
|
||||||
|
if (visibleItemCount < 0 && this.scrollTop_ > 0) {
|
||||||
|
logger.warn('Resetting scrollTop to 0. visibleItemCount is negative, scrollTop is positive.');
|
||||||
|
// we will reset the scroll top so that there is no blank space at the top of note list
|
||||||
|
this.scrollTop_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
topItemIndex: topItemIndex,
|
topItemIndex: topItemIndex,
|
||||||
bottomItemIndex: bottomItemIndex,
|
bottomItemIndex: bottomItemIndex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
offsetTop() {
|
public offsetTop() {
|
||||||
return this.listRef.current ? this.listRef.current.offsetTop : 0;
|
return this.listRef.current ? this.listRef.current.offsetTop : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
offsetScroll() {
|
public offsetScroll() {
|
||||||
return this.scrollTop_;
|
return this.scrollTop_;
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
public UNSAFE_componentWillMount() {
|
||||||
this.updateStateItemIndexes();
|
this.updateStateItemIndexes();
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(newProps: Props) {
|
public UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||||
this.updateStateItemIndexes(newProps);
|
this.updateStateItemIndexes(newProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
onScroll(event: any) {
|
public componentDidUpdate(): void {
|
||||||
|
// EDGE CASE
|
||||||
|
// scroll top is not updated when item list visibility is toggled
|
||||||
|
// if the user was at the bottom of the item list before hiding, blank spaces are added at the bottom of the item list
|
||||||
|
if (this.offsetScroll() !== this.listRef.current?.scrollTop) {
|
||||||
|
logger.warn(`scrollTop mismatch. Updating scrollTop with current listRef scrollTop(${this.listRef.current.scrollTop})`);
|
||||||
|
// update scroll postion once if there is a mismatch in scroll position after showing item list
|
||||||
|
this.onScroll({
|
||||||
|
target: {
|
||||||
|
scrollTop: this.listRef.current.scrollTop,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onScroll(event: any) {
|
||||||
this.scrollTop_ = event.target.scrollTop;
|
this.scrollTop_ = event.target.scrollTop;
|
||||||
this.updateStateItemIndexes();
|
this.updateStateItemIndexes();
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(event: any) {
|
public onKeyDown(event: any) {
|
||||||
if (this.props.onKeyDown) this.props.onKeyDown(event);
|
if (this.props.onKeyDown) this.props.onKeyDown(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDrop(event: any) {
|
public onDrop(event: any) {
|
||||||
if (this.props.onNoteDrop) this.props.onNoteDrop(event);
|
if (this.props.onNoteDrop) this.props.onNoteDrop(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeItemIndexVisible(itemIndex: number) {
|
public makeItemIndexVisible(itemIndex: number) {
|
||||||
const top = Math.min(this.props.items.length - 1, this.state.topItemIndex);
|
const top = Math.min(this.props.items.length - 1, this.state.topItemIndex);
|
||||||
const bottom = Math.max(0, this.state.bottomItemIndex);
|
const bottom = Math.max(0, this.state.bottomItemIndex);
|
||||||
|
|
||||||
@@ -119,7 +146,7 @@ class ItemList extends React.Component<Props, State> {
|
|||||||
// return true;
|
// return true;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const items = this.props.items;
|
const items = this.props.items;
|
||||||
const style = Object.assign({}, this.props.style, {
|
const style = Object.assign({}, this.props.style, {
|
||||||
overflowX: 'hidden',
|
overflowX: 'hidden',
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
|||||||
private styles_: any;
|
private styles_: any;
|
||||||
private promptOnClose_: Function;
|
private promptOnClose_: Function;
|
||||||
|
|
||||||
constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -250,11 +250,11 @@ class MainScreenComponent extends React.Component<Props, State> {
|
|||||||
return this.updateLayoutPluginViews(output, plugins);
|
return this.updateLayoutPluginViews(output, plugins);
|
||||||
}
|
}
|
||||||
|
|
||||||
window_resize() {
|
private window_resize() {
|
||||||
this.updateRootLayoutSize();
|
this.updateRootLayoutSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupAppCloseHandling() {
|
public setupAppCloseHandling() {
|
||||||
this.waitForNotesSavedIID_ = null;
|
this.waitForNotesSavedIID_ = null;
|
||||||
|
|
||||||
// This event is dispached from the main process when the app is about
|
// This event is dispached from the main process when the app is about
|
||||||
@@ -289,11 +289,11 @@ class MainScreenComponent extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
notePropertiesDialog_close() {
|
private notePropertiesDialog_close() {
|
||||||
this.setState({ notePropertiesDialogOptions: {} });
|
this.setState({ notePropertiesDialogOptions: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
noteContentPropertiesDialog_close() {
|
private noteContentPropertiesDialog_close() {
|
||||||
this.setState({ noteContentPropertiesDialogOptions: {} });
|
this.setState({ noteContentPropertiesDialogOptions: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,14 +305,14 @@ class MainScreenComponent extends React.Component<Props, State> {
|
|||||||
this.setState({ shareFolderDialogOptions: { visible: false, folderId: '' } });
|
this.setState({ shareFolderDialogOptions: { visible: false, folderId: '' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMainLayout(layout: LayoutItem) {
|
public updateMainLayout(layout: LayoutItem) {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: 'MAIN_LAYOUT_SET',
|
type: 'MAIN_LAYOUT_SET',
|
||||||
value: layout,
|
value: layout,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRootLayoutSize() {
|
public updateRootLayoutSize() {
|
||||||
this.updateMainLayout(produce(this.props.mainLayout, (draft: any) => {
|
this.updateMainLayout(produce(this.props.mainLayout, (draft: any) => {
|
||||||
const s = this.rootLayoutSize();
|
const s = this.rootLayoutSize();
|
||||||
draft.width = s.width;
|
draft.width = s.width;
|
||||||
@@ -320,7 +320,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Props, prevState: State) {
|
public componentDidUpdate(prevProps: Props, prevState: State) {
|
||||||
if (prevProps.style.width !== this.props.style.width ||
|
if (prevProps.style.width !== this.props.style.width ||
|
||||||
prevProps.style.height !== this.props.style.height ||
|
prevProps.style.height !== this.props.style.height ||
|
||||||
this.messageBoxVisible(prevProps) !== this.messageBoxVisible(this.props)
|
this.messageBoxVisible(prevProps) !== this.messageBoxVisible(this.props)
|
||||||
@@ -383,24 +383,24 @@ class MainScreenComponent extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layoutModeListenerKeyDown(event: any) {
|
public layoutModeListenerKeyDown(event: any) {
|
||||||
if (event.key !== 'Escape') return;
|
if (event.key !== 'Escape') return;
|
||||||
if (!this.props.layoutMoveMode) return;
|
if (!this.props.layoutMoveMode) return;
|
||||||
void CommandService.instance().execute('toggleLayoutMoveMode');
|
void CommandService.instance().execute('toggleLayoutMoveMode');
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount() {
|
||||||
window.addEventListener('keydown', this.layoutModeListenerKeyDown);
|
window.addEventListener('keydown', this.layoutModeListenerKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
this.unregisterCommands();
|
this.unregisterCommands();
|
||||||
|
|
||||||
window.removeEventListener('resize', this.window_resize);
|
window.removeEventListener('resize', this.window_resize);
|
||||||
window.removeEventListener('keydown', this.layoutModeListenerKeyDown);
|
window.removeEventListener('keydown', this.layoutModeListenerKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForNoteToSaved(noteId: string) {
|
public async waitForNoteToSaved(noteId: string) {
|
||||||
while (noteId && this.props.editorNoteStatuses[noteId] === 'saving') {
|
while (noteId && this.props.editorNoteStatuses[noteId] === 'saving') {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.info('Waiting for note to be saved...', this.props.editorNoteStatuses);
|
console.info('Waiting for note to be saved...', this.props.editorNoteStatuses);
|
||||||
@@ -408,7 +408,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async printTo_(target: string, options: any) {
|
public async printTo_(target: string, options: any) {
|
||||||
// Concurrent print calls are disallowed to avoid incorrect settings being restored upon completion
|
// Concurrent print calls are disallowed to avoid incorrect settings being restored upon completion
|
||||||
if (this.isPrinting_) {
|
if (this.isPrinting_) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@@ -449,23 +449,23 @@ class MainScreenComponent extends React.Component<Props, State> {
|
|||||||
this.isPrinting_ = false;
|
this.isPrinting_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
rootLayoutSize() {
|
public rootLayoutSize() {
|
||||||
return {
|
return {
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: this.rowHeight(),
|
height: this.rowHeight(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
rowHeight() {
|
public rowHeight() {
|
||||||
if (!this.props) return 0;
|
if (!this.props) return 0;
|
||||||
return this.props.style.height - (this.messageBoxVisible() ? this.messageBoxHeight() : 0);
|
return this.props.style.height - (this.messageBoxVisible() ? this.messageBoxHeight() : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
messageBoxHeight() {
|
public messageBoxHeight() {
|
||||||
return 50;
|
return 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
styles(themeId: number, width: number, height: number, messageBoxVisible: boolean) {
|
public styles(themeId: number, width: number, height: number, messageBoxVisible: boolean) {
|
||||||
const styleKey = [themeId, width, height, messageBoxVisible].join('_');
|
const styleKey = [themeId, width, height, messageBoxVisible].join('_');
|
||||||
if (styleKey === this.styleKey_) return this.styles_;
|
if (styleKey === this.styleKey_) return this.styles_;
|
||||||
|
|
||||||
@@ -539,7 +539,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderNotification(theme: any, styles: any) {
|
public renderNotification(theme: any, styles: any) {
|
||||||
if (!this.messageBoxVisible()) return null;
|
if (!this.messageBoxVisible()) return null;
|
||||||
|
|
||||||
const onViewStatusScreen = () => {
|
const onViewStatusScreen = () => {
|
||||||
@@ -658,33 +658,33 @@ class MainScreenComponent extends React.Component<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
messageBoxVisible(props: Props = null) {
|
public messageBoxVisible(props: Props = null) {
|
||||||
if (!props) props = this.props;
|
if (!props) props = this.props;
|
||||||
return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget || props.isSafeMode || this.showShareInvitationNotification(props) || this.props.needApiAuth || this.props.showInstallTemplatesPlugin;
|
return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget || props.isSafeMode || this.showShareInvitationNotification(props) || this.props.needApiAuth || this.props.showInstallTemplatesPlugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerCommands() {
|
public registerCommands() {
|
||||||
for (const command of commands) {
|
for (const command of commands) {
|
||||||
CommandService.instance().registerRuntime(command.declaration.name, command.runtime(this));
|
CommandService.instance().registerRuntime(command.declaration.name, command.runtime(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterCommands() {
|
public unregisterCommands() {
|
||||||
for (const command of commands) {
|
for (const command of commands) {
|
||||||
CommandService.instance().unregisterRuntime(command.declaration.name);
|
CommandService.instance().unregisterRuntime(command.declaration.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resizableLayout_resize(event: any) {
|
private resizableLayout_resize(event: any) {
|
||||||
this.updateMainLayout(event.layout);
|
this.updateMainLayout(event.layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
resizableLayout_moveButtonClick(event: MoveButtonClickEvent) {
|
private resizableLayout_moveButtonClick(event: MoveButtonClickEvent) {
|
||||||
const newLayout = move(this.props.mainLayout, event.itemKey, event.direction);
|
const newLayout = move(this.props.mainLayout, event.itemKey, event.direction);
|
||||||
this.updateMainLayout(newLayout);
|
this.updateMainLayout(newLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
resizableLayout_renderItem(key: string, event: any) {
|
private resizableLayout_renderItem(key: string, event: any) {
|
||||||
// Key should never be undefined but somehow it can happen, also not
|
// Key should never be undefined but somehow it can happen, also not
|
||||||
// clear how. For now in this case render nothing so that the app
|
// clear how. For now in this case render nothing so that the app
|
||||||
// doesn't crash.
|
// doesn't crash.
|
||||||
@@ -770,7 +770,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPluginDialogs() {
|
public renderPluginDialogs() {
|
||||||
const output = [];
|
const output = [];
|
||||||
const infos = pluginUtils.viewInfosByType(this.props.plugins, 'webview');
|
const infos = pluginUtils.viewInfosByType(this.props.plugins, 'webview');
|
||||||
|
|
||||||
@@ -801,7 +801,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const style = Object.assign(
|
const style = Object.assign(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import checkForUpdates from '../checkForUpdates';
|
|||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
import { reg } from '@joplin/lib/registry';
|
import { reg } from '@joplin/lib/registry';
|
||||||
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
|
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
|
||||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
|
||||||
const packageInfo = require('../packageInfo.js');
|
const packageInfo = require('../packageInfo.js');
|
||||||
const { clipboard } = require('electron');
|
const { clipboard } = require('electron');
|
||||||
const Menu = bridge().Menu;
|
const Menu = bridge().Menu;
|
||||||
@@ -128,6 +128,7 @@ interface Props {
|
|||||||
customCss: string;
|
customCss: string;
|
||||||
locale: string;
|
locale: string;
|
||||||
profileConfig: ProfileConfig;
|
profileConfig: ProfileConfig;
|
||||||
|
pluginSettings: PluginSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
const commandNames: string[] = menuCommandNames();
|
const commandNames: string[] = menuCommandNames();
|
||||||
@@ -487,7 +488,7 @@ function useMenu(props: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _showAbout() {
|
function _showAbout() {
|
||||||
const v = versionInfo(packageInfo, PluginService.instance().plugins);
|
const v = versionInfo(packageInfo, PluginService.instance().enabledPlugins(props.pluginSettings));
|
||||||
|
|
||||||
const copyToClipboard = bridge().showMessageBox(v.message, {
|
const copyToClipboard = bridge().showMessageBox(v.message, {
|
||||||
icon: `${bridge().electronApp().buildDir()}/icons/128x128.png`,
|
icon: `${bridge().electronApp().buildDir()}/icons/128x128.png`,
|
||||||
@@ -930,6 +931,7 @@ function useMenu(props: Props) {
|
|||||||
props['spellChecker.languages'],
|
props['spellChecker.languages'],
|
||||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||||
props['spellChecker.enabled'],
|
props['spellChecker.enabled'],
|
||||||
|
props.pluginSettings,
|
||||||
props.customCss,
|
props.customCss,
|
||||||
props.locale,
|
props.locale,
|
||||||
props.profileConfig,
|
props.profileConfig,
|
||||||
@@ -985,6 +987,7 @@ const mapStateToProps = (state: AppState) => {
|
|||||||
['folders.sortOrder.field']: state.settings['folders.sortOrder.field'],
|
['folders.sortOrder.field']: state.settings['folders.sortOrder.field'],
|
||||||
['notes.sortOrder.reverse']: state.settings['notes.sortOrder.reverse'],
|
['notes.sortOrder.reverse']: state.settings['notes.sortOrder.reverse'],
|
||||||
['folders.sortOrder.reverse']: state.settings['folders.sortOrder.reverse'],
|
['folders.sortOrder.reverse']: state.settings['folders.sortOrder.reverse'],
|
||||||
|
pluginSettings: state.settings['plugins.states'],
|
||||||
showNoteCounts: state.settings.showNoteCounts,
|
showNoteCounts: state.settings.showNoteCounts,
|
||||||
uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop,
|
uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop,
|
||||||
showCompletedTodos: state.settings.showCompletedTodos,
|
showCompletedTodos: state.settings.showCompletedTodos,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class NavigatorComponent extends React.Component<Props> {
|
class NavigatorComponent extends React.Component<Props> {
|
||||||
UNSAFE_componentWillReceiveProps(newProps: Props) {
|
public UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||||
if (newProps.route) {
|
if (newProps.route) {
|
||||||
const screenInfo = this.props.screens[newProps.route.routeName];
|
const screenInfo = this.props.screens[newProps.route.routeName];
|
||||||
const devMarker = Setting.value('env') === 'dev' ? ` (DEV - ${Setting.value('profileDir')})` : '';
|
const devMarker = Setting.value('env') === 'dev' ? ` (DEV - ${Setting.value('profileDir')})` : '';
|
||||||
@@ -21,7 +21,7 @@ class NavigatorComponent extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWindowTitle(title: string) {
|
public updateWindowTitle(title: string) {
|
||||||
try {
|
try {
|
||||||
if (bridge().window()) bridge().window().setTitle(title);
|
if (bridge().window()) bridge().window().setTitle(title);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -29,7 +29,7 @@ class NavigatorComponent extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
if (!this.props.route) throw new Error('Route must not be null');
|
if (!this.props.route) throw new Error('Route must not be null');
|
||||||
|
|
||||||
const route = this.props.route;
|
const route = this.props.route;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useCallback, useMemo } from 'react';
|
|||||||
import { ResourceInfos } from './types';
|
import { ResourceInfos } from './types';
|
||||||
import markupLanguageUtils from '../../../utils/markupLanguageUtils';
|
import markupLanguageUtils from '../../../utils/markupLanguageUtils';
|
||||||
import Setting from '@joplin/lib/models/Setting';
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
|
import shim from '@joplin/lib/shim';
|
||||||
|
|
||||||
const { themeStyle } = require('@joplin/lib/theme');
|
const { themeStyle } = require('@joplin/lib/theme');
|
||||||
import Note from '@joplin/lib/models/Note';
|
import Note from '@joplin/lib/models/Note';
|
||||||
@@ -23,6 +24,7 @@ export interface MarkupToHtmlOptions {
|
|||||||
useCustomPdfViewer?: boolean;
|
useCustomPdfViewer?: boolean;
|
||||||
noteId?: string;
|
noteId?: string;
|
||||||
vendorDir?: string;
|
vendorDir?: string;
|
||||||
|
platformName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useMarkupToHtml(deps: HookDependencies) {
|
export default function useMarkupToHtml(deps: HookDependencies) {
|
||||||
@@ -40,6 +42,7 @@ export default function useMarkupToHtml(deps: HookDependencies) {
|
|||||||
options = {
|
options = {
|
||||||
replaceResourceInternalToExternalLinks: false,
|
replaceResourceInternalToExternalLinks: false,
|
||||||
resourceInfos: {},
|
resourceInfos: {},
|
||||||
|
platformName: shim.platformName(),
|
||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,6 +65,7 @@ export default function useMarkupToHtml(deps: HookDependencies) {
|
|||||||
postMessageSyntax: 'ipcProxySendToHost',
|
postMessageSyntax: 'ipcProxySendToHost',
|
||||||
splitted: true,
|
splitted: true,
|
||||||
externalAssetsOnly: true,
|
externalAssetsOnly: true,
|
||||||
|
codeHighlightCacheKey: 'useMarkupToHtml',
|
||||||
}, options));
|
}, options));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -241,6 +241,7 @@ const NoteListComponent = (props: Props) => {
|
|||||||
event.dataTransfer.setDragImage(new Image(), 1, 1);
|
event.dataTransfer.setDragImage(new Image(), 1, 1);
|
||||||
event.dataTransfer.clearData();
|
event.dataTransfer.clearData();
|
||||||
event.dataTransfer.setData('text/x-jop-note-ids', JSON.stringify(noteIds));
|
event.dataTransfer.setData('text/x-jop-note-ids', JSON.stringify(noteIds));
|
||||||
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderItem = useCallback((item: any, index: number) => {
|
const renderItem = useCallback((item: any, index: number) => {
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ const { connect } = require('react-redux');
|
|||||||
const styled = require('styled-components').default;
|
const styled = require('styled-components').default;
|
||||||
|
|
||||||
enum BaseBreakpoint {
|
enum BaseBreakpoint {
|
||||||
Sm = 160,
|
Sm = 75,
|
||||||
Md = 190,
|
Md = 80,
|
||||||
Lg = 40,
|
Lg = 120,
|
||||||
Xl = 474,
|
Xl = 474,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +50,7 @@ const StyledButton = styled(Button)`
|
|||||||
width: auto;
|
width: auto;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
min-height: 26px;
|
min-height: 26px;
|
||||||
|
min-width: 37px;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
private styleKey_: number;
|
private styleKey_: number;
|
||||||
private styles_: any;
|
private styles_: any;
|
||||||
|
|
||||||
constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.revisionsLink_click = this.revisionsLink_click.bind(this);
|
this.revisionsLink_click = this.revisionsLink_click.bind(this);
|
||||||
@@ -56,17 +56,17 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount() {
|
||||||
void this.loadNote(this.props.noteId);
|
void this.loadNote(this.props.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
public componentDidUpdate() {
|
||||||
if (this.state.editedKey === null) {
|
if (this.state.editedKey === null) {
|
||||||
this.okButton.current.focus();
|
this.okButton.current.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadNote(noteId: string) {
|
public async loadNote(noteId: string) {
|
||||||
if (!noteId) {
|
if (!noteId) {
|
||||||
this.setState({ formNote: null });
|
this.setState({ formNote: null });
|
||||||
} else {
|
} else {
|
||||||
@@ -76,7 +76,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
latLongFromLocation(location: string) {
|
public latLongFromLocation(location: string) {
|
||||||
const o: any = {};
|
const o: any = {};
|
||||||
const l = location.split(',');
|
const l = location.split(',');
|
||||||
if (l.length === 2) {
|
if (l.length === 2) {
|
||||||
@@ -89,7 +89,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
noteToFormNote(note: NoteEntity) {
|
public noteToFormNote(note: NoteEntity) {
|
||||||
const formNote: any = {};
|
const formNote: any = {};
|
||||||
|
|
||||||
formNote.user_updated_time = time.formatMsToLocal(note.user_updated_time);
|
formNote.user_updated_time = time.formatMsToLocal(note.user_updated_time);
|
||||||
@@ -113,7 +113,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
return formNote;
|
return formNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
formNoteToNote(formNote: any) {
|
public formNoteToNote(formNote: any) {
|
||||||
const note = Object.assign({ id: formNote.id }, this.latLongFromLocation(formNote.location));
|
const note = Object.assign({ id: formNote.id }, this.latLongFromLocation(formNote.location));
|
||||||
note.user_created_time = time.formatLocalToMs(formNote.user_created_time);
|
note.user_created_time = time.formatLocalToMs(formNote.user_created_time);
|
||||||
note.user_updated_time = time.formatLocalToMs(formNote.user_updated_time);
|
note.user_updated_time = time.formatLocalToMs(formNote.user_updated_time);
|
||||||
@@ -127,7 +127,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
styles(themeId: number) {
|
public styles(themeId: number) {
|
||||||
const styleKey = themeId;
|
const styleKey = themeId;
|
||||||
if (styleKey === this.styleKey_) return this.styles_;
|
if (styleKey === this.styleKey_) return this.styles_;
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
return this.styles_;
|
return this.styles_;
|
||||||
}
|
}
|
||||||
|
|
||||||
async closeDialog(applyChanges: boolean) {
|
public async closeDialog(applyChanges: boolean) {
|
||||||
if (applyChanges) {
|
if (applyChanges) {
|
||||||
await this.saveProperty();
|
await this.saveProperty();
|
||||||
const note = this.formNoteToNote(this.state.formNote);
|
const note = this.formNoteToNote(this.state.formNote);
|
||||||
@@ -183,16 +183,16 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonRow_click(event: any) {
|
private buttonRow_click(event: any) {
|
||||||
void this.closeDialog(event.buttonName === 'ok');
|
void this.closeDialog(event.buttonName === 'ok');
|
||||||
}
|
}
|
||||||
|
|
||||||
revisionsLink_click() {
|
private revisionsLink_click() {
|
||||||
void this.closeDialog(false);
|
void this.closeDialog(false);
|
||||||
if (this.props.onRevisionLinkClick) this.props.onRevisionLinkClick();
|
if (this.props.onRevisionLinkClick) this.props.onRevisionLinkClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
editPropertyButtonClick(key: string, initialValue: any) {
|
public editPropertyButtonClick(key: string, initialValue: any) {
|
||||||
this.setState({
|
this.setState({
|
||||||
editedKey: key,
|
editedKey: key,
|
||||||
editedValue: initialValue,
|
editedValue: initialValue,
|
||||||
@@ -207,7 +207,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveProperty() {
|
public async saveProperty() {
|
||||||
if (!this.state.editedKey) return;
|
if (!this.state.editedKey) return;
|
||||||
|
|
||||||
return new Promise((resolve: Function) => {
|
return new Promise((resolve: Function) => {
|
||||||
@@ -233,7 +233,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancelProperty() {
|
public async cancelProperty() {
|
||||||
return new Promise((resolve: Function) => {
|
return new Promise((resolve: Function) => {
|
||||||
this.okButton.current.focus();
|
this.okButton.current.focus();
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -245,7 +245,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createNoteField(key: string, value: any) {
|
public createNoteField(key: string, value: any) {
|
||||||
const styles = this.styles(this.props.themeId);
|
const styles = this.styles(this.props.themeId);
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const labelComp = <label style={Object.assign({}, theme.textStyle, theme.controlBoxLabel)}>{this.formatLabel(key)}</label>;
|
const labelComp = <label style={Object.assign({}, theme.textStyle, theme.controlBoxLabel)}>{this.formatLabel(key)}</label>;
|
||||||
@@ -364,12 +364,12 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
formatLabel(key: string) {
|
public formatLabel(key: string) {
|
||||||
if (this.keyToLabel_[key]) return this.keyToLabel_[key];
|
if (this.keyToLabel_[key]) return this.keyToLabel_[key];
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
formatValue(key: string, note: NoteEntity) {
|
public formatValue(key: string, note: NoteEntity) {
|
||||||
if (key === 'location') {
|
if (key === 'location') {
|
||||||
if (!Number(note.latitude) && !Number(note.longitude)) return null;
|
if (!Number(note.latitude) && !Number(note.longitude)) return null;
|
||||||
const dms = formatcoords(Number(note.latitude), Number(note.longitude));
|
const dms = formatcoords(Number(note.latitude), Number(note.longitude));
|
||||||
@@ -383,7 +383,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
|
|||||||
return (note as any)[key];
|
return (note as any)[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const formNote = this.state.formNote;
|
const formNote = this.state.formNote;
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
|||||||
private viewerRef_: any;
|
private viewerRef_: any;
|
||||||
private helpButton_onClick: Function;
|
private helpButton_onClick: Function;
|
||||||
|
|
||||||
constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -57,7 +57,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
|||||||
this.webview_ipcMessage = this.webview_ipcMessage.bind(this);
|
this.webview_ipcMessage = this.webview_ipcMessage.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
style() {
|
public style() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
@@ -74,7 +74,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
|||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
async viewer_domReady() {
|
private async viewer_domReady() {
|
||||||
// this.viewerRef_.current.openDevTools();
|
// this.viewerRef_.current.openDevTools();
|
||||||
|
|
||||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, this.props.noteId);
|
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, this.props.noteId);
|
||||||
@@ -90,7 +90,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async importButton_onClick() {
|
private async importButton_onClick() {
|
||||||
if (!this.state.note) return;
|
if (!this.state.note) return;
|
||||||
this.setState({ restoring: true });
|
this.setState({ restoring: true });
|
||||||
await RevisionService.instance().importRevisionNote(this.state.note);
|
await RevisionService.instance().importRevisionNote(this.state.note);
|
||||||
@@ -98,11 +98,11 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
|||||||
alert(RevisionService.instance().restoreSuccessMessage(this.state.note));
|
alert(RevisionService.instance().restoreSuccessMessage(this.state.note));
|
||||||
}
|
}
|
||||||
|
|
||||||
backButton_click() {
|
private backButton_click() {
|
||||||
if (this.props.onBack) this.props.onBack();
|
if (this.props.onBack) this.props.onBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
revisionList_onChange(event: any) {
|
private revisionList_onChange(event: any) {
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -119,7 +119,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async reloadNote() {
|
public async reloadNote() {
|
||||||
let noteBody = '';
|
let noteBody = '';
|
||||||
let markupLanguage = MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN;
|
let markupLanguage = MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN;
|
||||||
if (!this.state.revisions.length || !this.state.currentRevId) {
|
if (!this.state.revisions.length || !this.state.currentRevId) {
|
||||||
@@ -153,7 +153,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async webview_ipcMessage(event: any) {
|
private async webview_ipcMessage(event: any) {
|
||||||
// For the revision view, we only suppport a minimal subset of the IPC messages.
|
// For the revision view, we only suppport a minimal subset of the IPC messages.
|
||||||
// For example, we don't need interactive checkboxes or sync between viewer and editor view.
|
// For example, we don't need interactive checkboxes or sync between viewer and editor view.
|
||||||
// We try to get most links work though, except for internal (joplin://) links.
|
// We try to get most links work though, except for internal (joplin://) links.
|
||||||
@@ -183,7 +183,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const style = this.style();
|
const style = this.style();
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class NoteSearchBar extends React.Component<Props> {
|
|||||||
|
|
||||||
private backgroundColor: any;
|
private backgroundColor: any;
|
||||||
|
|
||||||
constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.searchInput_change = this.searchInput_change.bind(this);
|
this.searchInput_change = this.searchInput_change.bind(this);
|
||||||
@@ -33,7 +33,7 @@ class NoteSearchBar extends React.Component<Props> {
|
|||||||
this.backgroundColor = undefined;
|
this.backgroundColor = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
style() {
|
public style() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
@@ -46,7 +46,7 @@ class NoteSearchBar extends React.Component<Props> {
|
|||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonIconComponent(iconName: string, clickHandler: any, isEnabled: boolean) {
|
public buttonIconComponent(iconName: string, clickHandler: any, isEnabled: boolean) {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const searchButton = {
|
const searchButton = {
|
||||||
@@ -74,12 +74,12 @@ class NoteSearchBar extends React.Component<Props> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchInput_change(event: any) {
|
private searchInput_change(event: any) {
|
||||||
const query = event.currentTarget.value;
|
const query = event.currentTarget.value;
|
||||||
this.triggerOnChange(query);
|
this.triggerOnChange(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchInput_keyDown(event: any) {
|
private searchInput_keyDown(event: any) {
|
||||||
if (event.keyCode === 13) {
|
if (event.keyCode === 13) {
|
||||||
// ENTER
|
// ENTER
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -106,28 +106,28 @@ class NoteSearchBar extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
previousButton_click() {
|
private previousButton_click() {
|
||||||
if (this.props.onPrevious) this.props.onPrevious();
|
if (this.props.onPrevious) this.props.onPrevious();
|
||||||
}
|
}
|
||||||
|
|
||||||
nextButton_click() {
|
private nextButton_click() {
|
||||||
if (this.props.onNext) this.props.onNext();
|
if (this.props.onNext) this.props.onNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
closeButton_click() {
|
private closeButton_click() {
|
||||||
if (this.props.onClose) this.props.onClose();
|
if (this.props.onClose) this.props.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerOnChange(query: string) {
|
public triggerOnChange(query: string) {
|
||||||
if (this.props.onChange) this.props.onChange(query);
|
if (this.props.onChange) this.props.onChange(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
public focus() {
|
||||||
(this.refs.searchInput as any).focus();
|
(this.refs.searchInput as any).focus();
|
||||||
(this.refs.searchInput as any).select();
|
(this.refs.searchInput as any).select();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const query = this.props.query ? this.props.query : '';
|
const query = this.props.query ? this.props.query : '';
|
||||||
|
|
||||||
// backgroundColor needs to cached to a local variable to prevent the
|
// backgroundColor needs to cached to a local variable to prevent the
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class NoteStatusBarComponent extends React.Component<Props> {
|
class NoteStatusBarComponent extends React.Component<Props> {
|
||||||
style() {
|
public style() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
@@ -24,7 +24,7 @@ class NoteStatusBarComponent extends React.Component<Props> {
|
|||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const note = this.props.note;
|
const note = this.props.note;
|
||||||
return <div style={this.style().root}>{time.formatMsToLocal(note.user_updated_time)}</div>;
|
return <div style={this.style().root}>{time.formatMsToLocal(note.user_updated_time)}</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
|||||||
private webviewRef_: any;
|
private webviewRef_: any;
|
||||||
private webviewListeners_: any = null;
|
private webviewListeners_: any = null;
|
||||||
|
|
||||||
constructor(props: any) {
|
public constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.webviewRef_ = React.createRef();
|
this.webviewRef_ = React.createRef();
|
||||||
@@ -41,20 +41,20 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
|||||||
this.webview_message = this.webview_message.bind(this);
|
this.webview_message = this.webview_message.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
webview_domReady(event: any) {
|
private webview_domReady(event: any) {
|
||||||
this.domReady_ = true;
|
this.domReady_ = true;
|
||||||
if (this.props.onDomReady) this.props.onDomReady(event);
|
if (this.props.onDomReady) this.props.onDomReady(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
webview_ipcMessage(event: any) {
|
private webview_ipcMessage(event: any) {
|
||||||
if (this.props.onIpcMessage) this.props.onIpcMessage(event);
|
if (this.props.onIpcMessage) this.props.onIpcMessage(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
webview_load() {
|
private webview_load() {
|
||||||
this.webview_domReady({});
|
this.webview_domReady({});
|
||||||
}
|
}
|
||||||
|
|
||||||
webview_message(event: any) {
|
private webview_message(event: any) {
|
||||||
if (!event.data || event.data.target !== 'main') return;
|
if (!event.data || event.data.target !== 'main') return;
|
||||||
|
|
||||||
const callName = event.data.name;
|
const callName = event.data.name;
|
||||||
@@ -68,11 +68,11 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
domReady() {
|
public domReady() {
|
||||||
return this.domReady_;
|
return this.domReady_;
|
||||||
}
|
}
|
||||||
|
|
||||||
initWebview() {
|
public initWebview() {
|
||||||
const wv = this.webviewRef_.current;
|
const wv = this.webviewRef_.current;
|
||||||
|
|
||||||
if (!this.webviewListeners_) {
|
if (!this.webviewListeners_) {
|
||||||
@@ -92,7 +92,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
|||||||
this.webviewRef_.current.contentWindow.addEventListener('message', this.webview_message);
|
this.webviewRef_.current.contentWindow.addEventListener('message', this.webview_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyWebview() {
|
public destroyWebview() {
|
||||||
const wv = this.webviewRef_.current;
|
const wv = this.webviewRef_.current;
|
||||||
if (!wv || !this.initialized_) return;
|
if (!wv || !this.initialized_) return;
|
||||||
|
|
||||||
@@ -115,28 +115,28 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
|||||||
this.domReady_ = false;
|
this.domReady_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
public focus() {
|
||||||
if (this.webviewRef_.current) {
|
if (this.webviewRef_.current) {
|
||||||
this.webviewRef_.current.focus();
|
this.webviewRef_.current.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tryInit() {
|
public tryInit() {
|
||||||
if (!this.initialized_ && this.webviewRef_.current) {
|
if (!this.initialized_ && this.webviewRef_.current) {
|
||||||
this.initWebview();
|
this.initWebview();
|
||||||
this.initialized_ = true;
|
this.initialized_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount() {
|
||||||
this.tryInit();
|
this.tryInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
public componentDidUpdate() {
|
||||||
this.tryInit();
|
this.tryInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
this.destroyWebview();
|
this.destroyWebview();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
|||||||
// Wrap WebView functions
|
// Wrap WebView functions
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
|
|
||||||
send(channel: string, arg0: any = null, arg1: any = null) {
|
public send(channel: string, arg0: any = null, arg1: any = null) {
|
||||||
const win = this.webviewRef_.current.contentWindow;
|
const win = this.webviewRef_.current.contentWindow;
|
||||||
|
|
||||||
if (channel === 'focus') {
|
if (channel === 'focus') {
|
||||||
@@ -172,7 +172,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
|
|||||||
// Wrap WebView functions (END)
|
// Wrap WebView functions (END)
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const viewerStyle = Object.assign({}, { border: 'none' }, this.props.viewerStyle);
|
const viewerStyle = Object.assign({}, { border: 'none' }, this.props.viewerStyle);
|
||||||
return <iframe className="noteTextViewer" ref={this.webviewRef_} style={viewerStyle} src="gui/note-viewer/index.html"></iframe>;
|
return <iframe className="noteTextViewer" ref={this.webviewRef_} style={viewerStyle} src="gui/note-viewer/index.html"></iframe>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class OneDriveLoginScreenComponent extends React.Component<any, any> {
|
class OneDriveLoginScreenComponent extends React.Component<any, any> {
|
||||||
constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -22,7 +22,7 @@ class OneDriveLoginScreenComponent extends React.Component<any, any> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
public async componentDidMount() {
|
||||||
const log = (s: any) => {
|
const log = (s: any) => {
|
||||||
this.setState((state: any) => {
|
this.setState((state: any) => {
|
||||||
const authLog = state.authLog.slice();
|
const authLog = state.authLog.slice();
|
||||||
@@ -48,15 +48,15 @@ class OneDriveLoginScreenComponent extends React.Component<any, any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startUrl() {
|
public startUrl() {
|
||||||
return reg.syncTarget().api().authCodeUrl(this.redirectUrl());
|
return reg.syncTarget().api().authCodeUrl(this.redirectUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectUrl() {
|
public redirectUrl() {
|
||||||
return reg.syncTarget().api().nativeClientRedirectUrl();
|
return reg.syncTarget().api().nativeClientRedirectUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const logComps = [];
|
const logComps = [];
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ export default class PromptDialog extends React.Component<Props, any> {
|
|||||||
private styles_: any;
|
private styles_: any;
|
||||||
private styleKey_: string;
|
private styleKey_: string;
|
||||||
|
|
||||||
constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.answerInput_ = React.createRef();
|
this.answerInput_ = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
public UNSAFE_componentWillMount() {
|
||||||
this.setState({
|
this.setState({
|
||||||
visible: false,
|
visible: false,
|
||||||
answer: this.props.defaultValue ? this.props.defaultValue : '',
|
answer: this.props.defaultValue ? this.props.defaultValue : '',
|
||||||
@@ -41,7 +41,7 @@ export default class PromptDialog extends React.Component<Props, any> {
|
|||||||
this.focusInput_ = true;
|
this.focusInput_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(newProps: Props) {
|
public UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||||
if ('visible' in newProps && newProps.visible !== this.props.visible) {
|
if ('visible' in newProps && newProps.visible !== this.props.visible) {
|
||||||
this.setState({ visible: newProps.visible });
|
this.setState({ visible: newProps.visible });
|
||||||
if (newProps.visible) this.focusInput_ = true;
|
if (newProps.visible) this.focusInput_ = true;
|
||||||
@@ -52,12 +52,12 @@ export default class PromptDialog extends React.Component<Props, any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
public componentDidUpdate() {
|
||||||
if (this.focusInput_ && this.answerInput_.current) this.answerInput_.current.focus();
|
if (this.focusInput_ && this.answerInput_.current) this.answerInput_.current.focus();
|
||||||
this.focusInput_ = false;
|
this.focusInput_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
styles(themeId: number, width: number, height: number, visible: boolean) {
|
public styles(themeId: number, width: number, height: number, visible: boolean) {
|
||||||
const styleKey = `${themeId}_${width}_${height}_${visible}`;
|
const styleKey = `${themeId}_${width}_${height}_${visible}`;
|
||||||
if (styleKey === this.styleKey_) return this.styles_;
|
if (styleKey === this.styleKey_) return this.styles_;
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ export default class PromptDialog extends React.Component<Props, any> {
|
|||||||
return this.styles_;
|
return this.styles_;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const style = this.props.style;
|
const style = this.props.style;
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const buttonTypes = this.props.buttons ? this.props.buttons : ['ok', 'cancel'];
|
const buttonTypes = this.props.buttons ? this.props.buttons : ['ok', 'cancel'];
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ describe('movements', () => {
|
|||||||
expect(canMove(MoveDirection.Right, findItemByKey(layout, 'col2'), findItemByKey(layout, 'root'))).toBe(false);
|
expect(canMove(MoveDirection.Right, findItemByKey(layout, 'col2'), findItemByKey(layout, 'root'))).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Container with only one child should take the width of its parent', () => {
|
test('container with only one child should take the width of its parent', () => {
|
||||||
let layout: LayoutItem = validateLayout({
|
let layout: LayoutItem = validateLayout({
|
||||||
key: 'root',
|
key: 'root',
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -170,7 +170,7 @@ describe('movements', () => {
|
|||||||
expect(layout.children[0].children[0].width).toBe(undefined);
|
expect(layout.children[0].children[0].width).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Temp container should take the width of the child it replaces', () => {
|
test('temp container should take the width of the child it replaces', () => {
|
||||||
let layout: LayoutItem = validateLayout({
|
let layout: LayoutItem = validateLayout({
|
||||||
key: 'root',
|
key: 'root',
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -198,7 +198,7 @@ describe('movements', () => {
|
|||||||
expect(layout.children[0].children[1].width).toBe(undefined);
|
expect(layout.children[0].children[1].width).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Last child should have flexible width if all siblings have fixed width', () => {
|
test('last child should have flexible width if all siblings have fixed width', () => {
|
||||||
let layout: LayoutItem = validateLayout({
|
let layout: LayoutItem = validateLayout({
|
||||||
key: 'root',
|
key: 'root',
|
||||||
width: 100,
|
width: 100,
|
||||||
|
|||||||
@@ -260,10 +260,6 @@ describe('useLayoutItemSizes', () => {
|
|||||||
expect(sizes.col4.width).toBe(50);
|
expect(sizes.col4.width).toBe(50);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('calculateMaxSizeAvailableForItem', () => {
|
|
||||||
|
|
||||||
test('should give maximum available space this item can take up during resizing', () => {
|
test('should give maximum available space this item can take up during resizing', () => {
|
||||||
const layout: LayoutItem = validateLayout({
|
const layout: LayoutItem = validateLayout({
|
||||||
key: 'root',
|
key: 'root',
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ const getNextSortingOrderType = (s: SortingType): SortingType => {
|
|||||||
const MAX_RESOURCES = 10000;
|
const MAX_RESOURCES = 10000;
|
||||||
|
|
||||||
class ResourceScreenComponent extends React.Component<Props, State> {
|
class ResourceScreenComponent extends React.Component<Props, State> {
|
||||||
constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
resources: undefined,
|
resources: undefined,
|
||||||
@@ -147,7 +147,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async reloadResources(sorting: ActiveSorting) {
|
public async reloadResources(sorting: ActiveSorting) {
|
||||||
this.setState({ isLoading: true });
|
this.setState({ isLoading: true });
|
||||||
const resources = await Resource.all({
|
const resources = await Resource.all({
|
||||||
order: [{
|
order: [{
|
||||||
@@ -161,11 +161,11 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
|||||||
this.setState({ resources, isLoading: false });
|
this.setState({ resources, isLoading: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount() {
|
||||||
void this.reloadResources(this.state.sorting);
|
void this.reloadResources(this.state.sorting);
|
||||||
}
|
}
|
||||||
|
|
||||||
onResourceDelete(resource: InnerResource) {
|
public onResourceDelete(resource: InnerResource) {
|
||||||
const ok = bridge().showConfirmMessageBox(_('Delete attachment "%s"?', resource.title), {
|
const ok = bridge().showConfirmMessageBox(_('Delete attachment "%s"?', resource.title), {
|
||||||
buttons: [_('Delete'), _('Cancel')],
|
buttons: [_('Delete'), _('Cancel')],
|
||||||
defaultId: 1,
|
defaultId: 1,
|
||||||
@@ -184,7 +184,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openResource(resource: InnerResource) {
|
public openResource(resource: InnerResource) {
|
||||||
const resourcePath = Resource.fullPath(resource);
|
const resourcePath = Resource.fullPath(resource);
|
||||||
const ok = bridge().openExternal(`file://${resourcePath}`);
|
const ok = bridge().openExternal(`file://${resourcePath}`);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -192,7 +192,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onToggleSortOrder(sortOrder: SortingOrder) {
|
public onToggleSortOrder(sortOrder: SortingOrder) {
|
||||||
let newSorting = { ...this.state.sorting };
|
let newSorting = { ...this.state.sorting };
|
||||||
if (sortOrder === this.state.sorting.order) {
|
if (sortOrder === this.state.sorting.order) {
|
||||||
newSorting.type = getNextSortingOrderType(newSorting.type);
|
newSorting.type = getNextSortingOrderType(newSorting.type);
|
||||||
@@ -206,7 +206,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
|||||||
void this.reloadResources(newSorting);
|
void this.reloadResources(newSorting);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const style = this.props.style;
|
const style = this.props.style;
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,15 @@ const StyledFoldersHolder = styled.div`
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
const TagsHolder = styled.div`
|
||||||
|
// linux bug: https://github.com/laurent22/joplin/issues/8000
|
||||||
|
// solution ref: https://github.com/laurent22/joplin/issues/7506#issuecomment-1447101057
|
||||||
|
& a.list-item {
|
||||||
|
${shim.isLinux() && {
|
||||||
|
opacity: 1,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
themeId: number;
|
themeId: number;
|
||||||
@@ -738,9 +747,9 @@ const SidebarComponent = (props: Props) => {
|
|||||||
tagItemsOrder_.current = result.order;
|
tagItemsOrder_.current = result.order;
|
||||||
|
|
||||||
items.push(
|
items.push(
|
||||||
<div className="tags" key="tag_items" style={{ display: props.tagHeaderIsExpanded ? 'block' : 'none' }}>
|
<TagsHolder className="tags" key="tag_items" style={{ display: props.tagHeaderIsExpanded ? 'block' : 'none' }}>
|
||||||
{tagItems}
|
{tagItems}
|
||||||
</div>
|
</TagsHolder>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import CommandService from '@joplin/lib/services/CommandService';
|
|||||||
import { AppState } from '../app.reducer';
|
import { AppState } from '../app.reducer';
|
||||||
|
|
||||||
class TagItemComponent extends React.Component {
|
class TagItemComponent extends React.Component {
|
||||||
render() {
|
public render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const style = Object.assign({}, theme.tagStyle);
|
const style = Object.assign({}, theme.tagStyle);
|
||||||
const { title, id } = this.props;
|
const { title, id } = this.props;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ interface Props {
|
|||||||
|
|
||||||
class ToolbarBaseComponent extends React.Component<Props, any> {
|
class ToolbarBaseComponent extends React.Component<Props, any> {
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
const style: any = Object.assign({
|
const style: any = Object.assign({
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ToolbarSpace extends React.Component<Props> {
|
class ToolbarSpace extends React.Component<Props> {
|
||||||
render() {
|
public render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const style = Object.assign({}, theme.toolbarStyle);
|
const style = Object.assign({}, theme.toolbarStyle);
|
||||||
style.minWidth = style.height / 2;
|
style.minWidth = style.height / 2;
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ const smalltalk = require('smalltalk');
|
|||||||
const logger = Logger.create('dialogs');
|
const logger = Logger.create('dialogs');
|
||||||
|
|
||||||
class Dialogs {
|
class Dialogs {
|
||||||
async alert(message: string, title = '') {
|
public async alert(message: string, title = '') {
|
||||||
await smalltalk.alert(title, message);
|
await smalltalk.alert(title, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirm(message: string, title = '', options: any = {}) {
|
public async confirm(message: string, title = '', options: any = {}) {
|
||||||
try {
|
try {
|
||||||
await smalltalk.confirm(title, message, options);
|
await smalltalk.confirm(title, message, options);
|
||||||
return true;
|
return true;
|
||||||
@@ -21,7 +21,7 @@ class Dialogs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async prompt(message: string, title = '', defaultValue = '', options: any = null) {
|
public async prompt(message: string, title = '', defaultValue = '', options: any = null) {
|
||||||
if (options === null) options = {};
|
if (options === null) options = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -56,6 +56,19 @@ if (typeof module !== 'undefined') {
|
|||||||
|
|
||||||
const markJsUtils = {};
|
const markJsUtils = {};
|
||||||
|
|
||||||
|
const isInsideContainer = (node, tagName) => {
|
||||||
|
if (!node) return false;
|
||||||
|
|
||||||
|
tagName = tagName.toLowerCase();
|
||||||
|
|
||||||
|
while (node) {
|
||||||
|
if (node.tagName && node.tagName.toLowerCase() === tagName) return true;
|
||||||
|
node = node.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
markJsUtils.markKeyword = (mark, keyword, stringUtils, extraOptions = null) => {
|
markJsUtils.markKeyword = (mark, keyword, stringUtils, extraOptions = null) => {
|
||||||
if (typeof keyword === 'string') {
|
if (typeof keyword === 'string') {
|
||||||
keyword = {
|
keyword = {
|
||||||
@@ -71,12 +84,13 @@ markJsUtils.markKeyword = (mark, keyword, stringUtils, extraOptions = null) => {
|
|||||||
if (isBasicSearch) accuracy = 'partially';
|
if (isBasicSearch) accuracy = 'partially';
|
||||||
if (keyword.type === 'regex') {
|
if (keyword.type === 'regex') {
|
||||||
accuracy = 'complementary';
|
accuracy = 'complementary';
|
||||||
// Remove the trailing wildcard and "accuracy = complementary" will take care of
|
// Remove the trailing wildcard and "accuracy = complementary" will take
|
||||||
// highlighting the relevant keywords.
|
// care of highlighting the relevant keywords.
|
||||||
|
|
||||||
// Known bug: it will also highlight word that contain the term as a suffix for example for "ent*", it will highlight "present"
|
// Known bug: it will also highlight word that contain the term as a
|
||||||
// which is incorrect (it should only highlight what starts with "ent") but for now will do. Mark.js doesn't have an option
|
// suffix for example for "ent*", it will highlight "present" which is
|
||||||
// to tweak this behaviour.
|
// incorrect (it should only highlight what starts with "ent") but for
|
||||||
|
// now will do. Mark.js doesn't have an option to tweak this behaviour.
|
||||||
value = keyword.value.substr(0, keyword.value.length - 1);
|
value = keyword.value.substr(0, keyword.value.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +100,18 @@ markJsUtils.markKeyword = (mark, keyword, stringUtils, extraOptions = null) => {
|
|||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
accuracy: accuracy,
|
accuracy: accuracy,
|
||||||
|
filter: (node, _term, _totalCounter, _counter) => {
|
||||||
|
// We exclude SVG because it creates a "<mark>" tag inside
|
||||||
|
// the document, which is not a valid SVG tag. As a result
|
||||||
|
// the content within that tag disappears.
|
||||||
|
//
|
||||||
|
// mark.js has an "exclude" parameter, but it doesn't work
|
||||||
|
// so we use "filter" instead.
|
||||||
|
//
|
||||||
|
// https://github.com/joplin/plugin-abc-sheet-music
|
||||||
|
if (isInsideContainer(node, 'SVG')) return false;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraOptions
|
extraOptions
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ interface ContextMenuProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class NoteListUtils {
|
export default class NoteListUtils {
|
||||||
static makeContextMenu(noteIds: string[], props: ContextMenuProps) {
|
public static makeContextMenu(noteIds: string[], props: ContextMenuProps) {
|
||||||
const cmdService = CommandService.instance();
|
const cmdService = CommandService.instance();
|
||||||
|
|
||||||
const menuUtils = new MenuUtils(cmdService);
|
const menuUtils = new MenuUtils(cmdService);
|
||||||
@@ -212,7 +212,7 @@ export default class NoteListUtils {
|
|||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async confirmDeleteNotes(noteIds: string[]) {
|
public static async confirmDeleteNotes(noteIds: string[]) {
|
||||||
if (!noteIds.length) return;
|
if (!noteIds.length) return;
|
||||||
|
|
||||||
const msg = await Note.deleteMessage(noteIds);
|
const msg = await Note.deleteMessage(noteIds);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const gulp = require('gulp');
|
|||||||
const utils = require('@joplin/tools/gulp/utils');
|
const utils = require('@joplin/tools/gulp/utils');
|
||||||
const compileSass = require('@joplin/tools/compileSass');
|
const compileSass = require('@joplin/tools/compileSass');
|
||||||
const compilePackageInfo = require('@joplin/tools/compilePackageInfo');
|
const compilePackageInfo = require('@joplin/tools/compilePackageInfo');
|
||||||
|
const bundleDefaultPlugins = require('@joplin/tools/bundleDefaultPlugins');
|
||||||
|
|
||||||
const tasks = {
|
const tasks = {
|
||||||
compileScripts: {
|
compileScripts: {
|
||||||
@@ -35,6 +36,11 @@ const tasks = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
bundleDefaultPlugins: {
|
||||||
|
fn: async () => {
|
||||||
|
await bundleDefaultPlugins();
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
utils.registerGulpTasks(gulp, tasks);
|
utils.registerGulpTasks(gulp, tasks);
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@joplin/app-desktop",
|
"name": "@joplin/app-desktop",
|
||||||
"version": "2.10.11",
|
"version": "2.11.1",
|
||||||
"description": "Joplin for Desktop",
|
"description": "Joplin for Desktop",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dist": "yarn run electronRebuild && npx electron-builder",
|
"dist": "yarn run bundleDefaultPlugins && yarn run electronRebuild && npx electron-builder",
|
||||||
|
"bundleDefaultPlugins": "gulp bundleDefaultPlugins",
|
||||||
"build": "gulp build",
|
"build": "gulp build",
|
||||||
"postinstall": "yarn run build",
|
"postinstall": "yarn run build",
|
||||||
"electronBuilder": "gulp electronBuilder",
|
"electronBuilder": "gulp electronBuilder",
|
||||||
@@ -90,7 +91,10 @@
|
|||||||
"CFBundleURLName": "org.joplinapp.x-callback-url"
|
"CFBundleURLName": "org.joplinapp.x-callback-url"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"binaries": [
|
||||||
|
"Contents/Resources/build/defaultPlugins/io.github.jackgruber.backup/plugin.jpl"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"linux": {
|
"linux": {
|
||||||
"icon": "../../Assets/LinuxIcons",
|
"icon": "../../Assets/LinuxIcons",
|
||||||
@@ -107,7 +111,9 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/laurent22/joplin#readme",
|
"homepage": "https://github.com/laurent22/joplin#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@joplin/tools": "~2.10",
|
"@electron/notarize": "1.2.3",
|
||||||
|
"@electron/rebuild": "3.2.10",
|
||||||
|
"@joplin/tools": "~2.11",
|
||||||
"@testing-library/react-hooks": "8.0.1",
|
"@testing-library/react-hooks": "8.0.1",
|
||||||
"@types/jest": "29.2.6",
|
"@types/jest": "29.2.6",
|
||||||
"@types/node": "18.11.18",
|
"@types/node": "18.11.18",
|
||||||
@@ -115,9 +121,7 @@
|
|||||||
"@types/react-redux": "7.1.25",
|
"@types/react-redux": "7.1.25",
|
||||||
"@types/styled-components": "5.1.26",
|
"@types/styled-components": "5.1.26",
|
||||||
"electron": "19.1.4",
|
"electron": "19.1.4",
|
||||||
"electron-builder": "23.6.0",
|
"electron-builder": "24.1.2",
|
||||||
"electron-notarize": "1.2.2",
|
|
||||||
"electron-rebuild": "3.2.9",
|
|
||||||
"glob": "8.1.0",
|
"glob": "8.1.0",
|
||||||
"gulp": "4.0.2",
|
"gulp": "4.0.2",
|
||||||
"jest": "29.4.3",
|
"jest": "29.4.3",
|
||||||
@@ -125,6 +129,7 @@
|
|||||||
"js-sha512": "0.8.0",
|
"js-sha512": "0.8.0",
|
||||||
"nan": "2.17.0",
|
"nan": "2.17.0",
|
||||||
"react-test-renderer": "18.2.0",
|
"react-test-renderer": "18.2.0",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "4.9.4"
|
"typescript": "4.9.4"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
@@ -136,9 +141,9 @@
|
|||||||
"@electron/remote": "2.0.9",
|
"@electron/remote": "2.0.9",
|
||||||
"@fortawesome/fontawesome-free": "5.15.4",
|
"@fortawesome/fontawesome-free": "5.15.4",
|
||||||
"@joeattardi/emoji-button": "4.6.4",
|
"@joeattardi/emoji-button": "4.6.4",
|
||||||
"@joplin/lib": "~2.10",
|
"@joplin/lib": "~2.11",
|
||||||
"@joplin/pdf-viewer": "~2.10",
|
"@joplin/pdf-viewer": "~2.11",
|
||||||
"@joplin/renderer": "~2.10",
|
"@joplin/renderer": "~2.11",
|
||||||
"async-mutex": "0.4.0",
|
"async-mutex": "0.4.0",
|
||||||
"codemirror": "5.65.9",
|
"codemirror": "5.65.9",
|
||||||
"color": "3.2.1",
|
"color": "3.2.1",
|
||||||
@@ -147,7 +152,7 @@
|
|||||||
"debounce": "1.2.1",
|
"debounce": "1.2.1",
|
||||||
"electron-window-state": "5.0.3",
|
"electron-window-state": "5.0.3",
|
||||||
"formatcoords": "1.1.3",
|
"formatcoords": "1.1.3",
|
||||||
"fs-extra": "11.1.0",
|
"fs-extra": "11.1.1",
|
||||||
"highlight.js": "11.7.0",
|
"highlight.js": "11.7.0",
|
||||||
"immer": "7.0.15",
|
"immer": "7.0.15",
|
||||||
"keytar": "7.9.0",
|
"keytar": "7.9.0",
|
||||||
@@ -163,15 +168,15 @@
|
|||||||
"react-datetime": "3.2.0",
|
"react-datetime": "3.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-redux": "8.0.5",
|
"react-redux": "8.0.5",
|
||||||
"react-select": "5.7.0",
|
"react-select": "5.7.2",
|
||||||
"react-toggle-button": "2.2.0",
|
"react-toggle-button": "2.2.0",
|
||||||
"react-tooltip": "4.5.1",
|
"react-tooltip": "4.5.1",
|
||||||
"redux": "4.2.1",
|
"redux": "4.2.1",
|
||||||
"reselect": "4.1.7",
|
"reselect": "4.1.7",
|
||||||
"roboto-fontface": "0.10.0",
|
"roboto-fontface": "0.10.0",
|
||||||
"smalltalk": "2.5.1",
|
"smalltalk": "2.5.1",
|
||||||
"sqlite3": "5.1.4",
|
"sqlite3": "5.1.6",
|
||||||
"styled-components": "5.3.6",
|
"styled-components": "5.3.9",
|
||||||
"styled-system": "5.1.5",
|
"styled-system": "5.1.5",
|
||||||
"taboverride": "4.0.3",
|
"taboverride": "4.0.3",
|
||||||
"tinymce": "5.10.6"
|
"tinymce": "5.10.6"
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class GotoAnything {
|
|||||||
public static Dialog: any;
|
public static Dialog: any;
|
||||||
public static manifest: any;
|
public static manifest: any;
|
||||||
|
|
||||||
onTrigger(event: any) {
|
public onTrigger(event: any) {
|
||||||
this.dispatch({
|
this.dispatch({
|
||||||
type: 'PLUGINLEGACY_DIALOG_SET',
|
type: 'PLUGINLEGACY_DIALOG_SET',
|
||||||
open: true,
|
open: true,
|
||||||
@@ -85,7 +85,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
private markupToHtml_: MarkupToHtml;
|
private markupToHtml_: MarkupToHtml;
|
||||||
private userCallback_: any = null;
|
private userCallback_: any = null;
|
||||||
|
|
||||||
constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const startString = props?.userData?.startString ? props?.userData?.startString : '';
|
const startString = props?.userData?.startString ? props?.userData?.startString : '';
|
||||||
@@ -119,7 +119,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
if (startString) this.scheduleListUpdate();
|
if (startString) this.scheduleListUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
style() {
|
public style() {
|
||||||
const styleKey = [this.props.themeId, this.state.listType, this.state.resultsInBody ? '1' : '0'].join('-');
|
const styleKey = [this.props.themeId, this.state.listType, this.state.resultsInBody ? '1' : '0'].join('-');
|
||||||
|
|
||||||
if (this.styles_[styleKey]) return this.styles_[styleKey];
|
if (this.styles_[styleKey]) return this.styles_[styleKey];
|
||||||
@@ -184,7 +184,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
return this.styles_[styleKey];
|
return this.styles_[styleKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount() {
|
||||||
document.addEventListener('keydown', this.onKeyDown);
|
document.addEventListener('keydown', this.onKeyDown);
|
||||||
|
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
@@ -193,7 +193,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_);
|
if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_);
|
||||||
document.removeEventListener('keydown', this.onKeyDown);
|
document.removeEventListener('keydown', this.onKeyDown);
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(event: any) {
|
public onKeyDown(event: any) {
|
||||||
if (event.keyCode === 27) { // ESCAPE
|
if (event.keyCode === 27) { // ESCAPE
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
pluginName: PLUGIN_NAME,
|
pluginName: PLUGIN_NAME,
|
||||||
@@ -213,7 +213,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
modalLayer_onClick(event: any) {
|
private modalLayer_onClick(event: any) {
|
||||||
if (event.currentTarget === event.target) {
|
if (event.currentTarget === event.target) {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
pluginName: PLUGIN_NAME,
|
pluginName: PLUGIN_NAME,
|
||||||
@@ -223,17 +223,17 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
helpButton_onClick() {
|
private helpButton_onClick() {
|
||||||
this.setState({ showHelp: !this.state.showHelp });
|
this.setState({ showHelp: !this.state.showHelp });
|
||||||
}
|
}
|
||||||
|
|
||||||
input_onChange(event: any) {
|
private input_onChange(event: any) {
|
||||||
this.setState({ query: event.target.value });
|
this.setState({ query: event.target.value });
|
||||||
|
|
||||||
this.scheduleListUpdate();
|
this.scheduleListUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleListUpdate() {
|
public scheduleListUpdate() {
|
||||||
if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_);
|
if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_);
|
||||||
|
|
||||||
this.listUpdateIID_ = shim.setTimeout(async () => {
|
this.listUpdateIID_ = shim.setTimeout(async () => {
|
||||||
@@ -242,12 +242,12 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
async keywords(searchQuery: string) {
|
public async keywords(searchQuery: string) {
|
||||||
const parsedQuery = await SearchEngine.instance().parseQuery(searchQuery);
|
const parsedQuery = await SearchEngine.instance().parseQuery(searchQuery);
|
||||||
return SearchEngine.instance().allParsedQueryTerms(parsedQuery);
|
return SearchEngine.instance().allParsedQueryTerms(parsedQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
markupToHtml() {
|
public markupToHtml() {
|
||||||
if (this.markupToHtml_) return this.markupToHtml_;
|
if (this.markupToHtml_) return this.markupToHtml_;
|
||||||
this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml();
|
this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml();
|
||||||
return this.markupToHtml_;
|
return this.markupToHtml_;
|
||||||
@@ -262,7 +262,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateList() {
|
public async updateList() {
|
||||||
let resultsInBody = false;
|
let resultsInBody = false;
|
||||||
|
|
||||||
if (!this.state.query) {
|
if (!this.state.query) {
|
||||||
@@ -402,7 +402,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
this.itemListRef.current.makeItemIndexVisible(index);
|
this.itemListRef.current.makeItemIndexVisible(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
async gotoItem(item: any) {
|
public async gotoItem(item: any) {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
pluginName: PLUGIN_NAME,
|
pluginName: PLUGIN_NAME,
|
||||||
type: 'PLUGINLEGACY_DIALOG_SET',
|
type: 'PLUGINLEGACY_DIALOG_SET',
|
||||||
@@ -465,7 +465,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
listItem_onClick(event: any) {
|
private listItem_onClick(event: any) {
|
||||||
const itemId = event.currentTarget.getAttribute('data-id');
|
const itemId = event.currentTarget.getAttribute('data-id');
|
||||||
const parentId = event.currentTarget.getAttribute('data-parent-id');
|
const parentId = event.currentTarget.getAttribute('data-parent-id');
|
||||||
const itemType = Number(event.currentTarget.getAttribute('data-type'));
|
const itemType = Number(event.currentTarget.getAttribute('data-type'));
|
||||||
@@ -478,7 +478,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem(item: SearchResult) {
|
public renderItem(item: SearchResult) {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const style = this.style();
|
const style = this.style();
|
||||||
const isSelected = item.id === this.state.selectedItemId;
|
const isSelected = item.id === this.state.selectedItemId;
|
||||||
@@ -502,7 +502,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedItemIndex(results: any[] = undefined, itemId: string = undefined) {
|
public selectedItemIndex(results: any[] = undefined, itemId: string = undefined) {
|
||||||
if (typeof results === 'undefined') results = this.state.results;
|
if (typeof results === 'undefined') results = this.state.results;
|
||||||
if (typeof itemId === 'undefined') itemId = this.state.selectedItemId;
|
if (typeof itemId === 'undefined') itemId = this.state.selectedItemId;
|
||||||
for (let i = 0; i < results.length; i++) {
|
for (let i = 0; i < results.length; i++) {
|
||||||
@@ -512,13 +512,13 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedItem() {
|
public selectedItem() {
|
||||||
const index = this.selectedItemIndex();
|
const index = this.selectedItemIndex();
|
||||||
if (index < 0) return null;
|
if (index < 0) return null;
|
||||||
return { ...this.state.results[index], commandArgs: this.state.commandArgs };
|
return { ...this.state.results[index], commandArgs: this.state.commandArgs };
|
||||||
}
|
}
|
||||||
|
|
||||||
input_onKeyDown(event: any) {
|
private input_onKeyDown(event: any) {
|
||||||
const keyCode = event.keyCode;
|
const keyCode = event.keyCode;
|
||||||
|
|
||||||
if (this.state.results.length > 0 && (keyCode === 40 || keyCode === 38)) { // DOWN / UP
|
if (this.state.results.length > 0 && (keyCode === 40 || keyCode === 38)) { // DOWN / UP
|
||||||
@@ -554,7 +554,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
return maxItemCount * itemHeight;
|
return maxItemCount * itemHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList() {
|
public renderList() {
|
||||||
const style = this.style();
|
const style = this.style();
|
||||||
|
|
||||||
const itemListStyle = {
|
const itemListStyle = {
|
||||||
@@ -573,7 +573,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const style = this.style();
|
const style = this.style();
|
||||||
const helpComp = !this.state.showHelp ? null : <div className="help-text" style={style.help}>{_('Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.')}</div>;
|
const helpComp = !this.state.showHelp ? null : <div className="help-text" style={style.help}>{_('Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.')}</div>;
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ const { shimInit } = require('@joplin/lib/shim-init-node.js');
|
|||||||
const folderId1 = 'aa012345678901234567890123456789';
|
const folderId1 = 'aa012345678901234567890123456789';
|
||||||
const folderId2 = 'bb012345678901234567890123456789';
|
const folderId2 = 'bb012345678901234567890123456789';
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
shimInit();
|
|
||||||
Setting.autoSaveEnabled = false;
|
|
||||||
PerFolderSortOrderService.initialize();
|
|
||||||
Setting.setValue('notes.perFolderSortOrderEnabled', true);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PerFolderSortOrderService', () => {
|
describe('PerFolderSortOrderService', () => {
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
shimInit();
|
||||||
|
Setting.autoSaveEnabled = false;
|
||||||
|
PerFolderSortOrderService.initialize();
|
||||||
|
Setting.setValue('notes.perFolderSortOrderEnabled', true);
|
||||||
|
});
|
||||||
|
|
||||||
test('get(), isSet() and set()', async () => {
|
test('get(), isSet() and set()', async () => {
|
||||||
// Clear all per-folder sort order
|
// Clear all per-folder sort order
|
||||||
expect(PerFolderSortOrderService.isSet(folderId1)).toBe(false);
|
expect(PerFolderSortOrderService.isSet(folderId1)).toBe(false);
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { notesSortOrderFieldArray, notesSortOrderNextField, setNotesSortOrder }
|
|||||||
import Setting from '@joplin/lib/models/Setting';
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
const { shimInit } = require('@joplin/lib/shim-init-node.js');
|
const { shimInit } = require('@joplin/lib/shim-init-node.js');
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
shimInit();
|
|
||||||
Setting.autoSaveEnabled = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('notesSortOrderUtils', () => {
|
describe('notesSortOrderUtils', () => {
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
shimInit();
|
||||||
|
Setting.autoSaveEnabled = false;
|
||||||
|
});
|
||||||
|
|
||||||
it('should always provide the same ordered fields', async () => {
|
it('should always provide the same ordered fields', async () => {
|
||||||
const expected = ['user_updated_time', 'user_created_time', 'title', 'order'];
|
const expected = ['user_updated_time', 'user_created_time', 'title', 'order'];
|
||||||
expect(notesSortOrderFieldArray()).toStrictEqual(expected);
|
expect(notesSortOrderFieldArray()).toStrictEqual(expected);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const electron_notarize = require('electron-notarize');
|
const electron_notarize = require('@electron/notarize');
|
||||||
const execCommand = require('./execCommand');
|
const execCommand = require('./execCommand');
|
||||||
|
|
||||||
function isDesktopAppTag(tagName) {
|
function isDesktopAppTag(tagName) {
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"**/node_modules",
|
"**/node_modules",
|
||||||
"**/dist",
|
"**/dist",
|
||||||
|
"gulpfile.ts",
|
||||||
],
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": [
|
||||||
|
"jest",
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
bracketSpacing: false,
|
|
||||||
jsxBracketSameLine: true,
|
|
||||||
singleQuote: true,
|
|
||||||
trailingComma: 'all',
|
|
||||||
};
|
|
||||||
@@ -6,24 +6,24 @@ import KvStore from '@joplin/lib/services/KvStore';
|
|||||||
|
|
||||||
export default class PluginAssetsLoader {
|
export default class PluginAssetsLoader {
|
||||||
|
|
||||||
static instance_: PluginAssetsLoader = null;
|
private static instance_: PluginAssetsLoader = null;
|
||||||
logger_: any = null;
|
private logger_: any = null;
|
||||||
|
|
||||||
static instance() {
|
public static instance() {
|
||||||
if (PluginAssetsLoader.instance_) return PluginAssetsLoader.instance_;
|
if (PluginAssetsLoader.instance_) return PluginAssetsLoader.instance_;
|
||||||
PluginAssetsLoader.instance_ = new PluginAssetsLoader();
|
PluginAssetsLoader.instance_ = new PluginAssetsLoader();
|
||||||
return PluginAssetsLoader.instance_;
|
return PluginAssetsLoader.instance_;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLogger(logger: any) {
|
public setLogger(logger: any) {
|
||||||
this.logger_ = logger;
|
this.logger_ = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger() {
|
public logger() {
|
||||||
return this.logger_;
|
return this.logger_;
|
||||||
}
|
}
|
||||||
|
|
||||||
async importAssets() {
|
public async importAssets() {
|
||||||
const destDir = `${Setting.value('resourceDir')}/pluginAssets`;
|
const destDir = `${Setting.value('resourceDir')}/pluginAssets`;
|
||||||
await shim.fsDriver().mkdir(destDir);
|
await shim.fsDriver().mkdir(destDir);
|
||||||
|
|
||||||
|
|||||||
@@ -150,8 +150,8 @@ android {
|
|||||||
applicationId "net.cozic.joplin"
|
applicationId "net.cozic.joplin"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 2097685
|
versionCode 2097687
|
||||||
versionName "2.10.9"
|
versionName "2.11.2"
|
||||||
// ndk {
|
// ndk {
|
||||||
// abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
// abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const { BackButtonService } = require('../services/back-button.js');
|
|||||||
const DialogBox = require('react-native-dialogbox').default;
|
const DialogBox = require('react-native-dialogbox').default;
|
||||||
|
|
||||||
export default class BackButtonDialogBox extends DialogBox {
|
export default class BackButtonDialogBox extends DialogBox {
|
||||||
constructor(props: any) {
|
public constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.backHandler_ = () => {
|
this.backHandler_ = () => {
|
||||||
@@ -14,7 +14,7 @@ export default class BackButtonDialogBox extends DialogBox {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidUpdate() {
|
public async componentDidUpdate() {
|
||||||
if (this.state.isVisible) {
|
if (this.state.isVisible) {
|
||||||
BackButtonService.addHandler(this.backHandler_);
|
BackButtonService.addHandler(this.backHandler_);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import Setting from '@joplin/lib/models/Setting';
|
|||||||
Icon.loadFont().catch((error: any) => { console.info(error); });
|
Icon.loadFont().catch((error: any) => { console.info(error); });
|
||||||
|
|
||||||
class CameraView extends Component {
|
class CameraView extends Component {
|
||||||
constructor() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const dimensions = Dimensions.get('window');
|
const dimensions = Dimensions.get('window');
|
||||||
@@ -34,18 +34,18 @@ class CameraView extends Component {
|
|||||||
this.onLayout = this.onLayout.bind(this);
|
this.onLayout = this.onLayout.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayout(event: any) {
|
public onLayout(event: any) {
|
||||||
this.setState({
|
this.setState({
|
||||||
screenWidth: event.nativeEvent.layout.width,
|
screenWidth: event.nativeEvent.layout.width,
|
||||||
screenHeight: event.nativeEvent.layout.height,
|
screenHeight: event.nativeEvent.layout.height,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
back_onPress() {
|
private back_onPress() {
|
||||||
if (this.props.onCancel) this.props.onCancel();
|
if (this.props.onCancel) this.props.onCancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
reverse_onPress() {
|
private reverse_onPress() {
|
||||||
if (this.props.cameraType === RNCamera.Constants.Type.back) {
|
if (this.props.cameraType === RNCamera.Constants.Type.back) {
|
||||||
Setting.setValue('camera.type', RNCamera.Constants.Type.front);
|
Setting.setValue('camera.type', RNCamera.Constants.Type.front);
|
||||||
} else {
|
} else {
|
||||||
@@ -53,7 +53,7 @@ class CameraView extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ratio_onPress() {
|
private ratio_onPress() {
|
||||||
if (this.state.ratios.length <= 1) return;
|
if (this.state.ratios.length <= 1) return;
|
||||||
|
|
||||||
let index = this.state.ratios.indexOf(this.props.cameraRatio);
|
let index = this.state.ratios.indexOf(this.props.cameraRatio);
|
||||||
@@ -62,7 +62,7 @@ class CameraView extends Component {
|
|||||||
Setting.setValue('camera.ratio', this.state.ratios[index]);
|
Setting.setValue('camera.ratio', this.state.ratios[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async photo_onPress() {
|
private async photo_onPress() {
|
||||||
if (!this.camera || !this.props.onPhoto) return;
|
if (!this.camera || !this.props.onPhoto) return;
|
||||||
|
|
||||||
this.setState({ snapping: true });
|
this.setState({ snapping: true });
|
||||||
@@ -79,14 +79,14 @@ class CameraView extends Component {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onCameraReady() {
|
public async onCameraReady() {
|
||||||
if (this.supportsRatios()) {
|
if (this.supportsRatios()) {
|
||||||
const ratios = await this.camera.getSupportedRatiosAsync();
|
const ratios = await this.camera.getSupportedRatiosAsync();
|
||||||
this.setState({ ratios: ratios });
|
this.setState({ ratios: ratios });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderButton(onPress: Function, iconNameOrIcon: any, style: any) {
|
public renderButton(onPress: Function, iconNameOrIcon: any, style: any) {
|
||||||
let icon = null;
|
let icon = null;
|
||||||
|
|
||||||
if (typeof iconNameOrIcon === 'string') {
|
if (typeof iconNameOrIcon === 'string') {
|
||||||
@@ -112,7 +112,7 @@ class CameraView extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fitRectIntoBounds(rect: any, bounds: any) {
|
public fitRectIntoBounds(rect: any, bounds: any) {
|
||||||
const rectRatio = rect.width / rect.height;
|
const rectRatio = rect.width / rect.height;
|
||||||
const boundsRatio = bounds.width / bounds.height;
|
const boundsRatio = bounds.width / bounds.height;
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ class CameraView extends Component {
|
|||||||
return newDimensions;
|
return newDimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
cameraRect(ratio: string) {
|
public cameraRect(ratio: string) {
|
||||||
// To keep the calculations simpler, it's assumed that the phone is in
|
// To keep the calculations simpler, it's assumed that the phone is in
|
||||||
// portrait orientation. Then at the end we swap the values if needed.
|
// portrait orientation. Then at the end we swap the values if needed.
|
||||||
const splitted = ratio.split(':');
|
const splitted = ratio.split(':');
|
||||||
@@ -152,11 +152,11 @@ class CameraView extends Component {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
supportsRatios() {
|
public supportsRatios() {
|
||||||
return shim.mobilePlatform() === 'android';
|
return shim.mobilePlatform() === 'android';
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const photoIcon = this.state.snapping ? 'md-checkmark' : 'md-camera';
|
const photoIcon = this.state.snapping ? 'md-checkmark' : 'md-camera';
|
||||||
|
|
||||||
const displayRatios = this.supportsRatios() && this.state.ratios.length > 1;
|
const displayRatios = this.supportsRatios() && this.state.ratios.length > 1;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* @jest-environment jsdom
|
* @jest-environment jsdom
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { EditorSettings } from '../types';
|
import { EditorSettings } from '../types';
|
||||||
import { initCodeMirror } from './CodeMirror';
|
import { initCodeMirror } from './CodeMirror';
|
||||||
import { themeStyle } from '@joplin/lib/theme';
|
import { themeStyle } from '@joplin/lib/theme';
|
||||||
@@ -9,6 +10,8 @@ import Setting from '@joplin/lib/models/Setting';
|
|||||||
import { forceParsing } from '@codemirror/language';
|
import { forceParsing } from '@codemirror/language';
|
||||||
import loadLangauges from './testUtil/loadLanguages';
|
import loadLangauges from './testUtil/loadLanguages';
|
||||||
|
|
||||||
|
import { expect, describe, it } from '@jest/globals';
|
||||||
|
|
||||||
|
|
||||||
const createEditorSettings = (themeId: number) => {
|
const createEditorSettings = (themeId: number) => {
|
||||||
const themeData = themeStyle(themeId);
|
const themeData = themeStyle(themeId);
|
||||||
@@ -23,6 +26,14 @@ const createEditorSettings = (themeId: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('CodeMirror', () => {
|
describe('CodeMirror', () => {
|
||||||
|
// This checks for a regression -- occasionally, when updating packages,
|
||||||
|
// syntax highlighting in the CodeMirror editor stops working. This is usually
|
||||||
|
// fixed by
|
||||||
|
// 1. removing all `@codemirror/` and `@lezer/` dependencies from yarn.lock,
|
||||||
|
// 2. upgrading all CodeMirror packages to the latest versions in package.json, and
|
||||||
|
// 3. re-running `yarn install`.
|
||||||
|
//
|
||||||
|
// See https://github.com/laurent22/joplin/issues/7253
|
||||||
it('should give headings a different style', async () => {
|
it('should give headings a different style', async () => {
|
||||||
const headerLineText = '# Testing...';
|
const headerLineText = '# Testing...';
|
||||||
const initialText = `${headerLineText}\nThis is a test.`;
|
const initialText = `${headerLineText}\nThis is a test.`;
|
||||||
|
|||||||
@@ -239,17 +239,5 @@ describe('markdownCommands', () => {
|
|||||||
expect(sel.from).toBe('> Testing...> \n> \n'.length);
|
expect(sel.from).toBe('> Testing...> \n> \n'.length);
|
||||||
expect(sel.to).toBe(editor.state.doc.length);
|
expect(sel.to).toBe(editor.state.doc.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('toggling inline code should both create and navigate out of an inline code region', async () => {
|
|
||||||
const initialDocText = 'Testing...\n\n';
|
|
||||||
const editor = await createEditor(initialDocText, EditorSelection.cursor(initialDocText.length), []);
|
|
||||||
|
|
||||||
toggleCode(editor);
|
|
||||||
editor.dispatch(editor.state.replaceSelection('f(x) = ...'));
|
|
||||||
toggleCode(editor);
|
|
||||||
|
|
||||||
editor.dispatch(editor.state.replaceSelection(' is a function.'));
|
|
||||||
expect(editor.state.doc.toString()).toBe('Testing...\n\n`f(x) = ...` is a function.');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -101,56 +101,56 @@ describe('markdownCommands.toggleList', () => {
|
|||||||
// );
|
// );
|
||||||
// });
|
// });
|
||||||
|
|
||||||
it('should not preserve indentation when removing sublists', async () => {
|
// it('should not preserve indentation when removing sublists', async () => {
|
||||||
const preSubListText = '# List test\n * This\n * is\n';
|
// const preSubListText = '# List test\n * This\n * is\n';
|
||||||
const initialDocText = `${preSubListText}\t1. a\n\t2. test\n * of list toggling`;
|
// const initialDocText = `${preSubListText}\t1. a\n\t2. test\n * of list toggling`;
|
||||||
|
|
||||||
const editor = await createEditor(
|
// const editor = await createEditor(
|
||||||
initialDocText,
|
// initialDocText,
|
||||||
EditorSelection.range(preSubListText.length, `${preSubListText}\t1. a\n\t2. test`.length),
|
// EditorSelection.range(preSubListText.length, `${preSubListText}\t1. a\n\t2. test`.length),
|
||||||
['ATXHeading1', 'BulletList', 'OrderedList']
|
// ['ATXHeading1', 'BulletList', 'OrderedList']
|
||||||
);
|
// );
|
||||||
|
|
||||||
// Indentation should not be preserved when removing lists
|
// // Indentation should not be preserved when removing lists
|
||||||
toggleList(ListType.OrderedList)(editor);
|
// toggleList(ListType.OrderedList)(editor);
|
||||||
expect(editor.state.selection.main.from).toBe(preSubListText.length);
|
// expect(editor.state.selection.main.from).toBe(preSubListText.length);
|
||||||
expect(editor.state.doc.toString()).toBe(
|
// expect(editor.state.doc.toString()).toBe(
|
||||||
'# List test\n * This\n * is\na\ntest\n * of list toggling'
|
// '# List test\n * This\n * is\na\ntest\n * of list toggling'
|
||||||
);
|
// );
|
||||||
|
|
||||||
// Put the cursor in the middle of the list
|
// // Put the cursor in the middle of the list
|
||||||
editor.dispatch({ selection: EditorSelection.cursor(preSubListText.length) });
|
// editor.dispatch({ selection: EditorSelection.cursor(preSubListText.length) });
|
||||||
|
|
||||||
// Sublists should be changed
|
// // Sublists should be changed
|
||||||
toggleList(ListType.CheckList)(editor);
|
// toggleList(ListType.CheckList)(editor);
|
||||||
const expectedChecklistPart =
|
// const expectedChecklistPart =
|
||||||
'# List test\n - [ ] This\n - [ ] is\n - [ ] a\n - [ ] test\n - [ ] of list toggling';
|
// '# List test\n - [ ] This\n - [ ] is\n - [ ] a\n - [ ] test\n - [ ] of list toggling';
|
||||||
expect(editor.state.doc.toString()).toBe(
|
// expect(editor.state.doc.toString()).toBe(
|
||||||
expectedChecklistPart
|
// expectedChecklistPart
|
||||||
);
|
// );
|
||||||
|
|
||||||
editor.dispatch({ selection: EditorSelection.cursor(editor.state.doc.length) });
|
// editor.dispatch({ selection: EditorSelection.cursor(editor.state.doc.length) });
|
||||||
editor.dispatch(editor.state.replaceSelection('\n\n\n'));
|
// editor.dispatch(editor.state.replaceSelection('\n\n\n'));
|
||||||
|
|
||||||
// toggleList should also create a new list if the cursor is on an empty line.
|
// // toggleList should also create a new list if the cursor is on an empty line.
|
||||||
toggleList(ListType.OrderedList)(editor);
|
// toggleList(ListType.OrderedList)(editor);
|
||||||
editor.dispatch(editor.state.replaceSelection('Test.\n2. Test2\n3. Test3'));
|
// editor.dispatch(editor.state.replaceSelection('Test.\n2. Test2\n3. Test3'));
|
||||||
|
|
||||||
expect(editor.state.doc.toString()).toBe(
|
// expect(editor.state.doc.toString()).toBe(
|
||||||
`${expectedChecklistPart}\n\n\n1. Test.\n2. Test2\n3. Test3`
|
// `${expectedChecklistPart}\n\n\n1. Test.\n2. Test2\n3. Test3`
|
||||||
);
|
// );
|
||||||
|
|
||||||
toggleList(ListType.CheckList)(editor);
|
// toggleList(ListType.CheckList)(editor);
|
||||||
expect(editor.state.doc.toString()).toBe(
|
// expect(editor.state.doc.toString()).toBe(
|
||||||
`${expectedChecklistPart}\n\n\n- [ ] Test.\n- [ ] Test2\n- [ ] Test3`
|
// `${expectedChecklistPart}\n\n\n- [ ] Test.\n- [ ] Test2\n- [ ] Test3`
|
||||||
);
|
// );
|
||||||
|
|
||||||
// The entire checklist should have been selected (and thus will now be indented)
|
// // The entire checklist should have been selected (and thus will now be indented)
|
||||||
increaseIndent(editor);
|
// increaseIndent(editor);
|
||||||
expect(editor.state.doc.toString()).toBe(
|
// expect(editor.state.doc.toString()).toBe(
|
||||||
`${expectedChecklistPart}\n\n\n\t- [ ] Test.\n\t- [ ] Test2\n\t- [ ] Test3`
|
// `${expectedChecklistPart}\n\n\n\t- [ ] Test.\n\t- [ ] Test2\n\t- [ ] Test3`
|
||||||
);
|
// );
|
||||||
});
|
// });
|
||||||
|
|
||||||
it('should toggle a numbered list without changing its sublists', async () => {
|
it('should toggle a numbered list without changing its sublists', async () => {
|
||||||
const initialDocText = '1. Foo\n2. Bar\n3. Baz\n\t- Test\n\t- of\n\t- sublists\n4. Foo';
|
const initialDocText = '1. Foo\n2. Bar\n3. Baz\n\t- Test\n\t- of\n\t- sublists\n4. Foo';
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ interface Props {
|
|||||||
initialSelection?: Selection;
|
initialSelection?: Selection;
|
||||||
style: ViewStyle;
|
style: ViewStyle;
|
||||||
contentStyle?: ViewStyle;
|
contentStyle?: ViewStyle;
|
||||||
|
toolbarEnabled: boolean;
|
||||||
|
|
||||||
onChange: ChangeEventHandler;
|
onChange: ChangeEventHandler;
|
||||||
onSelectionChange: SelectionChangeEventHandler;
|
onSelectionChange: SelectionChangeEventHandler;
|
||||||
@@ -364,6 +365,19 @@ function NoteEditor(props: Props, ref: any) {
|
|||||||
console.error('NoteEditor: webview error');
|
console.error('NoteEditor: webview error');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const toolbar = <MarkdownToolbar
|
||||||
|
style={{
|
||||||
|
// Don't show the markdown toolbar if there isn't enough space
|
||||||
|
// for it:
|
||||||
|
flexShrink: 1,
|
||||||
|
}}
|
||||||
|
editorSettings={editorSettings}
|
||||||
|
editorControl={editorControl}
|
||||||
|
selectionState={selectionState}
|
||||||
|
searchState={searchState}
|
||||||
|
onAttach={props.onAttach}
|
||||||
|
/>;
|
||||||
|
|
||||||
// - `scrollEnabled` prevents iOS from scrolling the document (has no effect on Android)
|
// - `scrollEnabled` prevents iOS from scrolling the document (has no effect on Android)
|
||||||
// when an editable region (e.g. a the full-screen NoteEditor) is focused.
|
// when an editable region (e.g. a the full-screen NoteEditor) is focused.
|
||||||
return (
|
return (
|
||||||
@@ -401,18 +415,7 @@ function NoteEditor(props: Props, ref: any) {
|
|||||||
searchState={searchState}
|
searchState={searchState}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MarkdownToolbar
|
{props.toolbarEnabled ? toolbar : null}
|
||||||
style={{
|
|
||||||
// Don't show the markdown toolbar if there isn't enough space
|
|
||||||
// for it:
|
|
||||||
flexShrink: 1,
|
|
||||||
}}
|
|
||||||
editorSettings={editorSettings}
|
|
||||||
editorControl={editorControl}
|
|
||||||
selectionState={selectionState}
|
|
||||||
searchState={searchState}
|
|
||||||
onAttach={props.onAttach}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const styles = StyleSheet.create({
|
|||||||
|
|
||||||
export default class SelectDateTimeDialog extends React.PureComponent<any, any> {
|
export default class SelectDateTimeDialog extends React.PureComponent<any, any> {
|
||||||
|
|
||||||
constructor(props: any) {
|
public constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -67,37 +67,37 @@ export default class SelectDateTimeDialog extends React.PureComponent<any, any>
|
|||||||
this.onSetDate = this.onSetDate.bind(this);
|
this.onSetDate = this.onSetDate.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(newProps: any) {
|
public UNSAFE_componentWillReceiveProps(newProps: any) {
|
||||||
if (newProps.date !== this.state.date) {
|
if (newProps.date !== this.state.date) {
|
||||||
this.setState({ date: newProps.date });
|
this.setState({ date: newProps.date });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onAccept() {
|
public onAccept() {
|
||||||
if (this.props.onAccept) this.props.onAccept(this.state.date);
|
if (this.props.onAccept) this.props.onAccept(this.state.date);
|
||||||
}
|
}
|
||||||
|
|
||||||
onReject() {
|
public onReject() {
|
||||||
if (this.props.onReject) this.props.onReject();
|
if (this.props.onReject) this.props.onReject();
|
||||||
}
|
}
|
||||||
|
|
||||||
onClear() {
|
public onClear() {
|
||||||
if (this.props.onAccept) this.props.onAccept(null);
|
if (this.props.onAccept) this.props.onAccept(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPickerConfirm(selectedDate: Date) {
|
public onPickerConfirm(selectedDate: Date) {
|
||||||
this.setState({ date: selectedDate, showPicker: false });
|
this.setState({ date: selectedDate, showPicker: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
onPickerCancel() {
|
public onPickerCancel() {
|
||||||
this.setState({ showPicker: false });
|
this.setState({ showPicker: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetDate() {
|
public onSetDate() {
|
||||||
this.setState({ showPicker: true });
|
this.setState({ showPicker: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContent() {
|
public renderContent() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -118,7 +118,7 @@ export default class SelectDateTimeDialog extends React.PureComponent<any, any>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const modalVisible = this.props.shown;
|
const modalVisible = this.props.shown;
|
||||||
|
|
||||||
if (!modalVisible) return null;
|
if (!modalVisible) return null;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Dimensions } from 'react-native';
|
|||||||
import { State } from '@joplin/lib/reducer';
|
import { State } from '@joplin/lib/reducer';
|
||||||
|
|
||||||
class SideMenuComponent extends SideMenu_ {
|
class SideMenuComponent extends SideMenu_ {
|
||||||
onLayoutChange(e: any) {
|
public onLayoutChange(e: any) {
|
||||||
const { width, height } = e.nativeEvent.layout;
|
const { width, height } = e.nativeEvent.layout;
|
||||||
const openMenuOffsetPercentage = this.props.openMenuOffset / Dimensions.get('window').width;
|
const openMenuOffsetPercentage = this.props.openMenuOffset / Dimensions.get('window').width;
|
||||||
const openMenuOffset = width * openMenuOffsetPercentage;
|
const openMenuOffset = width * openMenuOffsetPercentage;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class AppNavComponent extends Component<Props, State> {
|
|||||||
private keyboardDidHideListener: EmitterSubscription|null = null;
|
private keyboardDidHideListener: EmitterSubscription|null = null;
|
||||||
private keyboardWillChangeFrameListener: EmitterSubscription|null = null;
|
private keyboardWillChangeFrameListener: EmitterSubscription|null = null;
|
||||||
|
|
||||||
constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.previousRouteName_ = null;
|
this.previousRouteName_ = null;
|
||||||
@@ -35,7 +35,7 @@ class AppNavComponent extends Component<Props, State> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
public UNSAFE_componentWillMount() {
|
||||||
if (Platform.OS === 'ios') {
|
if (Platform.OS === 'ios') {
|
||||||
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
|
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
|
||||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide.bind(this));
|
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide.bind(this));
|
||||||
@@ -43,7 +43,7 @@ class AppNavComponent extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
this.keyboardDidShowListener?.remove();
|
this.keyboardDidShowListener?.remove();
|
||||||
this.keyboardDidHideListener?.remove();
|
this.keyboardDidHideListener?.remove();
|
||||||
this.keyboardWillChangeFrameListener?.remove();
|
this.keyboardWillChangeFrameListener?.remove();
|
||||||
@@ -53,15 +53,15 @@ class AppNavComponent extends Component<Props, State> {
|
|||||||
this.keyboardWillChangeFrameListener = null;
|
this.keyboardWillChangeFrameListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboardDidShow() {
|
public keyboardDidShow() {
|
||||||
this.setState({ autoCompletionBarExtraHeight: 30 });
|
this.setState({ autoCompletionBarExtraHeight: 30 });
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboardDidHide() {
|
public keyboardDidHide() {
|
||||||
this.setState({ autoCompletionBarExtraHeight: 0 });
|
this.setState({ autoCompletionBarExtraHeight: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboardWillChangeFrame = (evt: KeyboardEvent) => {
|
private keyboardWillChangeFrame = (evt: KeyboardEvent) => {
|
||||||
const windowWidth = Dimensions.get('window').width;
|
const windowWidth = Dimensions.get('window').width;
|
||||||
|
|
||||||
// If the keyboard isn't as wide as the window, the floating keyboard is diabled.
|
// If the keyboard isn't as wide as the window, the floating keyboard is diabled.
|
||||||
@@ -71,7 +71,7 @@ class AppNavComponent extends Component<Props, State> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
if (!this.props.route) throw new Error('Route must not be null');
|
if (!this.props.route) throw new Error('Route must not be null');
|
||||||
|
|
||||||
// Note: certain screens are kept into memory, in particular Notes and Search
|
// Note: certain screens are kept into memory, in particular Notes and Search
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ const React = require('react');
|
|||||||
import Setting from '@joplin/lib/models/Setting';
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { View, Dimensions, Alert, Button } from 'react-native';
|
import { View, Dimensions, Alert, Button } from 'react-native';
|
||||||
import FingerprintScanner from 'react-native-fingerprint-scanner';
|
|
||||||
import { SensorInfo } from './sensorInfo';
|
import { SensorInfo } from './sensorInfo';
|
||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import Logger from '@joplin/lib/Logger';
|
||||||
|
import biometricAuthenticate from './biometricAuthenticate';
|
||||||
|
|
||||||
|
const logger = Logger.create('BiometricPopup');
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
themeId: number;
|
themeId: number;
|
||||||
@@ -18,23 +21,31 @@ export default (props: Props) => {
|
|||||||
// doesn't work properly, we disable it. We only want the user to enable the
|
// doesn't work properly, we disable it. We only want the user to enable the
|
||||||
// feature after they've read the description in the config screen.
|
// feature after they've read the description in the config screen.
|
||||||
const [initialPromptDone, setInitialPromptDone] = useState(true); // useState(Setting.value('security.biometricsInitialPromptDone'));
|
const [initialPromptDone, setInitialPromptDone] = useState(true); // useState(Setting.value('security.biometricsInitialPromptDone'));
|
||||||
const [display, setDisplay] = useState(!!props.sensorInfo.supportedSensors && (props.sensorInfo.enabled || !initialPromptDone));
|
const [display, setDisplay] = useState(props.sensorInfo.enabled || !initialPromptDone);
|
||||||
const [tryBiometricsCheck, setTryBiometricsCheck] = useState(initialPromptDone);
|
const [tryBiometricsCheck, setTryBiometricsCheck] = useState(initialPromptDone);
|
||||||
|
|
||||||
|
logger.info('Render start');
|
||||||
|
logger.info('initialPromptDone', initialPromptDone);
|
||||||
|
logger.info('display', display);
|
||||||
|
logger.info('tryBiometricsCheck', tryBiometricsCheck);
|
||||||
|
logger.info('props.sensorInfo', props.sensorInfo);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!display || !tryBiometricsCheck) return;
|
if (!display || !tryBiometricsCheck) return;
|
||||||
|
|
||||||
const biometricsCheck = async () => {
|
const biometricsCheck = async () => {
|
||||||
|
logger.info('biometricsCheck: start');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await FingerprintScanner.authenticate({ description: _('Verify your identity') });
|
await biometricAuthenticate();
|
||||||
setTryBiometricsCheck(false);
|
|
||||||
setDisplay(false);
|
setDisplay(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Alert.alert(_('Could not verify your identify'), error.message);
|
Alert.alert(error.message);
|
||||||
setTryBiometricsCheck(false);
|
|
||||||
} finally {
|
|
||||||
FingerprintScanner.release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTryBiometricsCheck(false);
|
||||||
|
|
||||||
|
logger.info('biometricsCheck: end');
|
||||||
};
|
};
|
||||||
|
|
||||||
void biometricsCheck();
|
void biometricsCheck();
|
||||||
@@ -45,6 +56,9 @@ export default (props: Props) => {
|
|||||||
if (!display) return;
|
if (!display) return;
|
||||||
|
|
||||||
const complete = (enableBiometrics: boolean) => {
|
const complete = (enableBiometrics: boolean) => {
|
||||||
|
logger.info('complete: start');
|
||||||
|
logger.info('complete: enableBiometrics:', enableBiometrics);
|
||||||
|
|
||||||
setInitialPromptDone(true);
|
setInitialPromptDone(true);
|
||||||
Setting.setValue('security.biometricsInitialPromptDone', true);
|
Setting.setValue('security.biometricsInitialPromptDone', true);
|
||||||
Setting.setValue('security.biometricsEnabled', enableBiometrics);
|
Setting.setValue('security.biometricsEnabled', enableBiometrics);
|
||||||
@@ -59,6 +73,8 @@ export default (props: Props) => {
|
|||||||
type: 'BIOMETRICS_DONE_SET',
|
type: 'BIOMETRICS_DONE_SET',
|
||||||
value: true,
|
value: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logger.info('complete: end');
|
||||||
};
|
};
|
||||||
|
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
@@ -77,7 +93,7 @@ export default (props: Props) => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}, [initialPromptDone, props.sensorInfo.supportedSensors, display, props.dispatch]);
|
}, [initialPromptDone, display, props.dispatch]);
|
||||||
|
|
||||||
const windowSize = useMemo(() => {
|
const windowSize = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
@@ -87,12 +103,18 @@ export default (props: Props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
logger.info('effect 1: start');
|
||||||
|
|
||||||
if (!display) {
|
if (!display) {
|
||||||
|
logger.info('effect 1: display', display);
|
||||||
|
|
||||||
props.dispatch({
|
props.dispatch({
|
||||||
type: 'BIOMETRICS_DONE_SET',
|
type: 'BIOMETRICS_DONE_SET',
|
||||||
value: true,
|
value: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info('effect 1: end');
|
||||||
}, [display, props.dispatch]);
|
}, [display, props.dispatch]);
|
||||||
|
|
||||||
const renderTryAgainButton = () => {
|
const renderTryAgainButton = () => {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import Logger from '@joplin/lib/Logger';
|
||||||
|
import FingerprintScanner, { Errors } from 'react-native-fingerprint-scanner';
|
||||||
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
|
||||||
|
const logger = Logger.create('biometricAuthenticate');
|
||||||
|
|
||||||
|
export default async () => {
|
||||||
|
try {
|
||||||
|
logger.info('Authenticate...');
|
||||||
|
await FingerprintScanner.authenticate({ description: _('Verify your identity') });
|
||||||
|
logger.info('Authenticate done');
|
||||||
|
} catch (error) {
|
||||||
|
const errorName = (error as Errors).name;
|
||||||
|
|
||||||
|
let errorMessage = error.message;
|
||||||
|
if (errorName === 'FingerprintScannerNotEnrolled' || errorName === 'FingerprintScannerNotAvailable') {
|
||||||
|
errorMessage = _('Biometric unlock is not setup on the device. Please set it up in order to unlock Joplin. If the device is on lockout, consider switching it off and on to reset biometrics scanning.');
|
||||||
|
}
|
||||||
|
|
||||||
|
error.message = _('Could not verify your identify: %s', errorMessage);
|
||||||
|
|
||||||
|
logger.warn(error);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
FingerprintScanner.release();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import Logger from '@joplin/lib/Logger';
|
||||||
import Setting from '@joplin/lib/models/Setting';
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
import FingerprintScanner from 'react-native-fingerprint-scanner';
|
import FingerprintScanner from 'react-native-fingerprint-scanner';
|
||||||
|
const logger = Logger.create('sensorInfo');
|
||||||
|
|
||||||
export interface SensorInfo {
|
export interface SensorInfo {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -12,6 +14,9 @@ export default async (): Promise<SensorInfo> => {
|
|||||||
// FingerprintScanner scanner calls, since it seems they can fail and freeze
|
// FingerprintScanner scanner calls, since it seems they can fail and freeze
|
||||||
// the app.
|
// the app.
|
||||||
|
|
||||||
|
logger.info('Start');
|
||||||
|
logger.info('security.biometricsEnabled', Setting.value('security.biometricsEnabled'));
|
||||||
|
|
||||||
if (!Setting.value('security.biometricsEnabled')) {
|
if (!Setting.value('security.biometricsEnabled')) {
|
||||||
return {
|
return {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@@ -24,7 +29,21 @@ export default async (): Promise<SensorInfo> => {
|
|||||||
let supportedSensors = '';
|
let supportedSensors = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
logger.info('Getting isSensorAvailable...');
|
||||||
|
|
||||||
|
// Note: If `isSensorAvailable()` doesn't return anything, it seems we
|
||||||
|
// could assume that biometrics are not setup on the device, and thus we
|
||||||
|
// can unlock the app. However that's not always correct - on some
|
||||||
|
// devices (eg Galaxy S22), `isSensorAvailable()` will return nothing if
|
||||||
|
// the device is on lockout - i.e. if the user gave the wrong
|
||||||
|
// fingerprint multiple times.
|
||||||
|
//
|
||||||
|
// So we definitely can't unlock the app in that case, and it means
|
||||||
|
// `isSensorAvailable()` is pretty much useless. Instead we ask for
|
||||||
|
// fingerprint when the user turns on the feature and at that point we
|
||||||
|
// know if the device supports biometrics or not.
|
||||||
const result = await FingerprintScanner.isSensorAvailable();
|
const result = await FingerprintScanner.isSensorAvailable();
|
||||||
|
logger.info('isSensorAvailable result', result);
|
||||||
supportedSensors = result;
|
supportedSensors = result;
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
@@ -34,7 +53,7 @@ export default async (): Promise<SensorInfo> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Could not check for biometrics sensor:', error);
|
logger.warn('Could not check for biometrics sensor:', error);
|
||||||
Setting.setValue('security.biometricsSupportedSensors', '');
|
Setting.setValue('security.biometricsSupportedSensors', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ const testCases: testCase[] = [
|
|||||||
|
|
||||||
describe('getResponsiveValue', () => {
|
describe('getResponsiveValue', () => {
|
||||||
|
|
||||||
test('Should throw exception if value map is an empty object', () => {
|
test('should throw exception if value map is an empty object', () => {
|
||||||
const input = {};
|
const input = {};
|
||||||
expect(() => getResponsiveValue(input)).toThrow('valueMap cannot be an empty object!');
|
expect(() => getResponsiveValue(input)).toThrow('valueMap cannot be an empty object!');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Should return correct values', () => {
|
test('should return correct values', () => {
|
||||||
const mockReturnValues = [
|
const mockReturnValues = [
|
||||||
{ width: 400 },
|
{ width: 400 },
|
||||||
{ width: 480 },
|
{ width: 480 },
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ const { themeStyle } = require('../global-style.js');
|
|||||||
const shared = require('@joplin/lib/components/shared/config-shared.js');
|
const shared = require('@joplin/lib/components/shared/config-shared.js');
|
||||||
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
||||||
import { openDocumentTree } from '@joplin/react-native-saf-x';
|
import { openDocumentTree } from '@joplin/react-native-saf-x';
|
||||||
|
import biometricAuthenticate from '../biometrics/biometricAuthenticate';
|
||||||
|
|
||||||
class ConfigScreenComponent extends BaseScreenComponent {
|
class ConfigScreenComponent extends BaseScreenComponent {
|
||||||
static navigationOptions(): any {
|
public static navigationOptions(): any {
|
||||||
return { header: null };
|
return { header: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +201,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkFilesystemPermission() {
|
public async checkFilesystemPermission() {
|
||||||
if (Platform.OS !== 'android') {
|
if (Platform.OS !== 'android') {
|
||||||
// Not implemented yet
|
// Not implemented yet
|
||||||
return true;
|
return true;
|
||||||
@@ -212,11 +213,11 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
public UNSAFE_componentWillMount() {
|
||||||
this.setState({ settings: this.props.settings });
|
this.setState({ settings: this.props.settings });
|
||||||
}
|
}
|
||||||
|
|
||||||
styles() {
|
public styles() {
|
||||||
const themeId = this.props.themeId;
|
const themeId = this.props.themeId;
|
||||||
const theme = themeStyle(themeId);
|
const theme = themeStyle(themeId);
|
||||||
|
|
||||||
@@ -376,7 +377,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
|||||||
BackButtonService.removeHandler(this.handleBackButtonPress);
|
BackButtonService.removeHandler(this.handleBackButtonPress);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHeader(key: string, title: string) {
|
public renderHeader(key: string, title: string) {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
return (
|
return (
|
||||||
<View key={key} style={this.styles().headerWrapperStyle} onLayout={(event: any) => this.onHeaderLayout(key, event)}>
|
<View key={key} style={this.styles().headerWrapperStyle} onLayout={(event: any) => this.onHeaderLayout(key, event)}>
|
||||||
@@ -410,7 +411,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sectionToComponent(key: string, section: any, settings: any) {
|
public sectionToComponent(key: string, section: any, settings: any) {
|
||||||
const settingComps = [];
|
const settingComps = [];
|
||||||
|
|
||||||
for (let i = 0; i < section.metadatas.length; i++) {
|
for (let i = 0; i < section.metadatas.length; i++) {
|
||||||
@@ -463,7 +464,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
|||||||
<Text key="label" style={this.styles().switchSettingText}>
|
<Text key="label" style={this.styles().switchSettingText}>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
<Switch key="control" style={this.styles().switchSettingControl} trackColor={{ false: theme.dividerColor }} value={value} onValueChange={(value: any) => updateSettingValue(key, value)} />
|
<Switch key="control" style={this.styles().switchSettingControl} trackColor={{ false: theme.dividerColor }} value={value} onValueChange={(value: any) => void updateSettingValue(key, value)} />
|
||||||
</View>
|
</View>
|
||||||
{descriptionComp}
|
{descriptionComp}
|
||||||
</View>
|
</View>
|
||||||
@@ -474,13 +475,39 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
|||||||
return !hasDescription ? this.styles().settingContainer : this.styles().settingContainerNoBottomBorder;
|
return !hasDescription ? this.styles().settingContainer : this.styles().settingContainerNoBottomBorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
settingToComponent(key: string, value: any) {
|
private async handleSetting(key: string, value: any): Promise<boolean> {
|
||||||
|
// When the user tries to enable biometrics unlock, we ask for the
|
||||||
|
// fingerprint or Face ID, and if it's correct we save immediately. If
|
||||||
|
// it's not, we don't turn on the setting.
|
||||||
|
if (key === 'security.biometricsEnabled' && !!value) {
|
||||||
|
try {
|
||||||
|
await biometricAuthenticate();
|
||||||
|
shared.updateSettingValue(this, key, value);
|
||||||
|
await this.saveButton_press();
|
||||||
|
} catch (error) {
|
||||||
|
shared.updateSettingValue(this, key, false);
|
||||||
|
Alert.alert(error.message);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'security.biometricsEnabled' && !value) {
|
||||||
|
shared.updateSettingValue(this, key, value);
|
||||||
|
await this.saveButton_press();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public settingToComponent(key: string, value: any) {
|
||||||
const themeId = this.props.themeId;
|
const themeId = this.props.themeId;
|
||||||
const theme = themeStyle(themeId);
|
const theme = themeStyle(themeId);
|
||||||
const output: any = null;
|
const output: any = null;
|
||||||
|
|
||||||
const updateSettingValue = (key: string, value: any) => {
|
const updateSettingValue = async (key: string, value: any) => {
|
||||||
return shared.updateSettingValue(this, key, value);
|
const handled = await this.handleSetting(key, value);
|
||||||
|
if (!handled) shared.updateSettingValue(this, key, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const md = Setting.settingMetadata(key);
|
const md = Setting.settingMetadata(key);
|
||||||
@@ -517,7 +544,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
|||||||
fontSize: theme.fontSize,
|
fontSize: theme.fontSize,
|
||||||
}}
|
}}
|
||||||
onValueChange={(itemValue: string) => {
|
onValueChange={(itemValue: string) => {
|
||||||
updateSettingValue(key, itemValue);
|
void updateSettingValue(key, itemValue);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -553,7 +580,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
|||||||
</Text>
|
</Text>
|
||||||
<View style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', flex: 1 }}>
|
<View style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', flex: 1 }}>
|
||||||
<Text style={this.styles().sliderUnits}>{unitLabel}</Text>
|
<Text style={this.styles().sliderUnits}>{unitLabel}</Text>
|
||||||
<Slider key="control" style={{ flex: 1 }} step={md.step} minimumValue={minimum} maximumValue={maximum} value={value} onValueChange={value => updateSettingValue(key, value)} />
|
<Slider key="control" style={{ flex: 1 }} step={md.step} minimumValue={minimum} maximumValue={maximum} value={value} onValueChange={value => void updateSettingValue(key, value)} />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -577,7 +604,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
|||||||
<Text key="label" style={this.styles().settingText}>
|
<Text key="label" style={this.styles().settingText}>
|
||||||
{md.label()}
|
{md.label()}
|
||||||
</Text>
|
</Text>
|
||||||
<TextInput autoCorrect={false} autoComplete="off" selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} autoCapitalize="none" key="control" style={this.styles().settingControl} value={value} onChangeText={(value: any) => updateSettingValue(key, value)} secureTextEntry={!!md.secure} />
|
<TextInput autoCorrect={false} autoComplete="off" selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} autoCapitalize="none" key="control" style={this.styles().settingControl} value={value} onChangeText={(value: any) => void updateSettingValue(key, value)} secureTextEntry={!!md.secure} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -599,7 +626,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const settings = this.state.settings;
|
const settings = this.state.settings;
|
||||||
|
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ const emptyArray: any[] = [];
|
|||||||
const logger = Logger.create('screens/Note');
|
const logger = Logger.create('screens/Note');
|
||||||
|
|
||||||
class NoteScreenComponent extends BaseScreenComponent {
|
class NoteScreenComponent extends BaseScreenComponent {
|
||||||
static navigationOptions(): any {
|
public static navigationOptions(): any {
|
||||||
return { header: null };
|
return { header: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
this.state = {
|
this.state = {
|
||||||
note: Note.new(),
|
note: Note.new(),
|
||||||
@@ -280,7 +280,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
screenHeader_undoButtonPress() {
|
private screenHeader_undoButtonPress() {
|
||||||
if (this.useEditorBeta()) {
|
if (this.useEditorBeta()) {
|
||||||
this.editorRef.current.undo();
|
this.editorRef.current.undo();
|
||||||
} else {
|
} else {
|
||||||
@@ -288,7 +288,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
screenHeader_redoButtonPress() {
|
private screenHeader_redoButtonPress() {
|
||||||
if (this.useEditorBeta()) {
|
if (this.useEditorBeta()) {
|
||||||
this.editorRef.current.redo();
|
this.editorRef.current.redo();
|
||||||
} else {
|
} else {
|
||||||
@@ -296,13 +296,13 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
undoState(noteBody: string = null) {
|
public undoState(noteBody: string = null) {
|
||||||
return {
|
return {
|
||||||
body: noteBody === null ? this.state.note.body : noteBody,
|
body: noteBody === null ? this.state.note.body : noteBody,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
styles() {
|
public styles() {
|
||||||
const themeId = this.props.themeId;
|
const themeId = this.props.themeId;
|
||||||
const theme = themeStyle(themeId);
|
const theme = themeStyle(themeId);
|
||||||
|
|
||||||
@@ -392,11 +392,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
return this.styles_[cacheKey];
|
return this.styles_[cacheKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
isModified() {
|
public isModified() {
|
||||||
return shared.isModified(this);
|
return shared.isModified(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestGeoLocationPermissions() {
|
public async requestGeoLocationPermissions() {
|
||||||
if (!Setting.value('trackLocation')) return;
|
if (!Setting.value('trackLocation')) return;
|
||||||
|
|
||||||
const response = await checkPermissions(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, {
|
const response = await checkPermissions(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, {
|
||||||
@@ -413,7 +413,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
public async componentDidMount() {
|
||||||
BackButtonService.addHandler(this.backHandler);
|
BackButtonService.addHandler(this.backHandler);
|
||||||
NavService.addHandler(this.navHandler);
|
NavService.addHandler(this.navHandler);
|
||||||
|
|
||||||
@@ -436,11 +436,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
void this.requestGeoLocationPermissions();
|
void this.requestGeoLocationPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMarkForDownload(event: any) {
|
public onMarkForDownload(event: any) {
|
||||||
void ResourceFetcher.instance().markForDownload(event.resourceId);
|
void ResourceFetcher.instance().markForDownload(event.resourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: any) {
|
public componentDidUpdate(prevProps: any) {
|
||||||
if (this.doFocusUpdate_) {
|
if (this.doFocusUpdate_) {
|
||||||
this.doFocusUpdate_ = false;
|
this.doFocusUpdate_ = false;
|
||||||
this.focusUpdate();
|
this.focusUpdate();
|
||||||
@@ -454,7 +454,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
BackButtonService.removeHandler(this.backHandler);
|
BackButtonService.removeHandler(this.backHandler);
|
||||||
NavService.removeHandler(this.navHandler);
|
NavService.removeHandler(this.navHandler);
|
||||||
|
|
||||||
@@ -467,13 +467,13 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
if (this.undoRedoService_) this.undoRedoService_.off('stackChange', this.undoRedoService_stackChange);
|
if (this.undoRedoService_) this.undoRedoService_.off('stackChange', this.undoRedoService_stackChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
title_changeText(text: string) {
|
private title_changeText(text: string) {
|
||||||
shared.noteComponent_change(this, 'title', text);
|
shared.noteComponent_change(this, 'title', text);
|
||||||
this.setState({ newAndNoTitleChangeNoteId: null });
|
this.setState({ newAndNoTitleChangeNoteId: null });
|
||||||
this.scheduleSave();
|
this.scheduleSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
body_changeText(text: string) {
|
private body_changeText(text: string) {
|
||||||
if (!this.undoRedoService_.canUndo) {
|
if (!this.undoRedoService_.canUndo) {
|
||||||
this.undoRedoService_.push(this.undoState());
|
this.undoRedoService_.push(this.undoState());
|
||||||
} else {
|
} else {
|
||||||
@@ -484,7 +484,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
this.scheduleSave();
|
this.scheduleSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
body_selectionChange(event: any) {
|
private body_selectionChange(event: any) {
|
||||||
if (this.useEditorBeta()) {
|
if (this.useEditorBeta()) {
|
||||||
this.selection = event.selection;
|
this.selection = event.selection;
|
||||||
} else {
|
} else {
|
||||||
@@ -492,34 +492,34 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
makeSaveAction() {
|
public makeSaveAction() {
|
||||||
return async () => {
|
return async () => {
|
||||||
return shared.saveNoteButton_press(this, null, null);
|
return shared.saveNoteButton_press(this, null, null);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
saveActionQueue(noteId: string) {
|
public saveActionQueue(noteId: string) {
|
||||||
if (!this.saveActionQueues_[noteId]) {
|
if (!this.saveActionQueues_[noteId]) {
|
||||||
this.saveActionQueues_[noteId] = new AsyncActionQueue(500);
|
this.saveActionQueues_[noteId] = new AsyncActionQueue(500);
|
||||||
}
|
}
|
||||||
return this.saveActionQueues_[noteId];
|
return this.saveActionQueues_[noteId];
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleSave() {
|
public scheduleSave() {
|
||||||
this.saveActionQueue(this.state.note.id).push(this.makeSaveAction());
|
this.saveActionQueue(this.state.note.id).push(this.makeSaveAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveNoteButton_press(folderId: string = null) {
|
private async saveNoteButton_press(folderId: string = null) {
|
||||||
await shared.saveNoteButton_press(this, folderId, null);
|
await shared.saveNoteButton_press(this, folderId, null);
|
||||||
|
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveOneProperty(name: string, value: any) {
|
public async saveOneProperty(name: string, value: any) {
|
||||||
await shared.saveOneProperty(this, name, value);
|
await shared.saveOneProperty(this, name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteNote_onPress() {
|
private async deleteNote_onPress() {
|
||||||
const note = this.state.note;
|
const note = this.state.note;
|
||||||
if (!note.id) return;
|
if (!note.id) return;
|
||||||
|
|
||||||
@@ -546,7 +546,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async imageDimensions(uri: string) {
|
public async imageDimensions(uri: string) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Image.getSize(
|
Image.getSize(
|
||||||
uri,
|
uri,
|
||||||
@@ -560,7 +560,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async resizeImage(localFilePath: string, targetPath: string, mimeType: string) {
|
public async resizeImage(localFilePath: string, targetPath: string, mimeType: string) {
|
||||||
const maxSize = Resource.IMAGE_MAX_DIMENSION;
|
const maxSize = Resource.IMAGE_MAX_DIMENSION;
|
||||||
|
|
||||||
const dimensions: any = await this.imageDimensions(localFilePath);
|
const dimensions: any = await this.imageDimensions(localFilePath);
|
||||||
@@ -609,7 +609,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async attachFile(pickerResponse: any, fileType: string) {
|
public async attachFile(pickerResponse: any, fileType: string) {
|
||||||
if (!pickerResponse) {
|
if (!pickerResponse) {
|
||||||
// User has cancelled
|
// User has cancelled
|
||||||
return;
|
return;
|
||||||
@@ -727,11 +727,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
takePhoto_onPress() {
|
private takePhoto_onPress() {
|
||||||
this.setState({ showCamera: true });
|
this.setState({ showCamera: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
cameraView_onPhoto(data: any) {
|
private cameraView_onPhoto(data: any) {
|
||||||
void this.attachFile(
|
void this.attachFile(
|
||||||
{
|
{
|
||||||
uri: data.uri,
|
uri: data.uri,
|
||||||
@@ -743,7 +743,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
this.setState({ showCamera: false });
|
this.setState({ showCamera: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
cameraView_onCancel() {
|
private cameraView_onCancel() {
|
||||||
this.setState({ showCamera: false });
|
this.setState({ showCamera: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -754,34 +754,30 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleIsTodo_onPress() {
|
private toggleIsTodo_onPress() {
|
||||||
shared.toggleIsTodo_onPress(this);
|
shared.toggleIsTodo_onPress(this);
|
||||||
|
|
||||||
this.scheduleSave();
|
this.scheduleSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
tags_onPress() {
|
private tags_onPress() {
|
||||||
if (!this.state.note || !this.state.note.id) return;
|
if (!this.state.note || !this.state.note.id) return;
|
||||||
|
|
||||||
this.setState({ noteTagDialogShown: true });
|
this.setState({ noteTagDialogShown: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async share_onPress() {
|
private async share_onPress() {
|
||||||
await Share.share({
|
await Share.share({
|
||||||
message: `${this.state.note.title}\n\n${this.state.note.body}`,
|
message: `${this.state.note.title}\n\n${this.state.note.body}`,
|
||||||
title: this.state.note.title,
|
title: this.state.note.title,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
properties_onPress() {
|
private properties_onPress() {
|
||||||
this.props.dispatch({ type: 'SIDE_MENU_OPEN' });
|
this.props.dispatch({ type: 'SIDE_MENU_OPEN' });
|
||||||
}
|
}
|
||||||
|
|
||||||
setAlarm_onPress() {
|
public async onAlarmDialogAccept(date: Date) {
|
||||||
this.setState({ alarmDialogShown: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
async onAlarmDialogAccept(date: Date) {
|
|
||||||
const newNote = Object.assign({}, this.state.note);
|
const newNote = Object.assign({}, this.state.note);
|
||||||
newNote.todo_due = date ? date.getTime() : 0;
|
newNote.todo_due = date ? date.getTime() : 0;
|
||||||
|
|
||||||
@@ -790,11 +786,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
this.setState({ alarmDialogShown: false });
|
this.setState({ alarmDialogShown: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
onAlarmDialogReject() {
|
public onAlarmDialogReject() {
|
||||||
this.setState({ alarmDialogShown: false });
|
this.setState({ alarmDialogShown: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
async showOnMap_onPress() {
|
private async showOnMap_onPress() {
|
||||||
if (!this.state.note.id) return;
|
if (!this.state.note.id) return;
|
||||||
|
|
||||||
const note = await Note.load(this.state.note.id);
|
const note = await Note.load(this.state.note.id);
|
||||||
@@ -807,7 +803,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async showSource_onPress() {
|
private async showSource_onPress() {
|
||||||
if (!this.state.note.id) return;
|
if (!this.state.note.id) return;
|
||||||
|
|
||||||
const note = await Note.load(this.state.note.id);
|
const note = await Note.load(this.state.note.id);
|
||||||
@@ -818,12 +814,12 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
copyMarkdownLink_onPress() {
|
private copyMarkdownLink_onPress() {
|
||||||
const note = this.state.note;
|
const note = this.state.note;
|
||||||
Clipboard.setString(Note.markdownTag(note));
|
Clipboard.setString(Note.markdownTag(note));
|
||||||
}
|
}
|
||||||
|
|
||||||
sideMenuOptions() {
|
public sideMenuOptions() {
|
||||||
const note = this.state.note;
|
const note = this.state.note;
|
||||||
if (!note) return [];
|
if (!note) return [];
|
||||||
|
|
||||||
@@ -854,7 +850,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
async showAttachMenu() {
|
public async showAttachMenu() {
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
|
||||||
// On iOS, it will show "local files", which means certain files saved from the browser
|
// On iOS, it will show "local files", which means certain files saved from the browser
|
||||||
@@ -875,7 +871,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
if (buttonId === 'attachPhoto') void this.attachPhoto_onPress();
|
if (buttonId === 'attachPhoto') void this.attachPhoto_onPress();
|
||||||
}
|
}
|
||||||
|
|
||||||
menuOptions() {
|
public menuOptions() {
|
||||||
const note = this.state.note;
|
const note = this.state.note;
|
||||||
const isTodo = note && !!note.is_todo;
|
const isTodo = note && !!note.is_todo;
|
||||||
const isSaved = note && note.id;
|
const isSaved = note && note.id;
|
||||||
@@ -959,11 +955,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
async todoCheckbox_change(checked: boolean) {
|
private async todoCheckbox_change(checked: boolean) {
|
||||||
await this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0);
|
await this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleFocusUpdate() {
|
public scheduleFocusUpdate() {
|
||||||
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
|
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
|
||||||
|
|
||||||
this.focusUpdateIID_ = shim.setTimeout(() => {
|
this.focusUpdateIID_ = shim.setTimeout(() => {
|
||||||
@@ -972,7 +968,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
focusUpdate() {
|
public focusUpdate() {
|
||||||
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
|
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
|
||||||
this.focusUpdateIID_ = null;
|
this.focusUpdateIID_ = null;
|
||||||
|
|
||||||
@@ -990,7 +986,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
async folderPickerOptions_valueChanged(itemValue: any) {
|
private async folderPickerOptions_valueChanged(itemValue: any) {
|
||||||
const note = this.state.note;
|
const note = this.state.note;
|
||||||
const isProvisionalNote = this.props.provisionalNoteIds.includes(note.id);
|
const isProvisionalNote = this.props.provisionalNoteIds.includes(note.id);
|
||||||
|
|
||||||
@@ -1011,7 +1007,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
folderPickerOptions() {
|
public folderPickerOptions() {
|
||||||
const options = {
|
const options = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
selectedFolderId: this.state.folder ? this.state.folder.id : null,
|
selectedFolderId: this.state.folder ? this.state.folder.id : null,
|
||||||
@@ -1024,7 +1020,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
return this.folderPickerOptions_;
|
return this.folderPickerOptions_;
|
||||||
}
|
}
|
||||||
|
|
||||||
onBodyViewerLoadEnd() {
|
public onBodyViewerLoadEnd() {
|
||||||
shim.setTimeout(() => {
|
shim.setTimeout(() => {
|
||||||
this.setState({ HACK_webviewLoadingState: 1 });
|
this.setState({ HACK_webviewLoadingState: 1 });
|
||||||
shim.setTimeout(() => {
|
shim.setTimeout(() => {
|
||||||
@@ -1033,11 +1029,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}, 5);
|
}, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
onBodyViewerCheckboxChange(newBody: string) {
|
public onBodyViewerCheckboxChange(newBody: string) {
|
||||||
void this.saveOneProperty('body', newBody);
|
void this.saveOneProperty('body', newBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
if (this.state.isLoading) {
|
if (this.state.isLoading) {
|
||||||
return (
|
return (
|
||||||
<View style={this.styles().screen}>
|
<View style={this.styles().screen}>
|
||||||
@@ -1122,6 +1118,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
|
|
||||||
bodyComponent = <NoteEditor
|
bodyComponent = <NoteEditor
|
||||||
ref={this.editorRef}
|
ref={this.editorRef}
|
||||||
|
toolbarEnabled={this.props.toolbarEnabled}
|
||||||
themeId={this.props.themeId}
|
themeId={this.props.themeId}
|
||||||
initialText={note.body}
|
initialText={note.body}
|
||||||
initialSelection={this.selection}
|
initialSelection={this.selection}
|
||||||
@@ -1235,6 +1232,7 @@ const NoteScreen = connect((state: any) => {
|
|||||||
themeId: state.settings.theme,
|
themeId: state.settings.theme,
|
||||||
editorFont: [state.settings['style.editor.fontFamily']],
|
editorFont: [state.settings['style.editor.fontFamily']],
|
||||||
editorFontSize: state.settings['style.editor.fontSize'],
|
editorFontSize: state.settings['style.editor.fontSize'],
|
||||||
|
toolbarEnabled: state.settings['editor.mobile.toolbarEnabled'],
|
||||||
ftsEnabled: state.settings['db.ftsEnabled'],
|
ftsEnabled: state.settings['db.ftsEnabled'],
|
||||||
sharedData: state.sharedData,
|
sharedData: state.sharedData,
|
||||||
showSideMenu: state.showSideMenu,
|
showSideMenu: state.showSideMenu,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
|||||||
|
|
||||||
private onAppStateChangeSub_: NativeEventSubscription = null;
|
private onAppStateChangeSub_: NativeEventSubscription = null;
|
||||||
|
|
||||||
constructor() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.onAppStateChange_ = async () => {
|
this.onAppStateChange_ = async () => {
|
||||||
@@ -78,7 +78,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
styles() {
|
public styles() {
|
||||||
if (!this.styles_) this.styles_ = {};
|
if (!this.styles_) this.styles_ = {};
|
||||||
const themeId = this.props.themeId;
|
const themeId = this.props.themeId;
|
||||||
const cacheKey = themeId;
|
const cacheKey = themeId;
|
||||||
@@ -96,24 +96,24 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
|||||||
return this.styles_[cacheKey];
|
return this.styles_[cacheKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
public async componentDidMount() {
|
||||||
BackButtonService.addHandler(this.backHandler);
|
BackButtonService.addHandler(this.backHandler);
|
||||||
await this.refreshNotes();
|
await this.refreshNotes();
|
||||||
this.onAppStateChangeSub_ = RNAppState.addEventListener('change', this.onAppStateChange_);
|
this.onAppStateChangeSub_ = RNAppState.addEventListener('change', this.onAppStateChange_);
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentWillUnmount() {
|
public async componentWillUnmount() {
|
||||||
if (this.onAppStateChangeSub_) this.onAppStateChangeSub_.remove();
|
if (this.onAppStateChangeSub_) this.onAppStateChangeSub_.remove();
|
||||||
BackButtonService.removeHandler(this.backHandler);
|
BackButtonService.removeHandler(this.backHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidUpdate(prevProps: any) {
|
public async componentDidUpdate(prevProps: any) {
|
||||||
if (prevProps.notesOrder !== this.props.notesOrder || prevProps.selectedFolderId !== this.props.selectedFolderId || prevProps.selectedTagId !== this.props.selectedTagId || prevProps.selectedSmartFilterId !== this.props.selectedSmartFilterId || prevProps.notesParentType !== this.props.notesParentType) {
|
if (prevProps.notesOrder !== this.props.notesOrder || prevProps.selectedFolderId !== this.props.selectedFolderId || prevProps.selectedTagId !== this.props.selectedTagId || prevProps.selectedSmartFilterId !== this.props.selectedSmartFilterId || prevProps.notesParentType !== this.props.notesParentType) {
|
||||||
await this.refreshNotes(this.props);
|
await this.refreshNotes(this.props);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshNotes(props: any = null) {
|
public async refreshNotes(props: any = null) {
|
||||||
if (props === null) props = this.props;
|
if (props === null) props = this.props;
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
@@ -149,36 +149,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteFolder_onPress(folderId: string) {
|
public newNoteNavigate = async (folderId: string, isTodo: boolean) => {
|
||||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
|
||||||
dialogs.confirm(this, _('Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.')).then((ok: boolean) => {
|
|
||||||
if (!ok) return;
|
|
||||||
|
|
||||||
Folder.delete(folderId)
|
|
||||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
|
||||||
.then(() => {
|
|
||||||
this.props.dispatch({
|
|
||||||
type: 'NAV_GO',
|
|
||||||
routeName: 'Notes',
|
|
||||||
smartFilterId: 'c3176726992c11e9ac940492261af972',
|
|
||||||
});
|
|
||||||
})
|
|
||||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
|
||||||
.catch(error => {
|
|
||||||
alert(error.message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
editFolder_onPress(folderId: string) {
|
|
||||||
this.props.dispatch({
|
|
||||||
type: 'NAV_GO',
|
|
||||||
routeName: 'Folder',
|
|
||||||
folderId: folderId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
newNoteNavigate = async (folderId: string, isTodo: boolean) => {
|
|
||||||
const newNote = await Note.save({
|
const newNote = await Note.save({
|
||||||
parent_id: folderId,
|
parent_id: folderId,
|
||||||
is_todo: isTodo ? 1 : 0,
|
is_todo: isTodo ? 1 : 0,
|
||||||
@@ -191,7 +162,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
parentItem(props: any = null) {
|
public parentItem(props: any = null) {
|
||||||
if (!props) props = this.props;
|
if (!props) props = this.props;
|
||||||
|
|
||||||
let output = null;
|
let output = null;
|
||||||
@@ -208,7 +179,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
folderPickerOptions() {
|
public folderPickerOptions() {
|
||||||
const options = {
|
const options = {
|
||||||
enabled: this.props.noteSelectionEnabled,
|
enabled: this.props.noteSelectionEnabled,
|
||||||
mustSelect: true,
|
mustSelect: true,
|
||||||
@@ -220,7 +191,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
|||||||
return this.folderPickerOptions_;
|
return this.folderPickerOptions_;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const parent = this.parentItem();
|
const parent = this.parentItem();
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ class SearchScreenComponent extends BaseScreenComponent {
|
|||||||
private styles_: any = {};
|
private styles_: any = {};
|
||||||
private scheduleSearchTimer_: any = null;
|
private scheduleSearchTimer_: any = null;
|
||||||
|
|
||||||
static navigationOptions() {
|
public static navigationOptions() {
|
||||||
return { header: null } as any;
|
return { header: null } as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
this.state = {
|
this.state = {
|
||||||
query: '',
|
query: '',
|
||||||
@@ -39,7 +39,7 @@ class SearchScreenComponent extends BaseScreenComponent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
styles() {
|
public styles() {
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
if (this.styles_[this.props.themeId]) return this.styles_[this.props.themeId];
|
if (this.styles_[this.props.themeId]) return this.styles_[this.props.themeId];
|
||||||
@@ -72,17 +72,17 @@ class SearchScreenComponent extends BaseScreenComponent {
|
|||||||
return this.styles_[this.props.themeId];
|
return this.styles_[this.props.themeId];
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount() {
|
||||||
this.setState({ query: this.props.query });
|
this.setState({ query: this.props.query });
|
||||||
void this.refreshSearch(this.props.query);
|
void this.refreshSearch(this.props.query);
|
||||||
this.isMounted_ = true;
|
this.isMounted_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
this.isMounted_ = false;
|
this.isMounted_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearButton_press() {
|
private clearButton_press() {
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: 'SEARCH_QUERY',
|
type: 'SEARCH_QUERY',
|
||||||
query: '',
|
query: '',
|
||||||
@@ -92,7 +92,7 @@ class SearchScreenComponent extends BaseScreenComponent {
|
|||||||
void this.refreshSearch('');
|
void this.refreshSearch('');
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshSearch(query: string = null) {
|
public async refreshSearch(query: string = null) {
|
||||||
if (!this.props.visible) return;
|
if (!this.props.visible) return;
|
||||||
|
|
||||||
query = gotoAnythingStyleQuery(query);
|
query = gotoAnythingStyleQuery(query);
|
||||||
@@ -130,7 +130,7 @@ class SearchScreenComponent extends BaseScreenComponent {
|
|||||||
this.setState({ notes: notes });
|
this.setState({ notes: notes });
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleSearch() {
|
public scheduleSearch() {
|
||||||
if (this.scheduleSearchTimer_) clearTimeout(this.scheduleSearchTimer_);
|
if (this.scheduleSearchTimer_) clearTimeout(this.scheduleSearchTimer_);
|
||||||
|
|
||||||
this.scheduleSearchTimer_ = setTimeout(() => {
|
this.scheduleSearchTimer_ = setTimeout(() => {
|
||||||
@@ -139,7 +139,7 @@ class SearchScreenComponent extends BaseScreenComponent {
|
|||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchTextInput_changeText(text: string) {
|
private searchTextInput_changeText(text: string) {
|
||||||
this.setState({ query: text });
|
this.setState({ query: text });
|
||||||
|
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
@@ -150,7 +150,7 @@ class SearchScreenComponent extends BaseScreenComponent {
|
|||||||
this.scheduleSearch();
|
this.scheduleSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
if (!this.isMounted_) return null;
|
if (!this.isMounted_) return null;
|
||||||
|
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user