1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Merge branch 'dev' into new_note_list

This commit is contained in:
Laurent Cozic 2023-08-11 09:26:18 +01:00
commit 801b955ac8
37 changed files with 493 additions and 299 deletions

View File

@ -133,6 +133,7 @@ packages/app-desktop/gui/ClipperConfigScreen.js
packages/app-desktop/gui/ConfigScreen/ButtonBar.js
packages/app-desktop/gui/ConfigScreen/ConfigScreen.js
packages/app-desktop/gui/ConfigScreen/Sidebar.js
packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.js

View File

@ -171,6 +171,21 @@ if [ "$IS_PULL_REQUEST" == "1" ]; then
fi
fi
# =============================================================================
# Check that the website still builds
# =============================================================================
if [ "$IS_PULL_REQUEST" == "1" ] || [ "$IS_DEV_BRANCH" = "1" ]; then
echo "Step: Check that the website still builds..."
mkdir -p ../joplin-website/docs
SKIP_SPONSOR_PROCESSING=1 yarn run buildWebsite
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
# =============================================================================
# Find out if we should run the build or not. Electron-builder gets stuck when
# building PRs so we disable it in this case. The Linux build should provide

2
.gitignore vendored
View File

@ -50,6 +50,7 @@ packages/tools/github_oauth_token.txt
lerna-debug.log
.env
docs/**/*.mustache
.idea
# Yarn stuff
# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
@ -118,6 +119,7 @@ packages/app-desktop/gui/ClipperConfigScreen.js
packages/app-desktop/gui/ConfigScreen/ButtonBar.js
packages/app-desktop/gui/ConfigScreen/ConfigScreen.js
packages/app-desktop/gui/ConfigScreen/Sidebar.js
packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js
packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.js

View File

