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:
commit
801b955ac8
@ -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
|
||||
|
15
.github/scripts/run_ci.sh
vendored
15
.github/scripts/run_ci.sh
vendored
@ -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
2
.gitignore
vendored
@ -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
|
||||
|
10
README.md
10
README.md
@ -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 -->
|
||||
|
||||
|
@ -452,6 +452,8 @@ class Application extends BaseApplication {
|
||||
type: 'FOLDER_SELECT',
|
||||
id: Setting.value('activeFolderId'),
|
||||
});
|
||||
|
||||
this.startRotatingLogMaintenance(Setting.value('profileDir'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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": {
|
||||
|
@ -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,
|
||||
|
@ -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';
|
||||
}
|
||||
|
@ -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;
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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()];
|
||||
}
|
||||
|
@ -6,6 +6,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"typedoc": "0.17.8",
|
||||
"typescript": "5.0.4"
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const shim_1 = require("./shim");
|
||||
class RotatingLogs {
|
||||
constructor(logFilesDir, maxFileSize = null, inactiveMaxAge = null) {
|
||||
this.maxFileSize = 1024 * 1024 * 100;
|
||||
this.inactiveMaxAge = 90 * 24 * 60 * 60 * 1000;
|
||||
this.logFilesDir = logFilesDir;
|
||||
if (maxFileSize)
|
||||
this.maxFileSize = maxFileSize;
|
||||
if (inactiveMaxAge)
|
||||
this.inactiveMaxAge = inactiveMaxAge;
|
||||
}
|
||||
cleanActiveLogFile() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const stats = yield this.fsDriver().stat(this.logFileFullpath());
|
||||
if (stats.size >= this.maxFileSize) {
|
||||
const newLogFile = this.logFileFullpath(this.getNameToNonActiveLogFile());
|
||||
yield this.fsDriver().move(this.logFileFullpath(), newLogFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
getNameToNonActiveLogFile() {
|
||||
return `log-${Date.now()}.txt`;
|
||||
}
|
||||
deleteNonActiveLogFiles() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const files = yield this.fsDriver().readDirStats(this.logFilesDir);
|
||||
for (const file of files) {
|
||||
if (!file.path.match(/^log-[0-9]+.txt$/gi))
|
||||
continue;
|
||||
const ageOfTheFile = Date.now() - file.birthtime;
|
||||
if (ageOfTheFile >= this.inactiveMaxAge) {
|
||||
yield this.fsDriver().remove(this.logFileFullpath(file.path));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
logFileFullpath(fileName = 'log.txt') {
|
||||
return `${this.logFilesDir}/${fileName}`;
|
||||
}
|
||||
fsDriver() {
|
||||
return shim_1.default.fsDriver();
|
||||
}
|
||||
}
|
||||
exports.default = RotatingLogs;
|
||||
//# sourceMappingURL=RotatingLogs.js.map
|
@ -1,56 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const fs_extra_1 = require("fs-extra");
|
||||
const test_utils_1 = require("./testing/test-utils");
|
||||
const RotatingLogs_1 = require("./RotatingLogs");
|
||||
const createTestLogFile = (dir) => __awaiter(void 0, void 0, void 0, function* () {
|
||||
yield (0, fs_extra_1.writeFile)(`${dir}/log.txt`, 'some content');
|
||||
});
|
||||
describe('RotatingLogs', () => {
|
||||
test('should rename log.txt to log-TIMESTAMP.txt', () => __awaiter(void 0, void 0, void 0, function* () {
|
||||
let dir;
|
||||
try {
|
||||
dir = yield (0, test_utils_1.createTempDir)();
|
||||
yield createTestLogFile(dir);
|
||||
let files = yield (0, fs_extra_1.readdir)(dir);
|
||||
expect(files.find(file => file.match(/^log.txt$/gi))).toBeTruthy();
|
||||
expect(files.length).toBe(1);
|
||||
const rotatingLogs = new RotatingLogs_1.default(dir, 1, 1);
|
||||
yield rotatingLogs.cleanActiveLogFile();
|
||||
files = yield (0, fs_extra_1.readdir)(dir);
|
||||
expect(files.find(file => file.match(/^log.txt$/gi))).toBeFalsy();
|
||||
expect(files.find(file => file.match(/^log-[0-9]+.txt$/gi))).toBeTruthy();
|
||||
expect(files.length).toBe(1);
|
||||
}
|
||||
finally {
|
||||
yield (0, fs_extra_1.remove)(dir);
|
||||
}
|
||||
}));
|
||||
test('should delete inative log file after 1ms', () => __awaiter(void 0, void 0, void 0, function* () {
|
||||
let dir;
|
||||
try {
|
||||
dir = yield (0, test_utils_1.createTempDir)();
|
||||
yield createTestLogFile(dir);
|
||||
const rotatingLogs = new RotatingLogs_1.default(dir, 1, 1);
|
||||
yield rotatingLogs.cleanActiveLogFile();
|
||||
yield (0, test_utils_1.msleep)(1);
|
||||
yield rotatingLogs.deleteNonActiveLogFiles();
|
||||
const files = yield (0, fs_extra_1.readdir)(dir);
|
||||
expect(files.find(file => file.match(/^log-[0-9]+.txt$/gi))).toBeFalsy();
|
||||
expect(files.length).toBe(0);
|
||||
}
|
||||
finally {
|
||||
yield (0, fs_extra_1.remove)(dir);
|
||||
}
|
||||
}));
|
||||
});
|
||||
//# sourceMappingURL=RotatingLogs.test.js.map
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -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);
|
||||
|
19
packages/server/src/models/utils/user.test.ts
Normal file
19
packages/server/src/models/utils/user.test.ts
Normal 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);
|
||||
});
|
||||
});
|
18
packages/server/src/utils/auth.test.ts
Normal file
18
packages/server/src/utils/auth.test.ts
Normal 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);
|
||||
});
|
||||
|
||||
});
|
@ -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');
|
||||
};
|
||||
|
@ -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"
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
24
readme/release_cycle.md
Normal 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.
|
@ -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
193
yarn.lock
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user