diff --git a/.eslintignore b/.eslintignore index 62b4cdd85..d9e75125f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -405,6 +405,7 @@ packages/app-mobile/components/SideMenu.js packages/app-mobile/components/TextInput.js packages/app-mobile/components/app-nav.js packages/app-mobile/components/biometrics/BiometricPopup.js +packages/app-mobile/components/biometrics/biometricAuthenticate.js packages/app-mobile/components/biometrics/sensorInfo.js packages/app-mobile/components/getResponsiveValue.js packages/app-mobile/components/getResponsiveValue.test.js diff --git a/.github/workflows/github-actions-main.yml b/.github/workflows/github-actions-main.yml index 32fd6a286..36f3addcc 100644 --- a/.github/workflows/github-actions-main.yml +++ b/.github/workflows/github-actions-main.yml @@ -93,6 +93,7 @@ jobs: APPLE_ASC_PROVIDER: ${{ secrets.APPLE_ASC_PROVIDER }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} CSC_KEY_PASSWORD: ${{ secrets.APPLE_CSC_KEY_PASSWORD }} CSC_LINK: ${{ secrets.APPLE_CSC_LINK }} GH_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/.gitignore b/.gitignore index 07ac8254d..231023227 100644 --- a/.gitignore +++ b/.gitignore @@ -392,6 +392,7 @@ packages/app-mobile/components/SideMenu.js packages/app-mobile/components/TextInput.js packages/app-mobile/components/app-nav.js packages/app-mobile/components/biometrics/BiometricPopup.js +packages/app-mobile/components/biometrics/biometricAuthenticate.js packages/app-mobile/components/biometrics/sensorInfo.js packages/app-mobile/components/getResponsiveValue.js packages/app-mobile/components/getResponsiveValue.test.js diff --git a/README.md b/README.md index b06dcaae1..1f8382271 100644 --- a/README.md +++ b/README.md @@ -126,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) - [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) + - [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) - [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) diff --git a/gulpfile.js b/gulpfile.js index 8bd5d4a87..641e8b609 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,26 +1,52 @@ 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 = { - updateIgnoredTypeScriptBuild: require('./packages/tools/gulp/tasks/updateIgnoredTypeScriptBuild'), - buildCommandIndex: require('./packages/tools/gulp/tasks/buildCommandIndex'), completePublishAll: { fn: async () => { - await utils.execCommandVerbose('git', ['add', '-A']); - await utils.execCommandVerbose('git', ['commit', '-m', 'Releasing sub-packages']); + await execCommand('git', ['add', '-A']); + await execCommand('git', ['commit', '-m', 'Releasing sub-packages']); // Lerna does some unnecessary auth check that doesn't work with // automation tokens, thus the --no-verify-access. Automation token // is still used for access when publishing even with this flag // (publishing would fail otherwise). // 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 utils.execCommandVerbose('git', ['add', '-A']); - await utils.execCommandVerbose('git', ['commit', '-m', 'Lock file']); + await execCommand('yarn', ['install']); + await execCommand('git', ['add', '-A']); + await execCommand('git', ['commit', '-m', 'Lock file']); - await utils.execCommandVerbose('git', ['push']); + await execCommand('git', ['push']); }, }, build: { @@ -33,12 +59,14 @@ const tasks = { // faster, especially when having to rebuild after adding a // dependency. if (process.env.BUILD_SEQUENCIAL === '1') { - await utils.execCommandVerbose('yarn', ['run', 'buildSequential']); + await execCommand('yarn', ['run', 'buildSequential']); } 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); +} diff --git a/package.json b/package.json index 3bb7f32ed..75dfa02b1 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "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", "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/", "updateMarkdownDoc": "node ./packages/tools/updateMarkdownDoc", "updateNews": "node ./packages/tools/website/updateNews", @@ -53,7 +53,7 @@ "test-ci": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 2 run test-ci", "test": "yarn workspaces foreach --parallel --verbose --interlaced --jobs 2 run test", "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", "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\"" @@ -64,6 +64,7 @@ } }, "devDependencies": { + "@joplin/utils": "~2.11", "@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0", "@typescript-eslint/eslint-plugin": "5.48.2", "@typescript-eslint/parser": "5.48.2", @@ -74,6 +75,7 @@ "eslint-plugin-jest": "27.2.1", "eslint-plugin-promise": "6.1.1", "eslint-plugin-react": "7.32.0", + "execa": "5.1.1", "fs-extra": "11.1.1", "glob": "8.1.0", "gulp": "4.0.2", diff --git a/packages/app-clipper/manifest.json b/packages/app-clipper/manifest.json index ed33bd567..596464707 100644 --- a/packages/app-clipper/manifest.json +++ b/packages/app-clipper/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Joplin Web Clipper [DEV]", - "version": "2.11.0", + "version": "2.11.2", "description": "Capture and save web pages and screenshots from your browser to Joplin.", "homepage_url": "https://joplinapp.org", "content_security_policy": "script-src 'self'; object-src 'self'", diff --git a/packages/app-clipper/popup/package.json b/packages/app-clipper/popup/package.json index 3c966521c..63539f663 100644 --- a/packages/app-clipper/popup/package.json +++ b/packages/app-clipper/popup/package.json @@ -126,4 +126,4 @@ "react-app" ] } -} \ No newline at end of file +} diff --git a/packages/app-mobile/android/app/build.gradle b/packages/app-mobile/android/app/build.gradle index 6328968ac..57c0f304b 100644 --- a/packages/app-mobile/android/app/build.gradle +++ b/packages/app-mobile/android/app/build.gradle @@ -150,8 +150,8 @@ android { applicationId "net.cozic.joplin" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 2097685 - versionName "2.11.0" + versionCode 2097687 + versionName "2.11.2" // ndk { // abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64" // } diff --git a/packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.test.ts b/packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.test.ts index 572921885..34dc0784e 100644 --- a/packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.test.ts +++ b/packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.test.ts @@ -3,62 +3,57 @@ */ +import { EditorSettings } from '../types'; +import { initCodeMirror } from './CodeMirror'; +import { themeStyle } from '@joplin/lib/theme'; +import Setting from '@joplin/lib/models/Setting'; +import { forceParsing } from '@codemirror/language'; +import loadLangauges from './testUtil/loadLanguages'; -// Randomly fails on: -// -// > 42 | expect(headerLineContent.textContent).toBe(headerLineText); -// +import { expect, describe, it } from '@jest/globals'; +const createEditorSettings = (themeId: number) => { + const themeData = themeStyle(themeId); + const editorSettings: EditorSettings = { + katexEnabled: true, + spellcheckEnabled: true, + themeId, + themeData, + }; + + return editorSettings; +}; + describe('CodeMirror', () => { - it('should succeed', async () => { - expect(1).toBe(1); + // 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 () => { + const headerLineText = '# Testing...'; + const initialText = `${headerLineText}\nThis is a test.`; + const editorSettings = createEditorSettings(Setting.THEME_LIGHT); + + await loadLangauges(); + const editor = initCodeMirror(document.body, initialText, editorSettings); + + // Force the generation of the syntax tree now. + forceParsing(editor.editor); + + // CodeMirror nests the tag that styles the header within .cm-headerLine: + //
Testing...
+ const headerLineContent = document.body.querySelector('.cm-headerLine > span')!; + + + expect(headerLineContent.textContent).toBe(headerLineText); + + const style = getComputedStyle(headerLineContent); + expect(style.borderBottom).not.toBe(''); + expect(style.fontSize).toBe('1.6em'); }); }); - - - -// import { EditorSettings } from '../types'; -// import { initCodeMirror } from './CodeMirror'; -// import { themeStyle } from '@joplin/lib/theme'; -// import Setting from '@joplin/lib/models/Setting'; -// import { forceParsing } from '@codemirror/language'; -// import loadLangauges from './testUtil/loadLanguages'; - - -// const createEditorSettings = (themeId: number) => { -// const themeData = themeStyle(themeId); -// const editorSettings: EditorSettings = { -// katexEnabled: true, -// spellcheckEnabled: true, -// themeId, -// themeData, -// }; - -// return editorSettings; -// }; - -// describe('CodeMirror', () => { -// it('should give headings a different style', async () => { -// const headerLineText = '# Testing...'; -// const initialText = `${headerLineText}\nThis is a test.`; -// const editorSettings = createEditorSettings(Setting.THEME_LIGHT); - -// await loadLangauges(); -// const editor = initCodeMirror(document.body, initialText, editorSettings); - -// // Force the generation of the syntax tree now. -// forceParsing(editor.editor); - -// // CodeMirror nests the tag that styles the header within .cm-headerLine: -// //
Testing...
-// const headerLineContent = document.body.querySelector('.cm-headerLine > span')!; - - -// expect(headerLineContent.textContent).toBe(headerLineText); - -// const style = getComputedStyle(headerLineContent); -// expect(style.borderBottom).not.toBe(''); -// expect(style.fontSize).toBe('1.6em'); -// }); -// }); diff --git a/packages/app-mobile/components/biometrics/BiometricPopup.tsx b/packages/app-mobile/components/biometrics/BiometricPopup.tsx index bdc028fff..1d5b19ab9 100644 --- a/packages/app-mobile/components/biometrics/BiometricPopup.tsx +++ b/packages/app-mobile/components/biometrics/BiometricPopup.tsx @@ -2,9 +2,12 @@ const React = require('react'); import Setting from '@joplin/lib/models/Setting'; import { useEffect, useMemo, useState } from 'react'; import { View, Dimensions, Alert, Button } from 'react-native'; -import FingerprintScanner from 'react-native-fingerprint-scanner'; import { SensorInfo } from './sensorInfo'; import { _ } from '@joplin/lib/locale'; +import Logger from '@joplin/lib/Logger'; +import biometricAuthenticate from './biometricAuthenticate'; + +const logger = Logger.create('BiometricPopup'); interface Props { 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 // feature after they've read the description in the config screen. 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); + logger.info('Render start'); + logger.info('initialPromptDone', initialPromptDone); + logger.info('display', display); + logger.info('tryBiometricsCheck', tryBiometricsCheck); + logger.info('props.sensorInfo', props.sensorInfo); + useEffect(() => { if (!display || !tryBiometricsCheck) return; const biometricsCheck = async () => { + logger.info('biometricsCheck: start'); + try { - await FingerprintScanner.authenticate({ description: _('Verify your identity') }); - setTryBiometricsCheck(false); + await biometricAuthenticate(); setDisplay(false); } catch (error) { - Alert.alert(_('Could not verify your identify'), error.message); - setTryBiometricsCheck(false); - } finally { - FingerprintScanner.release(); + Alert.alert(error.message); } + + setTryBiometricsCheck(false); + + logger.info('biometricsCheck: end'); }; void biometricsCheck(); @@ -45,6 +56,9 @@ export default (props: Props) => { if (!display) return; const complete = (enableBiometrics: boolean) => { + logger.info('complete: start'); + logger.info('complete: enableBiometrics:', enableBiometrics); + setInitialPromptDone(true); Setting.setValue('security.biometricsInitialPromptDone', true); Setting.setValue('security.biometricsEnabled', enableBiometrics); @@ -59,6 +73,8 @@ export default (props: Props) => { type: 'BIOMETRICS_DONE_SET', value: true, }); + + logger.info('complete: end'); }; Alert.alert( @@ -77,7 +93,7 @@ export default (props: Props) => { }, ] ); - }, [initialPromptDone, props.sensorInfo.supportedSensors, display, props.dispatch]); + }, [initialPromptDone, display, props.dispatch]); const windowSize = useMemo(() => { return { @@ -87,12 +103,18 @@ export default (props: Props) => { }, []); useEffect(() => { + logger.info('effect 1: start'); + if (!display) { + logger.info('effect 1: display', display); + props.dispatch({ type: 'BIOMETRICS_DONE_SET', value: true, }); } + + logger.info('effect 1: end'); }, [display, props.dispatch]); const renderTryAgainButton = () => { diff --git a/packages/app-mobile/components/biometrics/biometricAuthenticate.ts b/packages/app-mobile/components/biometrics/biometricAuthenticate.ts new file mode 100644 index 000000000..fffb4c318 --- /dev/null +++ b/packages/app-mobile/components/biometrics/biometricAuthenticate.ts @@ -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(); + } +}; diff --git a/packages/app-mobile/components/biometrics/sensorInfo.ts b/packages/app-mobile/components/biometrics/sensorInfo.ts index 52066c2c4..fc780ebe7 100644 --- a/packages/app-mobile/components/biometrics/sensorInfo.ts +++ b/packages/app-mobile/components/biometrics/sensorInfo.ts @@ -1,5 +1,7 @@ +import Logger from '@joplin/lib/Logger'; import Setting from '@joplin/lib/models/Setting'; import FingerprintScanner from 'react-native-fingerprint-scanner'; +const logger = Logger.create('sensorInfo'); export interface SensorInfo { enabled: boolean; @@ -12,6 +14,9 @@ export default async (): Promise => { // FingerprintScanner scanner calls, since it seems they can fail and freeze // the app. + logger.info('Start'); + logger.info('security.biometricsEnabled', Setting.value('security.biometricsEnabled')); + if (!Setting.value('security.biometricsEnabled')) { return { enabled: false, @@ -24,7 +29,21 @@ export default async (): Promise => { let supportedSensors = ''; 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(); + logger.info('isSensorAvailable result', result); supportedSensors = result; if (result) { @@ -34,7 +53,7 @@ export default async (): Promise => { } } } catch (error) { - console.warn('Could not check for biometrics sensor:', error); + logger.warn('Could not check for biometrics sensor:', error); Setting.setValue('security.biometricsSupportedSensors', ''); } diff --git a/packages/app-mobile/components/screens/ConfigScreen.tsx b/packages/app-mobile/components/screens/ConfigScreen.tsx index b51cc5f42..7f74dbdd0 100644 --- a/packages/app-mobile/components/screens/ConfigScreen.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen.tsx @@ -23,6 +23,7 @@ const { themeStyle } = require('../global-style.js'); const shared = require('@joplin/lib/components/shared/config-shared.js'); import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry'; import { openDocumentTree } from '@joplin/react-native-saf-x'; +import biometricAuthenticate from '../biometrics/biometricAuthenticate'; class ConfigScreenComponent extends BaseScreenComponent { public static navigationOptions(): any { @@ -463,7 +464,7 @@ class ConfigScreenComponent extends BaseScreenComponent { {label} - updateSettingValue(key, value)} /> + void updateSettingValue(key, value)} /> {descriptionComp} @@ -474,13 +475,39 @@ class ConfigScreenComponent extends BaseScreenComponent { return !hasDescription ? this.styles().settingContainer : this.styles().settingContainerNoBottomBorder; } + private async handleSetting(key: string, value: any): Promise { + // 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 theme = themeStyle(themeId); const output: any = null; - const updateSettingValue = (key: string, value: any) => { - return shared.updateSettingValue(this, key, value); + const updateSettingValue = async (key: string, value: any) => { + const handled = await this.handleSetting(key, value); + if (!handled) shared.updateSettingValue(this, key, value); }; const md = Setting.settingMetadata(key); @@ -517,7 +544,7 @@ class ConfigScreenComponent extends BaseScreenComponent { fontSize: theme.fontSize, }} onValueChange={(itemValue: string) => { - updateSettingValue(key, itemValue); + void updateSettingValue(key, itemValue); }} /> @@ -553,7 +580,7 @@ class ConfigScreenComponent extends BaseScreenComponent { {unitLabel} - updateSettingValue(key, value)} /> + void updateSettingValue(key, value)} /> ); @@ -577,7 +604,7 @@ class ConfigScreenComponent extends BaseScreenComponent { {md.label()} - updateSettingValue(key, value)} secureTextEntry={!!md.secure} /> + void updateSettingValue(key, value)} secureTextEntry={!!md.secure} /> ); } else { diff --git a/packages/app-mobile/ios/Podfile.lock b/packages/app-mobile/ios/Podfile.lock index 6866f329f..8ac849607 100644 --- a/packages/app-mobile/ios/Podfile.lock +++ b/packages/app-mobile/ios/Podfile.lock @@ -328,7 +328,7 @@ PODS: - React-Core - react-native-image-resizer (1.4.5): - React-Core - - react-native-netinfo (9.3.7): + - react-native-netinfo (9.3.8): - React-Core - react-native-rsa-native (2.0.5): - React @@ -722,7 +722,7 @@ SPEC CHECKSUMS: react-native-get-random-values: a6ea6a8a65dc93e96e24a11105b1a9c8cfe1d72a react-native-image-picker: ec9b713e248760bfa0f879f0715391de4651a7cb react-native-image-resizer: d9fb629a867335bdc13230ac2a58702bb8c8828f - react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983 + react-native-netinfo: fbc23bc2fe217155d85f2f7e0644b1654df8029b react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a react-native-saf-x: 9bd5238d3b43d76bbec64aa82c173ac20a4bce9f react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc diff --git a/packages/app-mobile/package.json b/packages/app-mobile/package.json index 2d7ae4313..ccad19ba4 100644 --- a/packages/app-mobile/package.json +++ b/packages/app-mobile/package.json @@ -25,7 +25,7 @@ "@react-native-community/clipboard": "1.5.1", "@react-native-community/datetimepicker": "6.7.5", "@react-native-community/geolocation": "2.1.0", - "@react-native-community/netinfo": "9.3.7", + "@react-native-community/netinfo": "9.3.8", "@react-native-community/push-notification-ios": "1.10.1", "@react-native-community/slider": "4.4.2", "assert-browserify": "2.0.0", @@ -54,7 +54,7 @@ "react-native-image-picker": "5.3.1", "react-native-image-resizer": "1.4.5", "react-native-modal-datetime-picker": "14.0.1", - "react-native-paper": "5.4.1", + "react-native-paper": "5.5.0", "react-native-popup-menu": "0.16.1", "react-native-quick-actions": "0.3.13", "react-native-rsa-native": "2.0.5", @@ -79,19 +79,19 @@ "devDependencies": { "@babel/core": "7.16.0", "@babel/runtime": "7.16.3", - "@codemirror/commands": "6.1.2", + "@codemirror/commands": "6.2.2", "@codemirror/lang-cpp": "6.0.2", - "@codemirror/lang-html": "6.4.0", + "@codemirror/lang-html": "6.4.3", "@codemirror/lang-java": "6.0.1", - "@codemirror/lang-javascript": "6.1.1", - "@codemirror/lang-markdown": "6.0.5", + "@codemirror/lang-javascript": "6.1.5", + "@codemirror/lang-markdown": "6.1.0", "@codemirror/lang-php": "6.0.1", "@codemirror/lang-rust": "6.0.1", - "@codemirror/language": "6.3.2", - "@codemirror/legacy-modes": "6.3.1", - "@codemirror/search": "6.2.3", - "@codemirror/state": "6.1.4", - "@codemirror/view": "6.7.1", + "@codemirror/language": "6.6.0", + "@codemirror/legacy-modes": "6.3.2", + "@codemirror/search": "6.3.0", + "@codemirror/state": "6.2.0", + "@codemirror/view": "6.9.3", "@joplin/tools": "~2.11", "@lezer/highlight": "1.1.4", "@types/fs-extra": "9.0.13", diff --git a/packages/app-mobile/root.tsx b/packages/app-mobile/root.tsx index a48987c58..0d0238cf5 100644 --- a/packages/app-mobile/root.tsx +++ b/packages/app-mobile/root.tsx @@ -501,7 +501,7 @@ async function initialize(dispatch: Function) { if (Setting.value('env') === 'prod') { await db.open({ name: getDatabaseName(currentProfile, isSubProfile) }); } else { - await db.open({ name: getDatabaseName(currentProfile, isSubProfile, '-1') }); + await db.open({ name: getDatabaseName(currentProfile, isSubProfile, '-3') }); // await db.clearForTesting(); } @@ -984,6 +984,10 @@ class AppComponent extends React.Component { const biometricIsEnabled = !!this.state.sensorInfo && this.state.sensorInfo.enabled; const shouldShowMainContent = !biometricIsEnabled || this.props.biometricsDone; + logger.info('root.biometrics: biometricIsEnabled', biometricIsEnabled); + logger.info('root.biometrics: shouldShowMainContent', shouldShowMainContent); + logger.info('root.biometrics: this.state.sensorInfo', this.state.sensorInfo); + const mainContent = ( { + await task.fn(); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/tools/gulp/tasks/updateIgnoredTypeScriptBuildRun.js b/packages/tools/gulp/tasks/updateIgnoredTypeScriptBuildRun.js new file mode 100644 index 000000000..7f91f333d --- /dev/null +++ b/packages/tools/gulp/tasks/updateIgnoredTypeScriptBuildRun.js @@ -0,0 +1,12 @@ +// Allow running that task "updateIgnoredTypeScriptBuild" without gulp + +const task = require('./updateIgnoredTypeScriptBuild.js'); + +const main = async () => { + await task.fn(); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/tools/utils/loadSponsors.ts b/packages/tools/utils/loadSponsors.ts index 9f92037e4..1aa6e5f1b 100644 --- a/packages/tools/utils/loadSponsors.ts +++ b/packages/tools/utils/loadSponsors.ts @@ -1,10 +1,8 @@ import { readFile } from 'fs-extra'; -import { rootDir } from '@joplin/utils'; +import { getRootDir } from '@joplin/utils'; import { fetchWithRetry } from '@joplin/utils/net'; import { githubOauthToken } from '../tool-utils'; -const sponsorsPath = `${rootDir}/packages/tools/sponsors.json`; - export interface GithubSponsor { name: string; id: string; @@ -23,6 +21,7 @@ export interface OrgSponsor { } export const loadSponsors = async (): Promise => { + const sponsorsPath = `${await getRootDir()}/packages/tools/sponsors.json`; const output: Sponsors = JSON.parse(await readFile(sponsorsPath, 'utf8')); output.orgs = output.orgs.map(o => { diff --git a/packages/utils/execCommand.ts b/packages/utils/execCommand.ts index 7aae3cec7..6f67174f3 100644 --- a/packages/utils/execCommand.ts +++ b/packages/utils/execCommand.ts @@ -39,7 +39,7 @@ export default async (command: string | string[], options: ExecCommandOptions | args.splice(0, 1); const promise = execa(executableName, args); if (options.showStdout && promise.stdout) promise.stdout.pipe(process.stdout); - if (options.showStderr && promise.stdout) promise.stdout.pipe(process.stderr); + if (options.showStderr && promise.stderr) promise.stderr.pipe(process.stderr); const result = await promise; return result.stdout.trim(); }; diff --git a/packages/utils/index.ts b/packages/utils/index.ts index e35088eac..3f33cc819 100644 --- a/packages/utils/index.ts +++ b/packages/utils/index.ts @@ -2,12 +2,27 @@ import execCommand from './execCommand'; import commandToString from './commandToString'; import splitCommandString from './splitCommandString'; import { dirname } from 'path'; +import { pathExists } from 'fs-extra'; -const rootDir = dirname(dirname(dirname(__dirname))); +let rootDir_ = ''; + +const getRootDir = async () => { + if (rootDir_) return rootDir_; + + let p = dirname(dirname(dirname(__dirname))); + for (let i = 0; i < 9999; i++) { + if (await pathExists(`${p}/.eslintrc.js`)) { + rootDir_ = p; + return rootDir_; + } + p = dirname(p); + } + throw new Error('Could not find root dir'); +}; export { execCommand, commandToString, splitCommandString, - rootDir, + getRootDir, }; diff --git a/packages/utils/package.json b/packages/utils/package.json index a54bcd8f2..5b0583060 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -20,6 +20,7 @@ "license": "AGPL-3.0-or-later", "dependencies": { "execa": "5.1.1", + "fs-extra": "11.1.1", "node-fetch": "2.6.7" }, "devDependencies": { diff --git a/readme/changelog_android.md b/readme/changelog_android.md index 74ad5370d..61283af83 100644 --- a/readme/changelog_android.md +++ b/readme/changelog_android.md @@ -1,5 +1,20 @@ # Joplin Android app changelog +## [android-v2.11.2](https://github.com/laurent22/joplin/releases/tag/android-v2.11.2) (Pre-release) - 2023-04-09T12:04:06Z + +- Improved: Resolve #8022: Editor syntax highlighting was broken (#8023) (#8022 by Henry Heino) +- Improved: Updated packages @react-native-community/netinfo (v9.3.8) +- Fixed: Removed `MasterKey` from Sync Status report (#8026) (#7940 by Arun Kumar) +- Security: Prevent bypassing fingerprint lock on certain devices (6b72f86) + +## [android-v2.11.1](https://github.com/laurent22/joplin/releases/tag/android-v2.11.1) (Pre-release) - 2023-04-08T08:49:19Z + +- New: Add log info for biometrics feature (efdbaeb) +- New: Add setting to enable/disable the markdown toolbar (#7929 by Henry Heino) +- Fixed: Encode the non-ASCII characters in OneDrive URI (#7868) (#7851 by Self Not Found) +- Fixed: Fix OneDrive sync attempting to call method on `null` variable (#7987) (#7986 by Henry Heino) +- Updated packages @lezer/highlight (v1.1.4), fs-extra (v11.1.1), jsdom (v21.1.1), markdown-it-multimd-table (v4.2.1), nanoid (v3.3.6), node-persist (v3.1.3), nodemon (v2.0.22), react-native-document-picker (v8.1.4), react-native-image-picker (v5.3.1), react-native-paper (v5.4.1), react-native-share (v8.2.1), sass (v1.59.3), sqlite3 (v5.1.6), turndown (v7.1.2), yargs (v17.7.1) + ## [android-v2.10.9](https://github.com/laurent22/joplin/releases/tag/android-v2.10.9) (Pre-release) - 2023-03-22T18:40:57Z - Improved: Mark biometrics feature as beta and ensure no call is made if it is not enabled (e44a934) diff --git a/readme/cla_signatures.json b/readme/cla_signatures.json index 7165f4f79..938e27b62 100644 --- a/readme/cla_signatures.json +++ b/readme/cla_signatures.json @@ -415,6 +415,30 @@ "created_at": "2023-03-30T18:21:27Z", "repoId": 79162682, "pullRequestNo": 7999 + }, + { + "name": "gitstart", + "id": 1501599, + "comment_id": 1499665006, + "created_at": "2023-04-06T21:46:22Z", + "repoId": 79162682, + "pullRequestNo": 8024 + }, + { + "name": "Letty", + "id": 465678, + "comment_id": 1500142614, + "created_at": "2023-04-07T10:04:51Z", + "repoId": 79162682, + "pullRequestNo": 8029 + }, + { + "name": "tbjers", + "id": 1117052, + "comment_id": 1501316440, + "created_at": "2023-04-10T02:33:42Z", + "repoId": 79162682, + "pullRequestNo": 8036 } ] } \ No newline at end of file diff --git a/readme/spec/server_items.md b/readme/spec/server_items.md new file mode 100644 index 000000000..ff6dd1c7c --- /dev/null +++ b/readme/spec/server_items.md @@ -0,0 +1,19 @@ +# Joplin Server items + +To upload an item to Joplin Server: + +- Call `PUT /api/items` with the serialized Joplin item. Examples of serialized items are described in `packages/app-cli/tests/support/syncTargetSnapshots` + +- That route is in `packages/server/src/routes/api/items.ts`. In there it's going to do some basic processing on the item, and eventually will call `models.item().saveFromRawContent` + +- This `saveFromRawContent` is where most of the job is done - it's going to detect what the item is, whether it's a note, notebook, etc. (this is the serialised content, as described above), or a binary file (resource). + + - If it's a resource, the content is going to be saved as-is in the database + + - If it's an item, it's going to deserialise it because we want to save certain properties separately in the database, such as the parent ID, the type (whether it's a note, notebook, etc.). We save these properties separately purely for performance reasons. Once the properties have been extracted, the rest of the object is serialised back to JSON and saved to the database. + +- In the end, the content is saved to the `items` table. The JSON item or the resource binary content will be saved to the `content` field. Other Joplin items properties will be saved to the `jop_*` fields. For example, the ID, the parent ID, whether encryption is enabled, etc. + +- `items.jop_id` is the ID as it was generated on the client. `items.id` is the server-side ID. We need two different IDs because we have no way to guarantee that `items.jop_id` is globally unique since it's generated client-side. + +- In `ItemModel` there are various utility functions to deal with the content. This is because it may be saved in different places depending on configuration. It can be saved to the `items.content` field in the database, or it can be saved to S3, or to the filesystem. This is why any code that deals with item content must used these utility functions. diff --git a/readme/spec/sync.md b/readme/spec/sync.md new file mode 100644 index 000000000..68793afe0 --- /dev/null +++ b/readme/spec/sync.md @@ -0,0 +1,39 @@ +# Joplin synchronisation + +The Joplin applications are offline first - it means that data is saved locally on the device. In order to have the same data on all the user's devices, we use a synchronisation process. In a nutshell, each device uploads its notes, notebooks, tags, etc. to the server, and also downloads any notes they do not have, or any recent changes. If a note is deleted, it is also deleted from the server, and eventually deleted from each device too. + +## Vocabulary + +### Clients + +The sync clients are the Joplin applications - the desktop, mobile and terminal applications. + +### Sync targets + +The sync target is the location where the data is going to be saved. It can be for example Joplin Server, a Nextcloud instance, or a WebDAV server. + +### Items + +The "items" are the notes, notebooks, tags and resources that need to be synced. + +## General process + +Whenever the user makes a change to an item, it is uploaded to the sync target within a few seconds. Uploading items as soon as possible helps limit conflicts. Because that way, any client that connects to the sync target is more likely to get the latest version of the item. + +Additionally, every few minutes, the client is going to poll the server and download the latest changes, and apply them to the local note collection. + +## Code architecture + +- `packages/lib/Synchronizer.ts`: This file is responsible for the main synchronisation process. It download changes, upload them, and apply any deletion. The class is relatively generic and receive a `SyncTarget` object that handles sync target-specific operations. The synchroniser is also going encrypt and decrypt items if E2EE is enabled. + +- `packages/lib/SyncTarget*.ts`: These files are the entry points for the various sync targets. They expose some metadata such as name, description, what options they support, etc. Some may also implement a function to test whether the configuration is working (used from the configuration screen). Finally, the main role of this class is to initialise an instance of a `FileApi`. + +- `packages/lib/file-api-driver-*.ts`: Those are the file APIs. They must implement generic file-like operations to create, update, delete or list files. This API is in turn used by the synchroniser to created, update or delete items. + +- `packages/lib/*Api.ts`: The `file-api-driver` will call some low-level API to perform its operations. For example `file-api-driver-local` will use the `fs` package to read/write files, `file-api-driver-amazon-s3` will use the AWS API to work with S3. In some cases however such a low-level API is not available - in that case, we usually create an `*Api.ts` file, which is used by the file API driver to perform its operations. For example, there is a `JoplinServerApi.ts`, which is used to connect to Joplin Server. + +## See also + +- [Synchronisation lock](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_lock.md) +- [E2EE: Technical spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/e2ee.md) +- [E2EE: Workflow](https://github.com/laurent22/joplin/blob/dev/readme/spec/e2ee/workflow.md) diff --git a/renovate.json5 b/renovate.json5 index aeeb73456..822bcd3dd 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -4,13 +4,13 @@ "config:base" ], "major": { - "stabilityDays": 30, + "stabilityDays": 80, }, "minor": { - "stabilityDays": 20, + "stabilityDays": 40, }, "patch": { - "stabilityDays": 10, + "stabilityDays": 20, }, "prConcurrentLimit": 5, "prHourlyLimit": 0, @@ -38,6 +38,7 @@ "@codemirror/search", "@codemirror/state", "@codemirror/view", + "@lezer/highlight", "@fortawesome/fontawesome-svg-core", "@fortawesome/free-solid-svg-icons", "@svgr/webpack", diff --git a/yarn.lock b/yarn.lock index 757c3b191..9f5531276 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3191,8 +3191,8 @@ __metadata: linkType: hard "@codemirror/autocomplete@npm:^6.0.0": - version: 6.4.0 - resolution: "@codemirror/autocomplete@npm:6.4.0" + version: 6.4.2 + resolution: "@codemirror/autocomplete@npm:6.4.2" dependencies: "@codemirror/language": ^6.0.0 "@codemirror/state": ^6.0.0 @@ -3203,19 +3203,19 @@ __metadata: "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.0.0 "@lezer/common": ^1.0.0 - checksum: 3470fee01da60d3d71b8b4f8728629c0f0441e704b8b828592f98c000d75fdb2c9077727e82685626cf45b95cadbc0c1a03968261df2f0cfb4162418b5f4dd1f + checksum: c6cc4edb1c412153e6f6f27926674d7f1d386d1f30d6d4f60c5b52bfa0105870b0c70449b69891937bcf082340d8b0fa6d1f9f28f5eb60adc2974ed4c73aadc1 languageName: node linkType: hard -"@codemirror/commands@npm:6.1.2": - version: 6.1.2 - resolution: "@codemirror/commands@npm:6.1.2" +"@codemirror/commands@npm:6.2.2": + version: 6.2.2 + resolution: "@codemirror/commands@npm:6.2.2" dependencies: "@codemirror/language": ^6.0.0 - "@codemirror/state": ^6.0.0 + "@codemirror/state": ^6.2.0 "@codemirror/view": ^6.0.0 "@lezer/common": ^1.0.0 - checksum: 3e00b02ea12a5bb8a07162aea006e13e6dcbf9be8535e2fd645dd75634f39d8de2d18bb6ccf302fd22bd0956e38490ccf0bafc60d44945df19e449876b09a0df + checksum: d3aa1ca8cbd7b9434eedba6b6d783411670796bf6ab61990afc4fd0c04645189fe4dd55bb95e23b943e9089f9739bc7e92aa4b2ac3eac09cfa2b91a45f608d3e languageName: node linkType: hard @@ -3230,31 +3230,31 @@ __metadata: linkType: hard "@codemirror/lang-css@npm:^6.0.0": - version: 6.0.1 - resolution: "@codemirror/lang-css@npm:6.0.1" + version: 6.1.1 + resolution: "@codemirror/lang-css@npm:6.1.1" dependencies: "@codemirror/autocomplete": ^6.0.0 "@codemirror/language": ^6.0.0 "@codemirror/state": ^6.0.0 "@lezer/css": ^1.0.0 - checksum: c459baeeb912f424167cf8308df9fe20aaba3f6a65e5ce7b128e2d4d0ceda7474e3bf94b7b4b87ead7d10f79edfbf9807b2058a2354318adacf906eba4f6eb76 + checksum: 9b0bf7c7544fb604b67325689d783981e4099560f577bc1f10c52cb18e9d275ebdbdbd3f335a1dbb9c4910c36320f74ca015fc92ef99f930ecb9d481a2bf3511 languageName: node linkType: hard -"@codemirror/lang-html@npm:6.4.0, @codemirror/lang-html@npm:^6.0.0": - version: 6.4.0 - resolution: "@codemirror/lang-html@npm:6.4.0" +"@codemirror/lang-html@npm:6.4.3, @codemirror/lang-html@npm:^6.0.0": + version: 6.4.3 + resolution: "@codemirror/lang-html@npm:6.4.3" dependencies: "@codemirror/autocomplete": ^6.0.0 "@codemirror/lang-css": ^6.0.0 "@codemirror/lang-javascript": ^6.0.0 - "@codemirror/language": ^6.0.0 + "@codemirror/language": ^6.4.0 "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.2.2 "@lezer/common": ^1.0.0 "@lezer/css": ^1.1.0 - "@lezer/html": ^1.1.0 - checksum: 78fd811c6b2d2a355e1e41eaf599a9623dd03b8fd0ade603220dc443744520b06749125c85e5be64b09ca0aaea71de6268cf3518006ad17823522ef97aefab59 + "@lezer/html": ^1.3.0 + checksum: 6177d19147580964ecd6910ae951201929a96e63f4f0e624c3138e2805fa87ec6d6d952a3a888c5a52af78b6dd6d04d7d8c76c6a9cd65b1921dc467b5dbaea72 languageName: node linkType: hard @@ -3268,39 +3268,24 @@ __metadata: languageName: node linkType: hard -"@codemirror/lang-javascript@npm:6.1.1": - version: 6.1.1 - resolution: "@codemirror/lang-javascript@npm:6.1.1" +"@codemirror/lang-javascript@npm:6.1.5, @codemirror/lang-javascript@npm:^6.0.0": + version: 6.1.5 + resolution: "@codemirror/lang-javascript@npm:6.1.5" dependencies: "@codemirror/autocomplete": ^6.0.0 - "@codemirror/language": ^6.0.0 + "@codemirror/language": ^6.6.0 "@codemirror/lint": ^6.0.0 "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.0.0 "@lezer/common": ^1.0.0 "@lezer/javascript": ^1.0.0 - checksum: 9a1d082edf3d835e18f2e9e0aa603c92a3e8e86f95a5662635f5a9d1ee6b87bbfe3bac3f56bd2c8cd46bafb87bcf90e0955c5efcda334a224c5d9c2d0524d351 + checksum: f0355f9577fac03437137356b5c8826ec073480d9b0efc62289eac483172d47dafe569f31bf788e4228e8b789197e50a0768cf10b0cde5f600e89b6b469f52cc languageName: node linkType: hard -"@codemirror/lang-javascript@npm:^6.0.0": - version: 6.1.2 - resolution: "@codemirror/lang-javascript@npm:6.1.2" - dependencies: - "@codemirror/autocomplete": ^6.0.0 - "@codemirror/language": ^6.0.0 - "@codemirror/lint": ^6.0.0 - "@codemirror/state": ^6.0.0 - "@codemirror/view": ^6.0.0 - "@lezer/common": ^1.0.0 - "@lezer/javascript": ^1.0.0 - checksum: f4336b7efd44e4158b9979f0c23918184c897d0fe3e40b5414bd9243a9899ecdba4dfe13970fe5024a1894579af80cb4c5dd574c6c2b7bd7ff06d8c8cb88616b - languageName: node - linkType: hard - -"@codemirror/lang-markdown@npm:6.0.5": - version: 6.0.5 - resolution: "@codemirror/lang-markdown@npm:6.0.5" +"@codemirror/lang-markdown@npm:6.1.0": + version: 6.1.0 + resolution: "@codemirror/lang-markdown@npm:6.1.0" dependencies: "@codemirror/lang-html": ^6.0.0 "@codemirror/language": ^6.3.0 @@ -3308,7 +3293,7 @@ __metadata: "@codemirror/view": ^6.0.0 "@lezer/common": ^1.0.0 "@lezer/markdown": ^1.0.0 - checksum: d69148ea3f954aaae98cb22ef2ae790a1d51ad9a22344b2cf3e8b7cdb0e953c342f736257a88fb71b4b74f0fd4338480091fdb047e266b4c6665e4a83f552d4c + checksum: faee880c5e695391fc5b92788d1500bed3f0cc3766c987077cdc1643cf38b97eb1774a29491a7a75064089478b895e7c8fe5a4f08ac93c9614ccbbe188f10b47 languageName: node linkType: hard @@ -3335,9 +3320,9 @@ __metadata: languageName: node linkType: hard -"@codemirror/language@npm:6.3.2, @codemirror/language@npm:^6.0.0, @codemirror/language@npm:^6.3.0": - version: 6.3.2 - resolution: "@codemirror/language@npm:6.3.2" +"@codemirror/language@npm:6.6.0, @codemirror/language@npm:^6.0.0, @codemirror/language@npm:^6.3.0, @codemirror/language@npm:^6.4.0, @codemirror/language@npm:^6.6.0": + version: 6.6.0 + resolution: "@codemirror/language@npm:6.6.0" dependencies: "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.0.0 @@ -3345,56 +3330,56 @@ __metadata: "@lezer/highlight": ^1.0.0 "@lezer/lr": ^1.0.0 style-mod: ^4.0.0 - checksum: b70ed9b85d0bea79181c86e88a1f5c0bada30680ee1fe6a68efc01bc037c3d14f94a83602fc46cc4b4393589605ef7e986ed5174443502f3365dd61f883894fa + checksum: bb9411620e2f231653a3f0c4429e0d19a3843bff5dbc117df4649d7bf783ec4ad809c0add8bc0887a4ec3f48b4f8f941621168e47d76101d5383f0d670af1722 languageName: node linkType: hard -"@codemirror/legacy-modes@npm:6.3.1": - version: 6.3.1 - resolution: "@codemirror/legacy-modes@npm:6.3.1" +"@codemirror/legacy-modes@npm:6.3.2": + version: 6.3.2 + resolution: "@codemirror/legacy-modes@npm:6.3.2" dependencies: "@codemirror/language": ^6.0.0 - checksum: 9065e521bf14e33856e9d3ea114d7b352adf341a8b8d4fb94b4c866189336a32b5ed42ffc20f5d2fa3c839f1bdf29a868bbf9b74c105ed83fa9fd6080e0429e9 + checksum: fa5f5477fb9e19267251e2ecd3de8c1a4c2512813555bb60111dce3951f2c3f6080a2985a573b7542534ba1d2c34115f7e39ee23fdf8f6f81db6f8ce447c1efc languageName: node linkType: hard "@codemirror/lint@npm:^6.0.0": - version: 6.1.0 - resolution: "@codemirror/lint@npm:6.1.0" + version: 6.2.0 + resolution: "@codemirror/lint@npm:6.2.0" dependencies: "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.0.0 crelt: ^1.0.5 - checksum: 1b5179c2c18e0eb96c68b47cd6832053a6aab80d6126cc250876fa848418c537417f0656cd6f442a0e5858830546fdef6bdcbb7b9773d92989795b52c7646011 + checksum: b97e55a07bca9f7e357e495853ba189ae0ff7dfe7e7ae445d7a0d6c6926ec792c7f5c6b6c13a1f137fd9fedf44a6624e9d500f76d0d46a3c3e9d19c2cda9d28a languageName: node linkType: hard -"@codemirror/search@npm:6.2.3": - version: 6.2.3 - resolution: "@codemirror/search@npm:6.2.3" +"@codemirror/search@npm:6.3.0": + version: 6.3.0 + resolution: "@codemirror/search@npm:6.3.0" dependencies: "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.0.0 crelt: ^1.0.5 - checksum: 7ab0ffab7992f5c6260313e06ec8935f55807b95ca86f0327154ea1ae0ab984cd22c2fc1a812bd6cace1db131785353689fbfd080d2e12c660e3db0295dec355 + checksum: b757eebbb541c9d74fe36ccfdd03bc3e4e7aebb08b491e207d5898f24aaa612558c393ba49de5bf375972f5774de817fcfbad1ac551dda1a34badb41cf130d36 languageName: node linkType: hard -"@codemirror/state@npm:6.1.4, @codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.4": - version: 6.1.4 - resolution: "@codemirror/state@npm:6.1.4" - checksum: ef6bc495d3b89d2f0202b6b1c0fc3e3610f8d815f4ca91f86153d6093c9b93ddbe2cfb787802d07514a7114f0b200cf2eeb54c1cd34d7e00eff774ecea97d845 +"@codemirror/state@npm:6.2.0, @codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.4, @codemirror/state@npm:^6.2.0": + version: 6.2.0 + resolution: "@codemirror/state@npm:6.2.0" + checksum: fdc99c773dc09c700dd02bf918f06132aa8d3069c262cc4eb6ca5c810ce24ae2d7e90719ae7630a8158fd263018de6d40bd78f312e6bfba754e737b64e6c6b3d languageName: node linkType: hard -"@codemirror/view@npm:6.7.1, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.2.2, @codemirror/view@npm:^6.6.0": - version: 6.7.1 - resolution: "@codemirror/view@npm:6.7.1" +"@codemirror/view@npm:6.9.3, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.2.2, @codemirror/view@npm:^6.6.0": + version: 6.9.3 + resolution: "@codemirror/view@npm:6.9.3" dependencies: "@codemirror/state": ^6.1.4 style-mod: ^4.0.0 w3c-keyname: ^2.2.4 - checksum: 75a5846d61e63027e9bf1dfd0b507932934cb7650b7959c1191e68b161eb1756e9773f964c4331970b51864aef8f7954bc5cc8fdb51b0f6533de6c20568833ed + checksum: 718ecbb021ca75eb89003f73c846a07d36a708dcfec8345f0f0dbcfc0d0df5ea6f114918694b2730a6d49e5e50502bcce79ce7ff94ce55748e068e5a35073755 languageName: node linkType: hard @@ -4996,19 +4981,19 @@ __metadata: dependencies: "@babel/core": 7.16.0 "@babel/runtime": 7.16.3 - "@codemirror/commands": 6.1.2 + "@codemirror/commands": 6.2.2 "@codemirror/lang-cpp": 6.0.2 - "@codemirror/lang-html": 6.4.0 + "@codemirror/lang-html": 6.4.3 "@codemirror/lang-java": 6.0.1 - "@codemirror/lang-javascript": 6.1.1 - "@codemirror/lang-markdown": 6.0.5 + "@codemirror/lang-javascript": 6.1.5 + "@codemirror/lang-markdown": 6.1.0 "@codemirror/lang-php": 6.0.1 "@codemirror/lang-rust": 6.0.1 - "@codemirror/language": 6.3.2 - "@codemirror/legacy-modes": 6.3.1 - "@codemirror/search": 6.2.3 - "@codemirror/state": 6.1.4 - "@codemirror/view": 6.7.1 + "@codemirror/language": 6.6.0 + "@codemirror/legacy-modes": 6.3.2 + "@codemirror/search": 6.3.0 + "@codemirror/state": 6.2.0 + "@codemirror/view": 6.9.3 "@joplin/lib": ~2.11 "@joplin/react-native-alarm-notification": ~2.11 "@joplin/react-native-saf-x": ~2.11 @@ -5018,7 +5003,7 @@ __metadata: "@react-native-community/clipboard": 1.5.1 "@react-native-community/datetimepicker": 6.7.5 "@react-native-community/geolocation": 2.1.0 - "@react-native-community/netinfo": 9.3.7 + "@react-native-community/netinfo": 9.3.8 "@react-native-community/push-notification-ios": 1.10.1 "@react-native-community/slider": 4.4.2 "@types/fs-extra": 9.0.13 @@ -5062,7 +5047,7 @@ __metadata: react-native-image-picker: 5.3.1 react-native-image-resizer: 1.4.5 react-native-modal-datetime-picker: 14.0.1 - react-native-paper: 5.4.1 + react-native-paper: 5.5.0 react-native-popup-menu: 0.16.1 react-native-quick-actions: 0.3.13 react-native-rsa-native: 2.0.5 @@ -5199,7 +5184,7 @@ __metadata: moment: 2.29.4 multiparty: 4.2.3 mustache: 4.2.0 - nanoid: 3.3.4 + nanoid: 3.3.6 node-fetch: 2.6.7 node-notifier: 10.0.1 node-persist: 3.1.3 @@ -5490,6 +5475,7 @@ __metadata: "@types/jest": 29.5.0 "@types/node-fetch": 2.6.2 execa: 5.1.1 + fs-extra: 11.1.1 jest: 29.5.0 node-fetch: 2.6.7 ts-jest: 29.0.5 @@ -6388,12 +6374,12 @@ __metadata: linkType: hard "@lezer/cpp@npm:^1.0.0": - version: 1.0.0 - resolution: "@lezer/cpp@npm:1.0.0" + version: 1.1.0 + resolution: "@lezer/cpp@npm:1.1.0" dependencies: "@lezer/highlight": ^1.0.0 "@lezer/lr": ^1.0.0 - checksum: 6829550db06ea9ce149fbcd50db3c8988e69bb8e2ce557ecde8f711222b902c5c64453fa77c502c6ab13381d041f5f8e8d3cb80049b2e9e963d76294533e5e83 + checksum: 9b25c881fc9b64fd2b019a077a85b0ba7cfda0bbdd92dbb0ff43300c9ba1ec4360128fe912bfe0f06a1c1bb5a564c5ace375c8aad254d07a717768a8f268695d languageName: node linkType: hard @@ -6407,7 +6393,7 @@ __metadata: languageName: node linkType: hard -"@lezer/highlight@npm:1.1.4": +"@lezer/highlight@npm:1.1.4, @lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.1.3": version: 1.1.4 resolution: "@lezer/highlight@npm:1.1.4" dependencies: @@ -6416,52 +6402,43 @@ __metadata: languageName: node linkType: hard -"@lezer/highlight@npm:^1.0.0": - version: 1.1.3 - resolution: "@lezer/highlight@npm:1.1.3" - dependencies: - "@lezer/common": ^1.0.0 - checksum: 90ec143ce46b32f6779c3b245f1b5a540d66686939816d3daed8318821acc4bc719466dc222336cfd483bf04a8de4fdc6f279e904cf114d4d9f786f9feccbbd8 - languageName: node - linkType: hard - -"@lezer/html@npm:^1.1.0": - version: 1.3.0 - resolution: "@lezer/html@npm:1.3.0" +"@lezer/html@npm:^1.3.0": + version: 1.3.4 + resolution: "@lezer/html@npm:1.3.4" dependencies: "@lezer/common": ^1.0.0 "@lezer/highlight": ^1.0.0 "@lezer/lr": ^1.0.0 - checksum: e6efde94614a5b7ebf2713b244a110ef9025369561c7bf42fe6dd8f5877d2ee0c71f894b8b43d1284d23bf429fd3688ec3b6b0c2b8702df366c2b5e5cedc4c19 + checksum: 81dd134ac094edf7c40bae4c3b7126d336ce4c3c87756344bf604eff64d89b06fcb55f91618a4622eb0dae6d6015722f5bab58e2252d86e81fca8c3ced1a0c4d languageName: node linkType: hard "@lezer/java@npm:^1.0.0": - version: 1.0.0 - resolution: "@lezer/java@npm:1.0.0" + version: 1.0.3 + resolution: "@lezer/java@npm:1.0.3" dependencies: "@lezer/highlight": ^1.0.0 "@lezer/lr": ^1.0.0 - checksum: 0dcd3ea2aa431bc352ed1ca1e92c61a1d60e10d1c0e730200ef1f1dda4b42421e67d56e7808e2102f16b6ffd534f246b82249922998663e9099bd52c141ef1d9 + checksum: 2fffea6627d130413ffad4e61040267974cca3167d98881b9e5b5e2455530de74a82c234d93603e92a4972fad314671453c49c0a76b0f4547c4617d671fd7b99 languageName: node linkType: hard "@lezer/javascript@npm:^1.0.0": - version: 1.4.0 - resolution: "@lezer/javascript@npm:1.4.0" + version: 1.4.2 + resolution: "@lezer/javascript@npm:1.4.2" dependencies: - "@lezer/highlight": ^1.0.0 - "@lezer/lr": ^1.0.0 - checksum: 36c64e8530feef9b937cf75f8833aa8c0f5c8c0812c55c53a133d1af5deb491dd80084397d5773e873db90ff717aede25b45fa827eead66400cb81b097567c42 + "@lezer/highlight": ^1.1.3 + "@lezer/lr": ^1.3.0 + checksum: 542261c297709babfe450de1233c13fe2f5b111678d280cb0f8304f12bcdae294cb43c0ac64bbd647e5039de3286f6f0715d120fb132bd5af778363d1f612a1f languageName: node linkType: hard -"@lezer/lr@npm:^1.0.0": - version: 1.2.5 - resolution: "@lezer/lr@npm:1.2.5" +"@lezer/lr@npm:^1.0.0, @lezer/lr@npm:^1.1.0, @lezer/lr@npm:^1.3.0": + version: 1.3.3 + resolution: "@lezer/lr@npm:1.3.3" dependencies: "@lezer/common": ^1.0.0 - checksum: 9a2fb2663dba5608c0f8a7d51b4c1beeb37d391da972fb3569fe51b637167ac4889b055ceb0c5267b8612a0aa5dfd517cbbd1349975cd662d1ca7fea374916b1 + checksum: 1804074c794005a31c54d80ab72127f19ae5be29bb627c52bc001a57b1af97a9e62732ff13e3aeb7bc53b330202b6bd3747272c64d87f257dbba533e75a183a3 languageName: node linkType: hard @@ -6476,12 +6453,12 @@ __metadata: linkType: hard "@lezer/php@npm:^1.0.0": - version: 1.0.0 - resolution: "@lezer/php@npm:1.0.0" + version: 1.0.1 + resolution: "@lezer/php@npm:1.0.1" dependencies: "@lezer/highlight": ^1.0.0 - "@lezer/lr": ^1.0.0 - checksum: 06d20c423011119363ccd4dd30d0bdec56ddbdddda05888a6b5890fc090a6338740a310a77d367d3d69a958925fad73e0c7f9b62953eb3f189ec513bd71d9f59 + "@lezer/lr": ^1.1.0 + checksum: a847c255c030b4d38913ddf1d5bd7324d83be7ef8d1d244542870be03b9bf7dc71283afeb2415c40dfd188cb99f0cc44bad760b5f3b7c35c3b8e5e00253848fc languageName: node linkType: hard @@ -7082,12 +7059,12 @@ __metadata: languageName: node linkType: hard -"@react-native-community/netinfo@npm:9.3.7": - version: 9.3.7 - resolution: "@react-native-community/netinfo@npm:9.3.7" +"@react-native-community/netinfo@npm:9.3.8": + version: 9.3.8 + resolution: "@react-native-community/netinfo@npm:9.3.8" peerDependencies: react-native: ">=0.59" - checksum: 556e47312be4b3d74c4c8fa7ab0b526a9d8395745c4d94d813cd8f08284d2a16ba274d8a8fdc38d5a273c913ebbc356bc15e65a8c8ab4506f7612a08e43da83d + checksum: 1b41c2960211716189da94bf9a8070d28c34935dc914d1094d5a9de7b7137af093bd0e71f82fc158b973d9b5b13a7685ecca6430ad23d110b23129157907e102 languageName: node linkType: hard @@ -17108,7 +17085,7 @@ __metadata: dependencies: chalk: 2.4.2 jest: 29.4.3 - slugify: 1.6.5 + slugify: 1.6.6 yeoman-generator: 5.8.0 yosay: 2.0.2 languageName: unknown @@ -24021,12 +23998,12 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:3.3.4, nanoid@npm:^3.3.4": - version: 3.3.4 - resolution: "nanoid@npm:3.3.4" +"nanoid@npm:3.3.6": + version: 3.3.6 + resolution: "nanoid@npm:3.3.6" bin: nanoid: bin/nanoid.cjs - checksum: 2fddd6dee994b7676f008d3ffa4ab16035a754f4bb586c61df5a22cf8c8c94017aadd360368f47d653829e0569a92b129979152ff97af23a558331e47e37cd9c + checksum: 7d0eda657002738aa5206107bd0580aead6c95c460ef1bdd0b1a87a9c7ae6277ac2e9b945306aaa5b32c6dcb7feaf462d0f552e7f8b5718abfc6ead5c94a71b3 languageName: node linkType: hard @@ -24039,6 +24016,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.4": + version: 3.3.4 + resolution: "nanoid@npm:3.3.4" + bin: + nanoid: bin/nanoid.cjs + checksum: 2fddd6dee994b7676f008d3ffa4ab16035a754f4bb586c61df5a22cf8c8c94017aadd360368f47d653829e0569a92b129979152ff97af23a558331e47e37cd9c + languageName: node + linkType: hard + "nanomatch@npm:^1.2.9": version: 1.2.13 resolution: "nanomatch@npm:1.2.13" @@ -27375,9 +27361,9 @@ __metadata: languageName: node linkType: hard -"react-native-paper@npm:5.4.1": - version: 5.4.1 - resolution: "react-native-paper@npm:5.4.1" +"react-native-paper@npm:5.5.0": + version: 5.5.0 + resolution: "react-native-paper@npm:5.5.0" dependencies: "@callstack/react-theme-provider": ^3.0.8 color: ^3.1.2 @@ -27387,7 +27373,7 @@ __metadata: react-native: "*" react-native-safe-area-context: "*" react-native-vector-icons: "*" - checksum: b6bca70b935ca3b69ba4d2bc16717b938b4219d0a1fc17b1e0c7b87df753ba3cd8f52e8f17cf09ca862c7e6be243fb52092054fc176a4b284fa3143cf32d0672 + checksum: 1d433e5d48462a8a6477d979f7c9ae44278dfaf713d5f57ff218cfbd8d6f6556d42ace6d01cce410a922157242bd61f92bdf87e0cbd84c6231f1ab369af1777b languageName: node linkType: hard @@ -28809,6 +28795,7 @@ __metadata: version: 0.0.0-use.local resolution: "root@workspace:." dependencies: + "@joplin/utils": ~2.11 "@seiyab/eslint-plugin-react-hooks": 4.5.1-beta.0 "@types/fs-extra": 9.0.13 "@typescript-eslint/eslint-plugin": 5.48.2 @@ -28820,6 +28807,7 @@ __metadata: eslint-plugin-jest: 27.2.1 eslint-plugin-promise: 6.1.1 eslint-plugin-react: 7.32.0 + execa: 5.1.1 fs-extra: 11.1.1 glob: 8.1.0 gulp: 4.0.2 @@ -29634,10 +29622,10 @@ __metadata: languageName: node linkType: hard -"slugify@npm:1.6.5": - version: 1.6.5 - resolution: "slugify@npm:1.6.5" - checksum: a955a1b600201030f4c1daa9bb74a17d4402a0693fc40978bbd17e44e64fd72dad3bac4037422aa8aed55b5170edd57f3f4cd8f59ba331f5cf0f10f1a7795609 +"slugify@npm:1.6.6": + version: 1.6.6 + resolution: "slugify@npm:1.6.6" + checksum: 04773c2d3b7aea8d2a61fa47cc7e5d29ce04e1a96cbaec409da57139df906acb3a449fac30b167d203212c806e73690abd4ff94fbad0a9a7b7ea109a2a638ae9 languageName: node linkType: hard