feat: additional metadata
This commit is contained in:
@@ -85,6 +85,9 @@ export class ImportAnalysisEngine {
|
||||
private currentProjectId: string = '';
|
||||
private turndown: TurndownService;
|
||||
|
||||
// Progress callback for reporting analysis steps
|
||||
onProgress?: (step: string, detail?: string) => void;
|
||||
|
||||
constructor() {
|
||||
this.turndown = new TurndownService({
|
||||
headingStyle: 'atx',
|
||||
@@ -100,6 +103,8 @@ export class ImportAnalysisEngine {
|
||||
async analyzeWxr(wxrData: WxrData, sourceFile: string, uploadsFolder?: string): Promise<ImportAnalysisReport> {
|
||||
const db = getDatabase().getLocal();
|
||||
|
||||
this.onProgress?.('Loading existing posts...');
|
||||
|
||||
// Fetch existing posts for this project
|
||||
const existingPosts = await db
|
||||
.select({
|
||||
@@ -112,6 +117,8 @@ export class ImportAnalysisEngine {
|
||||
.where(eq(posts.projectId, this.currentProjectId))
|
||||
.all();
|
||||
|
||||
this.onProgress?.('Loading existing media...', `${existingPosts.length} posts in project`);
|
||||
|
||||
// Fetch existing media for this project
|
||||
const existingMedia = await db
|
||||
.select({
|
||||
@@ -123,6 +130,8 @@ export class ImportAnalysisEngine {
|
||||
.where(eq(media.projectId, this.currentProjectId))
|
||||
.all();
|
||||
|
||||
this.onProgress?.('Loading existing tags...', `${existingMedia.length} media in project`);
|
||||
|
||||
// Fetch existing tags for this project
|
||||
const existingTags = await db
|
||||
.select({
|
||||
@@ -155,13 +164,22 @@ export class ImportAnalysisEngine {
|
||||
// Build tag set
|
||||
const existingTagNames = new Set(existingTags.map(t => t.name.toLowerCase()));
|
||||
|
||||
this.onProgress?.('Analyzing posts...', `${wxrData.posts.length} posts to analyze`);
|
||||
|
||||
// Analyze posts
|
||||
const analyzedPosts = this.analyzePostItems(wxrData.posts, slugToPost, checksumToPost);
|
||||
|
||||
this.onProgress?.('Analyzing pages...', `${wxrData.pages.length} pages to analyze`);
|
||||
|
||||
const analyzedPages = this.analyzePostItems(wxrData.pages, slugToPost, checksumToPost);
|
||||
|
||||
this.onProgress?.('Analyzing media files...', `${wxrData.media.length} media files to analyze`);
|
||||
|
||||
// Analyze media
|
||||
const analyzedMedia = await this.analyzeMediaItems(wxrData.media, nameToMedia, checksumToMedia, uploadsFolder);
|
||||
|
||||
this.onProgress?.('Processing categories and tags...');
|
||||
|
||||
// Analyze categories
|
||||
const analyzedCategories: AnalyzedCategory[] = wxrData.categories.map(cat => ({
|
||||
name: cat.name,
|
||||
|
||||
@@ -747,7 +747,14 @@ export function registerIpcHandlers(): void {
|
||||
|
||||
// ============ Import Analysis Handlers ============
|
||||
|
||||
// Helper to emit progress events
|
||||
const emitImportProgress = (step: string, detail?: string) => {
|
||||
ipcMain.emit('forward-to-renderer', 'import:progress', { step, detail });
|
||||
};
|
||||
|
||||
safeHandle('import:selectAndAnalyze', async (_, uploadsFolder?: string) => {
|
||||
emitImportProgress('Selecting file...');
|
||||
|
||||
const result = await dialog.showOpenDialog({
|
||||
title: 'Select WordPress Export File (WXR)',
|
||||
filters: [
|
||||
@@ -762,12 +769,18 @@ export function registerIpcHandlers(): void {
|
||||
}
|
||||
|
||||
const filePath = result.filePaths[0];
|
||||
const fileName = filePath.split(/[/\\]/).pop() || filePath;
|
||||
|
||||
emitImportProgress('Parsing WXR file...', fileName);
|
||||
|
||||
const { WxrParser } = await import('../engine/WxrParser');
|
||||
const { ImportAnalysisEngine } = await import('../engine/ImportAnalysisEngine');
|
||||
|
||||
const parser = new WxrParser();
|
||||
const wxrData = await parser.parseFile(filePath);
|
||||
|
||||
emitImportProgress('Loading project data...', `Found ${wxrData.posts.length} posts, ${wxrData.media.length} media`);
|
||||
|
||||
const analysisEngine = new ImportAnalysisEngine();
|
||||
const projectEngine = getProjectEngine();
|
||||
const activeProject = await projectEngine.getActiveProject();
|
||||
@@ -775,16 +788,33 @@ export function registerIpcHandlers(): void {
|
||||
analysisEngine.setProjectContext(activeProject.id);
|
||||
}
|
||||
|
||||
return analysisEngine.analyzeWxr(wxrData, filePath, uploadsFolder || undefined);
|
||||
emitImportProgress('Analyzing posts...', `${wxrData.posts.length} posts`);
|
||||
|
||||
// Add progress callback to engine
|
||||
analysisEngine.onProgress = (step: string, detail?: string) => {
|
||||
emitImportProgress(step, detail);
|
||||
};
|
||||
|
||||
const report = await analysisEngine.analyzeWxr(wxrData, filePath, uploadsFolder || undefined);
|
||||
|
||||
emitImportProgress('Analysis complete');
|
||||
|
||||
return report;
|
||||
});
|
||||
|
||||
safeHandle('import:analyzeFile', async (_, filePath: string, uploadsFolder?: string) => {
|
||||
const fileName = filePath.split(/[/\\]/).pop() || filePath;
|
||||
|
||||
emitImportProgress('Parsing WXR file...', fileName);
|
||||
|
||||
const { WxrParser } = await import('../engine/WxrParser');
|
||||
const { ImportAnalysisEngine } = await import('../engine/ImportAnalysisEngine');
|
||||
|
||||
const parser = new WxrParser();
|
||||
const wxrData = await parser.parseFile(filePath);
|
||||
|
||||
emitImportProgress('Loading project data...', `Found ${wxrData.posts.length} posts, ${wxrData.media.length} media`);
|
||||
|
||||
const analysisEngine = new ImportAnalysisEngine();
|
||||
const projectEngine = getProjectEngine();
|
||||
const activeProject = await projectEngine.getActiveProject();
|
||||
@@ -792,7 +822,18 @@ export function registerIpcHandlers(): void {
|
||||
analysisEngine.setProjectContext(activeProject.id);
|
||||
}
|
||||
|
||||
return analysisEngine.analyzeWxr(wxrData, filePath, uploadsFolder || undefined);
|
||||
emitImportProgress('Analyzing posts...');
|
||||
|
||||
// Add progress callback to engine
|
||||
analysisEngine.onProgress = (step: string, detail?: string) => {
|
||||
emitImportProgress(step, detail);
|
||||
};
|
||||
|
||||
const report = await analysisEngine.analyzeWxr(wxrData, filePath, uploadsFolder || undefined);
|
||||
|
||||
emitImportProgress('Analysis complete');
|
||||
|
||||
return report;
|
||||
});
|
||||
|
||||
safeHandle('import:selectUploadsFolder', async () => {
|
||||
|
||||
@@ -155,6 +155,11 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
selectAndAnalyze: (uploadsFolder?: string) => ipcRenderer.invoke('import:selectAndAnalyze', uploadsFolder),
|
||||
analyzeFile: (filePath: string, uploadsFolder?: string) => ipcRenderer.invoke('import:analyzeFile', filePath, uploadsFolder),
|
||||
selectUploadsFolder: () => ipcRenderer.invoke('import:selectUploadsFolder'),
|
||||
onProgress: (callback: (data: { step: string; detail?: string }) => void) => {
|
||||
const subscription = (_event: Electron.IpcRendererEvent, data: { step: string; detail?: string }) => callback(data);
|
||||
ipcRenderer.on('import:progress', subscription);
|
||||
return () => ipcRenderer.removeListener('import:progress', subscription);
|
||||
},
|
||||
},
|
||||
|
||||
// Import Definition CRUD
|
||||
@@ -340,6 +345,7 @@ export interface ElectronAPI {
|
||||
selectAndAnalyze: (uploadsFolder?: string) => Promise<unknown>;
|
||||
analyzeFile: (filePath: string, uploadsFolder?: string) => Promise<unknown>;
|
||||
selectUploadsFolder: () => Promise<string | null>;
|
||||
onProgress: (callback: (data: { step: string; detail?: string }) => void) => () => void;
|
||||
};
|
||||
importDefinitions: {
|
||||
create: (name?: string) => Promise<unknown>;
|
||||
|
||||
@@ -145,6 +145,23 @@
|
||||
border-top-color: var(--vscode-button-background);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.import-progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.import-progress-step {
|
||||
font-size: 13px;
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
.import-progress-detail {
|
||||
font-size: 11px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
@@ -594,6 +611,42 @@
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* Post and Media rows with tooltip - enhanced hover state */
|
||||
.import-detail-table tr.post-row-with-tooltip,
|
||||
.import-detail-table tr.media-row-with-tooltip {
|
||||
cursor: help;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.import-detail-table tr.post-row-with-tooltip:hover,
|
||||
.import-detail-table tr.media-row-with-tooltip:hover {
|
||||
background-color: var(--vscode-list-hoverBackground, rgba(255, 255, 255, 0.04));
|
||||
}
|
||||
|
||||
/* Categories column styling */
|
||||
.import-detail-table .categories-cell {
|
||||
font-size: 11px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* MIME type column styling */
|
||||
.import-detail-table .mime-type-cell {
|
||||
font-size: 11px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-family: var(--vscode-editor-font-family, monospace);
|
||||
}
|
||||
|
||||
/* Post type column styling */
|
||||
.import-detail-table .post-type-cell {
|
||||
font-size: 11px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-family: var(--vscode-editor-font-family, monospace);
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.import-empty-state {
|
||||
display: flex;
|
||||
|
||||
@@ -33,7 +33,18 @@ interface MediaSection {
|
||||
}
|
||||
|
||||
interface AnalyzedPostItem {
|
||||
wxrPost: { wpId: number; title: string; slug: string; status: string };
|
||||
wxrPost: {
|
||||
wpId: number;
|
||||
title: string;
|
||||
slug: string;
|
||||
status: string;
|
||||
excerpt: string;
|
||||
pubDate: string | null;
|
||||
creator: string;
|
||||
postType: string;
|
||||
categories: string[];
|
||||
tags: string[];
|
||||
};
|
||||
status: string;
|
||||
contentHash: string;
|
||||
markdownPreview: string;
|
||||
@@ -41,7 +52,17 @@ interface AnalyzedPostItem {
|
||||
}
|
||||
|
||||
interface AnalyzedMediaItem {
|
||||
wxrMedia: { wpId: number; title: string; filename: string; url: string; relativePath: string };
|
||||
wxrMedia: {
|
||||
wpId: number;
|
||||
title: string;
|
||||
filename: string;
|
||||
url: string;
|
||||
relativePath: string;
|
||||
pubDate: string | null;
|
||||
parentId: number;
|
||||
mimeType: string;
|
||||
description: string;
|
||||
};
|
||||
status: string;
|
||||
fileHash: string | null;
|
||||
existingMedia?: { id: string; originalName: string };
|
||||
@@ -66,8 +87,19 @@ export const ImportAnalysisView: React.FC<ImportAnalysisViewProps> = ({ definiti
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isLoadingDefinition, setIsLoadingDefinition] = useState(true);
|
||||
const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>({});
|
||||
const [progressStep, setProgressStep] = useState<string>('');
|
||||
const [progressDetail, setProgressDetail] = useState<string>('');
|
||||
const nameInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// Subscribe to progress events
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.electronAPI?.import.onProgress(({ step, detail }) => {
|
||||
setProgressStep(step);
|
||||
setProgressDetail(detail || '');
|
||||
});
|
||||
return () => unsubscribe?.();
|
||||
}, []);
|
||||
|
||||
// Save the current report to the definition
|
||||
const persistReport = useCallback(async (updatedReport: AnalysisReport) => {
|
||||
await window.electronAPI?.importDefinitions.update(definitionId, {
|
||||
@@ -167,6 +199,8 @@ export const ImportAnalysisView: React.FC<ImportAnalysisViewProps> = ({ definiti
|
||||
const handleSelectAndAnalyze = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
setReport(null);
|
||||
setProgressStep('');
|
||||
setProgressDetail('');
|
||||
try {
|
||||
const result = await window.electronAPI?.import.selectAndAnalyze(uploadsFolder || undefined) as AnalysisReport | null;
|
||||
if (result) {
|
||||
@@ -181,6 +215,8 @@ export const ImportAnalysisView: React.FC<ImportAnalysisViewProps> = ({ definiti
|
||||
console.error('Import analysis failed:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setProgressStep('');
|
||||
setProgressDetail('');
|
||||
}
|
||||
}, [definitionId, uploadsFolder]);
|
||||
|
||||
@@ -240,7 +276,10 @@ export const ImportAnalysisView: React.FC<ImportAnalysisViewProps> = ({ definiti
|
||||
{isLoading && (
|
||||
<div className="import-loading">
|
||||
<div className="import-spinner" />
|
||||
Analyzing WXR file...
|
||||
<div className="import-progress">
|
||||
<div className="import-progress-step">{progressStep || 'Analyzing WXR file...'}</div>
|
||||
{progressDetail && <div className="import-progress-detail">{progressDetail}</div>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -276,12 +315,32 @@ export const ImportAnalysisView: React.FC<ImportAnalysisViewProps> = ({ definiti
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Posts section - only items with postType 'post' */}
|
||||
{(() => {
|
||||
const postsOnly = report.posts.items.filter(i => i.wxrPost.postType === 'post');
|
||||
return postsOnly.length > 0 && (
|
||||
<PostDetailSection
|
||||
title={`Posts (${report.posts.total})`}
|
||||
items={report.posts.items}
|
||||
title={`Posts (${postsOnly.length})`}
|
||||
items={postsOnly}
|
||||
expanded={expandedSections['posts'] ?? false}
|
||||
onToggle={() => toggleSection('posts')}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Other post types section */}
|
||||
{(() => {
|
||||
const otherPosts = report.posts.items.filter(i => i.wxrPost.postType !== 'post');
|
||||
return otherPosts.length > 0 && (
|
||||
<PostDetailSection
|
||||
title={`Other (${otherPosts.length})`}
|
||||
items={otherPosts}
|
||||
expanded={expandedSections['other'] ?? false}
|
||||
onToggle={() => toggleSection('other')}
|
||||
showType
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
|
||||
{report.pages.total > 0 && (
|
||||
<PostDetailSection
|
||||
@@ -336,19 +395,56 @@ const SiteInfoCard: React.FC<{ site: AnalysisReport['site']; sourceFile: string
|
||||
</div>
|
||||
);
|
||||
|
||||
const StatCards: React.FC<{ report: AnalysisReport }> = ({ report }) => (
|
||||
const StatCards: React.FC<{ report: AnalysisReport }> = ({ report }) => {
|
||||
// Split posts by type
|
||||
const postsOnly = report.posts.items.filter(i => i.wxrPost.postType === 'post');
|
||||
const otherPosts = report.posts.items.filter(i => i.wxrPost.postType !== 'post');
|
||||
|
||||
const postsStats = {
|
||||
total: postsOnly.length,
|
||||
new: postsOnly.filter(i => i.status === 'new').length,
|
||||
updates: postsOnly.filter(i => i.status === 'update').length,
|
||||
conflicts: postsOnly.filter(i => i.status === 'conflict').length,
|
||||
contentDuplicates: postsOnly.filter(i => i.status === 'content-duplicate').length,
|
||||
};
|
||||
|
||||
const otherStats = {
|
||||
total: otherPosts.length,
|
||||
new: otherPosts.filter(i => i.status === 'new').length,
|
||||
updates: otherPosts.filter(i => i.status === 'update').length,
|
||||
conflicts: otherPosts.filter(i => i.status === 'conflict').length,
|
||||
contentDuplicates: otherPosts.filter(i => i.status === 'content-duplicate').length,
|
||||
};
|
||||
|
||||
// Get unique other post types for display
|
||||
const otherTypes = [...new Set(otherPosts.map(i => i.wxrPost.postType))].join(', ');
|
||||
|
||||
return (
|
||||
<div className="import-stat-cards">
|
||||
<div className="import-stat-card">
|
||||
<h3>Posts</h3>
|
||||
<div className="import-stat-number">{report.posts.total}</div>
|
||||
<div className="import-stat-number">{postsStats.total}</div>
|
||||
<div className="import-stat-breakdown">
|
||||
{report.posts.new > 0 && <span className="import-stat-tag stat-new">{report.posts.new} new</span>}
|
||||
{report.posts.updates > 0 && <span className="import-stat-tag stat-update">{report.posts.updates} update</span>}
|
||||
{report.posts.conflicts > 0 && <span className="import-stat-tag stat-conflict">{report.posts.conflicts} conflict</span>}
|
||||
{report.posts.contentDuplicates > 0 && <span className="import-stat-tag stat-duplicate">{report.posts.contentDuplicates} duplicate</span>}
|
||||
{postsStats.new > 0 && <span className="import-stat-tag stat-new">{postsStats.new} new</span>}
|
||||
{postsStats.updates > 0 && <span className="import-stat-tag stat-update">{postsStats.updates} update</span>}
|
||||
{postsStats.conflicts > 0 && <span className="import-stat-tag stat-conflict">{postsStats.conflicts} conflict</span>}
|
||||
{postsStats.contentDuplicates > 0 && <span className="import-stat-tag stat-duplicate">{postsStats.contentDuplicates} duplicate</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{otherStats.total > 0 && (
|
||||
<div className="import-stat-card">
|
||||
<h3 title={otherTypes}>Other</h3>
|
||||
<div className="import-stat-number">{otherStats.total}</div>
|
||||
<div className="import-stat-breakdown">
|
||||
{otherStats.new > 0 && <span className="import-stat-tag stat-new">{otherStats.new} new</span>}
|
||||
{otherStats.updates > 0 && <span className="import-stat-tag stat-update">{otherStats.updates} update</span>}
|
||||
{otherStats.conflicts > 0 && <span className="import-stat-tag stat-conflict">{otherStats.conflicts} conflict</span>}
|
||||
{otherStats.contentDuplicates > 0 && <span className="import-stat-tag stat-duplicate">{otherStats.contentDuplicates} duplicate</span>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="import-stat-card">
|
||||
<h3>Pages</h3>
|
||||
<div className="import-stat-number">{report.pages.total}</div>
|
||||
@@ -405,6 +501,49 @@ const StatCards: React.FC<{ report: AnalysisReport }> = ({ report }) => (
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Helper function to format post metadata for tooltip
|
||||
function formatPostTooltip(wxrPost: AnalyzedPostItem['wxrPost']): string {
|
||||
const lines: string[] = [];
|
||||
lines.push(`WordPress ID: ${wxrPost.wpId}`);
|
||||
lines.push(`Type: ${wxrPost.postType}`);
|
||||
lines.push(`Author: ${wxrPost.creator || 'Unknown'}`);
|
||||
if (wxrPost.pubDate) {
|
||||
lines.push(`Published: ${new Date(wxrPost.pubDate).toLocaleDateString()}`);
|
||||
}
|
||||
if (wxrPost.excerpt) {
|
||||
const shortExcerpt = wxrPost.excerpt.length > 100
|
||||
? wxrPost.excerpt.substring(0, 100) + '...'
|
||||
: wxrPost.excerpt;
|
||||
lines.push(`Excerpt: ${shortExcerpt}`);
|
||||
}
|
||||
if (wxrPost.tags.length > 0) {
|
||||
lines.push(`Tags: ${wxrPost.tags.join(', ')}`);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// Helper function to format media metadata for tooltip
|
||||
function formatMediaTooltip(wxrMedia: AnalyzedMediaItem['wxrMedia']): string {
|
||||
const lines: string[] = [];
|
||||
lines.push(`WordPress ID: ${wxrMedia.wpId}`);
|
||||
lines.push(`MIME Type: ${wxrMedia.mimeType || 'Unknown'}`);
|
||||
if (wxrMedia.pubDate) {
|
||||
lines.push(`Uploaded: ${new Date(wxrMedia.pubDate).toLocaleDateString()}`);
|
||||
}
|
||||
if (wxrMedia.parentId) {
|
||||
lines.push(`Parent Post ID: ${wxrMedia.parentId}`);
|
||||
}
|
||||
lines.push(`URL: ${wxrMedia.url}`);
|
||||
if (wxrMedia.description) {
|
||||
const shortDesc = wxrMedia.description.length > 100
|
||||
? wxrMedia.description.substring(0, 100) + '...'
|
||||
: wxrMedia.description;
|
||||
lines.push(`Description: ${shortDesc}`);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
const ConflictsSection: React.FC<{
|
||||
title: string;
|
||||
@@ -423,14 +562,20 @@ const ConflictsSection: React.FC<{
|
||||
<tr>
|
||||
<th>Slug</th>
|
||||
<th>WXR Title</th>
|
||||
<th>Categories</th>
|
||||
<th>Existing Title</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, idx) => (
|
||||
<tr key={idx}>
|
||||
<tr key={idx} className="post-row-with-tooltip" title={formatPostTooltip(item.wxrPost)}>
|
||||
<td className="slug-cell">{item.wxrPost.slug}</td>
|
||||
<td>{item.wxrPost.title}</td>
|
||||
<td className="categories-cell">
|
||||
{item.wxrPost.categories.length > 0
|
||||
? item.wxrPost.categories.join(', ')
|
||||
: '--'}
|
||||
</td>
|
||||
<td className="existing-match">{item.existingPost?.title || '--'}</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -445,7 +590,8 @@ const PostDetailSection: React.FC<{
|
||||
items: AnalyzedPostItem[];
|
||||
expanded: boolean;
|
||||
onToggle: () => void;
|
||||
}> = ({ title, items, expanded, onToggle }) => (
|
||||
showType?: boolean;
|
||||
}> = ({ title, items, expanded, onToggle, showType }) => (
|
||||
<div className="import-detail-section">
|
||||
<h3 onClick={onToggle}>
|
||||
<span className={`toggle-icon ${expanded ? 'open' : ''}`}>▶</span>
|
||||
@@ -456,18 +602,26 @@ const PostDetailSection: React.FC<{
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
{showType && <th>Type</th>}
|
||||
<th>Title</th>
|
||||
<th>Slug</th>
|
||||
<th>Categories</th>
|
||||
<th>WP Status</th>
|
||||
<th>Existing Match</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, idx) => (
|
||||
<tr key={idx}>
|
||||
<tr key={idx} className="post-row-with-tooltip" title={formatPostTooltip(item.wxrPost)}>
|
||||
<td><span className={`status-badge ${item.status}`}>{item.status}</span></td>
|
||||
{showType && <td className="post-type-cell">{item.wxrPost.postType}</td>}
|
||||
<td>{item.wxrPost.title}</td>
|
||||
<td className="slug-cell">{item.wxrPost.slug}</td>
|
||||
<td className="categories-cell">
|
||||
{item.wxrPost.categories.length > 0
|
||||
? item.wxrPost.categories.join(', ')
|
||||
: '--'}
|
||||
</td>
|
||||
<td>{item.wxrPost.status}</td>
|
||||
<td className="existing-match">{item.existingPost?.title || '--'}</td>
|
||||
</tr>
|
||||
@@ -495,15 +649,17 @@ const MediaDetailSection: React.FC<{
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Filename</th>
|
||||
<th>Type</th>
|
||||
<th>Path</th>
|
||||
<th>Existing Match</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, idx) => (
|
||||
<tr key={idx}>
|
||||
<tr key={idx} className="media-row-with-tooltip" title={formatMediaTooltip(item.wxrMedia)}>
|
||||
<td><span className={`status-badge ${item.status}`}>{item.status}</span></td>
|
||||
<td>{item.wxrMedia.filename}</td>
|
||||
<td className="mime-type-cell">{item.wxrMedia.mimeType || '--'}</td>
|
||||
<td className="slug-cell">{item.wxrMedia.relativePath}</td>
|
||||
<td className="existing-match">{item.existingMedia?.originalName || '--'}</td>
|
||||
</tr>
|
||||
|
||||
1
src/renderer/types/electron.d.ts
vendored
1
src/renderer/types/electron.d.ts
vendored
@@ -396,6 +396,7 @@ export interface ElectronAPI {
|
||||
selectAndAnalyze: (uploadsFolder?: string) => Promise<unknown>;
|
||||
analyzeFile: (filePath: string, uploadsFolder?: string) => Promise<unknown>;
|
||||
selectUploadsFolder: () => Promise<string | null>;
|
||||
onProgress: (callback: (data: { step: string; detail?: string }) => void) => () => void;
|
||||
};
|
||||
importDefinitions: {
|
||||
create: (name?: string) => Promise<ImportDefinitionData>;
|
||||
|
||||
Reference in New Issue
Block a user