Desktop: Fixes #14661: hide new note/todo buttons when no notebook exists (#14674)

Signed-off-by: justin212407 <charlesjustin2124@gmail.com>
This commit is contained in:
Justin Charles
2026-03-10 17:45:34 +05:30
committed by GitHub
parent 714bbd6d23
commit 71a2e98155
6 changed files with 60 additions and 4 deletions

View File

@@ -284,9 +284,11 @@ interface ConnectProps {
const mapStateToProps = (state: AppState, ownProps: ConnectProps) => {
const whenClauseContext = stateToWhenClauseContext(state, { windowId: ownProps.windowId });
const windowState = stateUtils.windowStateById(state, ownProps.windowId);
const hasFolderForNewNotes = whenClauseContext.selectedFolderIsValid
&& windowState.selectedFolderId !== getTrashFolderId();
return {
showNewNoteButtons: windowState.selectedFolderId !== getTrashFolderId(),
showNewNoteButtons: hasFolderForNewNotes,
newNoteButtonEnabled: CommandService.instance().isEnabled('newNote', whenClauseContext),
newTodoButtonEnabled: CommandService.instance().isEnabled('newTodo', whenClauseContext),
sortOrderButtonsVisible: state.settings['notes.sortOrder.buttonsVisible'],

View File

@@ -60,7 +60,7 @@ const useNoteListControlsBreakpoints = (width: number, newNoteButtonElement: Ele
const [dynamicBreakpoints, setDynamicBreakpoints] = useState<Breakpoints>({ Sm: BaseBreakpoint.Sm, Md: BaseBreakpoint.Md, Lg: BaseBreakpoint.Lg, Xl: BaseBreakpoint.Xl });
const previousWidth = usePrevious(width);
const widthHasChanged = width !== previousWidth;
const showNewNoteButton = selectedFolderId !== getTrashFolderId();
const showNewNoteButton = !!selectedFolderId && selectedFolderId !== getTrashFolderId();
// Initialize language-specific breakpoints
useEffect(() => {

View File

@@ -3,7 +3,7 @@ import { _ } from '@joplin/lib/locale';
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
export const newNoteEnabledConditions = 'oneFolderSelected && !inConflictFolder && !folderIsReadOnly && !folderIsTrash';
export const newNoteEnabledConditions = 'oneFolderSelected && selectedFolderIsValid && !inConflictFolder && !folderIsReadOnly && !folderIsTrash';
export const declaration: CommandDeclaration = {
name: 'newNote',

View File

@@ -34,13 +34,14 @@ export default class MainScreen {
}
public async waitFor() {
await this.newNoteButton.waitFor();
await this.noteList.waitFor();
}
// Follows the steps a user would use to create a new note.
public async createNewNote(title: string) {
await this.waitFor();
// The new note button is only visible when a folder is selected -- wait for it explicitly.
await this.newNoteButton.waitFor();
// Create the new note. Retry this -- creating new notes can sometimes fail if done just after
// application startup.

View File

@@ -149,4 +149,47 @@ describe('stateToWhenClauseContext', () => {
stateToWhenClauseContext(applicationState, { commandFolderIds }),
).toHaveProperty('foldersAreDeleted', expectedDeletedState);
});
it.each([
{
label: 'should be false when no folders exist (new profile)',
selectedFolderId: 'non-existent-folder',
folders: [],
expected: false,
},
{
label: 'should be false when selectedFolderId is null',
selectedFolderId: null,
folders: [{ id: '1', deleted_time: 0, share_id: '', parent_id: '' }],
expected: false,
},
{
label: 'should be false when selected folder has been deleted',
selectedFolderId: '1',
folders: [{ id: '1', deleted_time: 1000, share_id: '', parent_id: '' }],
expected: false,
},
{
label: 'should be true when selected folder exists and is not deleted',
selectedFolderId: '1',
folders: [{ id: '1', deleted_time: 0, share_id: '', parent_id: '' }],
expected: true,
},
{
label: 'should be false when selectedFolderId does not match any folder',
selectedFolderId: 'stale-id',
folders: [{ id: '1', deleted_time: 0, share_id: '', parent_id: '' }],
expected: false,
},
])('should set selectedFolderIsValid correctly: $label', ({ selectedFolderId, folders, expected }) => {
const applicationState = buildState({
selectedFolderId,
folders,
notes: [],
});
expect(
stateToWhenClauseContext(applicationState),
).toHaveProperty('selectedFolderIsValid', expected);
});
});

View File

@@ -47,6 +47,7 @@ export interface WhenClauseContext {
noteTodoCompleted: boolean;
oneFolderSelected: boolean;
oneNoteSelected: boolean;
selectedFolderIsValid: boolean;
someNotesSelected: boolean;
syncStarted: boolean;
hasActivePluginEditor: boolean;
@@ -73,6 +74,14 @@ export default function stateToWhenClauseContext(state: State, options: WhenClau
const settings = state.settings || {};
// Check whether the window's selected folder actually exists and is not
// deleted. This resolves the case where selectedFolderId is stale (e.g.
// new profile with no notebooks) or points to a deleted folder.
const selectedFolder = windowState.selectedFolderId
? BaseModel.byId(state.folders, windowState.selectedFolderId)
: null;
const selectedFolderIsValid = !!selectedFolder && !selectedFolder.deleted_time;
return {
// Application state
notesAreBeingSaved: stateUtils.hasNotesBeingSaved(state),
@@ -98,6 +107,7 @@ export default function stateToWhenClauseContext(state: State, options: WhenClau
// Folder selection
oneFolderSelected: selectedFolderIds.length === 1,
selectedFolderIsValid,
// Current note properties
noteIsTodo: selectedNote ? !!selectedNote.is_todo : false,