Add conversion history log (ENG-647)

This commit is contained in:
Ethan Troy 2026-03-17 20:48:19 -04:00
parent a93ed8341d
commit d4fcaffc38
5 changed files with 268 additions and 1 deletions

39
main.ts
View file

@ -5,6 +5,7 @@ import {
DEFAULT_SETTINGS,
ConversionOptions,
ConversionResult,
ConversionLogEntry,
DependencyStatus,
PluginArgEntry,
} from './src/types/settings';
@ -17,9 +18,11 @@ import {
toVaultRelative,
} from './src/utils/paths';
import { isConvertible } from './src/utils/fileTypes';
import { addHistoryEntry } from './src/utils/history';
import { SettingsTab } from './src/settings/SettingsTab';
import { FileConvertModal } from './src/modals/FileConvertModal';
import { FolderConvertModal } from './src/modals/FolderConvertModal';
import { HistoryModal } from './src/modals/HistoryModal';
import { SetupModal } from './src/modals/SetupModal';
export default class MarkitdownPlugin extends Plugin {
@ -61,6 +64,12 @@ export default class MarkitdownPlugin extends Plugin {
callback: () => this.openFolderModal(),
});
this.addCommand({
id: 'view-conversion-history',
name: 'View conversion history',
callback: () => new HistoryModal(this.app, this).open(),
});
// Context menu
if (this.settings.enableContextMenu) {
this.registerFileMenu();
@ -128,7 +137,20 @@ export default class MarkitdownPlugin extends Plugin {
const options = this.buildConversionOptions(outputPath);
new Notice('Converting file...');
const startTime = Date.now();
const result = await this.converter.convert(inputPath, outputPath, options);
const elapsed = Date.now() - startTime;
addHistoryEntry(this.settings, {
inputFile: inputPath,
outputFile: outputPath,
timestamp: new Date().toISOString(),
success: result.success,
error: result.error,
processingTimeMs: elapsed,
imagesExtracted: result.imagesExtracted,
});
await this.saveSettings();
if (result.success) {
const msg = result.imagesExtracted
@ -150,7 +172,22 @@ export default class MarkitdownPlugin extends Plugin {
outputPath: string
): Promise<ConversionResult> {
const options = this.buildConversionOptions(outputPath);
return this.converter.convert(inputPath, outputPath, options);
const startTime = Date.now();
const result = await this.converter.convert(inputPath, outputPath, options);
const elapsed = Date.now() - startTime;
addHistoryEntry(this.settings, {
inputFile: inputPath,
outputFile: outputPath,
timestamp: new Date().toISOString(),
success: result.success,
error: result.error,
processingTimeMs: elapsed,
imagesExtracted: result.imagesExtracted,
});
await this.saveSettings();
return result;
}
/** Build ConversionOptions from current settings. */

105
src/modals/HistoryModal.ts Normal file
View file

@ -0,0 +1,105 @@
import { App, Modal, Notice } from 'obsidian';
import type MarkitdownPlugin from '../../main';
import { ConversionLogEntry } from '../types/settings';
export class HistoryModal extends Modal {
private plugin: MarkitdownPlugin;
constructor(app: App, plugin: MarkitdownPlugin) {
super(app);
this.plugin = plugin;
}
onOpen() {
const { contentEl } = this;
contentEl.addClass('markitdown-modal');
contentEl.createEl('h2', { text: 'Conversion history' });
const history = this.plugin.settings.conversionHistory;
if (history.length === 0) {
contentEl.createEl('p', {
text: 'No conversions have been recorded yet.',
cls: 'markitdown-history-empty',
});
return;
}
const listContainer = contentEl.createDiv('markitdown-history-list');
for (const entry of history) {
this.renderEntry(listContainer, entry);
}
// Clear history button
const buttonContainer = contentEl.createDiv('markitdown-button-container');
const clearButton = buttonContainer.createEl('button', {
text: 'Clear history',
cls: 'markitdown-history-clear-btn',
});
clearButton.addEventListener('click', async () => {
this.plugin.settings.conversionHistory = [];
await this.plugin.saveSettings();
new Notice('Conversion history cleared');
this.close();
});
}
private renderEntry(container: HTMLElement, entry: ConversionLogEntry) {
const entryEl = container.createDiv('markitdown-history-entry');
// Header row: timestamp + badge
const headerEl = entryEl.createDiv('markitdown-history-entry-header');
const timestamp = new Date(entry.timestamp);
headerEl.createSpan({
text: timestamp.toLocaleString(),
cls: 'markitdown-history-timestamp',
});
const badgeCls = entry.success
? 'markitdown-history-badge-success'
: 'markitdown-history-badge-fail';
headerEl.createSpan({
text: entry.success ? 'Success' : 'Failed',
cls: `markitdown-history-badge ${badgeCls}`,
});
// File name (show only basename for privacy)
const inputName =
entry.inputFile.split('/').pop()?.split('\\').pop() ?? entry.inputFile;
entryEl.createDiv({
text: inputName,
cls: 'markitdown-history-filename',
});
// Details row: processing time, images extracted
const details: string[] = [];
if (entry.processingTimeMs !== undefined) {
details.push(`${entry.processingTimeMs}ms`);
}
if (entry.imagesExtracted !== undefined && entry.imagesExtracted > 0) {
details.push(
`${entry.imagesExtracted} image${entry.imagesExtracted !== 1 ? 's' : ''} extracted`
);
}
if (details.length > 0) {
entryEl.createDiv({
text: details.join(' \u00B7 '),
cls: 'markitdown-history-details',
});
}
// Error message for failed entries
if (!entry.success && entry.error) {
entryEl.createDiv({
text: entry.error,
cls: 'markitdown-history-error',
});
}
}
onClose() {
this.contentEl.empty();
}
}

View file

@ -1,3 +1,13 @@
export interface ConversionLogEntry {
inputFile: string;
outputFile: string;
timestamp: string; // ISO 8601
success: boolean;
error?: string;
processingTimeMs?: number;
imagesExtracted?: number;
}
export interface PluginArgEntry {
key: string;
value: string;
@ -13,6 +23,7 @@ export interface MarkitdownSettings {
imageSubfolderTemplate: string;
enableBatchProgress: boolean;
enableContextMenu: boolean;
conversionHistory: ConversionLogEntry[];
}
export const DEFAULT_SETTINGS: MarkitdownSettings = {
@ -25,6 +36,7 @@ export const DEFAULT_SETTINGS: MarkitdownSettings = {
imageSubfolderTemplate: '{filename}-images',
enableBatchProgress: true,
enableContextMenu: true,
conversionHistory: [],
};
export interface ConversionOptions {

37
src/utils/history.ts Normal file
View file

@ -0,0 +1,37 @@
import { ConversionLogEntry, MarkitdownSettings } from '../types/settings';
const MAX_HISTORY_ENTRIES = 100;
/**
* Prepend a history entry to settings.conversionHistory, capping at
* MAX_HISTORY_ENTRIES to avoid bloating data.json.
* Returns the updated array (also mutates settings in-place).
*/
export function addHistoryEntry(
settings: MarkitdownSettings,
entry: ConversionLogEntry
): ConversionLogEntry[] {
settings.conversionHistory = [
entry,
...settings.conversionHistory,
].slice(0, MAX_HISTORY_ENTRIES);
return settings.conversionHistory;
}
/**
* Format a ConversionLogEntry for display as a single-line summary.
*/
export function formatHistoryEntry(entry: ConversionLogEntry): string {
const date = new Date(entry.timestamp);
const ts = date.toLocaleString();
const status = entry.success ? 'OK' : 'FAIL';
const inputName = entry.inputFile.split('/').pop()?.split('\\').pop() ?? entry.inputFile;
let line = `[${ts}] ${status} - ${inputName}`;
if (entry.processingTimeMs !== undefined) {
line += ` (${entry.processingTimeMs}ms)`;
}
if (!entry.success && entry.error) {
line += ` - ${entry.error}`;
}
return line;
}

View file

@ -219,3 +219,79 @@ Styles for the Markitdown Obsidian plugin
color: var(--text-normal);
border-color: var(--interactive-accent);
}
/* Conversion history modal */
.markitdown-history-list {
max-height: 400px;
overflow-y: auto;
margin: 12px 0;
}
.markitdown-history-empty {
color: var(--text-muted);
font-style: italic;
}
.markitdown-history-entry {
padding: 10px 12px;
margin-bottom: 6px;
background-color: var(--background-secondary);
border-radius: 6px;
border-left: 3px solid var(--background-modifier-border);
}
.markitdown-history-entry-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
}
.markitdown-history-timestamp {
font-size: 0.8em;
color: var(--text-muted);
}
.markitdown-history-badge {
font-size: 0.75em;
font-weight: 600;
padding: 2px 8px;
border-radius: 10px;
}
.markitdown-history-badge-success {
background-color: var(--background-modifier-success);
color: var(--interactive-success);
}
.markitdown-history-badge-fail {
background-color: var(--background-modifier-error);
color: var(--text-error);
}
.markitdown-history-filename {
font-weight: 500;
font-size: 0.95em;
margin-bottom: 2px;
word-break: break-all;
}
.markitdown-history-details {
font-size: 0.8em;
color: var(--text-muted);
}
.markitdown-history-error {
font-size: 0.8em;
color: var(--text-error);
margin-top: 4px;
padding: 4px 8px;
background-color: var(--background-primary-alt);
border-radius: 4px;
word-break: break-all;
}
.markitdown-history-clear-btn {
background-color: var(--background-modifier-error) !important;
color: var(--text-error) !important;
}