diff --git a/packages/app-desktop/gui/WindowCommandsAndDialogs/commands/newNote.ts b/packages/app-desktop/gui/WindowCommandsAndDialogs/commands/newNote.ts index 049a734c0b..a847f937cb 100644 --- a/packages/app-desktop/gui/WindowCommandsAndDialogs/commands/newNote.ts +++ b/packages/app-desktop/gui/WindowCommandsAndDialogs/commands/newNote.ts @@ -1,7 +1,7 @@ import { utils, CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; import { _ } from '@joplin/lib/locale'; -import Setting from '@joplin/lib/models/Setting'; import Note from '@joplin/lib/models/Note'; +import Folder from '@joplin/lib/models/Folder'; export const newNoteEnabledConditions = 'oneFolderSelected && !inConflictFolder && !folderIsReadOnly && !folderIsTrash'; @@ -14,7 +14,7 @@ export const declaration: CommandDeclaration = { export const runtime = (): CommandRuntime => { return { execute: async (_context: CommandContext, body = '', isTodo = false) => { - const folderId = Setting.value('activeFolderId'); + const folderId = await Folder.getValidActiveFolder(); if (!folderId) return; const defaultValues = Note.previewFieldsWithDefaultValues({ includeTimestamps: false }); diff --git a/packages/app-mobile/commands/newNote.ts b/packages/app-mobile/commands/newNote.ts index a36b2f9f38..e08171b6b3 100644 --- a/packages/app-mobile/commands/newNote.ts +++ b/packages/app-mobile/commands/newNote.ts @@ -2,7 +2,7 @@ import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/ import Logger from '@joplin/utils/Logger'; import goToNote, { GotoNoteOptions } from './util/goToNote'; import Note from '@joplin/lib/models/Note'; -import Setting from '@joplin/lib/models/Setting'; +import Folder from '@joplin/lib/models/Folder'; const logger = Logger.create('newNoteCommand'); @@ -13,7 +13,7 @@ export const declaration: CommandDeclaration = { export const runtime = (): CommandRuntime => { return { execute: async (_context: CommandContext, body = '', todo = false, options: GotoNoteOptions = null) => { - const folderId = Setting.value('activeFolderId'); + const folderId = await Folder.getValidActiveFolder(); if (!folderId) { logger.warn('Not creating new note -- no active folder ID.'); return; diff --git a/packages/lib/models/Folder.test.ts b/packages/lib/models/Folder.test.ts index e6e367b765..e97348fd95 100644 --- a/packages/lib/models/Folder.test.ts +++ b/packages/lib/models/Folder.test.ts @@ -3,6 +3,7 @@ import { FolderEntity } from '../services/database/types'; import { createNTestNotes, setupDatabaseAndSynchronizer, sleep, switchClient, checkThrowAsync, createFolderTree, simulateReadOnlyShareEnv, expectThrow, withWarningSilenced } from '../testing/test-utils'; import Folder from './Folder'; import Note from './Note'; +import Setting from './Setting'; async function allItems() { const folders = await Folder.all(); @@ -440,4 +441,40 @@ describe('models/Folder', () => { expect(Folder.atLeastOneRealFolderExists(folders)).toBe(false); }); + it('should get active folder when activeFolderId is valid', async () => { + const activeFolder = await Folder.save({ title: 'folder' }); + Setting.setValue('activeFolderId', activeFolder.id); + + const validFolder = await Folder.getValidActiveFolder(); + expect(validFolder).toBe(activeFolder.id); + }); + + it('should get default folder when activeFolderId is trashed', async () => { + const defaultFolder = await Folder.save({ title: 'default' }); + const activeFolder = await Folder.save({ title: 'folder' }); + await Folder.delete(activeFolder.id, { toTrash: true }); + Setting.setValue('activeFolderId', activeFolder.id); + + const validFolder = await Folder.getValidActiveFolder(); + expect(validFolder).toBe(defaultFolder.id); + }); + + it('should get no folder when activeFolderId is undefined', async () => { + Setting.setValue('activeFolderId', undefined); + + const validFolder = await Folder.getValidActiveFolder(); + expect(validFolder).toBeNull(); + }); + + it('should get no folder when activeFolderId is trashed and there are no other not trashed folders', async () => { + const activeFolder = await Folder.save({ title: 'folder' }); + const otherFolder = await Folder.save({ title: 'other' }); + await Folder.delete(activeFolder.id, { toTrash: true }); + await Folder.delete(otherFolder.id, { toTrash: true }); + Setting.setValue('activeFolderId', activeFolder.id); + + const validFolder = await Folder.getValidActiveFolder(); + expect(validFolder).toBeNull(); + }); + }); diff --git a/packages/lib/models/Folder.ts b/packages/lib/models/Folder.ts index 41df785d97..99e63c923b 100644 --- a/packages/lib/models/Folder.ts +++ b/packages/lib/models/Folder.ts @@ -18,6 +18,7 @@ import { getTrashFolder } from '../services/trash'; import getConflictFolderId from './utils/getConflictFolderId'; import getTrashFolderId from '../services/trash/getTrashFolderId'; import { getCollator } from './utils/getCollator'; +import Setting from './Setting'; const { substrWithEllipsis } = require('../string-utils.js'); const logger = Logger.create('models/Folder'); @@ -918,7 +919,7 @@ export default class Folder extends BaseItem { } public static defaultFolder() { - return this.modelSelectOne('SELECT * FROM folders ORDER BY created_time DESC LIMIT 1'); + return this.modelSelectOne('SELECT * FROM folders WHERE deleted_time = 0 ORDER BY created_time DESC LIMIT 1'); } public static async canNestUnder(folderId: string, targetFolderId: string) { @@ -1076,4 +1077,18 @@ export default class Folder extends BaseItem { return this.getRealFolders(folders).length > 0; } + public static async getValidActiveFolder() { + const folderId = Setting.value('activeFolderId'); + if (!folderId) return null; + + const folder = await Folder.load(folderId); + if (!folder || !!folder.deleted_time) { + const defaultFolder = await Folder.defaultFolder(); + if (!defaultFolder) return null; + return defaultFolder.id; + } + + return folderId; + } + }