mirror of
https://github.com/stablyai/orca.git
synced 2026-10-26 08:02:44 +00:00
284 lines
10 KiB
TypeScript
284 lines
10 KiB
TypeScript
import { useAppStore } from '@/store'
|
|
import type { MarkdownViewMode, OpenFile, PendingEditorReveal } from '@/store/slices/editor'
|
|
import type { GitDiffResult } from '../../../../shared/git-diff-compare-types'
|
|
import type { GitStatusEntry } from '../../../../shared/git-status-types'
|
|
import { CheckRunDetailsPanel } from './CheckRunDetailsPanel'
|
|
import { CombinedDiffViewer, MarkdownPreview } from './editor-lazy-views'
|
|
import { EditorConflictReviewSurface } from './EditorConflictReviewSurface'
|
|
import { EditorDiffFileSurface } from './EditorDiffFileSurface'
|
|
import { EditorEditFileSurface } from './EditorEditFileSurface'
|
|
import { EditorFileLoadErrorView } from './EditorFileLoadErrorView'
|
|
import type { FileContent } from './editor-panel-content-types'
|
|
import { translate } from '@/i18n/i18n'
|
|
import { useEditorConflictNavigation } from './useEditorConflictNavigation'
|
|
import { useMarkdownDocuments } from './useMarkdownDocuments'
|
|
import type { MarkdownRenderState } from './markdown-render-mode'
|
|
|
|
const noopCloseMarkdownTableOfContents = (): void => {}
|
|
|
|
export function getMarkdownSourceLineOffset(frontMatterRaw: string): number {
|
|
let offset = 0
|
|
for (let index = 0; index < frontMatterRaw.length; index++) {
|
|
const code = frontMatterRaw.charCodeAt(index)
|
|
if (code === 13) {
|
|
offset++
|
|
if (frontMatterRaw.charCodeAt(index + 1) === 10) {
|
|
index++
|
|
}
|
|
continue
|
|
}
|
|
if (code === 10) {
|
|
offset++
|
|
}
|
|
}
|
|
return offset
|
|
}
|
|
|
|
export function EditorContent({
|
|
activeFile,
|
|
viewStateScopeId,
|
|
fileContents,
|
|
diffContents,
|
|
editBuffers,
|
|
openFiles,
|
|
worktreeEntries,
|
|
resolvedLanguage,
|
|
isMarkdown,
|
|
isMermaid,
|
|
isCsv,
|
|
isNotebook,
|
|
mdViewMode,
|
|
inlineMarkdownRenderState,
|
|
isChangesMode,
|
|
sideBySide,
|
|
showMarkdownTableOfContents = false,
|
|
showMarkdownFrontmatter = false,
|
|
onCloseMarkdownTableOfContents = noopCloseMarkdownTableOfContents,
|
|
markdownAnnotationsEnabled = true,
|
|
pendingEditorReveal,
|
|
handleContentChange,
|
|
handleContentChangeForFile,
|
|
handleDirtyStateHint,
|
|
handleSave,
|
|
handleSaveForFile,
|
|
reloadContent
|
|
}: {
|
|
activeFile: OpenFile
|
|
viewStateScopeId: string
|
|
fileContents: Record<string, FileContent>
|
|
diffContents: Record<string, GitDiffResult>
|
|
editBuffers: Record<string, string>
|
|
openFiles: OpenFile[]
|
|
worktreeEntries: GitStatusEntry[]
|
|
resolvedLanguage: string
|
|
isMarkdown: boolean
|
|
isMermaid: boolean
|
|
isCsv: boolean
|
|
isNotebook: boolean
|
|
mdViewMode: MarkdownViewMode
|
|
inlineMarkdownRenderState: MarkdownRenderState | null
|
|
isChangesMode: boolean
|
|
sideBySide: boolean
|
|
showMarkdownTableOfContents?: boolean
|
|
showMarkdownFrontmatter?: boolean
|
|
onCloseMarkdownTableOfContents?: () => void
|
|
markdownAnnotationsEnabled?: boolean
|
|
pendingEditorReveal: PendingEditorReveal | null
|
|
handleContentChange: (content: string) => void
|
|
handleContentChangeForFile: (file: OpenFile, content: string) => void
|
|
handleDirtyStateHint: (dirty: boolean) => void
|
|
handleSave: (content: string) => Promise<boolean>
|
|
handleSaveForFile: (file: OpenFile, content: string) => Promise<boolean>
|
|
reloadContent: (file: OpenFile) => void
|
|
}): React.JSX.Element {
|
|
const editorViewStateKey =
|
|
viewStateScopeId === activeFile.id
|
|
? activeFile.filePath
|
|
: `${activeFile.filePath}::${viewStateScopeId}`
|
|
const diffViewStateKey =
|
|
viewStateScopeId === activeFile.id ? activeFile.id : `${activeFile.id}::${viewStateScopeId}`
|
|
const markdownPreviewViewStateKey =
|
|
viewStateScopeId === activeFile.id
|
|
? `${activeFile.id}:preview`
|
|
: `${activeFile.id}::${viewStateScopeId}:preview`
|
|
// Why: only the single-pane edit path gets PDF scroll memory — diff and conflict review mount several viewers on one path.
|
|
const pdfViewStateKey =
|
|
viewStateScopeId === activeFile.id
|
|
? `${activeFile.filePath}:pdf`
|
|
: `${activeFile.filePath}::${viewStateScopeId}:pdf`
|
|
const monacoLanguage = resolvedLanguage === 'notebook' ? 'json' : resolvedLanguage
|
|
const reloadOpenCheckRunDetailsTab = useAppStore((state) => state.reloadOpenCheckRunDetailsTab)
|
|
const markdownDocuments = useMarkdownDocuments(activeFile, isMarkdown, mdViewMode, handleSave)
|
|
const getConflictNavigation = useEditorConflictNavigation()
|
|
const activeConflictEntry =
|
|
worktreeEntries.find((entry) => entry.path === activeFile.relativePath) ?? null
|
|
const isCombinedDiff =
|
|
activeFile.mode === 'diff' &&
|
|
(activeFile.diffSource === 'combined-all' ||
|
|
activeFile.diffSource === 'combined-uncommitted' ||
|
|
activeFile.diffSource === 'combined-branch' ||
|
|
activeFile.diffSource === 'combined-commit')
|
|
|
|
if (activeFile.mode === 'check-details') {
|
|
const checkRunDetails = activeFile.checkRunDetails
|
|
if (!checkRunDetails) {
|
|
return (
|
|
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
|
{translate(
|
|
'auto.components.editor.EditorContent.6c4f1a8d2e',
|
|
'Check details are unavailable.'
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
const details = checkRunDetails.details
|
|
return (
|
|
<CheckRunDetailsPanel
|
|
check={checkRunDetails.check}
|
|
details={details}
|
|
loading={checkRunDetails.loading}
|
|
error={checkRunDetails.error}
|
|
openUrl={details?.detailsUrl ?? details?.url ?? checkRunDetails.check.url}
|
|
worktreeId={activeFile.worktreeId}
|
|
onRefresh={() => {
|
|
void reloadOpenCheckRunDetailsTab(activeFile.id)
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (activeFile.mode === 'conflict-review') {
|
|
return (
|
|
<EditorConflictReviewSurface
|
|
activeFile={activeFile}
|
|
viewStateScopeId={viewStateScopeId}
|
|
fileContents={fileContents}
|
|
editBuffers={editBuffers}
|
|
openFiles={openFiles}
|
|
worktreeEntries={worktreeEntries}
|
|
pendingEditorReveal={pendingEditorReveal}
|
|
getConflictNavigation={getConflictNavigation}
|
|
handleContentChangeForFile={handleContentChangeForFile}
|
|
handleSaveForFile={handleSaveForFile}
|
|
reloadContent={reloadContent}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (isCombinedDiff) {
|
|
return (
|
|
<CombinedDiffViewer
|
|
key={viewStateScopeId}
|
|
file={activeFile}
|
|
viewStateKey={diffViewStateKey}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (activeFile.mode === 'markdown-preview') {
|
|
const fileContent = fileContents[activeFile.id]
|
|
if (!fileContent) {
|
|
return (
|
|
<div className="flex items-center justify-center h-full text-muted-foreground text-sm">
|
|
{translate('auto.components.editor.EditorContent.37a0e81fa6', 'Loading preview...')}
|
|
</div>
|
|
)
|
|
}
|
|
if (fileContent.loadError) {
|
|
return (
|
|
<EditorFileLoadErrorView
|
|
message={fileContent.loadError}
|
|
onRetry={() => reloadContent(activeFile)}
|
|
/>
|
|
)
|
|
}
|
|
if (fileContent.isBinary) {
|
|
return (
|
|
<div className="flex h-full items-center justify-center px-6 text-center text-sm text-muted-foreground">
|
|
{translate(
|
|
'auto.components.editor.EditorContent.8608ce4cb1',
|
|
'Markdown preview is unavailable for binary files.'
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
const previewSourceFileId = activeFile.markdownPreviewSourceFileId ?? activeFile.filePath
|
|
return (
|
|
<div className="min-h-0 flex-1">
|
|
<MarkdownPreview
|
|
key={viewStateScopeId}
|
|
content={editBuffers[previewSourceFileId] ?? fileContent.content}
|
|
filePath={activeFile.filePath}
|
|
sourceFileId={previewSourceFileId}
|
|
sourceWorktreeId={activeFile.worktreeId}
|
|
sourceRuntimeEnvironmentId={activeFile.runtimeEnvironmentId}
|
|
scrollCacheKey={markdownPreviewViewStateKey}
|
|
initialAnchor={activeFile.markdownPreviewAnchor ?? null}
|
|
showTableOfContents={showMarkdownTableOfContents}
|
|
onCloseTableOfContents={onCloseMarkdownTableOfContents}
|
|
markdownAnnotationsEnabled={markdownAnnotationsEnabled}
|
|
{...markdownDocuments.previewProps}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (activeFile.mode === 'edit') {
|
|
return (
|
|
<EditorEditFileSurface
|
|
activeFile={activeFile}
|
|
viewStateScopeId={viewStateScopeId}
|
|
editorViewStateKey={editorViewStateKey}
|
|
diffViewStateKey={diffViewStateKey}
|
|
pdfViewStateKey={pdfViewStateKey}
|
|
fileContent={fileContents[activeFile.id]}
|
|
diffContent={diffContents[activeFile.id]}
|
|
editBuffer={editBuffers[activeFile.id]}
|
|
activeConflictEntry={activeConflictEntry}
|
|
monacoLanguage={monacoLanguage}
|
|
isMarkdown={isMarkdown}
|
|
isMermaid={isMermaid}
|
|
isCsv={isCsv}
|
|
isNotebook={isNotebook}
|
|
mdViewMode={mdViewMode}
|
|
inlineMarkdownRenderState={inlineMarkdownRenderState}
|
|
isChangesMode={isChangesMode}
|
|
sideBySide={sideBySide}
|
|
showMarkdownTableOfContents={showMarkdownTableOfContents}
|
|
showMarkdownFrontmatter={showMarkdownFrontmatter}
|
|
onCloseMarkdownTableOfContents={onCloseMarkdownTableOfContents}
|
|
markdownAnnotationsEnabled={markdownAnnotationsEnabled}
|
|
pendingEditorReveal={pendingEditorReveal}
|
|
markdownDocuments={markdownDocuments}
|
|
getConflictNavigation={getConflictNavigation}
|
|
getMarkdownSourceLineOffset={getMarkdownSourceLineOffset}
|
|
handleContentChange={handleContentChange}
|
|
handleDirtyStateHint={handleDirtyStateHint}
|
|
handleSave={handleSave}
|
|
reloadContent={reloadContent}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<EditorDiffFileSurface
|
|
activeFile={activeFile}
|
|
diffContent={diffContents[activeFile.id]}
|
|
editBuffer={editBuffers[activeFile.id]}
|
|
resolvedLanguage={monacoLanguage}
|
|
sideBySide={sideBySide}
|
|
viewStateScopeId={viewStateScopeId}
|
|
diffViewStateKey={diffViewStateKey}
|
|
mdViewMode={mdViewMode}
|
|
isMarkdown={isMarkdown}
|
|
showMarkdownTableOfContents={showMarkdownTableOfContents}
|
|
onCloseMarkdownTableOfContents={onCloseMarkdownTableOfContents}
|
|
markdownAnnotationsEnabled={markdownAnnotationsEnabled}
|
|
markdownDocuments={markdownDocuments}
|
|
onContentChange={handleContentChange}
|
|
onSave={handleSave}
|
|
reloadContent={reloadContent}
|
|
/>
|
|
)
|
|
}
|