1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-07-13 00:10:37 +02:00

Mobile: Resolves #10763: Support permanent note deletion on mobile (#10786)

This commit is contained in:
Henry Heino
2024-09-21 05:05:27 -07:00
committed by GitHub
parent 050a896c8b
commit 220f867814
9 changed files with 88 additions and 65 deletions

View File

@ -209,7 +209,6 @@ packages/app-desktop/gui/MainScreen/MainScreen.js
packages/app-desktop/gui/MainScreen/commands/addProfile.js packages/app-desktop/gui/MainScreen/commands/addProfile.js
packages/app-desktop/gui/MainScreen/commands/commandPalette.js packages/app-desktop/gui/MainScreen/commands/commandPalette.js
packages/app-desktop/gui/MainScreen/commands/deleteFolder.js packages/app-desktop/gui/MainScreen/commands/deleteFolder.js
packages/app-desktop/gui/MainScreen/commands/deleteNote.js
packages/app-desktop/gui/MainScreen/commands/duplicateNote.js packages/app-desktop/gui/MainScreen/commands/duplicateNote.js
packages/app-desktop/gui/MainScreen/commands/editAlarm.js packages/app-desktop/gui/MainScreen/commands/editAlarm.js
packages/app-desktop/gui/MainScreen/commands/exportPdf.js packages/app-desktop/gui/MainScreen/commands/exportPdf.js
@ -228,7 +227,6 @@ packages/app-desktop/gui/MainScreen/commands/openItem.js
packages/app-desktop/gui/MainScreen/commands/openNote.js packages/app-desktop/gui/MainScreen/commands/openNote.js
packages/app-desktop/gui/MainScreen/commands/openPdfViewer.js packages/app-desktop/gui/MainScreen/commands/openPdfViewer.js
packages/app-desktop/gui/MainScreen/commands/openTag.js packages/app-desktop/gui/MainScreen/commands/openTag.js
packages/app-desktop/gui/MainScreen/commands/permanentlyDeleteNote.js
packages/app-desktop/gui/MainScreen/commands/print.js packages/app-desktop/gui/MainScreen/commands/print.js
packages/app-desktop/gui/MainScreen/commands/renameFolder.js packages/app-desktop/gui/MainScreen/commands/renameFolder.js
packages/app-desktop/gui/MainScreen/commands/renameTag.js packages/app-desktop/gui/MainScreen/commands/renameTag.js
@ -921,10 +919,12 @@ packages/lib/array.js
packages/lib/callbackUrlUtils.test.js packages/lib/callbackUrlUtils.test.js
packages/lib/callbackUrlUtils.js packages/lib/callbackUrlUtils.js
packages/lib/clipperUtils.js packages/lib/clipperUtils.js
packages/lib/commands/deleteNote.js
packages/lib/commands/historyBackward.js packages/lib/commands/historyBackward.js
packages/lib/commands/historyForward.js packages/lib/commands/historyForward.js
packages/lib/commands/index.js packages/lib/commands/index.js
packages/lib/commands/openMasterPasswordDialog.js packages/lib/commands/openMasterPasswordDialog.js
packages/lib/commands/permanentlyDeleteNote.js
packages/lib/commands/synchronize.js packages/lib/commands/synchronize.js
packages/lib/components/EncryptionConfigScreen/utils.test.js packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js packages/lib/components/EncryptionConfigScreen/utils.js

4
.gitignore vendored
View File

