一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
217 行
9.2 KiB
TypeScript
217 行
9.2 KiB
TypeScript
import * as React from 'react';
|
|
import { useState } from 'react';
|
|
import { connect } from 'react-redux';
|
|
import { Platform } from 'react-native';
|
|
import { AppState } from '../../utils/types';
|
|
import WarningBox, { WarningBoxAction, WarningBoxTarget } from './WarningBox';
|
|
import NavService from '@joplin/lib/services/NavService';
|
|
import { _ } from '@joplin/lib/locale';
|
|
import { showMissingMasterKeyMessage, showUnknownKeyFormatBanner } from '@joplin/lib/services/e2ee/utils';
|
|
import { localSyncInfoFromState, noteLockKeyConflictFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils';
|
|
import { NoteLockMigrationStatus } from '@joplin/lib/reducer';
|
|
import { confirmAndSkipNoteLockKeyMigration, notMigratedMessage, withSkippedCount } from '@joplin/lib/services/noteLock/NoteLockKeyMigration';
|
|
import { Dispatch } from 'redux';
|
|
import Setting from '@joplin/lib/models/Setting';
|
|
import { ShareInvitation, ShareUserStatus } from '@joplin/lib/services/share/reducer';
|
|
import { substrWithEllipsis } from '@joplin/lib/string-utils';
|
|
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
|
|
import shim from '@joplin/lib/shim';
|
|
import Logger from '@joplin/utils/Logger';
|
|
import { reg } from '@joplin/lib/registry';
|
|
|
|
const logger = Logger.create('WarningBanner');
|
|
|
|
interface Props {
|
|
themeId: number;
|
|
showMissingMasterKeyMessage: boolean;
|
|
showUnknownKeyFormatMessage: boolean;
|
|
hasDisabledSyncItems: boolean;
|
|
shouldUpgradeSyncTarget: boolean;
|
|
showShouldUpgradeSyncTargetMessage: boolean|undefined;
|
|
hasDisabledEncryptionItems: boolean;
|
|
mustUpgradeAppMessage: string;
|
|
syncTargetAppMinVersion?: string;
|
|
shareInvitations: ShareInvitation[];
|
|
processingShareInvitationResponse: boolean;
|
|
showInvalidJoplinCloudCredential: boolean;
|
|
noteLockKeyConflict: boolean;
|
|
showNoteLockKeyConflictMessage: boolean|undefined;
|
|
noteLockMigrationStatus: NoteLockMigrationStatus|null;
|
|
dispatch: Dispatch;
|
|
}
|
|
|
|
const androidGooglePlayUrl = 'https://play.google.com/store/apps/details?id=net.cozic.joplin';
|
|
const androidPreReleaseUrl = 'https://github.com/laurent22/joplin-android/tags';
|
|
const iosAppStoreUrl = 'https://apps.apple.com/app/id1315599797';
|
|
|
|
const fetchAndroidVersionIsPreRelease = async (version: string) => {
|
|
const response = await shim.fetch(`https://api.github.com/repos/laurent22/joplin-android/releases/tags/android-v${version}`);
|
|
if (!response.ok) return null;
|
|
const release = await response.json();
|
|
return !!release.prerelease;
|
|
};
|
|
|
|
const WarningBannerComponent: React.FC<Props> = props => {
|
|
const warningComps = [];
|
|
|
|
const [isAndroidTargetPreRelease, setIsAndroidTargetPreRelease] = useState<boolean|null>(null);
|
|
|
|
useAsyncEffect(async event => {
|
|
setIsAndroidTargetPreRelease(null);
|
|
if (Platform.OS !== 'android' || !props.mustUpgradeAppMessage || !props.syncTargetAppMinVersion) return;
|
|
|
|
try {
|
|
const isPreRelease = await fetchAndroidVersionIsPreRelease(props.syncTargetAppMinVersion);
|
|
if (!event.cancelled) setIsAndroidTargetPreRelease(isPreRelease);
|
|
} catch (error) {
|
|
logger.error('Could not load release metadata for version', props.syncTargetAppMinVersion, error);
|
|
}
|
|
}, [props.mustUpgradeAppMessage, props.syncTargetAppMinVersion]);
|
|
|
|
const renderWarningBox = (key: string, message: string, target: WarningBoxTarget, actions?: WarningBoxAction[]) => {
|
|
return <WarningBox
|
|
key={key}
|
|
themeId={props.themeId}
|
|
target={target}
|
|
message={message}
|
|
testID='warning-box'
|
|
actions={actions}
|
|
/>;
|
|
};
|
|
|
|
const renderMustUpgradeAppMessage = () => {
|
|
if (props.syncTargetAppMinVersion) {
|
|
const upgradeMessage = (message: string) => _(
|
|
'In order to synchronise, Please upgrade your application to version %s: %s',
|
|
props.syncTargetAppMinVersion,
|
|
message,
|
|
);
|
|
|
|
if (Platform.OS === 'android' && isAndroidTargetPreRelease !== null) {
|
|
if (isAndroidTargetPreRelease) {
|
|
return renderWarningBox(
|
|
'UpgradeApp',
|
|
upgradeMessage(_('Download it from the Joplin Android repository')),
|
|
{ url: androidPreReleaseUrl },
|
|
);
|
|
}
|
|
|
|
return renderWarningBox(
|
|
'UpgradeApp',
|
|
upgradeMessage(_('Update it from Google Play')),
|
|
{ url: androidGooglePlayUrl },
|
|
);
|
|
}
|
|
|
|
if (Platform.OS === 'ios') {
|
|
return renderWarningBox(
|
|
'UpgradeApp',
|
|
upgradeMessage(_('Update it from the App Store')),
|
|
{ url: iosAppStoreUrl },
|
|
);
|
|
}
|
|
|
|
return renderWarningBox('UpgradeApp', _('In order to synchronise, Please upgrade your application to version %s', props.syncTargetAppMinVersion), null);
|
|
}
|
|
|
|
return renderWarningBox('UpgradeApp', props.mustUpgradeAppMessage, null);
|
|
};
|
|
|
|
if (props.showMissingMasterKeyMessage) {
|
|
warningComps.push(renderWarningBox('missingDecryptionPassword', _('Press to set the decryption password.'), { screen: 'EncryptionConfig' }));
|
|
}
|
|
if (props.showUnknownKeyFormatMessage) {
|
|
warningComps.push(
|
|
renderWarningBox('unknownKeyFormat', _('One or more encryption keys are stored in an unknown format. Press for more info.'), { screen: 'EncryptionConfig' }),
|
|
);
|
|
}
|
|
if (props.hasDisabledSyncItems) {
|
|
warningComps.push(renderWarningBox('cannotSync', _('Some items cannot be synchronised. Press for more info.'), { screen: 'Status' }));
|
|
}
|
|
if (props.shouldUpgradeSyncTarget && props.showShouldUpgradeSyncTargetMessage !== false) {
|
|
warningComps.push(renderWarningBox('upgradeSync', _('The sync target needs to be upgraded. Press this banner to proceed.'), { screen: 'UpgradeSyncTarget' }));
|
|
}
|
|
if (props.mustUpgradeAppMessage) {
|
|
warningComps.push(renderMustUpgradeAppMessage());
|
|
}
|
|
if (props.hasDisabledEncryptionItems) {
|
|
warningComps.push(renderWarningBox('cannotDecrypt', _('Some items cannot be decrypted.'), { screen: 'Status' }));
|
|
}
|
|
const migration = props.noteLockMigrationStatus;
|
|
if (props.showNoteLockKeyConflictMessage !== false) {
|
|
if (migration?.running) {
|
|
warningComps.push(renderWarningBox('noteLockMigration', _('Re-encrypting your locked notes with the synced key. Migration is in progress...'), null));
|
|
} else if (migration?.failed || migration?.awaitingDecryption) {
|
|
warningComps.push(renderWarningBox('noteLockMigration', notMigratedMessage(migration), null, [
|
|
{ title: _('Retry'), onPress: () => void NavService.go('NoteLockMigration') },
|
|
{ title: _('Skip'), onPress: () => void confirmAndSkipNoteLockKeyMigration(props.dispatch) },
|
|
]));
|
|
} else if (migration) {
|
|
const onDismiss = () => props.dispatch({ type: 'NOTE_LOCK_MIGRATION_STATUS_SET', value: null });
|
|
warningComps.push(renderWarningBox('noteLockMigration', `${withSkippedCount(_('The migration of your locked notes has completed.'), migration)} ${_('Press to dismiss.')}`, { onPress: onDismiss }));
|
|
} else if (props.noteLockKeyConflict) {
|
|
warningComps.push(renderWarningBox('noteLockMigration', _('The sync target uses a different note lock key to the one on your device. Press to migrate your locked notes.'), { screen: 'NoteLockMigration' }));
|
|
}
|
|
}
|
|
if (props.showInvalidJoplinCloudCredential) {
|
|
const target = { screen: 'JoplinCloudLogin' };
|
|
warningComps.push(renderWarningBox('syncLogin', _('Your Joplin Cloud credentials are invalid, please login.'), target));
|
|
}
|
|
|
|
const shareInvitation = props.shareInvitations.find(inv => inv.status === ShareUserStatus.Waiting);
|
|
if (
|
|
!props.processingShareInvitationResponse
|
|
&& !!shareInvitation
|
|
) {
|
|
const invitation = props.shareInvitations.find(inv => inv.status === ShareUserStatus.Waiting);
|
|
const sharer = invitation.share.user;
|
|
|
|
warningComps.push(renderWarningBox(
|
|
'incomingShare',
|
|
_('%s (%s) would like to share a notebook with you.',
|
|
substrWithEllipsis(sharer?.full_name ?? 'Unknown', 0, 48),
|
|
substrWithEllipsis(sharer?.email ?? 'Unknown', 0, 52)),
|
|
{ screen: 'ShareManager' },
|
|
));
|
|
}
|
|
|
|
return warningComps;
|
|
};
|
|
|
|
const isSyncLoginRoute = (state: AppState) => {
|
|
const syncTargetId = state.settings['sync.target'];
|
|
const syncTarget = syncTargetId ? reg.syncTarget(syncTargetId) : null;
|
|
if (syncTarget) {
|
|
return state.route?.routeName === syncTarget.authRouteName();
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
const isEncryptionConfigRoute = (state: AppState) => {
|
|
return state.route?.routeName === 'EncryptionConfig';
|
|
};
|
|
|
|
export default connect((state: AppState) => {
|
|
const syncInfo = localSyncInfoFromState(state);
|
|
|
|
return {
|
|
themeId: state.settings.theme,
|
|
hasDisabledEncryptionItems: state.hasDisabledEncryptionItems,
|
|
noteSelectionEnabled: state.noteSelectionEnabled,
|
|
selectedFolderId: state.selectedFolderId,
|
|
notesParentType: state.notesParentType,
|
|
showMissingMasterKeyMessage: !isEncryptionConfigRoute(state) && showMissingMasterKeyMessage(syncInfo, state.notLoadedMasterKeys),
|
|
showUnknownKeyFormatMessage: !isEncryptionConfigRoute(state) && showUnknownKeyFormatBanner(syncInfo),
|
|
hasDisabledSyncItems: state.hasDisabledSyncItems,
|
|
shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO,
|
|
mustUpgradeAppMessage: state.mustUpgradeAppMessage,
|
|
syncTargetAppMinVersion: state.mustUpgradeAppMinVersion,
|
|
shareInvitations: state.shareService.shareInvitations,
|
|
processingShareInvitationResponse: state.shareService.processingShareInvitationResponse,
|
|
showInvalidJoplinCloudCredential: state.settings['sync.target'] === 10 && !isSyncLoginRoute(state) && state.mustAuthenticate,
|
|
noteLockKeyConflict: noteLockKeyConflictFromState(state),
|
|
noteLockMigrationStatus: state.noteLockMigrationStatus,
|
|
};
|
|
})(WarningBannerComponent);
|