chore: phase 4 refactor
This commit is contained in:
7
TODO.md
7
TODO.md
@@ -213,8 +213,8 @@ Pure tab-policy helpers that decide:
|
||||
- `titleStrategy`: shared title resolver hooks
|
||||
|
||||
## Phase 4 — Editor Routing Registry
|
||||
- Replace long `if/else` tab-type rendering chain with declarative tab->component mapping.
|
||||
- Keep special cases explicit (e.g., tab ID transforms such as git-diff commit/file parsing).
|
||||
- [x] Replace long `if/else` tab-type rendering chain with declarative tab->component mapping.
|
||||
- [x] Keep special cases explicit (e.g., tab ID transforms such as git-diff commit/file parsing).
|
||||
|
||||
## Phase 5 — Menu/Event Harmonization
|
||||
- Route menu-driven view/tab actions through the same behavior layer where applicable.
|
||||
@@ -258,4 +258,5 @@ Pure tab-policy helpers that decide:
|
||||
- [x] Phase 3 continuation slice complete: migrated post/media open paths in Sidebar, Editor, LinkedMediaPanel, and Panel to shared entity-tab policy.
|
||||
- [x] Phase 3 completion slice: centralized `chat` and `import` tab-open specs and migrated sidebar call sites.
|
||||
- [x] Phase 3 completion slice: centralized `git-diff` file/commit ID/spec/open helpers and reused shared parsing in `TabBar`.
|
||||
- [ ] Next implementation slice: Phase 4 (editor routing registry).
|
||||
- [x] Phase 4 complete: introduced `editorRouting` registry/resolver and migrated `Editor` to declarative route->view rendering.
|
||||
- [ ] Next implementation slice: Phase 5 (menu/event harmonization).
|
||||
|
||||
@@ -22,6 +22,7 @@ import { AutoSaveManager, getContrastColor } from '../../utils';
|
||||
import { InsertModal } from '../InsertModal';
|
||||
import { AISuggestionsModal, AISuggestions } from '../AISuggestionsModal/AISuggestionsModal';
|
||||
import { openEntityTab } from '../../navigation/tabPolicy';
|
||||
import { EditorRoute, resolveEditorRoute } from '../../navigation/editorRouting';
|
||||
import { useI18n } from '../../i18n';
|
||||
import './Editor.css';
|
||||
|
||||
@@ -1724,20 +1725,7 @@ export const Editor: React.FC = () => {
|
||||
// Get the active tab
|
||||
const activeTab = tabs.find(t => t.id === activeTabId);
|
||||
|
||||
// Determine what to show based on active tab
|
||||
// Settings and tags should only show when their tab is active, not based on activeView
|
||||
// (activeView controls the sidebar, not the main content area)
|
||||
const showPost = activeTab?.type === 'post';
|
||||
const showMedia = activeTab?.type === 'media';
|
||||
const showSettings = activeTab?.type === 'settings';
|
||||
const showStyle = activeTab?.type === 'style';
|
||||
const showTags = activeTab?.type === 'tags';
|
||||
const showChat = activeTab?.type === 'chat';
|
||||
const showImport = activeTab?.type === 'import';
|
||||
const showMetadataDiff = activeTab?.type === 'metadata-diff';
|
||||
const showGitDiff = activeTab?.type === 'git-diff';
|
||||
const showDocumentation = activeTab?.type === 'documentation';
|
||||
const showSiteValidation = activeTab?.type === 'site-validation';
|
||||
const editorRoute = resolveEditorRoute(activeTab);
|
||||
|
||||
useEffect(() => {
|
||||
const activePostId = activeTab?.type === 'post' ? activeTab.id : null;
|
||||
@@ -1789,129 +1777,30 @@ export const Editor: React.FC = () => {
|
||||
<ConfirmDeleteModal details={confirmDeleteModal} onClose={hideConfirmDeleteModal} />
|
||||
);
|
||||
|
||||
// Show settings only if settings tab is active
|
||||
if (showSettings) {
|
||||
return (
|
||||
<div className="editor">
|
||||
<SettingsView />
|
||||
{renderErrorModal()}
|
||||
{renderConfirmDeleteModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const editorViewRenderers: Record<EditorRoute, () => React.ReactNode> = {
|
||||
settings: () => <SettingsView />,
|
||||
style: () => <StyleView />,
|
||||
tags: () => <TagsView />,
|
||||
chat: () => (editorRoute.tabId ? <ChatPanel key={editorRoute.tabId} conversationId={editorRoute.tabId} /> : <Dashboard />),
|
||||
import: () =>
|
||||
editorRoute.tabId ? <ImportAnalysisView key={editorRoute.tabId} definitionId={editorRoute.tabId} /> : <Dashboard />,
|
||||
'metadata-diff': () => <MetadataDiffPanel />,
|
||||
'git-diff': () =>
|
||||
editorRoute.tabId && editorRoute.gitDiffResource
|
||||
? <GitDiffView key={editorRoute.tabId} filePath={editorRoute.gitDiffResource} />
|
||||
: <Dashboard />,
|
||||
documentation: () => <DocumentationView />,
|
||||
'site-validation': () => <SiteValidationView />,
|
||||
post: () => (editorRoute.tabId ? <PostEditor key={editorRoute.tabId} postId={editorRoute.tabId} /> : <Dashboard />),
|
||||
media: () => (editorRoute.tabId ? <MediaEditor key={editorRoute.tabId} mediaId={editorRoute.tabId} /> : <Dashboard />),
|
||||
dashboard: () => <Dashboard />,
|
||||
};
|
||||
|
||||
if (showStyle) {
|
||||
return (
|
||||
<div className="editor">
|
||||
<StyleView />
|
||||
{renderErrorModal()}
|
||||
{renderConfirmDeleteModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const editorContent = editorViewRenderers[editorRoute.route]();
|
||||
|
||||
// Show tags if tags tab is active
|
||||
if (showTags) {
|
||||
return (
|
||||
<div className="editor">
|
||||
<TagsView />
|
||||
{renderErrorModal()}
|
||||
{renderConfirmDeleteModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show chat if chat tab is active
|
||||
if (showChat && activeTabId) {
|
||||
return (
|
||||
<div className="editor">
|
||||
<ChatPanel key={activeTabId} conversationId={activeTabId} />
|
||||
{renderErrorModal()}
|
||||
{renderConfirmDeleteModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show import analysis if import tab is active
|
||||
if (showImport && activeTabId) {
|
||||
return (
|
||||
<div className="editor">
|
||||
<ImportAnalysisView key={activeTabId} definitionId={activeTabId} />
|
||||
{renderErrorModal()}
|
||||
{renderConfirmDeleteModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show metadata diff if metadata-diff tab is active
|
||||
if (showMetadataDiff) {
|
||||
return (
|
||||
<div className="editor">
|
||||
<MetadataDiffPanel />
|
||||
{renderErrorModal()}
|
||||
{renderConfirmDeleteModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show git diff view if git-diff tab is active
|
||||
if (showGitDiff && activeTabId) {
|
||||
const filePath = activeTabId.startsWith('git-diff:') ? activeTabId.slice('git-diff:'.length) : activeTabId;
|
||||
return (
|
||||
<div className="editor">
|
||||
<GitDiffView key={activeTabId} filePath={filePath} />
|
||||
{renderErrorModal()}
|
||||
{renderConfirmDeleteModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (showDocumentation) {
|
||||
return (
|
||||
<div className="editor">
|
||||
<DocumentationView />
|
||||
{renderErrorModal()}
|
||||
{renderConfirmDeleteModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (showSiteValidation) {
|
||||
return (
|
||||
<div className="editor">
|
||||
<SiteValidationView />
|
||||
{renderErrorModal()}
|
||||
{renderConfirmDeleteModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show post editor if a post tab is active
|
||||
if (showPost && activeTabId) {
|
||||
return (
|
||||
<div className="editor">
|
||||
<PostEditor key={activeTabId} postId={activeTabId} />
|
||||
{renderErrorModal()}
|
||||
{renderConfirmDeleteModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show media editor if a media tab is active
|
||||
if (showMedia && activeTabId) {
|
||||
return (
|
||||
<div className="editor">
|
||||
<MediaEditor key={activeTabId} mediaId={activeTabId} />
|
||||
{renderErrorModal()}
|
||||
{renderConfirmDeleteModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// No tab active - show dashboard
|
||||
return (
|
||||
<div className="editor">
|
||||
<Dashboard />
|
||||
{editorContent}
|
||||
{renderErrorModal()}
|
||||
{renderConfirmDeleteModal()}
|
||||
</div>
|
||||
|
||||
60
src/renderer/navigation/editorRouting.ts
Normal file
60
src/renderer/navigation/editorRouting.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { Tab, TabType } from '../store/appStore';
|
||||
import { parseGitDiffTabId } from './tabPolicy';
|
||||
|
||||
export type EditorRoute =
|
||||
| 'dashboard'
|
||||
| 'post'
|
||||
| 'media'
|
||||
| 'settings'
|
||||
| 'style'
|
||||
| 'tags'
|
||||
| 'chat'
|
||||
| 'import'
|
||||
| 'metadata-diff'
|
||||
| 'git-diff'
|
||||
| 'documentation'
|
||||
| 'site-validation';
|
||||
|
||||
export const EDITOR_TAB_ROUTE_REGISTRY: Record<TabType, Exclude<EditorRoute, 'dashboard'>> = {
|
||||
post: 'post',
|
||||
media: 'media',
|
||||
settings: 'settings',
|
||||
style: 'style',
|
||||
tags: 'tags',
|
||||
chat: 'chat',
|
||||
import: 'import',
|
||||
'metadata-diff': 'metadata-diff',
|
||||
'git-diff': 'git-diff',
|
||||
documentation: 'documentation',
|
||||
'site-validation': 'site-validation',
|
||||
};
|
||||
|
||||
export interface EditorRouteResolution {
|
||||
route: EditorRoute;
|
||||
tabId: string | null;
|
||||
gitDiffResource: string | null;
|
||||
}
|
||||
|
||||
export function resolveEditorRoute(activeTab: Tab | undefined): EditorRouteResolution {
|
||||
if (!activeTab) {
|
||||
return {
|
||||
route: 'dashboard',
|
||||
tabId: null,
|
||||
gitDiffResource: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (activeTab.type === 'git-diff') {
|
||||
return {
|
||||
route: 'git-diff',
|
||||
tabId: activeTab.id,
|
||||
gitDiffResource: parseGitDiffTabId(activeTab.id).resource,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
route: EDITOR_TAB_ROUTE_REGISTRY[activeTab.type],
|
||||
tabId: activeTab.id,
|
||||
gitDiffResource: null,
|
||||
};
|
||||
}
|
||||
63
tests/renderer/navigation/editorRouting.test.ts
Normal file
63
tests/renderer/navigation/editorRouting.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { Tab } from '../../../src/renderer/store/appStore';
|
||||
import {
|
||||
EDITOR_TAB_ROUTE_REGISTRY,
|
||||
resolveEditorRoute,
|
||||
} from '../../../src/renderer/navigation/editorRouting';
|
||||
|
||||
describe('editorRouting', () => {
|
||||
it('defines canonical tab-type to editor-route mapping', () => {
|
||||
expect(EDITOR_TAB_ROUTE_REGISTRY).toEqual({
|
||||
post: 'post',
|
||||
media: 'media',
|
||||
settings: 'settings',
|
||||
style: 'style',
|
||||
tags: 'tags',
|
||||
chat: 'chat',
|
||||
import: 'import',
|
||||
'metadata-diff': 'metadata-diff',
|
||||
'git-diff': 'git-diff',
|
||||
documentation: 'documentation',
|
||||
'site-validation': 'site-validation',
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves dashboard route when no active tab is present', () => {
|
||||
expect(resolveEditorRoute(undefined)).toEqual({
|
||||
route: 'dashboard',
|
||||
tabId: null,
|
||||
gitDiffResource: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves post and media routes with active tab id', () => {
|
||||
const postTab: Tab = { type: 'post', id: 'post-1', isTransient: true };
|
||||
const mediaTab: Tab = { type: 'media', id: 'media-1', isTransient: false };
|
||||
|
||||
expect(resolveEditorRoute(postTab)).toEqual({
|
||||
route: 'post',
|
||||
tabId: 'post-1',
|
||||
gitDiffResource: null,
|
||||
});
|
||||
|
||||
expect(resolveEditorRoute(mediaTab)).toEqual({
|
||||
route: 'media',
|
||||
tabId: 'media-1',
|
||||
gitDiffResource: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves git-diff route and extracts resource from tab id', () => {
|
||||
const tab: Tab = {
|
||||
type: 'git-diff',
|
||||
id: 'git-diff:commit:abc123',
|
||||
isTransient: true,
|
||||
};
|
||||
|
||||
expect(resolveEditorRoute(tab)).toEqual({
|
||||
route: 'git-diff',
|
||||
tabId: 'git-diff:commit:abc123',
|
||||
gitDiffResource: 'commit:abc123',
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user