@ -75,11 +75,10 @@ A community maintained list of these distributions can be found here: [Unofficia
| | | | |
| :---: | :---: | :---: | :---: |
| <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/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/14873877?s=96&v=4"/></br>[dchecks](https://github.com/dchecks) | <img width="50" src="https://avatars2.githubusercontent.com/u/56287?s=96&v=4"/></br>[fats](https://github.com/fats) | <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/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/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/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/14873877?s=96&v=4"/></br>[dchecks](https://github.com/dchecks) | <img width="50" src="https://avatars2.githubusercontent.com/u/56287?s=96&v=4"/></br>[fats](https://github.com/fats) | <img width="50" src="https://avatars2.githubusercontent.com/u/8030470?s=96&v=4"/></br>[Galliver7](https://github.com/Galliver7) | <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/1310474?s=96&v=4"/></br>[jknowles](https://github.com/jknowles) | <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/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/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/31054972?s=96&v=4"/></br>[saarantras](https://github.com/saarantras) | <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) | | |
<!-- SPONSORS-GITHUB -->
<!-- TOC -->
@ -159,6 +158,7 @@ A community maintained list of these distributions can be found here: [Unofficia
- [Guiding principles](https://github.com/laurent22/joplin/blob/dev/readme/principles.md)
- [Stats](https://github.com/laurent22/joplin/blob/dev/readme/stats.md)
- [Brand guidelines](https://joplinapp.org/brand)
- [Release cycle](https://github.com/laurent22/joplin/blob/dev/readme/release_cycle.md)
- [Donate](https://github.com/laurent22/joplin/blob/dev/readme/donate.md)
<!-- TOC -->

View File

@ -452,6 +452,8 @@ class Application extends BaseApplication {
type: 'FOLDER_SELECT',
id: Setting.value('activeFolderId'),
});
this.startRotatingLogMaintenance(Setting.value('profileDir'));
}
}
}

View File

@ -57,7 +57,7 @@
"proper-lockfile": "4.1.2",
"read-chunk": "2.1.0",
"server-destroy": "1.0.1",
"sharp": "0.32.3",
"sharp": "0.32.4",
"sprintf-js": "1.1.2",
"sqlite3": "5.1.6",
"string-padding": "1.0.2",
@ -66,7 +66,7 @@
"terminal-kit": "3.0.0",
"tkwidgets": "0.5.27",
"url-parse": "1.5.10",
"word-wrap": "1.2.3",
"word-wrap": "1.2.4",
"yargs-parser": "21.1.1"
},
"devDependencies": {

View File

@ -566,6 +566,8 @@ class Application extends BaseApplication {
await SpellCheckerService.instance().initialize(new SpellCheckerServiceDriverNative());
this.startRotatingLogMaintenance(Setting.value('profileDir'));
// await populateDatabase(reg.db(), {
// clearDatabase: true,
// folderCount: 1000,

View File

@ -19,6 +19,7 @@ import PluginService from '@joplin/lib/services/plugins/PluginService';
import { getDefaultPluginsInstallState, updateDefaultPluginsInstallState } from '@joplin/lib/services/plugins/defaultPlugins/defaultPluginsUtils';
import getDefaultPluginsInfo from '@joplin/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo';
import JoplinCloudConfigScreen from '../JoplinCloudConfigScreen';
import ToggleAdvancedSettingsButton from './controls/ToggleAdvancedSettingsButton';
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');
const settingKeyToControl: any = {
@ -208,17 +209,11 @@ class ConfigScreenComponent extends React.Component<any, any> {
const advancedSettingsSectionStyle = { display: 'none' };
if (advancedSettingComps.length) {
const iconName = this.state.showAdvancedSettings ? 'fa fa-angle-down' : 'fa fa-angle-right';
// const advancedSettingsButtonStyle = { ...theme.buttonStyle, marginBottom: 10 };
advancedSettingsButton = (
<div style={{ marginBottom: 10 }}>
<Button
level={ButtonLevel.Secondary}
onClick={() => shared.advancedSettingsButton_click(this)}
iconName={iconName}
title={_('Show Advanced Settings')}
/>
</div>
<ToggleAdvancedSettingsButton
onClick={() => shared.advancedSettingsButton_click(this)}
advancedSettingsVisible={this.state.showAdvancedSettings}
/>
);
advancedSettingsSectionStyle.display = this.state.showAdvancedSettings ? 'block' : 'none';
}

View File

@ -0,0 +1,24 @@
import * as React from 'react';
import Button, { ButtonLevel } from '../../Button/Button';
import { _ } from '@joplin/lib/locale';
interface Props {
onClick: ()=> void;
advancedSettingsVisible: boolean;
}
const ToggleAdvancedSettingsButton: React.FunctionComponent<Props> = props => {
const iconName = props.advancedSettingsVisible ? 'fa fa-angle-down' : 'fa fa-angle-right';
return (
<div style={{ marginBottom: 10 }}>
<Button
level={ButtonLevel.Secondary}
onClick={props.onClick}
iconName={iconName}
title={_('Show Advanced Settings')}
/>
</div>
);
};
export default ToggleAdvancedSettingsButton;

View File

@ -10,12 +10,13 @@ import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types';
import { getEncryptionEnabled, masterKeyEnabled, SyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils';
import { getDefaultMasterKey, getMasterPasswordStatusMessage, masterPasswordIsValid, toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils';
import Button, { ButtonLevel } from '../Button/Button';
import { useCallback, useMemo } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { connect } from 'react-redux';
import { AppState } from '../../app.reducer';
import Setting from '@joplin/lib/models/Setting';
import CommandService from '@joplin/lib/services/CommandService';
import { PublicPrivateKeyPair } from '@joplin/lib/services/e2ee/ppk';
import ToggleAdvancedSettingsButton from '../ConfigScreen/controls/ToggleAdvancedSettingsButton';
interface Props {
themeId: any;
@ -83,34 +84,6 @@ const EncryptionConfigScreen = (props: Props) => {
);
};
const renderReencryptData = () => {
if (!shim.isElectron()) return null;
if (!props.shouldReencrypt) return null;
const theme = themeStyle(props.themeId);
const buttonLabel = _('Re-encrypt data');
const intro = props.shouldReencrypt ? _('The default encryption method has been changed to a more secure one and it is recommended that you apply it to your data.') : _('You may use the tool below to re-encrypt your data, for example if you know that some of your notes are encrypted with an obsolete encryption method.');
let t = `${intro}\n\n${_('In order to do so, your entire data set will have to be encrypted and synchronised, so it is best to run it overnight.\n\nTo start, please follow these instructions:\n\n1. Synchronise all your devices.\n2. Click "%s".\n3. Let it run to completion. While it runs, avoid changing any note on your other devices, to avoid conflicts.\n4. Once sync is done on this device, sync all your other devices and let it run to completion.\n\nImportant: you only need to run this ONCE on one device.', buttonLabel)}`;
t = t.replace(/\n\n/g, '</p><p>');
t = t.replace(/\n/g, '<br>');
t = `<p>${t}</p>`;
return (
<div>
<h2>{_('Re-encryption')}</h2>
<p style={theme.textStyle} dangerouslySetInnerHTML={{ __html: t }}></p>
<span style={{ marginRight: 10 }}>
<button onClick={() => void reencryptData()} style={theme.buttonStyle}>{buttonLabel}</button>
</span>
{ !props.shouldReencrypt ? null : <button onClick={() => dontReencryptData()} style={theme.buttonStyle}>{_('Ignore')}</button> }
</div>
);
};
const renderMasterKey = (mk: MasterKeyEntity) => {
const theme = themeStyle(props.themeId);
@ -121,6 +94,12 @@ const EncryptionConfigScreen = (props: Props) => {
borderColor: theme.dividerColor,
};
const missingPasswordCellStyle = {
...theme.textStyle,
border: '3px solid',
borderColor: theme.colorError,
};
const password = inputPasswords[mk.id] ? inputPasswords[mk.id] : '';
const isActive = props.activeMasterKeyId === mk.id;
const activeIcon = isActive ? '✔' : '';
@ -135,8 +114,15 @@ const EncryptionConfigScreen = (props: Props) => {
);
} else {
return (
<td style={theme.textStyle}>
<input type="password" style={passwordStyle} value={password} onChange={event => onInputPasswordChange(mk, event.target.value)} />{' '}
<td style={missingPasswordCellStyle}>
<input
type="password"
placeholder={_('Enter password')}
style={passwordStyle}
value={password}
onChange={event => onInputPasswordChange(mk, event.target.value)}
/>
{' '}
<button style={theme.buttonStyle} onClick={() => onSavePasswordClick(mk, { ...props.passwords, ...inputPasswords })}>
{_('Save')}
</button>
@ -239,7 +225,6 @@ const EncryptionConfigScreen = (props: Props) => {
/>
);
const needUpgradeSection = renderNeedUpgradeSection();
const reencryptDataSection = renderReencryptData();
return (
<div className="section">
@ -254,7 +239,6 @@ const EncryptionConfigScreen = (props: Props) => {
{decryptedItemsInfo}
{toggleButton}
{needUpgradeSection}
{props.shouldReencrypt ? reencryptDataSection : null}
</div>
</div>
);
@ -338,6 +322,56 @@ const EncryptionConfigScreen = (props: Props) => {
return nonExistingMasterKeySection;
};
const renderReencryptData = () => {
if (!shim.isElectron()) return null;
if (!props.encryptionEnabled) return null;
const theme = themeStyle(props.themeId);
const buttonLabel = _('Re-encrypt data');
const intro = props.shouldReencrypt ? _('The default encryption method has been changed to a more secure one and it is recommended that you apply it to your data.') : _('You may use the tool below to re-encrypt your data, for example if you know that some of your notes are encrypted with an obsolete encryption method.');
let t = `${intro}\n\n${_('In order to do so, your entire data set will have to be encrypted and synchronised, so it is best to run it overnight.\n\nTo start, please follow these instructions:\n\n1. Synchronise all your devices.\n2. Click "%s".\n3. Let it run to completion. While it runs, avoid changing any note on your other devices, to avoid conflicts.\n4. Once sync is done on this device, sync all your other devices and let it run to completion.\n\nImportant: you only need to run this ONCE on one device.', buttonLabel)}`;
t = t.replace(/\n\n/g, '</p><p>');
t = t.replace(/\n/g, '<br>');
t = `<p>${t}</p>`;
return (
<div>
<h2>{_('Re-encryption')}</h2>
<p style={theme.textStyle} dangerouslySetInnerHTML={{ __html: t }}></p>
<span style={{ marginRight: 10 }}>
<button onClick={() => void reencryptData()} style={theme.buttonStyle}>{buttonLabel}</button>
</span>
{ !props.shouldReencrypt ? null : <button onClick={() => dontReencryptData()} style={theme.buttonStyle}>{_('Ignore')}</button> }
</div>
);
};
// If the user should re-encrypt, ensure that the section is visible initially.
const [showAdvanced, setShowAdvanced] = useState<boolean>(props.shouldReencrypt);
const toggleAdvanced = useCallback(() => {
setShowAdvanced(!showAdvanced);
}, [showAdvanced]);
const renderAdvancedSection = () => {
const reEncryptSection = renderReencryptData();
if (!reEncryptSection) return null;
return (
<div>
<ToggleAdvancedSettingsButton
onClick={toggleAdvanced}
advancedSettingsVisible={showAdvanced}/>
{ showAdvanced ? reEncryptSection : null }
</div>
);
};
return (
<div className="config-screen-content">
{renderDebugSection()}
@ -346,6 +380,7 @@ const EncryptionConfigScreen = (props: Props) => {
{renderMasterKeySection(props.masterKeys.filter(mk => masterKeyEnabled(mk)), true)}
{renderMasterKeySection(props.masterKeys.filter(mk => !masterKeyEnabled(mk)), false)}
{renderNonExistingMasterKeysSection()}
{renderAdvancedSection()}
</div>
);
};

View File

@ -78,7 +78,7 @@ export async function commandAttachFileToBody(body: string, filePaths: string[]
logger.info(`Attaching ${filePath}`);
const newBody = await shim.attachFileToNoteBody(body, filePath, options.position, {
createFileURL: options.createFileURL,
resizeLargeImages: 'ask',
resizeLargeImages: Setting.value('imageResizing'),
});
if (!newBody) {

View File

@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.12.10",
"version": "2.12.11",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,

View File

@ -572,29 +572,16 @@ class NoteScreenComponent extends BaseScreenComponent {
public async resizeImage(localFilePath: string, targetPath: string, mimeType: string) {
const maxSize = Resource.IMAGE_MAX_DIMENSION;
const dimensions: any = await this.imageDimensions(localFilePath);
reg.logger().info('Original dimensions ', dimensions);
let mustResize = dimensions.width > maxSize || dimensions.height > maxSize;
if (mustResize) {
const buttonId = await dialogs.pop(this, _('You are about to attach a large image (%dx%d pixels). Would you like to resize it down to %d pixels before attaching it?', dimensions.width, dimensions.height, maxSize), [
{ text: _('Yes'), id: 'yes' },
{ text: _('No'), id: 'no' },
{ text: _('Cancel'), id: 'cancel' },
]);
if (buttonId === 'cancel') return false;
mustResize = buttonId === 'yes';
}
if (mustResize) {
const saveOriginalImage = async () => {
await shim.fsDriver().copy(localFilePath, targetPath);
return true;
};
const saveResizedImage = async () => {
dimensions.width = maxSize;
dimensions.height = maxSize;
reg.logger().info('New dimensions ', dimensions);
const format = mimeType === 'image/png' ? 'PNG' : 'JPEG';
@ -612,11 +599,27 @@ class NoteScreenComponent extends BaseScreenComponent {
} catch (error) {
reg.logger().warn('Error when unlinking cached file: ', error);
}
} else {
await shim.fsDriver().copy(localFilePath, targetPath);
return true;
};
const canResize = dimensions.width > maxSize || dimensions.height > maxSize;
if (canResize) {
const resizeLargeImages = Setting.value('imageResizing');
if (resizeLargeImages === 'alwaysAsk') {
const userAnswer = await dialogs.pop(this, `${_('You are about to attach a large image (%dx%d pixels). Would you like to resize it down to %d pixels before attaching it?', dimensions.width, dimensions.height, maxSize)}\n\n${_('(You may disable this prompt in the options)')}`, [
{ text: _('Yes'), id: 'yes' },
{ text: _('No'), id: 'no' },
{ text: _('Cancel'), id: 'cancel' },
]);
if (userAnswer === 'yes') return await saveResizedImage();
if (userAnswer === 'no') return await saveOriginalImage();
if (userAnswer === 'cancel') return false;
} else if (resizeLargeImages === 'alwaysResize') {
return await saveResizedImage();
}
}
return true;
return await saveOriginalImage();
}
public async attachFile(pickerResponse: any, fileType: string) {

View File

@ -343,7 +343,7 @@ PODS:
- React-Core
- react-native-camera/RN (4.2.1):
- React-Core
- react-native-document-picker (8.2.1):
- react-native-document-picker (9.0.1):
- React-Core
- react-native-fingerprint-scanner (6.0.0):
- React
@ -355,7 +355,7 @@ PODS:
- React-Core
- react-native-image-resizer (1.4.5):
- React-Core
- react-native-netinfo (9.3.11):
- react-native-netinfo (9.4.1):
- React-Core
- react-native-rsa-native (2.0.5):
- React
@ -465,9 +465,9 @@ PODS:
- React-Core
- RNCPushNotificationIOS (1.11.0):
- React-Core
- RNDateTimePicker (7.2.0):
- RNDateTimePicker (7.3.0):
- React-Core
- RNDeviceInfo (10.6.1):
- RNDeviceInfo (10.7.0):
- React-Core
- RNExitApp (1.1.0):
- React
@ -819,13 +819,13 @@ SPEC CHECKSUMS:
React-logger: ef2269b3afa6ba868da90496c3e17a4ec4f4cee0
react-native-alarm-notification: 0732f97be04975a23ba60e675bdb961a0aaf6aa6
react-native-camera: 3eae183c1d111103963f3dd913b65d01aef8110f
react-native-document-picker: 69ca2094d8780cfc1e7e613894d15290fdc54bba
react-native-document-picker: 2b8f18667caee73a96708a82b284a4f40b30a156
react-native-fingerprint-scanner: ac6656f18c8e45a7459302b84da41a44ad96dbbe
react-native-geolocation: 0f7fe8a4c2de477e278b0365cce27d089a8c5903
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
react-native-image-picker: db60857e03d63721f19b6f4027de20429ddd9cba
react-native-image-resizer: d9fb629a867335bdc13230ac2a58702bb8c8828f
react-native-netinfo: 3a48f51c18dbd9253440621955e11de71bc51b32
react-native-netinfo: fefd4e98d75cbdd6e85fc530f7111a8afdf2b0c5
react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a
react-native-saf-x: 129cd2ddf120a1f6164c724b2846d172666b33de
react-native-safe-area-context: 68b07eabfb0d14547d36f6929c0e98d818064f02
@ -849,8 +849,8 @@ SPEC CHECKSUMS:
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8
RNDateTimePicker: 3942382593f104af226ad9c56e16166960c7ae30
RNDeviceInfo: ab292735ad4fccc5f2aec0c773f7a7f03c7073ae
RNDateTimePicker: 01e6d27ba2e0931cd05049c5bff6171c3c027ea8
RNDeviceInfo: 25d818c85db769cc0e7083d39efaa01a6f450df3
RNExitApp: c4e052df2568b43bec8a37c7cd61194d4cfee2c3
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688

View File

@ -26,7 +26,7 @@
"@react-native-community/clipboard": "1.5.1",
"@react-native-community/datetimepicker": "7.3.0",
"@react-native-community/geolocation": "3.0.6",
"@react-native-community/netinfo": "9.3.11",
"@react-native-community/netinfo": "9.4.1",
"@react-native-community/push-notification-ios": "1.11.0",
"@react-native-community/slider": "4.4.2",
"assert-browserify": "2.0.0",
@ -45,9 +45,9 @@
"react-native": "0.71.10",
"react-native-action-button": "2.8.5",
"react-native-camera": "4.2.1",
"react-native-device-info": "10.6.1",
"react-native-device-info": "10.7.0",
"react-native-dialogbox": "0.6.10",
"react-native-document-picker": "8.2.1",
"react-native-document-picker": "9.0.1",
"react-native-drawer-layout": "3.2.1",
"react-native-dropdownalert": "4.5.1",
"react-native-exit-app": "1.1.0",
@ -60,7 +60,7 @@
"react-native-image-resizer": "1.4.5",
"react-native-localize": "3.0.2",
"react-native-modal-datetime-picker": "15.0.1",
"react-native-paper": "5.8.0",
"react-native-paper": "5.9.1",
"react-native-popup-menu": "0.16.1",
"react-native-quick-actions": "0.3.13",
"react-native-reanimated": "3.3.0",
@ -105,7 +105,7 @@
"@joplin/tools": "~2.12",
"@lezer/highlight": "1.1.4",
"@testing-library/jest-native": "5.4.2",
"@testing-library/react-native": "12.1.2",
"@testing-library/react-native": "12.1.3",
"@tsconfig/react-native": "2.0.2",
"@types/fs-extra": "11.0.1",
"@types/jest": "29.5.3",

View File

@ -351,7 +351,7 @@ export default class FsDriverRN extends FsDriverBase {
} else {
// the result is an array
if (multiple) {
result = await DocumentPicker.pickMultiple();
result = await DocumentPicker.pick({ allowMultiSelection: true });
} else {
result = [await DocumentPicker.pick()];
}

View File

@ -6,6 +6,6 @@
},
"dependencies": {
"typedoc": "0.17.8",
"typescript": "5.0.4"
"typescript": "4.7.4"
}
}

View File

@ -735,6 +735,20 @@ export default class BaseApplication {
return toSystemSlashes(output, 'linux');
}
protected startRotatingLogMaintenance(profileDir: string) {
this.rotatingLogs = new RotatingLogs(profileDir);
const processLogs = async () => {
try {
await this.rotatingLogs.cleanActiveLogFile();
await this.rotatingLogs.deleteNonActiveLogFiles();
} catch (error) {
appLogger.error(error);
}
};
shim.setTimeout(() => { void processLogs(); }, 60000);
shim.setInterval(() => { void processLogs(); }, 24 * 60 * 60 * 1000);
}
public async start(argv: string[], options: StartOptions = null): Promise<any> {
options = {
keychainEnabled: true,
@ -932,18 +946,6 @@ export default class BaseApplication {
await MigrationService.instance().run();
this.rotatingLogs = new RotatingLogs(profileDir);
const processLogs = async () => {
try {
await this.rotatingLogs.cleanActiveLogFile();
await this.rotatingLogs.deleteNonActiveLogFiles();
} catch (error) {
appLogger.error(error);
}
};
shim.setTimeout(() => { void processLogs(); }, 60000);
shim.setInterval(() => { void processLogs(); }, 24 * 60 * 60 * 1000);
return argv;
}
}

View File

@ -1,56 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const shim_1 = require("./shim");
class RotatingLogs {
constructor(logFilesDir, maxFileSize = null, inactiveMaxAge = null) {
this.maxFileSize = 1024 * 1024 * 100;
this.inactiveMaxAge = 90 * 24 * 60 * 60 * 1000;
this.logFilesDir = logFilesDir;
if (maxFileSize)
this.maxFileSize = maxFileSize;
if (inactiveMaxAge)
this.inactiveMaxAge = inactiveMaxAge;
}
cleanActiveLogFile() {
return __awaiter(this, void 0, void 0, function* () {
const stats = yield this.fsDriver().stat(this.logFileFullpath());
if (stats.size >= this.maxFileSize) {
const newLogFile = this.logFileFullpath(this.getNameToNonActiveLogFile());
yield this.fsDriver().move(this.logFileFullpath(), newLogFile);
}
});
}
getNameToNonActiveLogFile() {
return `log-${Date.now()}.txt`;
}
deleteNonActiveLogFiles() {
return __awaiter(this, void 0, void 0, function* () {
const files = yield this.fsDriver().readDirStats(this.logFilesDir);
for (const file of files) {
if (!file.path.match(/^log-[0-9]+.txt$/gi))
continue;
const ageOfTheFile = Date.now() - file.birthtime;
if (ageOfTheFile >= this.inactiveMaxAge) {
yield this.fsDriver().remove(this.logFileFullpath(file.path));
}
}
});
}
logFileFullpath(fileName = 'log.txt') {
return `${this.logFilesDir}/${fileName}`;
}
fsDriver() {
return shim_1.default.fsDriver();
}
}
exports.default = RotatingLogs;
//# sourceMappingURL=RotatingLogs.js.map

View File

@ -1,56 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_extra_1 = require("fs-extra");
const test_utils_1 = require("./testing/test-utils");
const RotatingLogs_1 = require("./RotatingLogs");
const createTestLogFile = (dir) => __awaiter(void 0, void 0, void 0, function* () {
yield (0, fs_extra_1.writeFile)(`${dir}/log.txt`, 'some content');
});
describe('RotatingLogs', () => {
test('should rename log.txt to log-TIMESTAMP.txt', () => __awaiter(void 0, void 0, void 0, function* () {
let dir;
try {
dir = yield (0, test_utils_1.createTempDir)();
yield createTestLogFile(dir);
let files = yield (0, fs_extra_1.readdir)(dir);
expect(files.find(file => file.match(/^log.txt$/gi))).toBeTruthy();
expect(files.length).toBe(1);
const rotatingLogs = new RotatingLogs_1.default(dir, 1, 1);
yield rotatingLogs.cleanActiveLogFile();
files = yield (0, fs_extra_1.readdir)(dir);
expect(files.find(file => file.match(/^log.txt$/gi))).toBeFalsy();
expect(files.find(file => file.match(/^log-[0-9]+.txt$/gi))).toBeTruthy();
expect(files.length).toBe(1);
}
finally {
yield (0, fs_extra_1.remove)(dir);
}
}));
test('should delete inative log file after 1ms', () => __awaiter(void 0, void 0, void 0, function* () {
let dir;
try {
dir = yield (0, test_utils_1.createTempDir)();
yield createTestLogFile(dir);
const rotatingLogs = new RotatingLogs_1.default(dir, 1, 1);
yield rotatingLogs.cleanActiveLogFile();
yield (0, test_utils_1.msleep)(1);
yield rotatingLogs.deleteNonActiveLogFiles();
const files = yield (0, fs_extra_1.readdir)(dir);
expect(files.find(file => file.match(/^log-[0-9]+.txt$/gi))).toBeFalsy();
expect(files.length).toBe(0);
}
finally {
yield (0, fs_extra_1.remove)(dir);
}
}));
});
//# sourceMappingURL=RotatingLogs.test.js.map

View File

@ -12,7 +12,7 @@ describe('RotatingLogs', () => {
try {
dir = await createTempDir();
await createTestLogFile(dir);
let files: string[] = await readdir(dir);
let files = await readdir(dir);
expect(files.find(file => file.match(/^log.txt$/gi))).toBeTruthy();
expect(files.length).toBe(1);
const rotatingLogs: RotatingLogs = new RotatingLogs(dir, 1, 1);
@ -26,7 +26,7 @@ describe('RotatingLogs', () => {
}
});
test('should delete inative log file after 1ms', async () => {
test('should delete inactive log file after 1ms', async () => {
let dir: string;
try {
dir = await createTempDir();
@ -42,4 +42,21 @@ describe('RotatingLogs', () => {
await remove(dir);
}
});
test('should not delete the log-timestamp.txt right after its be created', async () => {
let dir: string;
try {
dir = await createTempDir();
await createTestLogFile(dir);
await msleep(100);
const rotatingLogs: RotatingLogs = new RotatingLogs(dir, 1, 100);
await rotatingLogs.cleanActiveLogFile();
await rotatingLogs.deleteNonActiveLogFiles();
const files = await readdir(dir);
expect(files.find(file => file.match(/^log-[0-9]+.txt$/gi))).toBeTruthy();
expect(files.length).toBe(1);
} finally {
await remove(dir);
}
});
});

View File

@ -29,7 +29,8 @@ export default class RotatingLogs {
const files: Stat[] = await this.fsDriver().readDirStats(this.logFilesDir);
for (const file of files) {
if (!file.path.match(/^log-[0-9]+.txt$/gi)) continue;
const ageOfTheFile: number = Date.now() - file.birthtime;
const timestamp: number = parseInt(file.path.match(/[0-9]+/g)[0], 10);
const ageOfTheFile: number = Date.now() - timestamp;
if (ageOfTheFile >= this.inactiveMaxAge) {
await this.fsDriver().remove(this.logFileFullpath(file.path));
}

View File

@ -1121,7 +1121,25 @@ class Setting extends BaseModel {
storage: SettingStorage.File,
isGlobal: true,
},
imageResizing: {
value: 'alwaysAsk',
type: SettingItemType.String,
section: 'note',
isEnum: true,
public: true,
appTypes: [AppType.Mobile, AppType.Desktop],
label: () => _('Resize large images:'),
description: () => _('Shrink large images before adding them to notes to save storage space.'),
options: () => {
return {
alwaysAsk: _('Always ask'),
alwaysResize: _('Always resize'),
neverResize: _('Never resize'),
};
},
storage: SettingStorage.File,
isGlobal: true,
},
'plugins.states': {
value: '',
type: SettingItemType.Object,

View File

@ -25,7 +25,7 @@
"@types/uuid": "9.0.2",
"clean-html": "1.5.0",
"jest": "29.5.0",
"sharp": "0.32.3",
"sharp": "0.32.4",
"typescript": "5.1.3"
},
"dependencies": {
@ -91,7 +91,7 @@
"uglifycss": "0.0.29",
"url-parse": "1.5.10",
"uuid": "9.0.0",
"word-wrap": "1.2.3",
"word-wrap": "1.2.4",
"xml2js": "0.4.23"
},
"gitHead": "eb4b0e64eab40a51b0895d3a40a9d8c3cb7b1b14"

View File

@ -184,38 +184,42 @@ function shimInit(options = null) {
if (shim.isElectron()) {
// For Electron
const nativeImage = require('electron').nativeImage;
let image = nativeImage.createFromPath(filePath);
const image = nativeImage.createFromPath(filePath);
if (image.isEmpty()) throw new Error(`Image is invalid or does not exist: ${filePath}`);
const size = image.getSize();
let mustResize = size.width > maxDim || size.height > maxDim;
if (mustResize && resizeLargeImages === 'ask') {
const answer = shim.showMessageBox(_('You are about to attach a large image (%dx%d pixels). Would you like to resize it down to %d pixels before attaching it?', size.width, size.height, maxDim), {
buttons: [_('Yes'), _('No'), _('Cancel')],
});
if (answer === 2) return false;
mustResize = answer === 0;
}
if (!mustResize) {
const saveOriginalImage = async () => {
await shim.fsDriver().copy(filePath, targetPath);
return true;
};
const saveResizedImage = async () => {
const options = {};
if (size.width > size.height) {
options.width = maxDim;
} else {
options.height = maxDim;
}
const resizedImage = image.resize(options);
await shim.writeImageToFile(resizedImage, mime, targetPath);
return true;
};
const canResize = size.width > maxDim || size.height > maxDim;
if (canResize) {
if (resizeLargeImages === 'alwaysAsk') {
const Yes = 0, No = 1, Cancel = 2;
const userAnswer = shim.showMessageBox(`${_('You are about to attach a large image (%dx%d pixels). Would you like to resize it down to %d pixels before attaching it?', size.width, size.height, maxDim)}\n\n${_('(You may disable this prompt in the options)')}`, {
buttons: [_('Yes'), _('No'), _('Cancel')],
});
if (userAnswer === Yes) return await saveResizedImage();
if (userAnswer === No) return await saveOriginalImage();
if (userAnswer === Cancel) return false;
} else if (resizeLargeImages === 'alwaysResize') {
return await saveResizedImage();
}
}
const options = {};
if (size.width > size.height) {
options.width = maxDim;
} else {
options.height = maxDim;
}
image = image.resize(options);
await shim.writeImageToFile(image, mime, targetPath);
return await saveOriginalImage();
} else {
// For the CLI tool
const image = sharp(filePath);
@ -241,8 +245,6 @@ function shimInit(options = null) {
});
});
}
return true;
};
// This is a bit of an ugly method that's used to both create a new resource

View File

@ -43,7 +43,7 @@
"mustache": "4.2.0",
"nanoid": "2.1.11",
"node-cron": "3.0.2",
"nodemailer": "6.9.3",
"nodemailer": "6.9.4",
"nodemon": "2.0.22",
"pg": "8.11.1",
"pretty-bytes": "5.6.0",

View File

@ -1,6 +1,6 @@
import { createUserAndSession, beforeAllDb, afterAllTests, beforeEachDb, models, checkThrowAsync, expectThrow } from '../utils/testing/testUtils';
import { EmailSender, UserFlagType } from '../services/database/types';
import { ErrorUnprocessableEntity } from '../utils/errors';
import { ErrorBadRequest, ErrorUnprocessableEntity } from '../utils/errors';
import { betaUserDateRange, stripeConfig } from '../utils/stripe';
import { accountByType, AccountType } from './UserModel';
import { failedPaymentFinalAccount, failedPaymentWarningInterval } from './SubscriptionModel';
@ -425,4 +425,13 @@ describe('UserModel', () => {
expect((await models().user().load(user1.id)).enabled).toBe(0);
});
test('should throw an error if the password being saved seems to be hashed', async () => {
const passwordSimilarToHash = '$2a$10';
const error = await checkThrowAsync(async () => await models().user().save({ password: passwordSimilarToHash }));
expect(error.message).toBe('Unable to save user because password already seems to be hashed. User id: undefined');
expect(error instanceof ErrorBadRequest).toBe(true);
});
});

View File

@ -1,6 +1,6 @@
import BaseModel, { AclAction, SaveOptions, ValidateOptions } from './BaseModel';
import { EmailSender, Item, NotificationLevel, Subscription, User, UserFlagType, Uuid } from '../services/database/types';
import * as auth from '../utils/auth';
import { isHashedPassword, hashPassword, checkPassword } from '../utils/auth';
import { ErrorUnprocessableEntity, ErrorForbidden, ErrorPayloadTooLarge, ErrorNotFound, ErrorBadRequest } from '../utils/errors';
import { ModelType } from '@joplin/lib/BaseModel';
import { _ } from '@joplin/lib/locale';
@ -125,7 +125,7 @@ export default class UserModel extends BaseModel<User> {
public async login(email: string, password: string): Promise<User> {
const user = await this.loadByEmail(email);
if (!user) return null;
if (!auth.checkPassword(password, user.password)) return null;
if (!checkPassword(password, user.password)) return null;
return user;
}
@ -636,8 +636,11 @@ export default class UserModel extends BaseModel<User> {
const user = this.formatValues(object);
if (user.password) {
if (isHashedPassword(user.password)) {
throw new ErrorBadRequest(`Unable to save user because password already seems to be hashed. User id: ${user.id}`);
}
if (!options.skipValidation) this.validatePassword(user.password);
user.password = auth.hashPassword(user.password);
user.password = hashPassword(user.password);
}
const isNew = await this.isNew(object, options);

View File

@ -0,0 +1,19 @@
import { isHashedPassword } from '../../utils/auth';
describe('isHashedPassword', () => {
it('should be true if password starts with $2a$10', () => {
expect(isHashedPassword('$2a$10$LMKVPiNOWDZhtw9NizNIEuNGLsjOxQAcrwQJ0lnKuiaOtyFgZEnwO')).toBe(true);
});
it.each(
[
'password',
'123456',
'simple-password-that-takes-is-long',
'nuXUhqecx!RzK3wv6^xYaVEP%9fc$T%$E2k%9Q&TKvtDhR#2PUw3kA8KX3w2baAD8m#N9@52!DvfYn*X6hP#uAvpGF57*H9avcoePbR&4Q2XzckJnSW*EVm4G@a#YvnR',
]
)('should be false if password starts with $2a$10: %', (password) => {
expect(isHashedPassword(password)).toBe(false);
});
});

View File

@ -0,0 +1,18 @@
import { hashPassword } from './auth';
describe('hashPassword', () => {
it.each(
[
'password',
'123456',
'simple-password-that-takes-is-long',
'nuXUhqecx!RzK3wv6^xYaVEP%9fc$T%$E2k%9Q&TKvtDhR#2PUw3kA8KX3w2baAD8m#N9@52!DvfYn*X6hP#uAvpGF57*H9avcoePbR&4Q2XzckJnSW*EVm4G@a#YvnR',
'$2a$10',
'$2a$10$LMKVPiNOWDZhtw9NizNIEuNGLsjOxQAcrwQJ0lnKuiaOtyFgZEnwO',
]
)('should return a string that starts with $2a$10 for the password: %', async (plainText) => {
expect(hashPassword(plainText).startsWith('$2a$10')).toBe(true);
});
});

View File

@ -8,3 +8,7 @@ export function hashPassword(password: string): string {
export function checkPassword(password: string, hash: string): boolean {
return bcrypt.compareSync(password, hash);
}
export const isHashedPassword = (password: string) => {
return password.startsWith('$2a$10');
};

View File

@ -36,7 +36,7 @@
"node-fetch": "2.6.7",
"relative": "3.0.2",
"request": "2.88.2",
"sharp": "0.32.3",
"sharp": "0.32.4",
"source-map-support": "0.5.21",
"uri-template": "2.0.0",
"yargs": "17.7.2"

View File

@ -257,7 +257,7 @@ async function main() {
await remove(`${docDir}`);
await copy(websiteAssetDir, `${docDir}`);
const sponsors = await loadSponsors();
const sponsors = process.env.SKIP_SPONSOR_PROCESSING ? { github: [], orgs: [] } : await loadSponsors();
const partials = await loadMustachePartials(partialDir);
const assetUrls = await getAssetUrls();

View File

@ -663,6 +663,14 @@
"created_at": "2023-08-07T11:20:16Z",
"repoId": 79162682,
"pullRequestNo": 8627
},
{
"name": "TuTAH1",
"id": 15982179,
"comment_id": 1668635558,
"created_at": "2023-08-07T22:03:21Z",
"repoId": 79162682,
"pullRequestNo": 8635
}
]
}

24
readme/release_cycle.md Normal file
View File

@ -0,0 +1,24 @@
# Joplin release cycle
We release four major versions per year, one per quarter, following a three-phase process: "Release", "Freeze", and "Publishing". This reliable schedule empowers the community, as well as businesses and developers, to effectively plan their own roadmaps.
Here's what each phase entails:
**Phase 1: "Release" - Cycle start** During this phase, our team will focus on developing new features and improvements for the next release.
**Phase 2: "Freeze" - Stability and bug fixing** The "Freeze" phase will begin two weeks before the intended publishing date. At this point, we will halt the addition of new features and concentrate on stabilizing the software. Our main objective will be to fix any remaining bugs and optimize performance.
**Phase 3: "Publishing" - Final version** The "Publishing" phase is when we will officially release the new version of Joplin.
Below is our schedule related to the phases mentioned above:
| Release | Freeze | Publishing |
| --- | --- | --- |
| **Joplin 2.12** | Aug 17 2023 - Aug 31 2023 | Sept 1 2023 - Sept 7 2023 |
| **Joplin 2.13** | Nov 16 2023 - Nov 30 2023 | Dec 1 2023 - Dec 7 2023 |
| **Joplin 2.14** | Feb 15 2024 - Feb 29 2024 | Mar 1 2024 - Mar 7 2024 |
| **Joplin 2.15** | Jun 17 2024 - Jun 31 2024 | Jul 1 2024 - Jul 7 2024 |
| **Joplin 2.16** | Aug 17 2024 - Aug 31 2024 | Sept 1 2024 - Sept 7 2024 |
| **Joplin 2.17** | Nov 16 2024 - Nov 30 2024 | Dec 1 2024 - Dec 7 2024 |
Please note that during this release process, [prereleases are also regularly made available](https://joplinapp.org/prereleases/). This allows you to test the application before its final release and to provide feedback on the new features being added and influence development. Moreover, if you find an issue, you can report it and we will give it a high priority and attempt to fix it as soon as possible.

View File

@ -26,8 +26,8 @@
"packages/app-cli/tests/**",
"packages/app-clipper/popup",
"packages/app-mobile/android/app/build.gradle",
"packages/generate-plugin-doc/**",
"packages/plugins/**",
"packages/react-native-vosk",
],
"ignoreDeps": [
"@babel/core",
@ -130,6 +130,7 @@
"pretty-bytes",
"strip-ansi",
"formidable",
"node-emoji",
// @koa/cors has undocumented breaking changes, and the package is not
// well supported so we're stuck with latest v3 for now

193
yarn.lock
View File

@ -4525,11 +4525,11 @@ __metadata:
"@react-native-community/clipboard": 1.5.1
"@react-native-community/datetimepicker": 7.3.0
"@react-native-community/geolocation": 3.0.6
"@react-native-community/netinfo": 9.3.11
"@react-native-community/netinfo": 9.4.1
"@react-native-community/push-notification-ios": 1.11.0
"@react-native-community/slider": 4.4.2
"@testing-library/jest-native": 5.4.2
"@testing-library/react-native": 12.1.2
"@testing-library/react-native": 12.1.3
"@tsconfig/react-native": 2.0.2
"@types/fs-extra": 11.0.1
"@types/jest": 29.5.3
@ -4565,9 +4565,9 @@ __metadata:
react-native: 0.71.10
react-native-action-button: 2.8.5
react-native-camera: 4.2.1
react-native-device-info: 10.6.1
react-native-device-info: 10.7.0
react-native-dialogbox: 0.6.10
react-native-document-picker: 8.2.1
react-native-document-picker: 9.0.1
react-native-drawer-layout: 3.2.1
react-native-dropdownalert: 4.5.1
react-native-exit-app: 1.1.0
@ -4580,7 +4580,7 @@ __metadata:
react-native-image-resizer: 1.4.5
react-native-localize: 3.0.2
react-native-modal-datetime-picker: 15.0.1
react-native-paper: 5.8.0
react-native-paper: 5.9.1
react-native-popup-menu: 0.16.1
react-native-quick-actions: 0.3.13
react-native-reanimated: 3.3.0
@ -4734,7 +4734,7 @@ __metadata:
relative: 3.0.2
reselect: 4.1.8
server-destroy: 1.0.1
sharp: 0.32.3
sharp: 0.32.4
sprintf-js: 1.1.2
sqlite3: 5.1.6
string-padding: 1.0.2
@ -4745,7 +4745,7 @@ __metadata:
uglifycss: 0.0.29
url-parse: 1.5.10
uuid: 9.0.0
word-wrap: 1.2.3
word-wrap: 1.2.4
xml2js: 0.4.23
languageName: unknown
linkType: soft
@ -4916,7 +4916,7 @@ __metadata:
nanoid: 2.1.11
node-cron: 3.0.2
node-mocks-http: 1.12.2
nodemailer: 6.9.3
nodemailer: 6.9.4
nodemon: 2.0.22
pg: 8.11.1
pretty-bytes: 5.6.0
@ -4968,7 +4968,7 @@ __metadata:
request: 2.88.2
rss: 1.2.2
sass: 1.63.6
sharp: 0.32.3
sharp: 0.32.4
source-map-support: 0.5.21
sqlite3: 5.1.6
typescript: 5.1.3
@ -6875,12 +6875,12 @@ __metadata:
languageName: node
linkType: hard
"@react-native-community/netinfo@npm:9.3.11":
version: 9.3.11
resolution: "@react-native-community/netinfo@npm:9.3.11"
"@react-native-community/netinfo@npm:9.4.1":
version: 9.4.1
resolution: "@react-native-community/netinfo@npm:9.4.1"
peerDependencies:
react-native: ">=0.59"
checksum: 1021fe0a3bda6fe98f940d067ae56e688ba00de8696bcdb02a2802af1e91a707709e83c8cd1ac01edc7c6a9af798a408448d0661cf61c7bd1795cfb61e709455
checksum: cf6471a50a5282f858797cda7531c61ac3d94de2e1c379b14a11f6b049f582606dae55a041dd900c56b01faf69eb5cfef9b4e84b0ea7f02de52804aa5a6e22df
languageName: node
linkType: hard
@ -7232,9 +7232,9 @@ __metadata:
languageName: node
linkType: hard
"@testing-library/react-native@npm:12.1.2":
version: 12.1.2
resolution: "@testing-library/react-native@npm:12.1.2"
"@testing-library/react-native@npm:12.1.3":
version: 12.1.3
resolution: "@testing-library/react-native@npm:12.1.3"
dependencies:
pretty-format: ^29.0.0
peerDependencies:
@ -7245,7 +7245,7 @@ __metadata:
peerDependenciesMeta:
jest:
optional: true
checksum: 912fc961f213a8fa171b9b980d6f4edd8f11a012498fcf1b8e0d3ac1d20e85b61469a80914fda893aa48cb0d4b3f6075ec2723c58dae96eeac0ee1cd6e6daa3e
checksum: afc472bdf1f3b966d292749d9f1d4dc70112b7124935aa60f7d2e928fceef7b79dc4e7241b0461876f2ecf740440c6e400756075f684ecf9341c7528d3592603
languageName: node
linkType: hard
@ -8773,6 +8773,13 @@ __metadata:
languageName: node
linkType: hard
"@yarnpkg/lockfile@npm:^1.1.0":
version: 1.1.0
resolution: "@yarnpkg/lockfile@npm:1.1.0"
checksum: 05b881b4866a3546861fee756e6d3812776ea47fa6eb7098f983d6d0eefa02e12b66c3fff931574120f196286a7ad4879ce02743c8bb2be36c6a576c7852083a
languageName: node
linkType: hard
"@zkochan/cmd-shim@npm:^3.1.0":
version: 3.1.0
resolution: "@zkochan/cmd-shim@npm:3.1.0"
@ -11553,6 +11560,13 @@ __metadata:
languageName: node
linkType: hard
"ci-info@npm:^3.7.0":
version: 3.8.0
resolution: "ci-info@npm:3.8.0"
checksum: d0a4d3160497cae54294974a7246202244fff031b0a6ea20dd57b10ec510aa17399c41a1b0982142c105f3255aff2173e5c0dd7302ee1b2f28ba3debda375098
languageName: node
linkType: hard
"cipher-base@npm:^1.0.0, cipher-base@npm:^1.0.1, cipher-base@npm:^1.0.3":
version: 1.0.4
resolution: "cipher-base@npm:1.0.4"
@ -14231,6 +14245,13 @@ __metadata:
languageName: node
linkType: hard
"detect-libc@npm:^2.0.2":
version: 2.0.2
resolution: "detect-libc@npm:2.0.2"
checksum: 2b2cd3649b83d576f4be7cc37eb3b1815c79969c8b1a03a40a4d55d83bc74d010753485753448eacb98784abf22f7dbd3911fd3b60e29fda28fed2d1a997944d
languageName: node
linkType: hard
"detect-newline@npm:^3.0.0":
version: 3.1.0
resolution: "detect-newline@npm:3.1.0"
@ -16782,6 +16803,15 @@ __metadata:
languageName: node
linkType: hard
"find-yarn-workspace-root@npm:^2.0.0":
version: 2.0.0
resolution: "find-yarn-workspace-root@npm:2.0.0"
dependencies:
micromatch: ^4.0.2
checksum: fa5ca8f9d08fe7a54ce7c0a5931ff9b7e36f9ee7b9475fb13752bcea80ec6b5f180fa5102d60b376d5526ce924ea3fc6b19301262efa0a5d248dd710f3644242
languageName: node
linkType: hard
"findit@npm:^2.0.0":
version: 2.0.0
resolution: "findit@npm:2.0.0"
@ -17375,7 +17405,7 @@ __metadata:
resolution: "generate-plugin-doc@workspace:packages/generate-plugin-doc"
dependencies:
typedoc: 0.17.8
typescript: 5.0.4
typescript: 4.7.4
languageName: unknown
linkType: soft
@ -20086,7 +20116,7 @@ __metadata:
languageName: node
linkType: hard
"is-wsl@npm:^2.2.0":
"is-wsl@npm:^2.1.1, is-wsl@npm:^2.2.0":
version: 2.2.0
resolution: "is-wsl@npm:2.2.0"
dependencies:
@ -21020,7 +21050,7 @@ __metadata:
proper-lockfile: 4.1.2
read-chunk: 2.1.0
server-destroy: 1.0.1
sharp: 0.32.3
sharp: 0.32.4
sprintf-js: 1.1.2
sqlite3: 5.1.6
string-padding: 1.0.2
@ -21031,7 +21061,7 @@ __metadata:
tkwidgets: 0.5.27
typescript: 5.1.3
url-parse: 1.5.10
word-wrap: 1.2.3
word-wrap: 1.2.4
yargs-parser: 21.1.1
bin:
joplin: ./main.js
@ -21607,6 +21637,15 @@ __metadata:
languageName: node
linkType: hard
"klaw-sync@npm:^6.0.0":
version: 6.0.0
resolution: "klaw-sync@npm:6.0.0"
dependencies:
graceful-fs: ^4.1.11
checksum: 0da397f8961313c3ef8f79fb63af9002cde5a8fb2aeb1a37351feff0dd6006129c790400c3f5c3b4e757bedcabb13d21ec0a5eaef5a593d59515d4f2c291e475
languageName: node
linkType: hard
"klaw@npm:^1.0.0":
version: 1.3.1
resolution: "klaw@npm:1.3.1"
@ -23635,7 +23674,7 @@ __metadata:
languageName: node
linkType: hard
"micromatch@npm:^4.0.0, micromatch@npm:^4.0.5":
"micromatch@npm:^4.0.0, micromatch@npm:^4.0.2, micromatch@npm:^4.0.5":
version: 4.0.5
resolution: "micromatch@npm:4.0.5"
dependencies:
@ -24780,10 +24819,10 @@ __metadata:
languageName: node
linkType: hard
"nodemailer@npm:6.9.3":
version: 6.9.3
resolution: "nodemailer@npm:6.9.3"
checksum: 3bea8316652c0578515d9146d2f24660e4855807520153f061d39af76b440a4f61b4e70f10fed35f8f12f115f6aea1aeb483ea7ba0337c0e3e675f117c41c611
"nodemailer@npm:6.9.4":
version: 6.9.4
resolution: "nodemailer@npm:6.9.4"
checksum: 1a61039c9c6041ee9ed423c7fa3685cb300679f1ef8dcfd912fc58d9b8c429c34ac1187eebd4d86baf7158222f6e3a1ea2b4fd2a29630fb9ef0027cef46e90bb
languageName: node
linkType: hard
@ -25528,6 +25567,16 @@ __metadata:
languageName: node
linkType: hard
"open@npm:^7.4.2":
version: 7.4.2
resolution: "open@npm:7.4.2"
dependencies:
is-docker: ^2.0.0
is-wsl: ^2.1.1
checksum: 3333900ec0e420d64c23b831bc3467e57031461d843c801f569b2204a1acc3cd7b3ec3c7897afc9dde86491dfa289708eb92bba164093d8bd88fb2c231843c91
languageName: node
linkType: hard
"opencollective-postinstall@npm:^2.0.2":
version: 2.0.3
resolution: "opencollective-postinstall@npm:2.0.3"
@ -26162,6 +26211,30 @@ __metadata:
languageName: node
linkType: hard
"patch-package@npm:^7.0.0":
version: 7.0.2
resolution: "patch-package@npm:7.0.2"
dependencies:
"@yarnpkg/lockfile": ^1.1.0
chalk: ^4.1.2
ci-info: ^3.7.0
cross-spawn: ^7.0.3
find-yarn-workspace-root: ^2.0.0
fs-extra: ^9.0.0
klaw-sync: ^6.0.0
minimist: ^1.2.6
open: ^7.4.2
rimraf: ^2.6.3
semver: ^7.5.3
slash: ^2.0.0
tmp: ^0.0.33
yaml: ^2.2.2
bin:
patch-package: index.js
checksum: de2cf60effc8b59ee15d4930f84eea63c217525f0133c926850ee3a2437651d8aabbd0fec4ddee1b8b8da4fd380bcea90ef3c4acd69026057fa80cdc823b59a4
languageName: node
linkType: hard
"path-browserify@npm:1.0.1":
version: 1.0.1
resolution: "path-browserify@npm:1.0.1"
@ -27700,12 +27773,12 @@ __metadata:
languageName: node
linkType: hard
"react-native-device-info@npm:10.6.1":
version: 10.6.1
resolution: "react-native-device-info@npm:10.6.1"
"react-native-device-info@npm:10.7.0":
version: 10.7.0
resolution: "react-native-device-info@npm:10.7.0"
peerDependencies:
react-native: "*"
checksum: aa3e56fad9256994714aa310ef5ab2ef80118ae2af73ac830cda182fa82b72785b03fd0e522b871a59723e6914274dfa203f53e3467feebb9c913df63643a014
checksum: ae2f69f510f25026e128946efc108385920cd4bda5723d50f74406e960d5b0cc3bc0cf3f53a6a0f83ba274778a6ac63d1f64abcfe068728486ec642342931523
languageName: node
linkType: hard
@ -27721,9 +27794,9 @@ __metadata:
languageName: node
linkType: hard
"react-native-document-picker@npm:8.2.1":
version: 8.2.1
resolution: "react-native-document-picker@npm:8.2.1"
"react-native-document-picker@npm:9.0.1":
version: 9.0.1
resolution: "react-native-document-picker@npm:9.0.1"
dependencies:
invariant: ^2.2.4
peerDependencies:
@ -27733,7 +27806,7 @@ __metadata:
peerDependenciesMeta:
react-native-windows:
optional: true
checksum: 575d3bec391044fdaf8d4d740a115a0b3e5cbafdb893ce19157b0c5fefb052c208b9db6d352e171d358c723612e47fe67cda4634a60d3a245a30ff103b1cb84c
checksum: a8ad0bc2ed13290e8d7ff5f77aa7e35ffda9fa380a7d01d1286111e92656415985d61469a50567b8bcb67e42c41a36b89e879d0d08b40d3fdda171eaa08721e4
languageName: node
linkType: hard
@ -27887,19 +27960,20 @@ __metadata:
languageName: node
linkType: hard
"react-native-paper@npm:5.8.0":
version: 5.8.0
resolution: "react-native-paper@npm:5.8.0"
"react-native-paper@npm:5.9.1":
version: 5.9.1
resolution: "react-native-paper@npm:5.9.1"
dependencies:
"@callstack/react-theme-provider": ^3.0.8
color: ^3.1.2
patch-package: ^7.0.0
use-latest-callback: ^0.1.5
peerDependencies:
react: "*"
react-native: "*"
react-native-safe-area-context: "*"
react-native-vector-icons: "*"
checksum: b1cdf33b8d3140991c8fb7397ddcb7e898ce592f1ad76f505bda5b2d9cf8d9a6b2da6f73430d1457a48f8b973b548451211e88a3299b6ce99a259215352d5457
checksum: 47b23f827d3d5d296dc888d8c10c689465ccf2b8d815b2ecea8c5a41b2244776d153a6ffeaf14df61a231cd9542cc26199e85dd94687acec6b6adeb3f0ea271d
languageName: node
linkType: hard
@ -29954,12 +30028,12 @@ __metadata:
languageName: node
linkType: hard
"sharp@npm:0.32.3":
version: 0.32.3
resolution: "sharp@npm:0.32.3"
"sharp@npm:0.32.4":
version: 0.32.4
resolution: "sharp@npm:0.32.4"
dependencies:
color: ^4.2.3
detect-libc: ^2.0.1
detect-libc: ^2.0.2
node-addon-api: ^6.1.0
node-gyp: latest
prebuild-install: ^7.1.1
@ -29967,7 +30041,7 @@ __metadata:
simple-get: ^4.0.1
tar-fs: ^3.0.4
tunnel-agent: ^0.6.0
checksum: 8a6ed0d00bd4d3d6ba92c392fe1f00a4a207f138257a4d903ce5afe5c6d7f684b5218c5b4e8df1097aac960ac10e0114c823f6a8a7e18255bf4a8ec364087051
checksum: 52e3cfe8fbba2623a9b935be8a3d00d6993a2c56c775ac5cc89b273826db95f029f68a0029a37f96dcb6790aa2e3c05a02599035535b319f50ab31f5d86a13f0
languageName: node
linkType: hard
@ -32833,7 +32907,7 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:4 - 5, typescript@npm:5.0.4":
"typescript@npm:4 - 5":
version: 5.0.4
resolution: "typescript@npm:5.0.4"
bin:
@ -32843,6 +32917,16 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:4.7.4":
version: 4.7.4
resolution: "typescript@npm:4.7.4"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 5750181b1cd7e6482c4195825547e70f944114fb47e58e4aa7553e62f11b3f3173766aef9c281783edfd881f7b8299cf35e3ca8caebe73d8464528c907a164df
languageName: node
linkType: hard
"typescript@npm:5.1.3":
version: 5.1.3
resolution: "typescript@npm:5.1.3"
@ -32873,7 +32957,7 @@ __metadata:
languageName: node
linkType: hard
"typescript@patch:typescript@4 - 5#~builtin<compat/typescript>, typescript@patch:typescript@5.0.4#~builtin<compat/typescript>":
"typescript@patch:typescript@4 - 5#~builtin<compat/typescript>":
version: 5.0.4
resolution: "typescript@patch:typescript@npm%3A5.0.4#~builtin<compat/typescript>::version=5.0.4&hash=ad5954"
bin:
@ -32883,6 +32967,16 @@ __metadata:
languageName: node
linkType: hard
"typescript@patch:typescript@4.7.4#~builtin<compat/typescript>":
version: 4.7.4
resolution: "typescript@patch:typescript@npm%3A4.7.4#~builtin<compat/typescript>::version=4.7.4&hash=65a307"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 9096d8f6c16cb80ef3bf96fcbbd055bf1c4a43bd14f3b7be45a9fbe7ada46ec977f604d5feed3263b4f2aa7d4c7477ce5f9cd87de0d6feedec69a983f3a4f93e
languageName: node
linkType: hard
"typescript@patch:typescript@5.1.3#~builtin<compat/typescript>":
version: 5.1.3
resolution: "typescript@patch:typescript@npm%3A5.1.3#~builtin<compat/typescript>::version=5.1.3&hash=ad5954"
@ -34244,7 +34338,14 @@ __metadata:
languageName: node
linkType: hard
"word-wrap@npm:1.2.3, word-wrap@npm:^1.2.3, word-wrap@npm:~1.2.3":
"word-wrap@npm:1.2.4":
version: 1.2.4
resolution: "word-wrap@npm:1.2.4"
checksum: 8f1f2e0a397c0e074ca225ba9f67baa23f99293bc064e31355d426ae91b8b3f6b5f6c1fc9ae5e9141178bb362d563f55e62fd8d5c31f2a77e3ade56cb3e35bd1
languageName: node
linkType: hard
"word-wrap@npm:^1.2.3, word-wrap@npm:~1.2.3":
version: 1.2.3
resolution: "word-wrap@npm:1.2.3"
checksum: 30b48f91fcf12106ed3186ae4fa86a6a1842416df425be7b60485de14bec665a54a68e4b5156647dec3a70f25e84d270ca8bc8cd23182ed095f5c7206a938c1f