Merge branch 'main' into pr-726

This commit is contained in:
github-actions[bot]
2026-05-26 10:05:58 +00:00
committed by GitHub
37 changed files with 386 additions and 34 deletions

View File

@@ -36,6 +36,7 @@ files:
command.navigation.showArchivedNotes: 241399b908884adfe8608a3ab827ca74
command.navigation.listType: 4f70c27c6826a37543c8ada561397c22
command.note.newNote: 23c4edda8b815f750782f01870d27271
command.note.newNoteInCurrentFolder: 167b765903c70ab1b645908ff8a899ab
command.note.newType: adce5108f18945cc502a06c02445e32d
command.note.newTypedNote: 1493eda772c2179cb6247169d00723b2
command.note.saveNote: 2a309cda46e95b00d2a5a8afb3cc0047
@@ -366,6 +367,7 @@ files:
sidebar.action.createFolder: 5dc1e97c14fbe72d8b566ce29c39f010
sidebar.action.renameFolder: db76034f8218cda07dadb746a5643019
sidebar.action.deleteFolder: 2a2f15300f6f7c81d69860ac1796b517
sidebar.action.createNodeInFolderMenu: 857a479229a4ab0bc62bbfb621d3180a
sidebar.action.revealFolderMenu: 76f4ed52da9937ae432dcf5a108fabb4
sidebar.action.copyFolderPathMenu: 1779ec6018a385912c1a5499a1bbf2b2
sidebar.action.renameFolderMenu: deb1d8fa030292e7eda2c244b313aca2

View File