@ -186,7 +186,6 @@ packages/app-desktop/gui/MainScreen/MainScreen.js
packages/app-desktop/gui/MainScreen/commands/addProfile.js packages/app-desktop/gui/MainScreen/commands/addProfile.js
packages/app-desktop/gui/MainScreen/commands/commandPalette.js packages/app-desktop/gui/MainScreen/commands/commandPalette.js
packages/app-desktop/gui/MainScreen/commands/deleteFolder.js packages/app-desktop/gui/MainScreen/commands/deleteFolder.js
packages/app-desktop/gui/MainScreen/commands/deleteNote.js
packages/app-desktop/gui/MainScreen/commands/duplicateNote.js packages/app-desktop/gui/MainScreen/commands/duplicateNote.js
packages/app-desktop/gui/MainScreen/commands/editAlarm.js packages/app-desktop/gui/MainScreen/commands/editAlarm.js
packages/app-desktop/gui/MainScreen/commands/exportPdf.js packages/app-desktop/gui/MainScreen/commands/exportPdf.js
@ -205,7 +204,6 @@ packages/app-desktop/gui/MainScreen/commands/openItem.js
packages/app-desktop/gui/MainScreen/commands/openNote.js packages/app-desktop/gui/MainScreen/commands/openNote.js
packages/app-desktop/gui/MainScreen/commands/openPdfViewer.js packages/app-desktop/gui/MainScreen/commands/openPdfViewer.js
packages/app-desktop/gui/MainScreen/commands/openTag.js packages/app-desktop/gui/MainScreen/commands/openTag.js
packages/app-desktop/gui/MainScreen/commands/permanentlyDeleteNote.js
packages/app-desktop/gui/MainScreen/commands/print.js packages/app-desktop/gui/MainScreen/commands/print.js
packages/app-desktop/gui/MainScreen/commands/renameFolder.js packages/app-desktop/gui/MainScreen/commands/renameFolder.js
packages/app-desktop/gui/MainScreen/commands/renameTag.js packages/app-desktop/gui/MainScreen/commands/renameTag.js
@ -898,10 +896,12 @@ packages/lib/array.js
packages/lib/callbackUrlUtils.test.js packages/lib/callbackUrlUtils.test.js
packages/lib/callbackUrlUtils.js packages/lib/callbackUrlUtils.js
packages/lib/clipperUtils.js packages/lib/clipperUtils.js
packages/lib/commands/deleteNote.js
packages/lib/commands/historyBackward.js packages/lib/commands/historyBackward.js
packages/lib/commands/historyForward.js packages/lib/commands/historyForward.js
packages/lib/commands/index.js packages/lib/commands/index.js
packages/lib/commands/openMasterPasswordDialog.js packages/lib/commands/openMasterPasswordDialog.js
packages/lib/commands/permanentlyDeleteNote.js
packages/lib/commands/synchronize.js packages/lib/commands/synchronize.js
packages/lib/components/EncryptionConfigScreen/utils.test.js packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js packages/lib/components/EncryptionConfigScreen/utils.js

View File

