Desktop: Fixes #11738: Context menu was empty for notes on Trash folder (#11743)

renovate/branches/renovate/thiserror-1.x-lockfile
pedr 2025-01-30 11:57:55 -03:00 committed by GitHub
parent 9b82578253
commit db81064c98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 224 additions and 51 deletions

View File

@ -496,6 +496,7 @@ packages/app-desktop/gui/style/StyledInput.js
packages/app-desktop/gui/style/StyledLink.js packages/app-desktop/gui/style/StyledLink.js
packages/app-desktop/gui/style/StyledMessage.js packages/app-desktop/gui/style/StyledMessage.js
packages/app-desktop/gui/style/StyledTextInput.js packages/app-desktop/gui/style/StyledTextInput.js
packages/app-desktop/gui/utils/NoteListUtils.test.js
packages/app-desktop/gui/utils/NoteListUtils.js packages/app-desktop/gui/utils/NoteListUtils.js
packages/app-desktop/gui/utils/announceForAccessibility.js packages/app-desktop/gui/utils/announceForAccessibility.js
packages/app-desktop/gui/utils/convertToScreenCoordinates.js packages/app-desktop/gui/utils/convertToScreenCoordinates.js
@ -572,6 +573,7 @@ packages/app-desktop/utils/customProtocols/constants.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.js packages/app-desktop/utils/customProtocols/handleCustomProtocols.js
packages/app-desktop/utils/customProtocols/registerCustomProtocols.js packages/app-desktop/utils/customProtocols/registerCustomProtocols.js
packages/app-desktop/utils/initializeCommandService.js
packages/app-desktop/utils/isSafeToOpen.test.js packages/app-desktop/utils/isSafeToOpen.test.js
packages/app-desktop/utils/isSafeToOpen.js packages/app-desktop/utils/isSafeToOpen.js
packages/app-desktop/utils/restartInSafeModeFromMain.test.js packages/app-desktop/utils/restartInSafeModeFromMain.test.js

2
.gitignore vendored
View File

@ -471,6 +471,7 @@ packages/app-desktop/gui/style/StyledInput.js
packages/app-desktop/gui/style/StyledLink.js packages/app-desktop/gui/style/StyledLink.js
packages/app-desktop/gui/style/StyledMessage.js packages/app-desktop/gui/style/StyledMessage.js
packages/app-desktop/gui/style/StyledTextInput.js packages/app-desktop/gui/style/StyledTextInput.js
packages/app-desktop/gui/utils/NoteListUtils.test.js
packages/app-desktop/gui/utils/NoteListUtils.js packages/app-desktop/gui/utils/NoteListUtils.js
packages/app-desktop/gui/utils/announceForAccessibility.js packages/app-desktop/gui/utils/announceForAccessibility.js
packages/app-desktop/gui/utils/convertToScreenCoordinates.js packages/app-desktop/gui/utils/convertToScreenCoordinates.js
@ -547,6 +548,7 @@ packages/app-desktop/utils/customProtocols/constants.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js
packages/app-desktop/utils/customProtocols/handleCustomProtocols.js packages/app-desktop/utils/customProtocols/handleCustomProtocols.js
packages/app-desktop/utils/customProtocols/registerCustomProtocols.js packages/app-desktop/utils/customProtocols/registerCustomProtocols.js
packages/app-desktop/utils/initializeCommandService.js
packages/app-desktop/utils/isSafeToOpen.test.js packages/app-desktop/utils/isSafeToOpen.test.js
packages/app-desktop/utils/isSafeToOpen.js packages/app-desktop/utils/isSafeToOpen.js
packages/app-desktop/utils/restartInSafeModeFromMain.test.js packages/app-desktop/utils/restartInSafeModeFromMain.test.js

View File

