feat: Initial clean release of CHM
一些检查失败了
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
一些检查失败了
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
这个提交包含在:
@@ -0,0 +1,216 @@
|
||||
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);
|
||||
在新工单中引用
屏蔽一个用户