Desktop: Show export warnings on export completion to match mobile behaviour (#16682)

This commit is contained in:
mrjo118 authored and GitHub committed 2026-10-03 20:11:32 +01:00
1 parent cd32d862c4
commit 97ab341275
6 files changed
+62 -10

No files matched your search

+7 -1
View File
@@ -228,17 +228,23 @@ export default class InteropServiceHelper {
if (options.sourceNoteIds) exportOptions.sourceNoteIds = options.sourceNoteIds;
const service = InteropService.instance();
let keepModalOpen = false;
try {
const result = await service.export(exportOptions);
// eslint-disable-next-line no-console
console.info('Export result: ', result);
if (result.warnings.length) {
keepModalOpen = true;
const message = [_('Export completed, but some items were skipped because they have not been downloaded to this device or could not be exported:'), '', ...result.warnings].join('\n');
void CommandService.instance().execute('showModalMessage', message, true);
}
} catch (error) {
console.error(error);
bridge().showErrorMessageBox(_('Could not export notes: %s', error.message));
}
void CommandService.instance().execute('hideModalMessage');
if (!keepModalOpen) void CommandService.instance().execute('hideModalMessage');
}
}
+8 -2
View File
@@ -100,6 +100,7 @@ export interface AppState extends State, AppWindowState {
layoutMoveMode: boolean;
startupPluginsLoaded: boolean;
modalOverlayMessage: string|null;
modalOverlayHasCloseButton: boolean;
// Extra reducer keys go here
mainLayout: LayoutItem;
@@ -143,6 +144,7 @@ export function createAppDefaultState(resourceEditWatcherDefaultState: Partial<A
startupPluginsLoaded: false,
isResettingLayout: false,
modalOverlayMessage: null,
modalOverlayHasCloseButton: false,
aiStatus: defaultAiStatus(),
...resourceEditWatcherDefaultState,
};
@@ -396,11 +398,15 @@ export default function(state: AppState, action: any) {
}
case 'SHOW_MODAL_MESSAGE':
newState = { ...newState, modalOverlayMessage: action.message };
newState = {
...newState,
modalOverlayMessage: action.message,
modalOverlayHasCloseButton: !!action.hasCloseButton,
};
break;
case 'HIDE_MODAL_MESSAGE':
newState = { ...newState, modalOverlayMessage: null };
newState = { ...newState, modalOverlayMessage: null, modalOverlayHasCloseButton: false };
break;
case 'NOTE_FILE_WATCHER_ADD':
@@ -1,23 +1,29 @@
import * as React from 'react';
import Dialog from '@joplin/lib/components/Dialog';
import { _ } from '@joplin/lib/locale';
interface Props {
message: string;
hasCloseButton: boolean;
onClose: ()=> void;
}
const ModalMessageOverlay: React.FC<Props> = ({ message }) => {
const ModalMessageOverlay: React.FC<Props> = ({ message, hasCloseButton, onClose }) => {
let brIndex = 1;
const lines = message.split('\n').map((line: string) => {
if (!line.trim()) return <br key={`${brIndex++}`}/>;
return <div key={line} className="text">{line}</div>;
});
return <Dialog contentFillsScreen={true}>
<div className="modal-message">
<div className="loading-animation" />
<div className="text" role="status">
return <Dialog contentFillsScreen={true} onCancel={hasCloseButton ? onClose : undefined}>
<div className={`modal-message ${hasCloseButton ? '-with-close-button' : ''}`}>
{!hasCloseButton && <div className="loading-animation" />}
<div className={`text ${hasCloseButton ? 'modal-message-scrollable-content' : ''}`} role="status">
{lines}
</div>
{hasCloseButton && <div className="modal-message-actions">
<button type="button" onClick={onClose}>{_('OK')}</button>
</div>}
</div>
</Dialog>;
};
@@ -32,6 +32,7 @@ interface Props {
appDialogStates: AppStateDialog[];
pluginsLegacy: Record<string, { dialogOpen?: boolean; userData?: unknown }>;
modalMessage: string|null;
modalMessageHasCloseButton: boolean;
customCss: string;
editorNoteStatuses: EditorNoteStatuses;
@@ -127,7 +128,11 @@ const WindowCommandsAndDialogs: React.FC<Props> = props => {
return <>
<div ref={setReferenceElement}/>
{pluginDialog}
{props.modalMessage !== null ? <ModalMessageOverlay message={props.modalMessage}/> : null}
{props.modalMessage !== null ? <ModalMessageOverlay
message={props.modalMessage}
hasCloseButton={props.modalMessageHasCloseButton}
onClose={() => props.dispatch({ type: 'HIDE_MODAL_MESSAGE' })}
/> : null}
<PluginDialogs
themeId={props.themeId}
visibleDialogs={props.visibleDialogs}
@@ -210,5 +215,6 @@ export default connect((state: AppState, ownProps: ConnectProps) => {
editorNoteStatuses: state.editorNoteStatuses,
pluginsLegacy: state.pluginsLegacy,
modalMessage: state.modalOverlayMessage,
modalMessageHasCloseButton: state.modalOverlayHasCloseButton,
};
})(WindowCommandsAndDialogs);
@@ -6,10 +6,11 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => {
return {
execute: async (context: CommandContext, message: string) => {
execute: async (context: CommandContext, message: string, hasCloseButton = false) => {
context.dispatch({
type: 'SHOW_MODAL_MESSAGE',
message,
hasCloseButton,
});
},
};
+27
View File
@@ -126,6 +126,33 @@ a {
color: grey;
font-size: 1.2em;
margin: 40px 20px;
&.-with-close-button {
align-items: stretch;
flex-direction: column;
height: calc(100vh - 80px);
justify-content: flex-start;
max-height: calc(100vh - 80px);
min-width: min(600px, calc(100vw - 40px));
}
}
.modal-message-scrollable-content {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
}
.modal-message-actions {
display: flex;
flex-shrink: 0;
justify-content: flex-end;
padding-top: 20px;
button {
min-width: 80px;
padding: 6px 16px;
}
}
.loading-animation {