@ -2,7 +2,6 @@
import * as addProfile from './addProfile'; import * as addProfile from './addProfile';
import * as commandPalette from './commandPalette'; import * as commandPalette from './commandPalette';
import * as deleteFolder from './deleteFolder'; import * as deleteFolder from './deleteFolder';
import * as deleteNote from './deleteNote';
import * as duplicateNote from './duplicateNote'; import * as duplicateNote from './duplicateNote';
import * as editAlarm from './editAlarm'; import * as editAlarm from './editAlarm';
import * as exportPdf from './exportPdf'; import * as exportPdf from './exportPdf';
@ -20,7 +19,6 @@ import * as openItem from './openItem';
import * as openNote from './openNote'; import * as openNote from './openNote';
import * as openPdfViewer from './openPdfViewer'; import * as openPdfViewer from './openPdfViewer';
import * as openTag from './openTag'; import * as openTag from './openTag';
import * as permanentlyDeleteNote from './permanentlyDeleteNote';
import * as print from './print'; import * as print from './print';
import * as renameFolder from './renameFolder'; import * as renameFolder from './renameFolder';
import * as renameTag from './renameTag'; import * as renameTag from './renameTag';
@ -52,7 +50,6 @@ const index: any[] = [
addProfile, addProfile,
commandPalette, commandPalette,
deleteFolder, deleteFolder,
deleteNote,
duplicateNote, duplicateNote,
editAlarm, editAlarm,
exportPdf, exportPdf,
@ -70,7 +67,6 @@ const index: any[] = [
openNote, openNote,
openPdfViewer, openPdfViewer,
openTag, openTag,
permanentlyDeleteNote,
print, print,
renameFolder, renameFolder,
renameTag, renameTag,

View File

@ -23,6 +23,7 @@ import ItemChange from '@joplin/lib/models/ItemChange';
import { getDisplayParentId } from '@joplin/lib/services/trash'; import { getDisplayParentId } from '@joplin/lib/services/trash';
import { itemIsReadOnlySync, ItemSlice } from '@joplin/lib/models/utils/readOnly'; import { itemIsReadOnlySync, ItemSlice } from '@joplin/lib/models/utils/readOnly';
import { LayoutChangeEvent } from 'react-native'; import { LayoutChangeEvent } from 'react-native';
import shim from '@joplin/lib/shim';
interface WrapperProps { interface WrapperProps {
} }
@ -140,6 +141,24 @@ describe('Note', () => {
}); });
}); });
it('pressing "delete permanently" should permanently delete a note', async () => {
const noteId = await openNewNote({ title: 'To be deleted', body: '...', deleted_time: Date.now() });
render(<WrappedNoteScreen />);
// Permanently delete note shows a confirmation dialog -- mock it.
const deleteId = 0;
shim.showMessageBox = jest.fn(async () => deleteId);
await openNoteActionsMenu();
const deleteButton = await screen.findByText('Permanently delete note');
fireEvent.press(deleteButton);
await waitFor(async () => {
expect(await Note.load(noteId)).toBeUndefined();
});
expect(shim.showMessageBox).toHaveBeenCalled();
});
it('delete should be disabled in a read-only note', async () => { it('delete should be disabled in a read-only note', async () => {
const shareId = 'testShare'; const shareId = 'testShare';
const noteId = await openNewNote({ const noteId = await openNewNote({

View File

@ -627,21 +627,6 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
await shared.saveOneProperty(this, name, value); await shared.saveOneProperty(this, name, value);
} }
private async deleteNote_onPress() {
const note = this.state.note;
if (!note.id) return;
const folderId = note.parent_id;
await Note.delete(note.id, { toTrash: true, sourceDescription: 'Delete note button' });
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Notes',
folderId: folderId,
});
}
private async pickDocuments() { private async pickDocuments() {
const result = await pickDocument({ multiple: true }); const result = await pickDocument({ multiple: true });
return result; return result;
@ -1283,30 +1268,33 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
}); });
} }
output.push({ const commandService = CommandService.instance();
title: _('Delete'), const whenContext = commandService.currentWhenClauseContext();
onPress: () => { const addButtonFromCommand = (commandName: string, title?: string) => {
void this.deleteNote_onPress(); if (commandName === '-') {
}, output.push({ isDivider: true });
disabled: readOnly, } else {
}); output.push({
title: title ?? commandService.description(commandName),
onPress: async () => {
void commandService.execute(commandName);
},
disabled: !commandService.isEnabled(commandName, whenContext),
});
}
};
if (whenContext.inTrash) {
addButtonFromCommand('permanentlyDeleteNote');
} else {
addButtonFromCommand('deleteNote', _('Delete'));
}
if (pluginCommands.length) { if (pluginCommands.length) {
output.push({ isDivider: true }); output.push({ isDivider: true });
const commandService = CommandService.instance();
for (const commandName of pluginCommands) { for (const commandName of pluginCommands) {
if (commandName === '-') { addButtonFromCommand(commandName);
output.push({ isDivider: true });
} else {
output.push({
title: commandService.description(commandName),
onPress: async () => {
void commandService.execute(commandName);
},
disabled: !commandService.isEnabled(commandName),
});
}
} }
} }

View File

