mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-02 12:47:41 +02:00
This commit is contained in:
parent
9e80e2e072
commit
5078341c15
@ -550,7 +550,14 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
for (let i = 0; i < noteIds.length; i++) {
|
for (let i = 0; i < noteIds.length; i++) {
|
||||||
await Note.moveToFolder(noteIds[i], folderId);
|
await Note.moveToFolder(
|
||||||
|
noteIds[i],
|
||||||
|
folderId,
|
||||||
|
// By default, the note selection is preserved on mobile when a note is moved to
|
||||||
|
// a different folder. However, when moving notes from the note list, this shouldn't be
|
||||||
|
// the case:
|
||||||
|
{ dispatchOptions: { preserveSelection: false } },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert(_n('This note could not be moved: %s', 'These notes could not be moved: %s', noteIds.length, error.message));
|
alert(_n('This note could not be moved: %s', 'These notes could not be moved: %s', noteIds.length, error.message));
|
||||||
|
@ -45,7 +45,7 @@ import { isSupportedLanguage } from '../../../services/voiceTyping/vosk';
|
|||||||
import { ChangeEvent as EditorChangeEvent, SelectionRangeChangeEvent, UndoRedoDepthChangeEvent } from '@joplin/editor/events';
|
import { ChangeEvent as EditorChangeEvent, SelectionRangeChangeEvent, UndoRedoDepthChangeEvent } from '@joplin/editor/events';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import { RefObject, useContext, useRef } from 'react';
|
import { RefObject, useContext } from 'react';
|
||||||
import { SelectionRange } from '../../NoteEditor/types';
|
import { SelectionRange } from '../../NoteEditor/types';
|
||||||
import { getNoteCallbackUrl } from '@joplin/lib/callbackUrlUtils';
|
import { getNoteCallbackUrl } from '@joplin/lib/callbackUrlUtils';
|
||||||
import { AppState } from '../../../utils/types';
|
import { AppState } from '../../../utils/types';
|
||||||
@ -1615,19 +1615,9 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
|
|||||||
// which can cause some bugs where previously set state to another note would interfere
|
// which can cause some bugs where previously set state to another note would interfere
|
||||||
// how the new note should be rendered
|
// how the new note should be rendered
|
||||||
const NoteScreenWrapper = (props: Props) => {
|
const NoteScreenWrapper = (props: Props) => {
|
||||||
const lastNonNullNoteIdRef = useRef(props.noteId);
|
|
||||||
if (props.noteId) {
|
|
||||||
lastNonNullNoteIdRef.current = props.noteId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This keeps the current note open even if it's no longer present in selectedNoteIds.
|
|
||||||
// This might happen, for example, if the selected note is moved to an unselected
|
|
||||||
// folder.
|
|
||||||
const noteId = lastNonNullNoteIdRef.current;
|
|
||||||
|
|
||||||
const dialogs = useContext(DialogContext);
|
const dialogs = useContext(DialogContext);
|
||||||
return (
|
return (
|
||||||
<NoteScreenComponent key={noteId} dialogs={dialogs} {...props} />
|
<NoteScreenComponent key={props.noteId} dialogs={dialogs} {...props} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ interface Props {
|
|||||||
showCompletedTodos: boolean;
|
showCompletedTodos: boolean;
|
||||||
noteSelectionEnabled: boolean;
|
noteSelectionEnabled: boolean;
|
||||||
|
|
||||||
|
selectedNoteIds: string[];
|
||||||
activeFolderId: string;
|
activeFolderId: string;
|
||||||
selectedFolderId: string;
|
selectedFolderId: string;
|
||||||
selectedTagId: string;
|
selectedTagId: string;
|
||||||
|
@ -18,5 +18,8 @@ const appDefaultState: AppState = {
|
|||||||
showPanelsDialog: false,
|
showPanelsDialog: false,
|
||||||
newNoteAttachFileAction: null,
|
newNoteAttachFileAction: null,
|
||||||
...defaultState,
|
...defaultState,
|
||||||
|
|
||||||
|
// On mobile, it's possible to select notes that aren't in the selected folder/tag/etc.
|
||||||
|
allowSelectionInOtherFolders: true,
|
||||||
};
|
};
|
||||||
export default appDefaultState;
|
export default appDefaultState;
|
||||||
|
@ -709,13 +709,19 @@ describe('reducer', () => {
|
|||||||
|
|
||||||
// Regression test for #10589.
|
// Regression test for #10589.
|
||||||
it.each([
|
it.each([
|
||||||
true, false,
|
[true, false],
|
||||||
])('should preserve note selection if specified while moving a note (preserveSelection: %j)', async (preserveSelection) => {
|
[undefined, false],
|
||||||
|
[undefined, true],
|
||||||
|
[false, true],
|
||||||
|
])('should preserve note selection if specified with an option (preserveSelection: %j, allowSelectionInOtherFolders: %j)', async (
|
||||||
|
preserveSelectionOption, allowSelectionInOtherFolders,
|
||||||
|
) => {
|
||||||
const folders = await createNTestFolders(3);
|
const folders = await createNTestFolders(3);
|
||||||
const notes = await createNTestNotes(5, folders[0]);
|
const notes = await createNTestNotes(5, folders[0]);
|
||||||
|
|
||||||
// select the 1st folder and the 1st note
|
// select the 1st folder and the 1st note
|
||||||
let state = initTestState(folders, 0, notes, [0]);
|
let state = initTestState(folders, 0, notes, [0]);
|
||||||
|
state = { ...state, allowSelectionInOtherFolders };
|
||||||
state = goToNote(notes, [0], state);
|
state = goToNote(notes, [0], state);
|
||||||
|
|
||||||
expect(state.selectedNoteIds).toHaveLength(1);
|
expect(state.selectedNoteIds).toHaveLength(1);
|
||||||
@ -729,11 +735,15 @@ describe('reducer', () => {
|
|||||||
await Note.moveToFolder(
|
await Note.moveToFolder(
|
||||||
state.selectedNoteIds[0],
|
state.selectedNoteIds[0],
|
||||||
folders[1].id,
|
folders[1].id,
|
||||||
preserveSelection ? { dispatchOptions: { preserveSelection: true } } : undefined,
|
{ dispatchOptions: { preserveSelection: preserveSelectionOption } },
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(BaseModel.dispatch).toHaveBeenCalled();
|
expect(BaseModel.dispatch).toHaveBeenCalled();
|
||||||
if (preserveSelection) {
|
|
||||||
|
// preserveSelectionOption takes precedence over allowSelectionInOtherFolders
|
||||||
|
const shouldPreserveSelection = preserveSelectionOption ?? allowSelectionInOtherFolders;
|
||||||
|
|
||||||
|
if (shouldPreserveSelection) {
|
||||||
expect(state.selectedNoteIds).toMatchObject([notes[0].id]);
|
expect(state.selectedNoteIds).toMatchObject([notes[0].id]);
|
||||||
} else {
|
} else {
|
||||||
expect(state.selectedNoteIds).toMatchObject([notes[1].id]);
|
expect(state.selectedNoteIds).toMatchObject([notes[1].id]);
|
||||||
@ -743,6 +753,27 @@ describe('reducer', () => {
|
|||||||
expect(state.selectedFolderId).toBe(folders[0].id);
|
expect(state.selectedFolderId).toBe(folders[0].id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('when selection is allowed in unselected folders, NOTE_UPDATE_ALL should not remove items from the selection', async () => {
|
||||||
|
const folders = await createNTestFolders(2);
|
||||||
|
const notes = await createNTestNotes(2, folders[0]);
|
||||||
|
|
||||||
|
// select the 1st folder and the 1st note
|
||||||
|
let state = initTestState(folders, 0, notes, [0]);
|
||||||
|
state = { ...state, allowSelectionInOtherFolders: true };
|
||||||
|
state = goToNote(notes, [0], state);
|
||||||
|
|
||||||
|
expect(state.selectedNoteIds).toEqual([notes[0].id]);
|
||||||
|
expect(state.notes).toHaveLength(2);
|
||||||
|
|
||||||
|
state = reducer(state, {
|
||||||
|
type: 'NOTE_UPDATE_ALL',
|
||||||
|
notes: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(state.notes).toHaveLength(0);
|
||||||
|
expect(state.selectedNoteIds).toEqual([notes[0].id]);
|
||||||
|
});
|
||||||
|
|
||||||
// window tests
|
// window tests
|
||||||
test('switching windows should move state from background to the foreground', async () => {
|
test('switching windows should move state from background to the foreground', async () => {
|
||||||
const folders = await createNTestFolders(2);
|
const folders = await createNTestFolders(2);
|
||||||
|
@ -169,6 +169,8 @@ export interface State extends WindowState {
|
|||||||
mustUpgradeAppMessage: string;
|
mustUpgradeAppMessage: string;
|
||||||
mustAuthenticate: boolean;
|
mustAuthenticate: boolean;
|
||||||
|
|
||||||
|
allowSelectionInOtherFolders: boolean;
|
||||||
|
|
||||||
// Extra reducer keys go here:
|
// Extra reducer keys go here:
|
||||||
pluginService: PluginServiceState;
|
pluginService: PluginServiceState;
|
||||||
shareService: ShareServiceState;
|
shareService: ShareServiceState;
|
||||||
@ -236,6 +238,7 @@ export const defaultState: State = {
|
|||||||
lastDeletionNotificationTime: 0,
|
lastDeletionNotificationTime: 0,
|
||||||
mustUpgradeAppMessage: '',
|
mustUpgradeAppMessage: '',
|
||||||
mustAuthenticate: false,
|
mustAuthenticate: false,
|
||||||
|
allowSelectionInOtherFolders: false,
|
||||||
|
|
||||||
pluginService: pluginServiceDefaultState,
|
pluginService: pluginServiceDefaultState,
|
||||||
shareService: shareServiceDefaultState,
|
shareService: shareServiceDefaultState,
|
||||||
@ -1080,7 +1083,9 @@ const reducer = produce((draft: Draft<State> = defaultState, action: any) => {
|
|||||||
draft.notes = action.notes;
|
draft.notes = action.notes;
|
||||||
draft.notesSource = action.notesSource;
|
draft.notesSource = action.notesSource;
|
||||||
draft.noteListLastSortTime = Date.now(); // Notes are already sorted when they are set this way.
|
draft.noteListLastSortTime = Date.now(); // Notes are already sorted when they are set this way.
|
||||||
updateSelectedNotesFromExistingNotes(draft);
|
if (!draft.allowSelectionInOtherFolders) {
|
||||||
|
updateSelectedNotesFromExistingNotes(draft);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Insert the note into the note list if it's new, or
|
// Insert the note into the note list if it's new, or
|
||||||
@ -1148,7 +1153,8 @@ const reducer = produce((draft: Draft<State> = defaultState, action: any) => {
|
|||||||
// For example, if the user drags the current note to a different folder,
|
// For example, if the user drags the current note to a different folder,
|
||||||
// a new note should be selected.
|
// a new note should be selected.
|
||||||
// In some cases, however, the selection needs to be preserved (e.g. the mobile app).
|
// In some cases, however, the selection needs to be preserved (e.g. the mobile app).
|
||||||
if (noteFolderHasChanged && !action.preserveSelection) {
|
const preserveSelection = action.preserveSelection ?? draft.allowSelectionInOtherFolders;
|
||||||
|
if (noteFolderHasChanged && !preserveSelection) {
|
||||||
let newIndex = movedNotePreviousIndex;
|
let newIndex = movedNotePreviousIndex;
|
||||||
if (newIndex >= newNotes.length) newIndex = newNotes.length - 1;
|
if (newIndex >= newNotes.length) newIndex = newNotes.length - 1;
|
||||||
if (!newNotes.length) newIndex = -1;
|
if (!newNotes.length) newIndex = -1;
|
||||||
|
Loading…
Reference in New Issue
Block a user