@ -19,7 +19,6 @@ import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerS
import SpellCheckerServiceDriverNative from './services/spellChecker/SpellCheckerServiceDriverNative'; import SpellCheckerServiceDriverNative from './services/spellChecker/SpellCheckerServiceDriverNative';
import bridge from './services/bridge'; import bridge from './services/bridge';
import menuCommandNames from './gui/menuCommandNames'; import menuCommandNames from './gui/menuCommandNames';
import stateToWhenClauseContext from './services/commands/stateToWhenClauseContext';
import ResourceService from '@joplin/lib/services/ResourceService'; import ResourceService from '@joplin/lib/services/ResourceService';
import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher'; import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
import appReducer, { createAppDefaultState } from './app.reducer'; import appReducer, { createAppDefaultState } from './app.reducer';
@ -35,29 +34,15 @@ const PluginManager = require('@joplin/lib/services/PluginManager');
import RevisionService from '@joplin/lib/services/RevisionService'; import RevisionService from '@joplin/lib/services/RevisionService';
import MigrationService from '@joplin/lib/services/MigrationService'; import MigrationService from '@joplin/lib/services/MigrationService';
import { loadCustomCss } from '@joplin/lib/CssUtils'; import { loadCustomCss } from '@joplin/lib/CssUtils';
import mainScreenCommands from './gui/WindowCommandsAndDialogs/commands/index';
import noteEditorCommands from './gui/NoteEditor/commands/index';
import noteListCommands from './gui/NoteList/commands/index';
import noteListControlsCommands from './gui/NoteListControls/commands/index';
import sidebarCommands from './gui/Sidebar/commands/index';
import appCommands from './commands/index';
import libCommands from '@joplin/lib/commands/index';
import { homedir } from 'os'; import { homedir } from 'os';
import getDefaultPluginsInfo from '@joplin/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo'; import getDefaultPluginsInfo from '@joplin/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo';
const electronContextMenu = require('./services/electron-context-menu'); const electronContextMenu = require('./services/electron-context-menu');
// import populateDatabase from '@joplin/lib/services/debug/populateDatabase'; // import populateDatabase from '@joplin/lib/services/debug/populateDatabase';
const commands = mainScreenCommands
.concat(noteEditorCommands)
.concat(noteListCommands)
.concat(noteListControlsCommands)
.concat(sidebarCommands);
// Commands that are not tied to any particular component. // Commands that are not tied to any particular component.
// The runtime for these commands can be loaded when the app starts. // The runtime for these commands can be loaded when the app starts.
const globalCommands = appCommands.concat(libCommands);
import editorCommandDeclarations from './gui/NoteEditor/editorCommandDeclarations';
import PerFolderSortOrderService from './services/sortOrder/PerFolderSortOrderService'; import PerFolderSortOrderService from './services/sortOrder/PerFolderSortOrderService';
import ShareService from '@joplin/lib/services/share/ShareService'; import ShareService from '@joplin/lib/services/share/ShareService';
import checkForUpdates from './checkForUpdates'; import checkForUpdates from './checkForUpdates';
@ -74,6 +59,7 @@ import SearchEngine from '@joplin/lib/services/search/SearchEngine';
import { PackageInfo } from '@joplin/lib/versionInfo'; import { PackageInfo } from '@joplin/lib/versionInfo';
import { CustomProtocolHandler } from './utils/customProtocols/handleCustomProtocols'; import { CustomProtocolHandler } from './utils/customProtocols/handleCustomProtocols';
import { refreshFolders } from '@joplin/lib/folders-screen-utils'; import { refreshFolders } from '@joplin/lib/folders-screen-utils';
import initializeCommandService from './utils/initializeCommandService';
const pluginClasses = [ const pluginClasses = [
require('./plugins/GotoAnything').default, require('./plugins/GotoAnything').default,
@ -492,20 +478,7 @@ class Application extends BaseApplication {
PerFolderSortOrderService.initialize(); PerFolderSortOrderService.initialize();
CommandService.instance().initialize(this.store(), Setting.value('env') === 'dev', stateToWhenClauseContext); initializeCommandService(this.store(), Setting.value('env') === 'dev');
for (const command of commands) {
CommandService.instance().registerDeclaration(command.declaration);
}
for (const command of globalCommands) {
CommandService.instance().registerDeclaration(command.declaration);
CommandService.instance().registerRuntime(command.declaration.name, command.runtime());
}
for (const declaration of editorCommandDeclarations) {
CommandService.instance().registerDeclaration(declaration);
}
const keymapService = KeymapService.instance(); const keymapService = KeymapService.instance();
// We only add the commands that appear in the menu because only // We only add the commands that appear in the menu because only

View File

@ -0,0 +1,158 @@
import NoteListUtils from './NoteListUtils';
import KeymapService from '@joplin/lib/services/KeymapService';
import menuCommandNames from '../menuCommandNames';
import { MenuItem as MenuItemType } from '@joplin/lib/services/commands/MenuUtils';
import initializeCommandService from '../../utils/initializeCommandService';
import { createAppDefaultWindowState } from '../../app.reducer';
type MenuItemWrapper = {
value: MenuItemType;
};
jest.mock('../../services/bridge', () => ({
__esModule: true,
default: () => ({
MenuItem: class MenuItem {
public value: MenuItemType;
public constructor(value: MenuItemType) {
this.value = value;
}
},
Menu: class MockMenu {
public items: string[] = [];
public append(item: MenuItemWrapper) {
const identifier = item.value.id ? item.value.id : (
item.value.label ? item.value.label : item.value.type
);
this.items.push(identifier);
}
},
}),
}));
const mockDispatch = jest.fn();
describe('NoteListUtils', () => {
beforeEach(() => {
const mockStore = {
getState: () => {
return {
...createAppDefaultWindowState(),
settings: {},
};
},
};
initializeCommandService(mockStore, false);
const keymapService = KeymapService.instance();
keymapService.initialize(menuCommandNames());
});
it('should show only trash menu options on deleted note', () => {
const noteIds = ['noteId1'];
const deletedNote = {
id: 'noteId1',
deleted_time: new Date().getTime(),
};
const menu = NoteListUtils.makeContextMenu(noteIds, {
notes: [
deletedNote,
],
dispatch: mockDispatch,
watchedNoteFiles: [],
plugins: {},
inConflictFolder: false,
customCss: '',
});
expect(menu.items).toEqual([
'restoreNote',
'permanentlyDeleteNote',
]);
});
it('should show menu options for normal notes', () => {
const noteIds = ['noteId1'];
const normalNote = {
id: 'noteId1',
};
const menu = NoteListUtils.makeContextMenu(noteIds, {
notes: [
normalNote,
],
dispatch: mockDispatch,
watchedNoteFiles: [],
plugins: {},
inConflictFolder: false,
customCss: '',
});
expect(menu.items).toEqual([
'openNoteInNewWindow',
'startExternalEditing',
'separator',
'setTags',
'separator',
'toggleNoteType',
'moveToFolder',
'duplicateNote',
'deleteNote',
'separator',
'Copy Markdown link',
'Copy external link',
'separator',
'Export',
]);
});
it('should show options when more than one note is selected', () => {
const noteIds = ['noteId1', 'noteId2'];
const menu = NoteListUtils.makeContextMenu(noteIds, {
notes: [
{ id: 'noteId1' },
{ id: 'noteId2' },
],
dispatch: mockDispatch,
watchedNoteFiles: [],
plugins: {},
inConflictFolder: false,
customCss: '',
});
expect(menu.items).toEqual([
'setTags',
'separator',
'Switch to note type',
'Switch to to-do type',
'moveToFolder',
'duplicateNote',
'deleteNote',
'separator',
'Copy Markdown link',
'separator',
'Export',
]);
});
it('should hide all options for encrypted', () => {
const noteIds = ['noteId1'];
const encrypted = {
id: 'noteId1',
encryption_applied: 1,
};
const menu = NoteListUtils.makeContextMenu(noteIds, {
notes: [
encrypted,
],
dispatch: mockDispatch,
watchedNoteFiles: [],
plugins: {},
inConflictFolder: false,
customCss: '',
});
expect(menu.items).toEqual([]);
});
});

View File

@ -107,28 +107,12 @@ export default class NoteListUtils {
new MenuItem(menuUtils.commandToStatefulMenuItem('duplicateNote', noteIds) as any), new MenuItem(menuUtils.commandToStatefulMenuItem('duplicateNote', noteIds) as any),
); );
if (includeDeletedNotes) { menu.append(
menu.append( new MenuItem(
new MenuItem( // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied menuUtils.commandToStatefulMenuItem('deleteNote', noteIds) as any,
menuUtils.commandToStatefulMenuItem('restoreNote', noteIds) as any, ),
), );
);
menu.append(
new MenuItem(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
menuUtils.commandToStatefulMenuItem('permanentlyDeleteNote', noteIds) as any,
),
);
} else {
menu.append(
new MenuItem(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
menuUtils.commandToStatefulMenuItem('deleteNote', noteIds) as any,
),
);
}
menu.append(new MenuItem({ type: 'separator' })); menu.append(new MenuItem({ type: 'separator' }));
@ -204,6 +188,23 @@ export default class NoteListUtils {
menu.append(exportMenuItem); menu.append(exportMenuItem);
} }
if (includeDeletedNotes) {
menu.append(
new MenuItem(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
menuUtils.commandToStatefulMenuItem('restoreNote', noteIds) as any,
),
);
menu.append(
new MenuItem(
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
menuUtils.commandToStatefulMenuItem('permanentlyDeleteNote', noteIds) as any,
),
);
}
const pluginViewInfos = pluginUtils.viewInfosByType(props.plugins, 'menuItem'); const pluginViewInfos = pluginUtils.viewInfosByType(props.plugins, 'menuItem');
for (const info of pluginViewInfos) { for (const info of pluginViewInfos) {

View File

@ -0,0 +1,37 @@
import CommandService from '@joplin/lib/services/CommandService';
import stateToWhenClauseContext from '../services/commands/stateToWhenClauseContext';
import mainScreenCommands from '../gui/WindowCommandsAndDialogs/commands/index';
import noteEditorCommands from '../gui/NoteEditor/commands/index';
import noteListCommands from '../gui/NoteList/commands/index';
import noteListControlsCommands from '../gui/NoteListControls/commands/index';
import sidebarCommands from '../gui/Sidebar/commands/index';
import appCommands from '../commands/index';
import libCommands from '@joplin/lib/commands/index';
import editorCommandDeclarations from '../gui/NoteEditor/editorCommandDeclarations';
const commands = mainScreenCommands
.concat(noteEditorCommands)
.concat(noteListCommands)
.concat(noteListControlsCommands)
.concat(sidebarCommands);
// Commands that are not tied to any particular component.
// The runtime for these commands can be loaded when the app starts.
const globalCommands = appCommands.concat(libCommands);
export default function initializeCommandService(store: object, devMode: boolean) {
CommandService.instance().initialize(store, devMode, stateToWhenClauseContext);
for (const command of commands) {
CommandService.instance().registerDeclaration(command.declaration);
}
for (const command of globalCommands) {
CommandService.instance().registerDeclaration(command.declaration);
CommandService.instance().registerRuntime(command.declaration.name, command.runtime());
}
for (const declaration of editorCommandDeclarations) {
CommandService.instance().registerDeclaration(declaration);
}
}