@ -1,6 +1,6 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; import { CommandRuntime, CommandDeclaration, CommandContext } from '../services/CommandService';
import { _ } from '@joplin/lib/locale'; import { _ } from '../locale';
import Note from '@joplin/lib/models/Note'; import Note from '../models/Note';
export const declaration: CommandDeclaration = { export const declaration: CommandDeclaration = {
name: 'deleteNote', name: 'deleteNote',

View File

@ -1,13 +1,17 @@
// AUTO-GENERATED using `gulp buildScriptIndexes` // AUTO-GENERATED using `gulp buildScriptIndexes`
import * as deleteNote from './deleteNote';
import * as historyBackward from './historyBackward'; import * as historyBackward from './historyBackward';
import * as historyForward from './historyForward'; import * as historyForward from './historyForward';
import * as openMasterPasswordDialog from './openMasterPasswordDialog'; import * as openMasterPasswordDialog from './openMasterPasswordDialog';
import * as permanentlyDeleteNote from './permanentlyDeleteNote';
import * as synchronize from './synchronize'; import * as synchronize from './synchronize';
const index: any[] = [ const index: any[] = [
deleteNote,
historyBackward, historyBackward,
historyForward, historyForward,
openMasterPasswordDialog, openMasterPasswordDialog,
permanentlyDeleteNote,
synchronize, synchronize,
]; ];

View File

@ -1,7 +1,7 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; import { CommandRuntime, CommandDeclaration, CommandContext } from '../services/CommandService';
import { _ } from '@joplin/lib/locale'; import { _ } from '../locale';
import Note from '@joplin/lib/models/Note'; import Note from '../models/Note';
import bridge from '../../../services/bridge'; import shim from '../shim';
export const declaration: CommandDeclaration = { export const declaration: CommandDeclaration = {
name: 'permanentlyDeleteNote', name: 'permanentlyDeleteNote',
@ -16,12 +16,15 @@ export const runtime = (): CommandRuntime => {
if (!noteIds.length) return; if (!noteIds.length) return;
const msg = await Note.permanentlyDeleteMessage(noteIds); const msg = await Note.permanentlyDeleteMessage(noteIds);
const ok = bridge().showConfirmMessageBox(msg, { const deleteIndex = 0;
const result = await shim.showMessageBox(msg, {
buttons: [_('Delete'), _('Cancel')], buttons: [_('Delete'), _('Cancel')],
defaultId: 1, defaultId: 1,
cancelId: 1,
type: 'question',
}); });
if (ok) { if (result === deleteIndex) {
await Note.batchDelete(noteIds, { toTrash: false, sourceDescription: 'permanentlyDeleteNote command' }); await Note.batchDelete(noteIds, { toTrash: false, sourceDescription: 'permanentlyDeleteNote command' });
} }
}, },

View File

@ -280,19 +280,32 @@ shared.initState = async function(comp: BaseNoteScreenComponent) {
comp.scheduleFocusUpdate(); comp.scheduleFocusUpdate();
} }
const folder = Folder.byId(comp.props.folders, note.parent_id); const fromShare = !!comp.props.sharedData;
if (note) {
comp.setState({ const folder = Folder.byId(comp.props.folders, note.parent_id);
lastSavedNote: { ...note }, comp.setState({
note: note, lastSavedNote: { ...note },
mode: mode, note: note,
folder: folder, mode: mode,
isLoading: false, folder: folder,
fromShare: !!comp.props.sharedData, isLoading: false,
noteResources: await shared.attachedResources(note ? note.body : ''), fromShare: !!comp.props.sharedData,
readOnly: itemIsReadOnlySync(ModelType.Note, ItemChange.SOURCE_UNSPECIFIED, note as ItemSlice, Setting.value('sync.userId'), BaseItem.syncShareCache), noteResources: await shared.attachedResources(note ? note.body : ''),
}); readOnly: itemIsReadOnlySync(ModelType.Note, ItemChange.SOURCE_UNSPECIFIED, note as ItemSlice, Setting.value('sync.userId'), BaseItem.syncShareCache),
});
} else {
// Handle the case where a non-existent note is loaded. This can happen briefly after deleting a note.
comp.setState({
lastSavedNote: {},
note: {},
mode,
folder: null,
isLoading: true,
fromShare,
noteResources: [],
readOnly: true,
});
}
if (comp.props.sharedData) { if (comp.props.sharedData) {
if (comp.props.sharedData.title) { if (comp.props.sharedData.title) {
@ -315,7 +328,7 @@ shared.initState = async function(comp: BaseNoteScreenComponent) {
} }
// eslint-disable-next-line require-atomic-updates // eslint-disable-next-line require-atomic-updates
comp.lastLoadedNoteId_ = note.id; comp.lastLoadedNoteId_ = note?.id;
}; };
shared.toggleIsTodo_onPress = function(comp: BaseNoteScreenComponent) { shared.toggleIsTodo_onPress = function(comp: BaseNoteScreenComponent) {