fix: keep vault loading shell visible

This commit is contained in:
lucaronin
2026-04-29 22:52:19 +02:00
parent 2a2ea1b8ef
commit 1f4889fe66
14 changed files with 938 additions and 124 deletions

View File

@@ -2,6 +2,7 @@ import type React from 'react'
import { useCallback, useEffect, useRef } from 'react'
import { cn } from '@/lib/utils'
import { translate, type AppLocale } from '../../lib/i18n'
import type { VaultEntry } from '../../types'
import { dispatchEditorFindAvailability } from '../../utils/editorFindEvents'
import { DiffView } from '../DiffView'
import { BreadcrumbBar } from '../BreadcrumbBar'
@@ -38,9 +39,43 @@ type BreadcrumbActions = Pick<
| 'onToggleNoteWidth'
>
const LOADING_BREADCRUMB_ENTRY: VaultEntry = {
path: '',
filename: 'loading.md',
title: '',
isA: 'Note',
aliases: [],
belongsTo: [],
relatedTo: [],
status: null,
archived: false,
modifiedAt: null,
createdAt: null,
fileSize: 0,
snippet: '',
wordCount: 0,
relationships: {},
icon: null,
color: null,
order: null,
sidebarLabel: null,
template: null,
sort: null,
view: null,
visible: true,
organized: false,
favorite: false,
favoriteIndex: null,
listPropertiesDisplay: [],
outgoingLinks: [],
properties: {},
hasH1: false,
fileKind: 'markdown',
}
function EditorLoadingSkeleton() {
return (
<div className="flex flex-1 flex-col gap-3 p-8 animate-pulse" style={{ minHeight: 0 }}>
<div data-testid="editor-content-skeleton" className="flex flex-1 flex-col gap-3 py-5 animate-pulse" style={{ minHeight: 0 }}>
<div className="h-6 w-2/5 rounded bg-muted" />
<div className="h-4 w-4/5 rounded bg-muted" />
<div className="h-4 w-3/5 rounded bg-muted" />
@@ -50,6 +85,16 @@ function EditorLoadingSkeleton() {
)
}
function EditorLoadingCanvas({ cssVars }: Pick<EditorContentModel, 'cssVars'>) {
return (
<div className="editor-scroll-area" style={cssVars as React.CSSProperties}>
<div className="editor-content-wrapper">
<EditorLoadingSkeleton />
</div>
</div>
)
}
function DiffModeView({ diffContent, locale = 'en', onToggleDiff }: { diffContent: string | null; locale?: AppLocale; onToggleDiff: () => void }) {
const label = translate(locale, 'editor.toolbar.rawReturn')
@@ -117,6 +162,7 @@ function ActiveTabBreadcrumb({
path,
actions,
locale,
loadingTitle,
}: {
activeTab: NonNullable<EditorContentModel['activeTab']>
barRef: React.RefObject<HTMLDivElement | null>
@@ -124,12 +170,14 @@ function ActiveTabBreadcrumb({
path: string
actions: BreadcrumbActions
locale?: AppLocale
loadingTitle?: boolean
}) {
return (
<BreadcrumbBar
entry={activeTab.entry}
wordCount={wordCount}
barRef={barRef}
loadingTitle={loadingTitle}
showDiffToggle={actions.showDiffToggle}
diffMode={actions.diffMode}
diffLoading={actions.diffLoading}
@@ -156,6 +204,106 @@ function ActiveTabBreadcrumb({
)
}
function EditorLoadingBreadcrumb({
actions,
barRef,
locale,
}: {
actions: BreadcrumbActions
barRef: React.RefObject<HTMLDivElement | null>
locale?: AppLocale
}) {
return (
<BreadcrumbBar
entry={LOADING_BREADCRUMB_ENTRY}
wordCount={0}
barRef={barRef}
loadingTitle
showDiffToggle={false}
diffMode={false}
diffLoading={false}
onToggleDiff={actions.onToggleDiff}
rawMode={false}
forceRawMode={false}
showAIChat={actions.showAIChat}
onToggleAIChat={actions.onToggleAIChat}
inspectorCollapsed={actions.inspectorCollapsed}
onToggleInspector={actions.onToggleInspector}
noteWidth={actions.noteWidth}
onToggleNoteWidth={actions.onToggleNoteWidth}
locale={locale}
/>
)
}
function buildBreadcrumbActions(model: EditorContentModel): BreadcrumbActions {
return {
diffMode: model.diffMode,
diffLoading: model.diffLoading,
onToggleDiff: model.onToggleDiff,
effectiveRawMode: model.effectiveRawMode,
onToggleRaw: model.onToggleRaw,
forceRawMode: model.forceRawMode,
showAIChat: model.showAIChat,
onToggleAIChat: model.onToggleAIChat,
inspectorCollapsed: model.inspectorCollapsed,
onToggleInspector: model.onToggleInspector,
showDiffToggle: model.showDiffToggle,
onToggleFavorite: model.onToggleFavorite,
onToggleOrganized: model.onToggleOrganized,
onRevealFile: model.onRevealFile,
onCopyFilePath: model.onCopyFilePath,
onDeleteNote: model.onDeleteNote,
onArchiveNote: model.onArchiveNote,
onUnarchiveNote: model.onUnarchiveNote,
onRenameFilename: model.onRenameFilename,
noteWidth: model.noteWidth,
onToggleNoteWidth: model.onToggleNoteWidth,
}
}
function EditorBreadcrumbArea({
actions,
barRef,
chromePath,
chromeTab,
chromeWordCount,
isVaultLoading,
locale,
}: {
actions: BreadcrumbActions
barRef: React.RefObject<HTMLDivElement | null>
chromePath: string
chromeTab: EditorContentModel['activeTab'] | EditorContentModel['loadingTab']
chromeWordCount: number
isVaultLoading?: boolean
locale?: AppLocale
}) {
if (chromeTab) {
return (
<ActiveTabBreadcrumb
activeTab={chromeTab}
barRef={barRef}
wordCount={chromeWordCount}
path={chromePath}
locale={locale}
loadingTitle={isVaultLoading}
actions={actions}
/>
)
}
if (!isVaultLoading) return null
return (
<EditorLoadingBreadcrumb
actions={actions}
barRef={barRef}
locale={locale}
/>
)
}
function EditorChrome({
isArchived,
onUnarchiveNote,
@@ -262,6 +410,7 @@ function EditorFindScope({
export function EditorContentLayout(model: EditorContentModel) {
const {
activeTab,
loadingTab,
isLoadingNewTab,
entries,
editor,
@@ -290,87 +439,69 @@ export function EditorContentLayout(model: EditorContentModel) {
noteWidth,
findRequest,
locale,
isVaultLoading,
} = model
const rootClassName = cn(
'flex flex-1 flex-col min-w-0 min-h-0',
noteWidth === 'wide' ? 'editor-content-width--wide' : 'editor-content-width--normal',
)
if (!activeTab) {
return (
<div className={rootClassName}>
{isLoadingNewTab && showEditor && <EditorLoadingSkeleton />}
</div>
)
}
const chromeTab = activeTab ?? loadingTab
const chromePath = chromeTab?.entry.path ?? path
const chromeWordCount = activeTab ? wordCount : 0
const showActiveContent = activeTab && !isVaultLoading
const showLoadingContent = isVaultLoading || (isLoadingNewTab && showEditor)
const breadcrumbActions = buildBreadcrumbActions(model)
return (
<div className={rootClassName}>
<ActiveTabBreadcrumb
activeTab={activeTab}
<EditorBreadcrumbArea
actions={breadcrumbActions}
barRef={breadcrumbBarRef}
wordCount={wordCount}
path={path}
locale={locale}
actions={{
diffMode: model.diffMode,
diffLoading: model.diffLoading,
onToggleDiff: model.onToggleDiff,
effectiveRawMode: model.effectiveRawMode,
onToggleRaw: model.onToggleRaw,
forceRawMode: model.forceRawMode,
showAIChat: model.showAIChat,
onToggleAIChat: model.onToggleAIChat,
inspectorCollapsed: model.inspectorCollapsed,
onToggleInspector: model.onToggleInspector,
showDiffToggle: model.showDiffToggle,
onToggleFavorite: model.onToggleFavorite,
onToggleOrganized: model.onToggleOrganized,
onRevealFile: model.onRevealFile,
onCopyFilePath: model.onCopyFilePath,
onDeleteNote: model.onDeleteNote,
onArchiveNote: model.onArchiveNote,
onUnarchiveNote: model.onUnarchiveNote,
onRenameFilename: model.onRenameFilename,
noteWidth: model.noteWidth,
onToggleNoteWidth: model.onToggleNoteWidth,
}}
/>
<EditorChrome
isArchived={isArchived}
onUnarchiveNote={onUnarchiveNote}
path={path}
isConflicted={isConflicted}
onKeepMine={onKeepMine}
onKeepTheirs={onKeepTheirs}
diffMode={diffMode}
diffContent={diffContent}
onToggleDiff={onToggleDiff}
chromePath={chromePath}
chromeTab={chromeTab}
chromeWordCount={chromeWordCount}
isVaultLoading={isVaultLoading}
locale={locale}
/>
<RawModeEditorSection
activeTab={activeTab}
entries={entries}
findRequest={findRequest}
rawMode={effectiveRawMode}
rawModeContent={rawModeContent}
onRawContentChange={onRawContentChange}
onSave={onSave}
rawLatestContentRef={rawLatestContentRef}
vaultPath={vaultPath}
locale={locale}
/>
<EditorCanvas
showEditor={showEditor}
cssVars={cssVars}
vaultPath={vaultPath}
editor={editor}
entries={entries}
onNavigateWikilink={onNavigateWikilink}
onEditorChange={onEditorChange}
isDeletedPreview={isDeletedPreview}
/>
{isLoadingNewTab && showEditor && <EditorLoadingSkeleton />}
{showActiveContent && (
<>
<EditorChrome
isArchived={isArchived}
onUnarchiveNote={onUnarchiveNote}
path={path}
isConflicted={isConflicted}
onKeepMine={onKeepMine}
onKeepTheirs={onKeepTheirs}
diffMode={diffMode}
diffContent={diffContent}
onToggleDiff={onToggleDiff}
locale={locale}
/>
<RawModeEditorSection
activeTab={activeTab}
entries={entries}
findRequest={findRequest}
rawMode={effectiveRawMode}
rawModeContent={rawModeContent}
onRawContentChange={onRawContentChange}
onSave={onSave}
rawLatestContentRef={rawLatestContentRef}
vaultPath={vaultPath}
locale={locale}
/>
<EditorCanvas
showEditor={showEditor}
cssVars={cssVars}
vaultPath={vaultPath}
editor={editor}
entries={entries}
onNavigateWikilink={onNavigateWikilink}
onEditorChange={onEditorChange}
isDeletedPreview={isDeletedPreview}
/>
</>
)}
{showLoadingContent && <EditorLoadingCanvas cssVars={cssVars} />}
</div>
)
}