Merge branch 'main' into pr-728
This commit is contained in:
@@ -2,6 +2,7 @@ import { useCallback, useMemo } from 'react'
|
||||
import type { VaultEntry } from '../types'
|
||||
import { isMissingFrontmatterTargetError, type FrontmatterOpOptions } from './frontmatterOps'
|
||||
import { trackEvent } from '../lib/telemetry'
|
||||
import { findTypeDefinition } from '../utils/typeDefinitions'
|
||||
|
||||
interface EntryActionsConfig {
|
||||
entries: VaultEntry[]
|
||||
@@ -49,14 +50,6 @@ interface RenameTypeSectionArgs {
|
||||
label: string
|
||||
}
|
||||
|
||||
function findTypeEntry(entries: VaultEntry[], typeName: string, typeEntryPath?: string): VaultEntry | undefined {
|
||||
if (typeEntryPath) {
|
||||
const entry = entries.find((candidate) => candidate.path === typeEntryPath)
|
||||
if (entry?.isA === 'Type') return entry
|
||||
}
|
||||
return entries.find((entry) => entry.isA === 'Type' && entry.title === typeName)
|
||||
}
|
||||
|
||||
function logOptimisticRollback(label: string, error: unknown): void {
|
||||
if (isMissingFrontmatterTargetError(error)) {
|
||||
console.warn(label, error)
|
||||
@@ -70,7 +63,7 @@ async function findOrCreateType(
|
||||
typeName: string,
|
||||
typeEntryPath?: string,
|
||||
): Promise<VaultEntry | null> {
|
||||
const existingType = findTypeEntry(deps.entries, typeName, typeEntryPath)
|
||||
const existingType = findTypeDefinition({ entries: deps.entries, type: typeName, typeEntryPath })
|
||||
if (existingType) return existingType
|
||||
if (typeEntryPath) return null
|
||||
try {
|
||||
|
||||
@@ -23,6 +23,19 @@ function setup(entries: VaultEntry[]) {
|
||||
}
|
||||
|
||||
describe('useEntryActions type visibility', () => {
|
||||
it('targets an existing Type entry using normalized Type identity', async () => {
|
||||
const typeEntry = makeEntry({ isA: 'Type', title: 'Journal', path: '/vault/journal.md', visible: null })
|
||||
const { result, createTypeEntry, handleUpdateFrontmatter, updateEntry } = setup([typeEntry])
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleToggleTypeVisibility(' journal ')
|
||||
})
|
||||
|
||||
expect(createTypeEntry).not.toHaveBeenCalled()
|
||||
expect(handleUpdateFrontmatter).toHaveBeenCalledWith('/vault/journal.md', 'visible', false)
|
||||
expect(updateEntry).toHaveBeenCalledWith('/vault/journal.md', { visible: false })
|
||||
})
|
||||
|
||||
it('targets the provided Type entry path when duplicate type names exist', async () => {
|
||||
const hiddenTypeEntry = makeEntry({ isA: 'Type', title: 'Journal', path: '/vault/main/journal.md', visible: false })
|
||||
const visibleTypeEntry = makeEntry({ isA: 'Type', title: 'Journal', path: '/vault/work/journal.md', visible: null })
|
||||
|
||||
74
src/utils/typeDefinitions.ts
Normal file
74
src/utils/typeDefinitions.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { VaultEntry } from '../types'
|
||||
|
||||
const NO_WORKSPACE_KEY = '__tolaria_no_workspace__'
|
||||
|
||||
interface TypeNameQuery {
|
||||
type: string
|
||||
}
|
||||
|
||||
interface WorkspacePathQuery {
|
||||
path?: string | null
|
||||
}
|
||||
|
||||
interface TypeDefinitionLookup {
|
||||
entries: VaultEntry[]
|
||||
type: string
|
||||
typeEntryPath?: string
|
||||
}
|
||||
|
||||
interface WorkspaceTypeDefinitionLookup {
|
||||
entries: VaultEntry[]
|
||||
type: string
|
||||
workspacePath: string
|
||||
}
|
||||
|
||||
export function normalizeTypeName({ type }: TypeNameQuery): string {
|
||||
return type.trim().toLowerCase()
|
||||
}
|
||||
|
||||
export function typeWorkspaceKey({ path }: WorkspacePathQuery): string {
|
||||
return path?.trim() || NO_WORKSPACE_KEY
|
||||
}
|
||||
|
||||
export function entryTypeWorkspaceKey(entry: Pick<VaultEntry, 'workspace'>): string {
|
||||
return typeWorkspaceKey({ path: entry.workspace?.path })
|
||||
}
|
||||
|
||||
export function isMarkdownEntry(entry: Pick<VaultEntry, 'fileKind'>): boolean {
|
||||
return entry.fileKind === 'markdown' || !entry.fileKind
|
||||
}
|
||||
|
||||
export function isActiveTypeDefinition(entry: VaultEntry): boolean {
|
||||
return isMarkdownEntry(entry) && entry.isA === 'Type' && !entry.archived
|
||||
}
|
||||
|
||||
export function isTypeDefinitionForName(entry: VaultEntry, query: TypeNameQuery): boolean {
|
||||
return isActiveTypeDefinition(entry) && normalizeTypeName({ type: entry.title }) === normalizeTypeName(query)
|
||||
}
|
||||
|
||||
export function findTypeDefinition({
|
||||
entries,
|
||||
type,
|
||||
typeEntryPath,
|
||||
}: TypeDefinitionLookup): VaultEntry | null {
|
||||
if (typeEntryPath) {
|
||||
const entry = entries.find((candidate) => candidate.path === typeEntryPath)
|
||||
return entry && isActiveTypeDefinition(entry) ? entry : null
|
||||
}
|
||||
const key = normalizeTypeName({ type })
|
||||
if (!key) return null
|
||||
return entries.find((entry) => isTypeDefinitionForName(entry, { type: key })) ?? null
|
||||
}
|
||||
|
||||
export function findTypeDefinitionForWorkspace({
|
||||
entries,
|
||||
type,
|
||||
workspacePath,
|
||||
}: WorkspaceTypeDefinitionLookup): VaultEntry | null {
|
||||
const key = normalizeTypeName({ type })
|
||||
if (!key) return null
|
||||
return entries.find((entry) => (
|
||||
isTypeDefinitionForName(entry, { type: key })
|
||||
&& entry.workspace?.path === workspacePath
|
||||
)) ?? null
|
||||
}
|
||||
@@ -1,37 +1,24 @@
|
||||
import type { VaultEntry, WorkspaceIdentity } from '../types'
|
||||
|
||||
const NO_WORKSPACE_KEY = '__tolaria_no_workspace__'
|
||||
import {
|
||||
entryTypeWorkspaceKey,
|
||||
findTypeDefinitionForWorkspace as findTypeDefinitionForWorkspaceByQuery,
|
||||
isActiveTypeDefinition,
|
||||
isMarkdownEntry,
|
||||
isTypeDefinitionForName,
|
||||
normalizeTypeName,
|
||||
typeWorkspaceKey,
|
||||
} from './typeDefinitions'
|
||||
|
||||
export type TypeVisibilityLookup = Record<string, Record<string, boolean>>
|
||||
|
||||
function isMarkdown(entry: VaultEntry): boolean {
|
||||
return entry.fileKind === 'markdown' || !entry.fileKind
|
||||
}
|
||||
|
||||
function typeKey(type: string): string {
|
||||
return type.trim().toLowerCase()
|
||||
}
|
||||
|
||||
function workspaceKey(path?: string | null): string {
|
||||
return path?.trim() || NO_WORKSPACE_KEY
|
||||
}
|
||||
|
||||
function entryWorkspaceKey(entry: Pick<VaultEntry, 'workspace'>): string {
|
||||
return workspaceKey(entry.workspace?.path)
|
||||
}
|
||||
|
||||
function isActiveTypeDefinition(entry: VaultEntry): boolean {
|
||||
return isMarkdown(entry) && entry.isA === 'Type' && !entry.archived
|
||||
}
|
||||
|
||||
export function buildTypeVisibilityLookup(entries: VaultEntry[]): TypeVisibilityLookup {
|
||||
const lookup: TypeVisibilityLookup = {}
|
||||
for (const entry of entries) {
|
||||
if (!isActiveTypeDefinition(entry)) continue
|
||||
const key = typeKey(entry.title)
|
||||
const key = normalizeTypeName({ type: entry.title })
|
||||
if (!key) continue
|
||||
lookup[key] = lookup[key] ?? {}
|
||||
lookup[key][entryWorkspaceKey(entry)] = entry.visible !== false
|
||||
lookup[key][entryTypeWorkspaceKey(entry)] = entry.visible !== false
|
||||
}
|
||||
return lookup
|
||||
}
|
||||
@@ -41,9 +28,9 @@ export function isTypeVisibleInWorkspace(
|
||||
type: string,
|
||||
workspacePath?: string | null,
|
||||
): boolean {
|
||||
const typeLookup = lookup[typeKey(type)]
|
||||
const typeLookup = lookup[normalizeTypeName({ type })]
|
||||
if (!typeLookup) return true
|
||||
const visible = typeLookup[workspaceKey(workspacePath)]
|
||||
const visible = typeLookup[typeWorkspaceKey({ path: workspacePath })]
|
||||
return visible !== false
|
||||
}
|
||||
|
||||
@@ -52,14 +39,10 @@ export function isSectionEntryVisibleForType(
|
||||
type: string,
|
||||
lookup: TypeVisibilityLookup,
|
||||
): boolean {
|
||||
if (!isMarkdown(entry) || entry.isA !== type) return false
|
||||
if (!isMarkdownEntry(entry) || entry.isA !== type) return false
|
||||
return isTypeVisibleInWorkspace(lookup, type, entry.workspace?.path)
|
||||
}
|
||||
|
||||
function isMatchingTypeDefinition(entry: VaultEntry, type: string): boolean {
|
||||
return isActiveTypeDefinition(entry) && typeKey(entry.title) === typeKey(type)
|
||||
}
|
||||
|
||||
export function isTypeSectionVisible(
|
||||
entries: VaultEntry[],
|
||||
type: string,
|
||||
@@ -69,7 +52,7 @@ export function isTypeSectionVisible(
|
||||
|
||||
for (const entry of entries) {
|
||||
if (isSectionEntryVisibleForType(entry, type, lookup)) return true
|
||||
if (!isMatchingTypeDefinition(entry, type)) continue
|
||||
if (!isTypeDefinitionForName(entry, { type })) continue
|
||||
hasMatchingTypeDefinition = true
|
||||
if (isTypeVisibleInWorkspace(lookup, type, entry.workspace?.path)) return true
|
||||
}
|
||||
@@ -102,9 +85,5 @@ export function findTypeDefinitionForWorkspace(
|
||||
type: string,
|
||||
workspacePath: string,
|
||||
): VaultEntry | null {
|
||||
const key = typeKey(type)
|
||||
return entries.find((entry) => (
|
||||
isMatchingTypeDefinition(entry, key)
|
||||
&& entry.workspace?.path === workspacePath
|
||||
)) ?? null
|
||||
return findTypeDefinitionForWorkspaceByQuery({ entries, type, workspacePath })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user