mirror of
https://github.com/ethanolivertroy/obsidian-markitdown.git
synced 2026-07-22 05:41:54 +00:00
Merge branch 'worktree-agent-a910b7af'
# Conflicts: # main.ts # src/types/settings.ts
This commit is contained in:
commit
061ea6e058
6 changed files with 269 additions and 2 deletions
39
main.ts
39
main.ts
|
|
@ -6,6 +6,7 @@ import {
|
|||
DEFAULT_SETTINGS,
|
||||
ConversionOptions,
|
||||
ConversionResult,
|
||||
ConversionLogEntry,
|
||||
DependencyStatus,
|
||||
PluginArgEntry,
|
||||
TriedPath,
|
||||
|
|
@ -20,10 +21,12 @@ 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 { UrlConvertModal } from './src/modals/UrlConvertModal';
|
||||
import { HistoryModal } from './src/modals/HistoryModal';
|
||||
import { SetupModal } from './src/modals/SetupModal';
|
||||
import { PreviewModal } from './src/modals/PreviewModal';
|
||||
|
||||
|
|
@ -79,6 +82,12 @@ export default class MarkitdownPlugin extends Plugin {
|
|||
callback: () => this.openUrlModal(),
|
||||
});
|
||||
|
||||
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();
|
||||
|
|
@ -243,7 +252,20 @@ export default class MarkitdownPlugin extends Plugin {
|
|||
const options = this.buildConversionOptions(outputPath, inputPath);
|
||||
|
||||
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) {
|
||||
let content: string;
|
||||
|
|
@ -284,7 +306,22 @@ export default class MarkitdownPlugin extends Plugin {
|
|||
outputPath: string
|
||||
): Promise<ConversionResult> {
|
||||
const options = this.buildConversionOptions(outputPath, inputPath);
|
||||
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
105
src/modals/HistoryModal.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -18,6 +28,7 @@ export interface MarkitdownSettings {
|
|||
enableDragDrop: boolean;
|
||||
enableAutoFrontmatter: boolean;
|
||||
autoTags: string;
|
||||
conversionHistory: ConversionLogEntry[];
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: MarkitdownSettings = {
|
||||
|
|
@ -35,6 +46,7 @@ export const DEFAULT_SETTINGS: MarkitdownSettings = {
|
|||
enableDragDrop: true,
|
||||
enableAutoFrontmatter: false,
|
||||
autoTags: '',
|
||||
conversionHistory: [],
|
||||
};
|
||||
|
||||
export interface ConversionOptions {
|
||||
|
|
|
|||
37
src/utils/history.ts
Normal file
37
src/utils/history.ts
Normal 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;
|
||||
}
|
||||
76
styles.css
76
styles.css
|
|
@ -384,3 +384,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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@
|
|||
"lib": ["DOM", "ES2020"]
|
||||
},
|
||||
"include": ["main.ts", "src/**/*.ts"],
|
||||
"exclude": ["node_modules", "python"]
|
||||
"exclude": ["node_modules", "python", "src/**/__tests__/**", "src/__mocks__/**", "jest.config.js"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue