2026-04-08 14:01:19 +02:00
|
|
|
import type React from 'react'
|
2026-04-27 10:12:13 +02:00
|
|
|
import { useCallback, useEffect, useRef } from 'react'
|
2026-04-26 04:41:18 +02:00
|
|
|
import { cn } from '@/lib/utils'
|
2026-04-26 19:37:21 +02:00
|
|
|
import { translate, type AppLocale } from '../../lib/i18n'
|
2026-04-29 22:52:19 +02:00
|
|
|
import type { VaultEntry } from '../../types'
|
2026-04-27 10:12:13 +02:00
|
|
|
import { dispatchEditorFindAvailability } from '../../utils/editorFindEvents'
|
2026-04-08 14:01:19 +02:00
|
|
|
import { DiffView } from '../DiffView'
|
|
|
|
|
import { BreadcrumbBar } from '../BreadcrumbBar'
|
|
|
|
|
import { ArchivedNoteBanner } from '../ArchivedNoteBanner'
|
|
|
|
|
import { ConflictNoteBanner } from '../ConflictNoteBanner'
|
|
|
|
|
import { RawEditorView } from '../RawEditorView'
|
|
|
|
|
import { SingleEditorView } from '../SingleEditorView'
|
|
|
|
|
import type { useEditorContentModel } from './useEditorContentModel'
|
|
|
|
|
|
|
|
|
|
type EditorContentModel = ReturnType<typeof useEditorContentModel>
|
|
|
|
|
|
|
|
|
|
type BreadcrumbActions = Pick<
|
|
|
|
|
EditorContentModel,
|
|
|
|
|
| 'diffMode'
|
|
|
|
|
| 'diffLoading'
|
|
|
|
|
| 'onToggleDiff'
|
|
|
|
|
| 'effectiveRawMode'
|
|
|
|
|
| 'onToggleRaw'
|
|
|
|
|
| 'forceRawMode'
|
|
|
|
|
| 'showAIChat'
|
|
|
|
|
| 'onToggleAIChat'
|
2026-05-04 06:00:16 +02:00
|
|
|
| 'showTableOfContents'
|
|
|
|
|
| 'onToggleTableOfContents'
|
2026-04-08 14:01:19 +02:00
|
|
|
| 'inspectorCollapsed'
|
|
|
|
|
| 'onToggleInspector'
|
|
|
|
|
| 'showDiffToggle'
|
|
|
|
|
| 'onToggleFavorite'
|
|
|
|
|
| 'onToggleOrganized'
|
2026-05-05 15:46:18 +02:00
|
|
|
| 'onEnterNeighborhood'
|
2026-04-27 01:45:16 +02:00
|
|
|
| 'onRevealFile'
|
|
|
|
|
| 'onCopyFilePath'
|
2026-04-08 14:01:19 +02:00
|
|
|
| 'onDeleteNote'
|
|
|
|
|
| 'onArchiveNote'
|
|
|
|
|
| 'onUnarchiveNote'
|
2026-04-10 17:59:21 +02:00
|
|
|
| 'onRenameFilename'
|
2026-04-29 19:26:24 +02:00
|
|
|
| 'noteWidth'
|
|
|
|
|
| 'onToggleNoteWidth'
|
2026-04-08 14:01:19 +02:00
|
|
|
>
|
|
|
|
|
|
2026-04-29 22:52:19 +02:00
|
|
|
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',
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 14:01:19 +02:00
|
|
|
function EditorLoadingSkeleton() {
|
|
|
|
|
return (
|
2026-04-29 22:52:19 +02:00
|
|
|
<div data-testid="editor-content-skeleton" className="flex flex-1 flex-col gap-3 py-5 animate-pulse" style={{ minHeight: 0 }}>
|
2026-04-08 14:01:19 +02:00
|
|
|
<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" />
|
|
|
|
|
<div className="h-4 w-4/5 rounded bg-muted" />
|
|
|
|
|
<div className="h-4 w-2/5 rounded bg-muted" />
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 22:52:19 +02:00
|
|
|
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>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 19:37:21 +02:00
|
|
|
function DiffModeView({ diffContent, locale = 'en', onToggleDiff }: { diffContent: string | null; locale?: AppLocale; onToggleDiff: () => void }) {
|
|
|
|
|
const label = translate(locale, 'editor.toolbar.rawReturn')
|
|
|
|
|
|
2026-04-08 14:01:19 +02:00
|
|
|
return (
|
|
|
|
|
<div className="flex-1 overflow-auto">
|
|
|
|
|
<button
|
|
|
|
|
className="flex items-center gap-1.5 px-4 py-2 text-xs text-primary bg-muted border-b border-border cursor-pointer hover:bg-accent transition-colors w-full border-t-0 border-l-0 border-r-0"
|
|
|
|
|
onClick={onToggleDiff}
|
2026-04-26 19:37:21 +02:00
|
|
|
title={label}
|
2026-04-08 14:01:19 +02:00
|
|
|
>
|
|
|
|
|
<span style={{ fontSize: 14, lineHeight: 1 }}>←</span>
|
2026-04-26 19:37:21 +02:00
|
|
|
{label}
|
2026-04-08 14:01:19 +02:00
|
|
|
</button>
|
|
|
|
|
<DiffView diff={diffContent ?? ''} />
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function RawModeEditorSection({
|
|
|
|
|
activeTab,
|
|
|
|
|
entries,
|
2026-04-27 10:12:13 +02:00
|
|
|
findRequest,
|
2026-04-08 14:01:19 +02:00
|
|
|
rawMode,
|
2026-04-12 11:45:59 +02:00
|
|
|
rawModeContent,
|
2026-04-08 14:01:19 +02:00
|
|
|
onRawContentChange,
|
|
|
|
|
onSave,
|
|
|
|
|
rawLatestContentRef,
|
|
|
|
|
vaultPath,
|
2026-04-26 19:37:21 +02:00
|
|
|
locale,
|
2026-04-08 14:01:19 +02:00
|
|
|
}: Pick<
|
|
|
|
|
EditorContentModel,
|
2026-04-27 10:12:13 +02:00
|
|
|
'activeTab' | 'entries' | 'findRequest' | 'onRawContentChange' | 'onSave' | 'rawLatestContentRef' | 'rawModeContent' | 'vaultPath'
|
2026-04-08 14:01:19 +02:00
|
|
|
> & {
|
|
|
|
|
rawMode: boolean
|
2026-04-26 19:37:21 +02:00
|
|
|
locale?: AppLocale
|
2026-04-08 14:01:19 +02:00
|
|
|
}) {
|
|
|
|
|
if (!rawMode || !activeTab) return null
|
|
|
|
|
|
|
|
|
|
return (
|
2026-04-27 10:12:13 +02:00
|
|
|
<EditorFindScope className="editor-scroll-area">
|
2026-04-27 11:54:15 +02:00
|
|
|
<RawEditorView
|
|
|
|
|
key={activeTab.entry.path}
|
|
|
|
|
content={rawModeContent ?? activeTab.content}
|
|
|
|
|
path={activeTab.entry.path}
|
|
|
|
|
entries={entries}
|
|
|
|
|
findRequest={findRequest}
|
|
|
|
|
onContentChange={onRawContentChange ?? (() => {})}
|
|
|
|
|
onSave={onSave ?? (() => {})}
|
|
|
|
|
latestContentRef={rawLatestContentRef}
|
|
|
|
|
vaultPath={vaultPath}
|
|
|
|
|
locale={locale}
|
|
|
|
|
/>
|
2026-04-27 10:12:13 +02:00
|
|
|
</EditorFindScope>
|
2026-04-08 14:01:19 +02:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function bindPath(cb: ((path: string) => void) | undefined, path: string) {
|
|
|
|
|
return cb ? () => cb(path) : undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ActiveTabBreadcrumb({
|
|
|
|
|
activeTab,
|
|
|
|
|
barRef,
|
|
|
|
|
wordCount,
|
|
|
|
|
path,
|
|
|
|
|
actions,
|
2026-04-26 19:37:21 +02:00
|
|
|
locale,
|
2026-04-29 22:52:19 +02:00
|
|
|
loadingTitle,
|
2026-04-08 14:01:19 +02:00
|
|
|
}: {
|
|
|
|
|
activeTab: NonNullable<EditorContentModel['activeTab']>
|
|
|
|
|
barRef: React.RefObject<HTMLDivElement | null>
|
|
|
|
|
wordCount: number
|
|
|
|
|
path: string
|
|
|
|
|
actions: BreadcrumbActions
|
2026-04-26 19:37:21 +02:00
|
|
|
locale?: AppLocale
|
2026-04-29 22:52:19 +02:00
|
|
|
loadingTitle?: boolean
|
2026-04-08 14:01:19 +02:00
|
|
|
}) {
|
|
|
|
|
return (
|
|
|
|
|
<BreadcrumbBar
|
|
|
|
|
entry={activeTab.entry}
|
|
|
|
|
wordCount={wordCount}
|
|
|
|
|
barRef={barRef}
|
2026-04-29 22:52:19 +02:00
|
|
|
loadingTitle={loadingTitle}
|
2026-04-08 14:01:19 +02:00
|
|
|
showDiffToggle={actions.showDiffToggle}
|
|
|
|
|
diffMode={actions.diffMode}
|
|
|
|
|
diffLoading={actions.diffLoading}
|
|
|
|
|
onToggleDiff={actions.onToggleDiff}
|
|
|
|
|
rawMode={actions.effectiveRawMode}
|
|
|
|
|
onToggleRaw={actions.onToggleRaw}
|
|
|
|
|
forceRawMode={actions.forceRawMode}
|
|
|
|
|
showAIChat={actions.showAIChat}
|
|
|
|
|
onToggleAIChat={actions.onToggleAIChat}
|
2026-05-04 06:00:16 +02:00
|
|
|
showTableOfContents={actions.showTableOfContents}
|
|
|
|
|
onToggleTableOfContents={actions.onToggleTableOfContents}
|
2026-04-08 14:01:19 +02:00
|
|
|
inspectorCollapsed={actions.inspectorCollapsed}
|
|
|
|
|
onToggleInspector={actions.onToggleInspector}
|
|
|
|
|
onToggleFavorite={bindPath(actions.onToggleFavorite, path)}
|
|
|
|
|
onToggleOrganized={bindPath(actions.onToggleOrganized, path)}
|
2026-05-05 15:46:18 +02:00
|
|
|
onEnterNeighborhood={actions.onEnterNeighborhood}
|
2026-04-27 01:45:16 +02:00
|
|
|
onRevealFile={actions.onRevealFile}
|
|
|
|
|
onCopyFilePath={actions.onCopyFilePath}
|
2026-04-08 14:01:19 +02:00
|
|
|
onDelete={bindPath(actions.onDeleteNote, path)}
|
|
|
|
|
onArchive={bindPath(actions.onArchiveNote, path)}
|
|
|
|
|
onUnarchive={bindPath(actions.onUnarchiveNote, path)}
|
2026-04-10 17:59:21 +02:00
|
|
|
onRenameFilename={actions.onRenameFilename}
|
2026-04-29 19:26:24 +02:00
|
|
|
noteWidth={actions.noteWidth}
|
|
|
|
|
onToggleNoteWidth={actions.onToggleNoteWidth}
|
2026-04-26 19:37:21 +02:00
|
|
|
locale={locale}
|
2026-04-08 14:01:19 +02:00
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 22:52:19 +02:00
|
|
|
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}
|
2026-05-04 06:00:16 +02:00
|
|
|
showTableOfContents={actions.showTableOfContents}
|
|
|
|
|
onToggleTableOfContents={actions.onToggleTableOfContents}
|
2026-04-29 22:52:19 +02:00
|
|
|
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,
|
2026-05-04 06:00:16 +02:00
|
|
|
showTableOfContents: model.showTableOfContents,
|
|
|
|
|
onToggleTableOfContents: model.onToggleTableOfContents,
|
2026-04-29 22:52:19 +02:00
|
|
|
inspectorCollapsed: model.inspectorCollapsed,
|
|
|
|
|
onToggleInspector: model.onToggleInspector,
|
|
|
|
|
showDiffToggle: model.showDiffToggle,
|
|
|
|
|
onToggleFavorite: model.onToggleFavorite,
|
|
|
|
|
onToggleOrganized: model.onToggleOrganized,
|
2026-05-05 15:46:18 +02:00
|
|
|
onEnterNeighborhood: model.onEnterNeighborhood,
|
2026-04-29 22:52:19 +02:00
|
|
|
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}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 20:40:14 +02:00
|
|
|
function EditorChrome({
|
|
|
|
|
isArchived,
|
|
|
|
|
onUnarchiveNote,
|
|
|
|
|
path,
|
|
|
|
|
isConflicted,
|
|
|
|
|
onKeepMine,
|
|
|
|
|
onKeepTheirs,
|
|
|
|
|
diffMode,
|
|
|
|
|
diffContent,
|
|
|
|
|
onToggleDiff,
|
2026-04-26 19:37:21 +02:00
|
|
|
locale,
|
2026-04-08 20:40:14 +02:00
|
|
|
}: Pick<
|
|
|
|
|
EditorContentModel,
|
2026-04-26 19:37:21 +02:00
|
|
|
'isArchived' | 'onUnarchiveNote' | 'path' | 'isConflicted' | 'onKeepMine' | 'onKeepTheirs' | 'diffMode' | 'diffContent' | 'onToggleDiff' | 'locale'
|
2026-04-08 20:40:14 +02:00
|
|
|
>) {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{isArchived && onUnarchiveNote && (
|
2026-04-26 19:37:21 +02:00
|
|
|
<ArchivedNoteBanner onUnarchive={() => onUnarchiveNote(path)} locale={locale} />
|
2026-04-08 20:40:14 +02:00
|
|
|
)}
|
|
|
|
|
{isConflicted && (
|
|
|
|
|
<ConflictNoteBanner
|
|
|
|
|
onKeepMine={() => onKeepMine?.(path)}
|
|
|
|
|
onKeepTheirs={() => onKeepTheirs?.(path)}
|
2026-04-26 19:37:21 +02:00
|
|
|
locale={locale}
|
2026-04-08 20:40:14 +02:00
|
|
|
/>
|
|
|
|
|
)}
|
2026-04-26 19:37:21 +02:00
|
|
|
{diffMode && <DiffModeView diffContent={diffContent} locale={locale} onToggleDiff={onToggleDiff} />}
|
2026-04-08 20:40:14 +02:00
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function EditorCanvas({
|
|
|
|
|
showEditor,
|
|
|
|
|
cssVars,
|
|
|
|
|
editor,
|
|
|
|
|
entries,
|
|
|
|
|
onNavigateWikilink,
|
|
|
|
|
onEditorChange,
|
|
|
|
|
isDeletedPreview,
|
2026-04-11 23:51:58 +02:00
|
|
|
vaultPath,
|
2026-04-30 05:49:40 +02:00
|
|
|
locale,
|
2026-04-08 20:40:14 +02:00
|
|
|
}: Pick<
|
|
|
|
|
EditorContentModel,
|
|
|
|
|
| 'showEditor'
|
|
|
|
|
| 'cssVars'
|
|
|
|
|
| 'editor'
|
|
|
|
|
| 'entries'
|
|
|
|
|
| 'onNavigateWikilink'
|
|
|
|
|
| 'onEditorChange'
|
|
|
|
|
| 'isDeletedPreview'
|
2026-04-11 23:51:58 +02:00
|
|
|
| 'vaultPath'
|
2026-04-30 05:49:40 +02:00
|
|
|
| 'locale'
|
2026-04-08 20:40:14 +02:00
|
|
|
>) {
|
|
|
|
|
if (!showEditor) return null
|
|
|
|
|
|
|
|
|
|
return (
|
2026-05-01 23:14:36 +02:00
|
|
|
<EditorFindScope
|
|
|
|
|
className="editor-scroll-area"
|
|
|
|
|
style={cssVars as React.CSSProperties}
|
|
|
|
|
>
|
2026-04-08 20:40:14 +02:00
|
|
|
<div className="editor-content-wrapper">
|
|
|
|
|
<SingleEditorView
|
|
|
|
|
editor={editor}
|
|
|
|
|
entries={entries}
|
|
|
|
|
onNavigateWikilink={onNavigateWikilink}
|
|
|
|
|
onChange={onEditorChange}
|
|
|
|
|
vaultPath={vaultPath}
|
|
|
|
|
editable={!isDeletedPreview}
|
2026-04-30 05:49:40 +02:00
|
|
|
locale={locale}
|
2026-04-08 20:40:14 +02:00
|
|
|
/>
|
|
|
|
|
</div>
|
2026-04-27 10:12:13 +02:00
|
|
|
</EditorFindScope>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function EditorFindScope({
|
|
|
|
|
children,
|
|
|
|
|
className,
|
|
|
|
|
style,
|
|
|
|
|
}: {
|
|
|
|
|
children: React.ReactNode
|
|
|
|
|
className?: string
|
|
|
|
|
style?: React.CSSProperties
|
|
|
|
|
}) {
|
|
|
|
|
const scopeRef = useRef<HTMLDivElement | null>(null)
|
|
|
|
|
const syncAvailability = useCallback(() => {
|
|
|
|
|
const activeElement = document.activeElement
|
|
|
|
|
const enabled = activeElement instanceof Node
|
|
|
|
|
&& scopeRef.current?.contains(activeElement) === true
|
|
|
|
|
dispatchEditorFindAvailability(enabled)
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
useEffect(() => () => dispatchEditorFindAvailability(false), [])
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
ref={scopeRef}
|
|
|
|
|
className={className}
|
|
|
|
|
data-editor-find-scope="true"
|
|
|
|
|
onFocusCapture={() => dispatchEditorFindAvailability(true)}
|
|
|
|
|
onBlurCapture={() => requestAnimationFrame(syncAvailability)}
|
|
|
|
|
style={style}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
2026-04-08 20:40:14 +02:00
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 14:01:19 +02:00
|
|
|
export function EditorContentLayout(model: EditorContentModel) {
|
|
|
|
|
const {
|
|
|
|
|
activeTab,
|
2026-04-29 22:52:19 +02:00
|
|
|
loadingTab,
|
2026-04-08 14:01:19 +02:00
|
|
|
isLoadingNewTab,
|
|
|
|
|
entries,
|
|
|
|
|
editor,
|
|
|
|
|
diffMode,
|
|
|
|
|
diffContent,
|
|
|
|
|
onToggleDiff,
|
|
|
|
|
effectiveRawMode,
|
|
|
|
|
onRawContentChange,
|
|
|
|
|
onSave,
|
|
|
|
|
showEditor,
|
|
|
|
|
isArchived,
|
|
|
|
|
onUnarchiveNote,
|
|
|
|
|
path,
|
|
|
|
|
isConflicted,
|
|
|
|
|
onKeepMine,
|
|
|
|
|
onKeepTheirs,
|
|
|
|
|
breadcrumbBarRef,
|
|
|
|
|
wordCount,
|
|
|
|
|
vaultPath,
|
|
|
|
|
cssVars,
|
|
|
|
|
onNavigateWikilink,
|
|
|
|
|
onEditorChange,
|
|
|
|
|
isDeletedPreview,
|
|
|
|
|
rawLatestContentRef,
|
2026-04-12 11:45:59 +02:00
|
|
|
rawModeContent,
|
2026-04-29 19:26:24 +02:00
|
|
|
noteWidth,
|
2026-04-27 10:12:13 +02:00
|
|
|
findRequest,
|
2026-04-26 19:37:21 +02:00
|
|
|
locale,
|
2026-04-29 22:52:19 +02:00
|
|
|
isVaultLoading,
|
2026-04-08 14:01:19 +02:00
|
|
|
} = model
|
2026-04-26 04:41:18 +02:00
|
|
|
const rootClassName = cn(
|
|
|
|
|
'flex flex-1 flex-col min-w-0 min-h-0',
|
2026-04-29 19:26:24 +02:00
|
|
|
noteWidth === 'wide' ? 'editor-content-width--wide' : 'editor-content-width--normal',
|
2026-04-26 04:41:18 +02:00
|
|
|
)
|
2026-04-29 22:52:19 +02:00
|
|
|
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)
|
2026-04-08 14:01:19 +02:00
|
|
|
|
|
|
|
|
return (
|
2026-04-26 04:41:18 +02:00
|
|
|
<div className={rootClassName}>
|
2026-04-29 22:52:19 +02:00
|
|
|
<EditorBreadcrumbArea
|
|
|
|
|
actions={breadcrumbActions}
|
2026-04-08 14:01:19 +02:00
|
|
|
barRef={breadcrumbBarRef}
|
2026-04-29 22:52:19 +02:00
|
|
|
chromePath={chromePath}
|
|
|
|
|
chromeTab={chromeTab}
|
|
|
|
|
chromeWordCount={chromeWordCount}
|
|
|
|
|
isVaultLoading={isVaultLoading}
|
2026-04-26 19:37:21 +02:00
|
|
|
locale={locale}
|
2026-04-08 20:40:14 +02:00
|
|
|
/>
|
2026-04-29 22:52:19 +02:00
|
|
|
{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}
|
2026-04-30 05:49:40 +02:00
|
|
|
locale={locale}
|
2026-04-29 22:52:19 +02:00
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
{showLoadingContent && <EditorLoadingCanvas cssVars={cssVars} />}
|
2026-04-08 14:01:19 +02:00
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|