@@ -4,6 +4,7 @@ import { describe, it, expect, vi } from 'vitest'
import { FolderTree } from './FolderTree'
import { FOLDER_ROW_SINGLE_CLICK_DELAY_MS } from './folder-tree/useFolderRowInteractions'
import { FOLDER_ROW_NESTING_INDENT, getFolderConnectorLeft } from './folder-tree/folderTreeLayout'
import { CREATE_NOTE_IN_FOLDER_EVENT } from '../hooks/noteCreationRequests'
import type { FolderNode, SidebarSelection } from '../types'
const mockFolders: FolderNode[] = [
@@ -526,6 +527,44 @@ describe('FolderTree', () => {
expect(onCopyFolderPath).toHaveBeenCalledWith('projects')
})
it('creates a note in the right-clicked mounted folder', () => {
const onCreateNoteInFolder = vi.fn()
const folders: FolderNode[] = [
{
name: 'Personal',
path: '',
rootPath: '/Users/luca/Personal',
children: [{ name: 'projects', path: 'projects', rootPath: '/Users/luca/Personal', children: [] }],
},
{
name: 'Team',
path: '',
rootPath: '/Users/luca/Team',
children: [{ name: 'projects', path: 'projects', rootPath: '/Users/luca/Team', children: [] }],
},
]
render(
<FolderTree
folders={folders}
selection={defaultSelection}
onSelect={vi.fn()}
vaultRootPath="/Users/luca/Team"
/>,
)
window.addEventListener(CREATE_NOTE_IN_FOLDER_EVENT, onCreateNoteInFolder)
fireEvent.contextMenu(screen.getAllByTestId('folder-row:projects')[1])
fireEvent.click(screen.getByTestId('create-node-in-folder-menu-item'))
expect(onCreateNoteInFolder).toHaveBeenCalledOnce()
expect((onCreateNoteInFolder.mock.calls[0][0] as CustomEvent).detail).toEqual({
folderPath: 'projects',
rootPath: '/Users/luca/Team',
})
window.removeEventListener(CREATE_NOTE_IN_FOLDER_EVENT, onCreateNoteInFolder)
})
it('keeps destructive folder actions off the vault root row and menu', () => {
render(
<FolderTree

View File

@@ -162,6 +162,7 @@ export const FolderTree = memo(function FolderTree({
closeContextMenu,
contextMenu,
handleCopyPathFromMenu,
handleCreateNoteFromMenu,
handleDeleteFromMenu,
handleOpenMenu,
handleRevealFromMenu,
@@ -221,6 +222,7 @@ export const FolderTree = memo(function FolderTree({
onDelete={handleDeleteFromMenu}
onReveal={handleRevealFromMenu}
onCopyPath={handleCopyPathFromMenu}
onCreateNote={handleCreateNoteFromMenu}
onRename={handleRenameFromMenu}
locale={locale}
/>

View File

@@ -1,5 +1,5 @@
import { ArrowsOut as Maximize2 } from '@phosphor-icons/react'
import { useEffect, useId, useMemo, useRef, useState, type SyntheticEvent } from 'react'
import { useEffect, useId, useMemo, useState, type SyntheticEvent } from 'react'
import { Button } from '@/components/ui/button'
import {
Dialog,
@@ -159,13 +159,7 @@ function MermaidLightbox({ svg }: { svg: string }) {
}
function MermaidSourceFallback({ source }: { source: string }) {
const sourceRef = useRef<HTMLPreElement>(null)
useEffect(() => {
sourceRef.current?.setAttribute('aria-label', 'Mermaid source')
}, [])
return <pre ref={sourceRef}><code>{source}</code></pre>
return <pre aria-label="Mermaid source"><code>{source}</code></pre>
}
export function MermaidDiagram({ diagram, source }: MermaidDiagramProps) {

View File

@@ -188,6 +188,26 @@ describe('NoteList rendering', () => {
expect(onCreateNote).toHaveBeenCalledWith(undefined)
})
it('shows the active folder name and creates notes inside that folder', () => {
const { onCreateNote } = renderNoteList({
selection: {
kind: 'folder',
path: 'Projects/2026 Planning',
rootPath: '/Users/luca/Laputa',
},
})
expect(screen.getByRole('heading', { name: '2026 Planning' })).toBeInTheDocument()
fireEvent.click(screen.getByTitle('Create new note'))
expect(onCreateNote).toHaveBeenCalledWith(undefined, {
creationPath: 'folder_header',
folderPath: 'Projects/2026 Planning',
vaultPath: '/Users/luca/Laputa',
})
})
it('pins the current entity and shows grouped children', () => {
renderNoteList({ selection: { kind: 'entity', entry: mockEntries[0] } })
expect(screen.getAllByText('Build Laputa App').length).toBeGreaterThanOrEqual(1)

View File

@@ -1,10 +1,11 @@
import type { RefObject } from 'react'
import { ClipboardText, FolderOpen, PencilSimple, Trash } from '@phosphor-icons/react'
import { ClipboardText, FolderOpen, PencilSimple, Plus, Trash } from '@phosphor-icons/react'
import { Button } from '@/components/ui/button'
import { translate, type AppLocale } from '../../lib/i18n'
export interface FolderContextMenuState {
path: string
rootPath?: string
x: number
y: number
}
@@ -15,6 +16,7 @@ interface FolderContextMenuProps {
onDelete?: (folderPath: string) => void
onReveal?: (folderPath: string) => void
onCopyPath?: (folderPath: string) => void
onCreateNote?: (folderPath: string, rootPath?: string) => void
onRename: (folderPath: string) => void
locale?: AppLocale
}
@@ -25,6 +27,7 @@ export function FolderContextMenu({
onDelete,
onReveal,
onCopyPath,
onCreateNote,
onRename,
locale = 'en',
}: FolderContextMenuProps) {
@@ -38,6 +41,18 @@ export function FolderContextMenu({
style={{ left: menu.x, top: menu.y, minWidth: 180 }}
data-testid="folder-context-menu"
>
{onCreateNote && (
<Button
type="button"
variant="ghost"
className="h-auto w-full justify-start gap-2 px-2 py-1.5 text-sm"
onClick={() => onCreateNote(menu.path, menu.rootPath)}
data-testid="create-node-in-folder-menu-item"
>
<Plus size={14} />
{translate(locale, 'sidebar.action.createNodeInFolderMenu')}
</Button>
)}
{onReveal && (
<Button
type="button"

View File

@@ -1,6 +1,7 @@
import { useCallback, type MouseEvent as ReactMouseEvent } from 'react'
import type { FolderNode } from '../../types'
import type { FolderFileActions } from '../../hooks/useFileActions'
import { requestCreateNoteInFolder } from '../../hooks/noteCreationRequests'
import { useSidebarContextMenu } from '../sidebar/sidebarHooks'
interface UseFolderContextMenuInput {
@@ -19,12 +20,17 @@ export function useFolderContextMenu({
contextMenu,
contextMenuRef,
openContextMenuFromPointer,
} = useSidebarContextMenu<string>()
} = useSidebarContextMenu<{ path: string; rootPath?: string }>()
const handleOpenMenu = useCallback((node: FolderNode, event: ReactMouseEvent<HTMLElement>) => {
openContextMenuFromPointer(node.path, event)
openContextMenuFromPointer({ path: node.path, rootPath: node.rootPath }, event)
}, [openContextMenuFromPointer])
const handleCreateNoteFromMenu = useCallback((folderPath: string, rootPath?: string) => {
closeContextMenu()
requestCreateNoteInFolder(folderPath, rootPath)
}, [closeContextMenu])
const handleRenameFromMenu = useCallback((folderPath: string) => {
closeContextMenu()
onStartRenameFolder?.(folderPath)
@@ -45,7 +51,8 @@ export function useFolderContextMenu({
folderFileActions?.copyFolderPath(folderPath)
}, [closeContextMenu, folderFileActions])
const menu = contextMenu ? {
path: contextMenu.target,
path: contextMenu.target.path,
rootPath: contextMenu.target.rootPath,
x: contextMenu.pos.x,
y: contextMenu.pos.y,
} : null
@@ -54,6 +61,7 @@ export function useFolderContextMenu({
closeContextMenu,
contextMenu: menu,
handleCopyPathFromMenu,
handleCreateNoteFromMenu,
handleDeleteFromMenu,
handleOpenMenu,
handleRevealFromMenu,

View File

@@ -1,5 +1,6 @@
import { useState, useMemo, useCallback, useEffect, useRef } from 'react'
import type { VaultEntry, SidebarSelection, ModifiedFile, NoteStatus, ViewDefinition, ViewFile } from '../../types'
import type { ImmediateCreateOptions } from '../../hooks/useNoteCreation'
import {
type SortOption, type SortDirection, type SortConfig, type NoteListFilter,
getSortComparator, extractSortableProperties,
@@ -996,7 +997,36 @@ interface UseNoteListInteractionsParams {
onAutoTriggerDiff?: () => void
onDiscardFile?: (relativePath: string) => Promise<void>
openContextMenuForEntry: (entry: VaultEntry, point: { x: number; y: number }) => void
onCreateNote: (type?: string) => void
onCreateNote: (type?: string, options?: ImmediateCreateOptions) => void
}
function createNoteRequestForSelection(selection: SidebarSelection): {
options?: ImmediateCreateOptions
type?: string
} {
if (selection.kind === 'sectionGroup') return { type: selection.type }
if (selection.kind === 'folder') {
return {
options: {
creationPath: 'folder_header',
folderPath: selection.path,
vaultPath: selection.rootPath,
},
}
}
return {}
}
function createNoteForSelection(
onCreateNote: (type?: string, options?: ImmediateCreateOptions) => void,
selection: SidebarSelection,
): void {
const request = createNoteRequestForSelection(selection)
if (request.options) {
onCreateNote(request.type, request.options)
return
}
onCreateNote(request.type)
}
function resolveChangesContextMenuEntry(
@@ -1232,7 +1262,7 @@ export function useNoteListInteractions({
})
const handleCreateNote = useCallback(() => {
onCreateNote(selection.kind === 'sectionGroup' ? selection.type : undefined)
createNoteForSelection(onCreateNote, selection)
}, [onCreateNote, selection])
const toggleGroup = useCallback((label: string) => {

View File

@@ -2,6 +2,7 @@ import type { VaultEntry, SidebarSelection, SidebarFilter, ModifiedFile, NoteSta
import type { RelationshipGroup } from '../../utils/noteListHelpers'
import { translate, type AppLocale } from '../../lib/i18n'
import { filenameStemToTitle } from '../../utils/noteTitle'
import { vaultRelativePathLabel } from '../../utils/notePathIdentity'
export interface DeletedNoteEntry extends VaultEntry {
__deletedNotePreview: true
@@ -30,12 +31,20 @@ function resolveSelectionFilterTitle(selection: SidebarSelection, locale: AppLoc
return translate(locale, FILTER_TITLE_KEYS[selection.filter])
}
function resolveFolderTitle(selection: SidebarSelection): string | null {
if (selection.kind !== 'folder') return null
if (selection.path.trim()) return vaultRelativePathLabel(selection.path)
return selection.rootPath ? vaultRelativePathLabel(selection.rootPath) : null
}
export function resolveHeaderTitle(selection: SidebarSelection, typeDocument: VaultEntry | null, views?: ViewFile[], locale: AppLocale = 'en'): string {
if (selection.kind === 'view') {
const view = views?.find((v) => v.filename === selection.filename)
return view?.definition.name ?? translate(locale, 'noteList.title.view')
}
if (selection.kind === 'entity') return selection.entry.title
const folderTitle = resolveFolderTitle(selection)
if (folderTitle) return folderTitle
if (typeDocument) return typeDocument.title
return resolveSelectionFilterTitle(selection, locale) ?? translate(locale, 'noteList.title.notes')

View File

@@ -13,6 +13,7 @@ import type { NoteListFilter } from '../../utils/noteListHelpers'
import { countByFilter, countAllByFilter, countAllNotesByFilter } from '../../utils/noteListHelpers'
import type { AllNotesFileVisibility } from '../../utils/allNotesFileVisibility'
import type { GitRepositoryOption } from '../../utils/gitRepositories'
import type { ImmediateCreateOptions } from '../../hooks/useNoteCreation'
import { NoteItem } from '../NoteItem'
import { prefetchNoteContent } from '../../hooks/useTabManagement'
import type { MultiSelectState } from '../../hooks/useMultiSelect'
@@ -312,7 +313,7 @@ interface UseNoteListInteractionStateParams {
onCopyFilePath?: (path: string) => void
onAutoTriggerDiff?: () => void
onDiscardFile?: (relativePath: string) => Promise<void>
onCreateNote: (type?: string) => void
onCreateNote: (type?: string, options?: ImmediateCreateOptions) => void
onBulkArchive?: (paths: string[]) => void
onBulkDeletePermanently?: (paths: string[]) => void
locale: AppLocale

View File

@@ -25,6 +25,7 @@ const STATIC_LABEL_KEYS: Partial<Record<string, TranslationKey>> = {
'filter-open': 'command.navigation.showOpenNotes',
'filter-archived': 'command.navigation.showArchivedNotes',
'create-note': 'command.note.newNote',
'create-note-current-folder': 'command.note.newNoteInCurrentFolder',
'create-type': 'command.note.newType',
'save-note': 'command.note.saveNote',
'paste-plain-text': 'command.note.pastePlainText',

View File

@@ -1,5 +1,6 @@
import { APP_COMMAND_IDS, getAppCommandShortcutDisplay } from '../appCommandCatalog'
import { buildEditorFindCommands } from './editorFindCommands'
import type { ImmediateCreateOptions } from '../useNoteCreation'
import type { CommandAction } from './types'
interface NoteCommandsConfig {
@@ -8,8 +9,9 @@ interface NoteCommandsConfig {
activeFileKind?: 'markdown' | 'text' | 'binary'
isArchived: boolean
activeNoteHasIcon?: boolean
onCreateNote: () => void
onCreateNote: (type?: string, options?: ImmediateCreateOptions) => void
onCreateType?: () => void
currentFolderCreateOptions?: ImmediateCreateOptions
onSave: () => void
onFindInNote?: () => void
onReplaceInNote?: () => void
@@ -63,6 +65,16 @@ function createNoteCommand(config: NoteCommandConfig): CommandAction {
}
}
function buildCurrentFolderNoteCommand(config: NoteCommandsConfig): CommandAction {
return createNoteCommand({
id: 'create-note-current-folder',
label: 'Create New Note in Current Folder',
keywords: ['new', 'create', 'add', 'folder', 'current'],
enabled: config.currentFolderCreateOptions !== undefined,
execute: () => config.onCreateNote(undefined, config.currentFolderCreateOptions),
})
}
function buildCoreNoteCommands(config: NoteCommandsConfig): CommandAction[] {
return [
createNoteCommand({
@@ -73,6 +85,7 @@ function buildCoreNoteCommands(config: NoteCommandsConfig): CommandAction[] {
enabled: true,
execute: config.onCreateNote,
}),
buildCurrentFolderNoteCommand(config),
createNoteCommand({
id: 'create-type',
label: 'New Type',

View File

@@ -0,0 +1,33 @@
import { useEffect } from 'react'
import type { ImmediateCreateOptions } from './useNoteCreation'
export const CREATE_NOTE_IN_FOLDER_EVENT = 'tolaria:create-note-in-folder'
interface CreateNoteInFolderDetail {
folderPath: string
rootPath?: string
}
type ImmediateCreate = (type?: string, options?: ImmediateCreateOptions) => void
export function requestCreateNoteInFolder(folderPath: string, rootPath?: string): void {
window.dispatchEvent(new CustomEvent<CreateNoteInFolderDetail>(CREATE_NOTE_IN_FOLDER_EVENT, {
detail: { folderPath, rootPath },
}))
}
export function useCreateNoteInFolderRequests(createNote: ImmediateCreate): void {
useEffect(() => {
const handleCreateNoteInFolder = (event: Event) => {
const { folderPath, rootPath } = (event as CustomEvent<CreateNoteInFolderDetail>).detail
createNote(undefined, {
creationPath: 'folder_context_menu',
folderPath,
vaultPath: rootPath,
})
}
window.addEventListener(CREATE_NOTE_IN_FOLDER_EVENT, handleCreateNoteInFolder)
return () => window.removeEventListener(CREATE_NOTE_IN_FOLDER_EVENT, handleCreateNoteInFolder)
}, [createNote])
}

View File

@@ -12,6 +12,7 @@ import type { NoteWidthMode, SidebarSelection, SidebarFilter, VaultEntry } from
import { requestAddRemote } from '../utils/addRemoteEvents'
import type { NoteListFilter } from '../utils/noteListHelpers'
import type { ViewMode } from './useViewMode'
import type { ImmediateCreateOptions } from './useNoteCreation'
import type { NoteListMultiSelectionCommands } from '../components/note-list/multiSelectionCommands'
import type { GitRepositoryOption } from '../utils/gitRepositories'
@@ -29,7 +30,7 @@ interface AppCommandsConfig {
onFindInNote?: () => void
onReplaceInNote?: () => void
onPastePlainText: () => void
onCreateNote: () => void
onCreateNote: (type?: string, options?: ImmediateCreateOptions) => void
onCreateNoteOfType: (type: string) => void
onSave: () => void
onOpenSettings: () => void

View File

@@ -652,6 +652,36 @@ describe('useCommandRegistry', () => {
})
})
it('exposes a current-folder create command only when a folder is selected', () => {
const onCreateNote = vi.fn()
const { result } = renderHook(() => useCommandRegistry(makeConfig({
onCreateNote,
selection: { kind: 'folder', path: 'Projects/2026 Planning', rootPath: '/vault' },
})))
const command = findCommand(result.current, 'create-note-current-folder')
expect(command).toMatchObject({
label: 'Create New Note in Current Folder',
group: 'Note',
enabled: true,
})
command!.execute()
expect(onCreateNote).toHaveBeenCalledWith(undefined, {
creationPath: 'folder_command_palette',
folderPath: 'Projects/2026 Planning',
vaultPath: '/vault',
})
})
it('disables the current-folder create command outside folder selections', () => {
const { result } = renderHook(() => useCommandRegistry(makeConfig({
selection: { kind: 'filter', filter: 'all' },
})))
expect(findCommand(result.current, 'create-note-current-folder')?.enabled).toBe(false)
})
it('exposes paste without formatting in the command palette', () => {
const onPastePlainText = vi.fn()
const { result } = renderHook(() => useCommandRegistry(makeConfig({ onPastePlainText })))

View File

@@ -17,6 +17,7 @@ import { buildFilterCommands } from './commands/filterCommands'
import { localizeCommandActions } from './commands/localizeCommands'
import { extractVaultTypes } from '../utils/vaultTypes'
import type { GitRepositoryOption } from '../utils/gitRepositories'
import type { ImmediateCreateOptions } from './useNoteCreation'
// Re-export types and helpers for backward compatibility
export type { CommandAction, CommandGroup } from './commands/types'
@@ -65,7 +66,7 @@ interface CommandRegistryConfig {
onRestoreDeletedNote?: () => void
canRestoreDeletedNote?: boolean
onQuickOpen: () => void
onCreateNote: () => void
onCreateNote: (type?: string, options?: ImmediateCreateOptions) => void
onCreateNoteOfType: (type: string) => void
onSave: () => void
onPastePlainText: () => void
@@ -129,6 +130,15 @@ interface CommandRegistryConfig {
onSetNoteListFilter?: (filter: NoteListFilter) => void
}
function currentFolderCreateOptions(selection: SidebarSelection | undefined): ImmediateCreateOptions | undefined {
if (selection?.kind !== 'folder') return undefined
return {
creationPath: 'folder_command_palette',
folderPath: selection.path,
vaultPath: selection.rootPath,
}
}
export function useCommandRegistry(config: CommandRegistryConfig): import('./commands/types').CommandAction[] {
const {
activeTabPath, entries, modifiedCount,
@@ -166,6 +176,7 @@ export function useCommandRegistry(config: CommandRegistryConfig): import('./com
const isArchived = activeEntry?.archived ?? false
const isFavorite = activeEntry?.favorite ?? false
const isSectionGroup = selection?.kind === 'sectionGroup'
const folderCreateOptions = useMemo(() => currentFolderCreateOptions(selection), [selection])
const noteListColumnsLabel = config.noteListColumnsLabel ?? (
selection?.kind === 'filter' && selection.filter === 'all'
? 'Customize All Notes columns'
@@ -195,7 +206,7 @@ export function useCommandRegistry(config: CommandRegistryConfig): import('./com
const noteCommands = useMemo(() => buildNoteCommands({
hasActiveNote, activeTabPath, activeFileKind: activeEntry?.fileKind ?? 'markdown', isArchived,
onCreateNote, onCreateType, onSave,
currentFolderCreateOptions: folderCreateOptions, onCreateNote, onCreateType, onSave,
onFindInNote, onReplaceInNote, onPastePlainText,
onDeleteNote, onArchiveNote, onUnarchiveNote,
onChangeNoteType, onMoveNoteToFolder, canMoveNoteToFolder,
@@ -206,7 +217,7 @@ export function useCommandRegistry(config: CommandRegistryConfig): import('./com
onRestoreDeletedNote, canRestoreDeletedNote,
}), [
hasActiveNote, activeTabPath, activeEntry?.fileKind, isArchived,
onCreateNote, onCreateType, onSave, onFindInNote, onReplaceInNote, onPastePlainText, onDeleteNote, onArchiveNote, onUnarchiveNote,
folderCreateOptions, onCreateNote, onCreateType, onSave, onFindInNote, onReplaceInNote, onPastePlainText, onDeleteNote, onArchiveNote, onUnarchiveNote,
onChangeNoteType, onMoveNoteToFolder, canMoveNoteToFolder,
onSetNoteIcon, onRemoveNoteIcon, activeNoteHasIcon, onOpenInNewWindow,
onRevealActiveFile, onCopyActiveFilePath, onOpenActiveFileExternal,

View File

@@ -362,6 +362,34 @@ describe('useNoteCreation hook', () => {
vi.restoreAllMocks()
})
it('handleCreateNoteImmediate can target a nested folder in a mounted vault', async () => {
vi.mocked(isTauri).mockReturnValue(true)
vi.mocked(invoke).mockResolvedValueOnce(undefined)
vi.spyOn(Date, 'now').mockReturnValue(1700000000000)
const { result } = renderHook(() => useNoteCreation(makeConfig(), tabDeps))
await act(async () => {
result.current.handleCreateNoteImmediate(undefined, {
creationPath: 'folder_header',
folderPath: 'Projects/2026 Planning',
vaultPath: '/Users/luca/Team',
})
await flushImmediateCreate()
})
const createdPath = '/Users/luca/Team/Projects/2026 Planning/untitled-note-1700000000.md'
expect(vi.mocked(invoke)).toHaveBeenCalledWith('create_note_content', {
path: createdPath,
content: expect.stringContaining('type: Note'),
vaultPath: '/Users/luca/Team',
})
expect(addEntry).toHaveBeenCalledWith(expect.objectContaining({
path: createdPath,
workspace: expect.objectContaining({ path: '/Users/luca/Team' }),
}))
vi.restoreAllMocks()
})
it('handleCreateNoteImmediate generates unique names on rapid calls via timestamp', async () => {
vi.useFakeTimers()
let ts = 1700000000000

View File

@@ -6,11 +6,17 @@ import { slugifyNoteStem as slugify } from '../utils/noteSlug'
import { resolveEntry } from '../utils/wikilink'
import { trackEvent } from '../lib/telemetry'
import { cacheNoteContent } from './useTabManagement'
import { findByCollidingNotePath, joinVaultPath, notePathFilename } from '../utils/notePathIdentity'
import {
findByCollidingNotePath,
joinVaultPath,
normalizeVaultRelativePath,
notePathFilename,
} from '../utils/notePathIdentity'
import { canonicalFrontmatterKey } from '../utils/systemMetadata'
import { canonicalizeTypeName } from '../utils/vaultTypes'
import { labelFromWorkspacePath, workspaceIdentityFromVault } from '../utils/workspaces'
import type { VaultOption } from '../components/status-bar/types'
import { useCreateNoteInFolderRequests } from './noteCreationRequests'
export interface NewEntryParams {
path: string
@@ -639,7 +645,20 @@ interface ImmediateCreateDeps {
setToastMessage: (msg: string | null) => void
}
interface ImmediateCreateRequest {
type ImmediateCreationPath =
| 'cmd_n'
| 'folder_command_palette'
| 'folder_context_menu'
| 'folder_header'
| 'type_section'
export interface ImmediateCreateOptions {
creationPath?: ImmediateCreationPath
folderPath?: string
vaultPath?: string
}
interface ImmediateCreateRequest extends ImmediateCreateOptions {
type?: string
}
@@ -697,16 +716,26 @@ async function persistImmediateEntry(
}
/** Create an untitled note and write its backing file before opening it. */
async function createNoteImmediate(deps: ImmediateCreateDeps, type?: string): Promise<boolean> {
const noteType = type || 'Note'
function resolveImmediateCreationVaultPath(deps: ImmediateCreateDeps, request: ImmediateCreateRequest): string {
return request.vaultPath ?? resolveCreationVaultPath(deps.vaultPath, deps.defaultWorkspacePath, deps.vaults)
}
function immediateNoteRelativePath(slug: string, folderPath?: string): string {
const folder = normalizeVaultRelativePath(folderPath ?? '')
return folder ? `${folder}/${slug}.md` : `${slug}.md`
}
async function createNoteImmediate(deps: ImmediateCreateDeps, request: ImmediateCreateRequest): Promise<boolean> {
const noteType = request.type || 'Note'
const slug = generateUntitledFilename(deps.entries, noteType, deps.pendingSlugs)
const title = slug_to_title(slug)
const template = resolveTemplate({ entries: deps.entries, typeName: noteType })
const defaults = resolveTypeInstanceDefaults({ entries: deps.entries, typeName: noteType })
const status = null
const creationVaultPath = resolveCreationVaultPath(deps.vaultPath, deps.defaultWorkspacePath, deps.vaults)
const creationVaultPath = resolveImmediateCreationVaultPath(deps, request)
const relativePath = immediateNoteRelativePath(slug, request.folderPath)
const entry = {
...buildNewEntry({ path: joinVaultPath(creationVaultPath, `${slug}.md`), slug, title, type: noteType, status }),
...buildNewEntry({ path: joinVaultPath(creationVaultPath, relativePath), slug, title, type: noteType, status }),
workspace: workspaceForVaultPath(creationVaultPath, deps.vaults, deps.defaultWorkspacePath),
}
const resolved = applyTypeDefaults({
@@ -728,7 +757,7 @@ function trackImmediateCreate(request: ImmediateCreateRequest, didCreate: boolea
if (!didCreate) return
trackEvent('note_created', {
has_type: request.type ? 1 : 0,
creation_path: request.type ? 'type_section' : 'cmd_n',
creation_path: request.creationPath ?? (request.type ? 'type_section' : 'cmd_n'),
})
}
@@ -784,7 +813,9 @@ function useLatestImmediateCreateDeps(
return { latestDepsRef, syncDeps }
}
function useImmediateCreateQueue(config: ImmediateCreateQueueConfig): (type?: string) => void {
function useImmediateCreateQueue(
config: ImmediateCreateQueueConfig,
): (type?: string, options?: ImmediateCreateOptions) => void {
const pendingSlugsRef = useRef<Set<string>>(new Set())
const queuedImmediateCreatesRef = useRef<ImmediateCreateRequest[]>([])
const immediateCreateLockedRef = useRef(false)
@@ -797,7 +828,7 @@ function useImmediateCreateQueue(config: ImmediateCreateQueueConfig): (type?: st
if (!deps) return
try {
const didCreate = await createNoteImmediate(deps, request.type)
const didCreate = await createNoteImmediate(deps, request)
trackImmediateCreate(request, didCreate)
} catch (error) {
console.warn('Failed to create immediate note:', error)
@@ -831,9 +862,9 @@ function useImmediateCreateQueue(config: ImmediateCreateQueueConfig): (type?: st
}
}, [])
return useCallback((type?: string) => {
return useCallback((type?: string, options: ImmediateCreateOptions = {}) => {
syncDeps()
const request = { type }
const request = { ...options, type }
if (immediateCreateLockedRef.current) {
queuedImmediateCreatesRef.current.push(request)
return
@@ -930,6 +961,7 @@ export function useNoteCreation(config: NoteCreationConfig, tabDeps: CreationTab
removePendingSave,
setToastMessage,
})
useCreateNoteInFolderRequests(handleCreateNoteImmediate)
return {
handleCreateNote,

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "Паказаць архіваваныя нататкі",
"command.navigation.listType": "Спіс {type}",
"command.note.newNote": "Новая нататка",
"command.note.newNoteInCurrentFolder": "Стварыць новую нататку ў бягучай папцы",
"command.note.newType": "Новы тып",
"command.note.newTypedNote": "Стварыць {type}",
"command.note.saveNote": "Захаваць нататку",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "Стварыць папку",
"sidebar.action.renameFolder": "Перайменаваць папку",
"sidebar.action.deleteFolder": "Выдаліць папку",
"sidebar.action.createNodeInFolderMenu": "Стварыць новы вузел у гэтай папцы",
"sidebar.action.revealFolderMenu": "Паказаць у Finder / Explorer",
"sidebar.action.copyFolderPathMenu": "Капіяваць шлях",
"sidebar.action.renameFolderMenu": "Перайменаваць…",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "Pakazać archivavanyja natatki",
"command.navigation.listType": "Spis {type}",
"command.note.newNote": "Novaja natatka",
"command.note.newNoteInCurrentFolder": "Stvaryć novuju natatku ŭ biahučaj papcy",
"command.note.newType": "Novy typ",
"command.note.newTypedNote": "Stvaryć {type}",
"command.note.saveNote": "Zachavać natatku",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "Stvaryć papku",
"sidebar.action.renameFolder": "Pierajmienavać papku",
"sidebar.action.deleteFolder": "Vydalić papku",
"sidebar.action.createNodeInFolderMenu": "Stvaryć novy vuziel u hetaj papcy",
"sidebar.action.revealFolderMenu": "Pakazać u Finder / Explorer",
"sidebar.action.copyFolderPathMenu": "Kapijavać šliach",
"sidebar.action.renameFolderMenu": "Pierajmienavać…",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "Archivierte Notizen anzeigen",
"command.navigation.listType": "{type}-Liste",
"command.note.newNote": "Neue Notiz",
"command.note.newNoteInCurrentFolder": "Neue Notiz im aktuellen Ordner erstellen",
"command.note.newType": "Neuer Typ",
"command.note.newTypedNote": "Neuer {type}",
"command.note.saveNote": "Notiz speichern",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "Ordner erstellen",
"sidebar.action.renameFolder": "Ordner umbenennen",
"sidebar.action.deleteFolder": "Ordner löschen",
"sidebar.action.createNodeInFolderMenu": "Neuen Knoten in diesem Ordner erstellen",
"sidebar.action.revealFolderMenu": "Im Finder anzeigen",
"sidebar.action.copyFolderPathMenu": "Ordnerpfad kopieren",
"sidebar.action.renameFolderMenu": "Ordner umbenennen …",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "Show Archived Notes",
"command.navigation.listType": "List {type}",
"command.note.newNote": "New Note",
"command.note.newNoteInCurrentFolder": "Create New Note in Current Folder",
"command.note.newType": "New Type",
"command.note.newTypedNote": "New {type}",
"command.note.saveNote": "Save Note",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "Create folder",
"sidebar.action.renameFolder": "Rename folder",
"sidebar.action.deleteFolder": "Delete folder",
"sidebar.action.createNodeInFolderMenu": "Create new node in this folder",
"sidebar.action.revealFolderMenu": "Reveal in Finder",
"sidebar.action.copyFolderPathMenu": "Copy folder path",
"sidebar.action.renameFolderMenu": "Rename folder...",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "Mostrar notas archivadas",
"command.navigation.listType": "Lista {type}",
"command.note.newNote": "Nueva nota",
"command.note.newNoteInCurrentFolder": "Crear nueva nota en la carpeta actual",
"command.note.newType": "Nuevo tipo",
"command.note.newTypedNote": "Nuevo {type}",
"command.note.saveNote": "Guardar nota",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "Crear carpeta",
"sidebar.action.renameFolder": "Cambiar el nombre de la carpeta",
"sidebar.action.deleteFolder": "Eliminar carpeta",
"sidebar.action.createNodeInFolderMenu": "Crear un nuevo nodo en esta carpeta",
"sidebar.action.revealFolderMenu": "Mostrar en el Finder",
"sidebar.action.copyFolderPathMenu": "Copiar ruta de la carpeta",
"sidebar.action.renameFolderMenu": "Cambiar el nombre de la carpeta...",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "Mostrar notas archivadas",
"command.navigation.listType": "Lista {type}",
"command.note.newNote": "Nueva nota",
"command.note.newNoteInCurrentFolder": "Crear nueva nota en la carpeta actual",
"command.note.newType": "Nuevo tipo",
"command.note.newTypedNote": "Nuevo {type}",
"command.note.saveNote": "Guardar nota",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "Crear carpeta",
"sidebar.action.renameFolder": "Cambiar el nombre de la carpeta",
"sidebar.action.deleteFolder": "Eliminar carpeta",
"sidebar.action.createNodeInFolderMenu": "Crear un nuevo nodo en esta carpeta",
"sidebar.action.revealFolderMenu": "Mostrar en el Finder",
"sidebar.action.copyFolderPathMenu": "Copiar ruta de la carpeta",
"sidebar.action.renameFolderMenu": "Cambiar el nombre de la carpeta...",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "Afficher les notes archivées",
"command.navigation.listType": "Liste {type}",
"command.note.newNote": "Nouvelle note",
"command.note.newNoteInCurrentFolder": "Créer une nouvelle note dans le dossier actuel",
"command.note.newType": "Nouveau type",
"command.note.newTypedNote": "Nouveau {type}",
"command.note.saveNote": "Enregistrer la note",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "Créer un dossier",
"sidebar.action.renameFolder": "Renommer le dossier",
"sidebar.action.deleteFolder": "Supprimer le dossier",
"sidebar.action.createNodeInFolderMenu": "Créer un nouveau nœud dans ce dossier",
"sidebar.action.revealFolderMenu": "Afficher dans le Finder",
"sidebar.action.copyFolderPathMenu": "Copier le chemin du dossier",
"sidebar.action.renameFolderMenu": "Renommer le dossier…",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "Mostra note archiviate",
"command.navigation.listType": "Elenco {type}",
"command.note.newNote": "Nuova nota",
"command.note.newNoteInCurrentFolder": "Crea nuova nota nella cartella corrente",
"command.note.newType": "Nuovo tipo",
"command.note.newTypedNote": "Nuovo {type}",
"command.note.saveNote": "Salva nota",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "Crea cartella",
"sidebar.action.renameFolder": "Rinomina cartella",
"sidebar.action.deleteFolder": "Elimina cartella",
"sidebar.action.createNodeInFolderMenu": "Crea un nuovo nodo in questa cartella",
"sidebar.action.revealFolderMenu": "Mostra in Finder",
"sidebar.action.copyFolderPathMenu": "Copia percorso della cartella",
"sidebar.action.renameFolderMenu": "Rinomina cartella...",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "アーカイブされたノートを表示",
"command.navigation.listType": "{type}のリスト",
"command.note.newNote": "新規ノート",
"command.note.newNoteInCurrentFolder": "現在のフォルダに新しいノートを作成",
"command.note.newType": "新しいタイプ",
"command.note.newTypedNote": "新規{type}",
"command.note.saveNote": "メモを保存",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "フォルダーを作成",
"sidebar.action.renameFolder": "フォルダー名を変更",
"sidebar.action.deleteFolder": "フォルダーを削除",
"sidebar.action.createNodeInFolderMenu": "このフォルダに新しいノードを作成",
"sidebar.action.revealFolderMenu": "Finder で表示",
"sidebar.action.copyFolderPathMenu": "フォルダパスをコピー",
"sidebar.action.renameFolderMenu": "フォルダー名を変更...",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "보관된 노트 표시",
"command.navigation.listType": "{type} 목록",
"command.note.newNote": "새 노트",
"command.note.newNoteInCurrentFolder": "현재 폴더에 새 노트 만들기",
"command.note.newType": "새 유형",
"command.note.newTypedNote": "새 {type}",
"command.note.saveNote": "노트 저장",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "폴더 만들기",
"sidebar.action.renameFolder": "폴더 이름 변경",
"sidebar.action.deleteFolder": "폴더 삭제",
"sidebar.action.createNodeInFolderMenu": "이 폴더에 새 노드 만들기",
"sidebar.action.revealFolderMenu": "Finder에서 표시",
"sidebar.action.copyFolderPathMenu": "폴더 경로 복사",
"sidebar.action.renameFolderMenu": "폴더 이름 변경...",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "Pokaż zarchiwizowane notatki",
"command.navigation.listType": "Lista {type}",
"command.note.newNote": "Nowa notatka",
"command.note.newNoteInCurrentFolder": "Utwórz nową notatkę w bieżącym folderze",
"command.note.newType": "Nowy typ",
"command.note.newTypedNote": "Nowy {type}",
"command.note.saveNote": "Zapisz notatkę",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "Utwórz folder",
"sidebar.action.renameFolder": "Zmień nazwę folderu",
"sidebar.action.deleteFolder": "Usuń folder",
"sidebar.action.createNodeInFolderMenu": "Utwórz nowy węzeł w tym folderze",
"sidebar.action.revealFolderMenu": "Pokaż w Finderze",
"sidebar.action.copyFolderPathMenu": "Kopiuj ścieżkę folderu",
"sidebar.action.renameFolderMenu": "Zmień nazwę folderu...",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "Exibir notas arquivadas",
"command.navigation.listType": "Lista {type}",
"command.note.newNote": "Nova nota",
"command.note.newNoteInCurrentFolder": "Criar nova nota na pasta atual",
"command.note.newType": "Novo tipo",
"command.note.newTypedNote": "Novo {type}",
"command.note.saveNote": "Salvar nota",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "Criar pasta",
"sidebar.action.renameFolder": "Renomear pasta",
"sidebar.action.deleteFolder": "Excluir pasta",
"sidebar.action.createNodeInFolderMenu": "Criar novo nó nesta pasta",
"sidebar.action.revealFolderMenu": "Exibir no Finder",
"sidebar.action.copyFolderPathMenu": "Copiar caminho da pasta",
"sidebar.action.renameFolderMenu": "Renomear pasta...",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "Mostrar notas arquivadas",
"command.navigation.listType": "Lista {type}",
"command.note.newNote": "Nova nota",
"command.note.newNoteInCurrentFolder": "Criar nova nota na pasta atual",
"command.note.newType": "Novo tipo",
"command.note.newTypedNote": "Novo {type}",
"command.note.saveNote": "Guardar nota",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "Criar pasta",
"sidebar.action.renameFolder": "Mudar o nome da pasta",
"sidebar.action.deleteFolder": "Eliminar pasta",
"sidebar.action.createNodeInFolderMenu": "Criar novo nó nesta pasta",
"sidebar.action.revealFolderMenu": "Mostrar no Finder",
"sidebar.action.copyFolderPathMenu": "Copiar caminho da pasta",
"sidebar.action.renameFolderMenu": "Mudar o nome da pasta...",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "Показать заархивированные заметки",
"command.navigation.listType": "Список {type}",
"command.note.newNote": "Новая заметка",
"command.note.newNoteInCurrentFolder": "Создать новую заметку в текущей папке",
"command.note.newType": "Новый тип",
"command.note.newTypedNote": "Новый {type}",
"command.note.saveNote": "Сохранить заметку",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "Создать папку",
"sidebar.action.renameFolder": "Переименовать папку",
"sidebar.action.deleteFolder": "Удалить папку",
"sidebar.action.createNodeInFolderMenu": "Создать новый узел в этой папке",
"sidebar.action.revealFolderMenu": "Показать в Finder",
"sidebar.action.copyFolderPathMenu": "Копировать путь к папке",
"sidebar.action.renameFolderMenu": "Переименовать папку...",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "Hiển thị ghi chú đã lưu trữ",
"command.navigation.listType": "Danh sách {type}",
"command.note.newNote": "Ghi chú mới",
"command.note.newNoteInCurrentFolder": "Tạo ghi chú mới trong thư mục hiện tại",
"command.note.newType": "Loại mới",
"command.note.newTypedNote": "{type} mới",
"command.note.saveNote": "Lưu ghi chú",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "Tạo thư mục",
"sidebar.action.renameFolder": "Đổi tên thư mục",
"sidebar.action.deleteFolder": "Xóa thư mục",
"sidebar.action.createNodeInFolderMenu": "Tạo nút mới trong thư mục này",
"sidebar.action.revealFolderMenu": "Hiển thị trong Finder",
"sidebar.action.copyFolderPathMenu": "Sao chép đường dẫn thư mục",
"sidebar.action.renameFolderMenu": "Đổi tên thư mục...",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "显示归档笔记",
"command.navigation.listType": "列出{type}",
"command.note.newNote": "新建笔记",
"command.note.newNoteInCurrentFolder": "在当前文件夹中创建新笔记",
"command.note.newType": "新建类型",
"command.note.newTypedNote": "新建{type}",
"command.note.saveNote": "保存笔记",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "创建文件夹",
"sidebar.action.renameFolder": "重命名文件夹",
"sidebar.action.deleteFolder": "删除文件夹",
"sidebar.action.createNodeInFolderMenu": "在此文件夹中创建新节点",
"sidebar.action.revealFolderMenu": "在访达中显示",
"sidebar.action.copyFolderPathMenu": "复制文件夹路径",
"sidebar.action.renameFolderMenu": "重命名文件夹...",

View File

@@ -34,6 +34,7 @@
"command.navigation.showArchivedNotes": "顯示歸檔筆記",
"command.navigation.listType": "列出{type}",
"command.note.newNote": "新建筆記",
"command.note.newNoteInCurrentFolder": "在目前資料夾中建立新筆記",
"command.note.newType": "新建型別",
"command.note.newTypedNote": "新建{type}",
"command.note.saveNote": "儲存筆記",
@@ -364,6 +365,7 @@
"sidebar.action.createFolder": "建立資料夾",
"sidebar.action.renameFolder": "重新命名資料夾",
"sidebar.action.deleteFolder": "刪除資料夾",
"sidebar.action.createNodeInFolderMenu": "在此資料夾中建立新節點",
"sidebar.action.revealFolderMenu": "在訪達中顯示",
"sidebar.action.copyFolderPathMenu": "複製資料夾路徑",
"sidebar.action.renameFolderMenu": "重新命名資料夾...",

View File

@@ -2,6 +2,7 @@ import { expect, type Page } from '@playwright/test'
import fs from 'fs'
import os from 'os'
import path from 'path'
import type { FolderNode } from '../../src/types'
import { installFixtureVaultDesktopBridgeInBrowser } from './fixtureVaultDesktopBridge'
const FIXTURE_VAULT = path.resolve('tests/fixtures/test-vault')
@@ -15,6 +16,7 @@ interface FixtureVaultPageArgs {
page: Page
vaultPath: string
isGitRepo: boolean
folders: FolderNode[]
}
interface FixturePageArgs {
@@ -24,6 +26,7 @@ interface FixturePageArgs {
interface FixtureVaultOptions {
isGitRepo?: boolean
expectedReadyTitle?: string
folders?: FolderNode[]
}
interface CopyDirArgs {
@@ -68,8 +71,8 @@ export function removeFixtureVaultCopy(tempVaultDir: string | null | undefined):
removeFixtureVaultDirectory({ tempVaultDir })
}
async function installFixtureVaultInitScript({ page, vaultPath, isGitRepo }: FixtureVaultPageArgs): Promise<void> {
await page.addInitScript(({ dismissedKey, initialIsGitRepo, resolvedVaultPath }: { dismissedKey: string; initialIsGitRepo: boolean; resolvedVaultPath: string }) => {
async function installFixtureVaultInitScript({ page, vaultPath, isGitRepo, folders }: FixtureVaultPageArgs): Promise<void> {
await page.addInitScript(({ dismissedKey, fixtureFolders, initialIsGitRepo, resolvedVaultPath }: { dismissedKey: string; fixtureFolders: FolderNode[]; initialIsGitRepo: boolean; resolvedVaultPath: string }) => {
localStorage.clear()
localStorage.setItem(dismissedKey, '1')
let gitRepoReady = initialIsGitRepo
@@ -303,7 +306,7 @@ async function installFixtureVaultInitScript({ page, vaultPath, isGitRepo }: Fix
const buildFixtureReadHandlers = () => ({
list_vault: (commandArgs?: FixtureCommandArgs) => readVaultList(commandArgs),
reload_vault: (commandArgs?: FixtureCommandArgs) => readVaultList(commandArgs, true),
list_vault_folders: () => [],
list_vault_folders: () => fixtureFolders,
list_views: () => [],
get_modified_files: () => [],
detect_renames: () => [],
@@ -437,6 +440,7 @@ async function installFixtureVaultInitScript({ page, vaultPath, isGitRepo }: Fix
})
}, {
dismissedKey: CLAUDE_CODE_ONBOARDING_DISMISSED_KEY,
fixtureFolders: folders,
initialIsGitRepo: isGitRepo,
resolvedVaultPath: vaultPath,
})
@@ -460,6 +464,7 @@ export async function openFixtureVault(
page,
vaultPath,
isGitRepo: options.isGitRepo ?? true,
folders: options.folders ?? [],
})
await waitForFixtureVaultReady({
page,

View File

@@ -0,0 +1,44 @@
import fs from 'fs'
import path from 'path'
import { test, expect } from '@playwright/test'
import {
createFixtureVaultCopy,
openFixtureVaultDesktopHarness,
removeFixtureVaultCopy,
} from '../helpers/fixtureVault'
import { executeCommand, openCommandPalette } from './helpers'
let tempVaultDir: string
function untitledNotesInProject(): string[] {
const projectDir = path.join(tempVaultDir, 'project')
return fs.readdirSync(projectDir).filter((name) => /^untitled-note-\d+(?:-\d+)?\.md$/.test(name))
}
test.beforeEach(async ({ page }) => {
tempVaultDir = createFixtureVaultCopy()
await openFixtureVaultDesktopHarness(page, tempVaultDir, {
folders: [{ children: [], name: 'project', path: 'project' }],
})
await expect(page.getByTestId('folder-row:project')).toBeVisible({ timeout: 5_000 })
})
test.afterEach(() => {
removeFixtureVaultCopy(tempVaultDir)
})
test('creates new notes inside the targeted folder @smoke', async ({ page }) => {
await page.getByTestId('folder-row:project').click({ button: 'right' })
await page.getByTestId('create-node-in-folder-menu-item').click()
await expect.poll(untitledNotesInProject, { timeout: 5_000 }).toHaveLength(1)
await page.getByTestId('folder-row:project').click()
await openCommandPalette(page)
await executeCommand(page, 'Create New Note in Current Folder')
await expect.poll(untitledNotesInProject, { timeout: 5_000 }).toHaveLength(2)
for (const filename of untitledNotesInProject()) {
expect(fs.readFileSync(path.join(tempVaultDir, 'project', filename), 'utf8')).toContain('type: Note')
}
})