From d4fcaffc387fab87bdf0bc232406b669e80dabb8 Mon Sep 17 00:00:00 2001 From: Ethan Troy Date: Tue, 17 Mar 2026 20:48:19 -0400 Subject: [PATCH] Add conversion history log (ENG-647) --- main.ts | 39 +++++++++++++- src/modals/HistoryModal.ts | 105 +++++++++++++++++++++++++++++++++++++ src/types/settings.ts | 12 +++++ src/utils/history.ts | 37 +++++++++++++ styles.css | 76 +++++++++++++++++++++++++++ 5 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 src/modals/HistoryModal.ts create mode 100644 src/utils/history.ts diff --git a/main.ts b/main.ts index a700c40..7099224 100644 --- a/main.ts +++ b/main.ts @@ -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 { 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. */ diff --git a/src/modals/HistoryModal.ts b/src/modals/HistoryModal.ts new file mode 100644 index 0000000..8dd97c7 --- /dev/null +++ b/src/modals/HistoryModal.ts @@ -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(); + } +} diff --git a/src/types/settings.ts b/src/types/settings.ts index a2e1e8d..39c35ac 100644 --- a/src/types/settings.ts +++ b/src/types/settings.ts @@ -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 { diff --git a/src/utils/history.ts b/src/utils/history.ts new file mode 100644 index 0000000..8109dd2 --- /dev/null +++ b/src/utils/history.ts @@ -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; +} diff --git a/styles.css b/styles.css index 2c297b6..551e786 100644 --- a/styles.css +++ b/styles.css @@ -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; +}