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,
|
DEFAULT_SETTINGS,
|
||||||
ConversionOptions,
|
ConversionOptions,
|
||||||
ConversionResult,
|
ConversionResult,
|
||||||
|
ConversionLogEntry,
|
||||||
DependencyStatus,
|
DependencyStatus,
|
||||||
PluginArgEntry,
|
PluginArgEntry,
|
||||||
TriedPath,
|
TriedPath,
|
||||||
|
|
@ -20,10 +21,12 @@ import {
|
||||||
toVaultRelative,
|
toVaultRelative,
|
||||||
} from './src/utils/paths';
|
} from './src/utils/paths';
|
||||||
import { isConvertible } from './src/utils/fileTypes';
|
import { isConvertible } from './src/utils/fileTypes';
|
||||||
|
import { addHistoryEntry } from './src/utils/history';
|
||||||
import { SettingsTab } from './src/settings/SettingsTab';
|
import { SettingsTab } from './src/settings/SettingsTab';
|
||||||
import { FileConvertModal } from './src/modals/FileConvertModal';
|
import { FileConvertModal } from './src/modals/FileConvertModal';
|
||||||
import { FolderConvertModal } from './src/modals/FolderConvertModal';
|
import { FolderConvertModal } from './src/modals/FolderConvertModal';
|
||||||
import { UrlConvertModal } from './src/modals/UrlConvertModal';
|
import { UrlConvertModal } from './src/modals/UrlConvertModal';
|
||||||
|
import { HistoryModal } from './src/modals/HistoryModal';
|
||||||
import { SetupModal } from './src/modals/SetupModal';
|
import { SetupModal } from './src/modals/SetupModal';
|
||||||
import { PreviewModal } from './src/modals/PreviewModal';
|
import { PreviewModal } from './src/modals/PreviewModal';
|
||||||
|
|
||||||
|
|
@ -79,6 +82,12 @@ export default class MarkitdownPlugin extends Plugin {
|
||||||
callback: () => this.openUrlModal(),
|
callback: () => this.openUrlModal(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: 'view-conversion-history',
|
||||||
|
name: 'View conversion history',
|
||||||
|
callback: () => new HistoryModal(this.app, this).open(),
|
||||||
|
});
|
||||||
|
|
||||||
// Context menu
|
// Context menu
|
||||||
if (this.settings.enableContextMenu) {
|
if (this.settings.enableContextMenu) {
|
||||||
this.registerFileMenu();
|
this.registerFileMenu();
|
||||||
|
|
@ -243,7 +252,20 @@ export default class MarkitdownPlugin extends Plugin {
|
||||||
const options = this.buildConversionOptions(outputPath, inputPath);
|
const options = this.buildConversionOptions(outputPath, inputPath);
|
||||||
|
|
||||||
new Notice('Converting file...');
|
new Notice('Converting file...');
|
||||||
|
const startTime = Date.now();
|
||||||
const result = await this.converter.convert(inputPath, outputPath, options);
|
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) {
|
if (result.success) {
|
||||||
let content: string;
|
let content: string;
|
||||||
|
|
@ -284,7 +306,22 @@ export default class MarkitdownPlugin extends Plugin {
|
||||||
outputPath: string
|
outputPath: string
|
||||||
): Promise<ConversionResult> {
|
): Promise<ConversionResult> {
|
||||||
const options = this.buildConversionOptions(outputPath, inputPath);
|
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. */
|
/** 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 {
|
export interface PluginArgEntry {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
|
@ -18,6 +28,7 @@ export interface MarkitdownSettings {
|
||||||
enableDragDrop: boolean;
|
enableDragDrop: boolean;
|
||||||
enableAutoFrontmatter: boolean;
|
enableAutoFrontmatter: boolean;
|
||||||
autoTags: string;
|
autoTags: string;
|
||||||
|
conversionHistory: ConversionLogEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: MarkitdownSettings = {
|
export const DEFAULT_SETTINGS: MarkitdownSettings = {
|
||||||
|
|
@ -35,6 +46,7 @@ export const DEFAULT_SETTINGS: MarkitdownSettings = {
|
||||||
enableDragDrop: true,
|
enableDragDrop: true,
|
||||||
enableAutoFrontmatter: false,
|
enableAutoFrontmatter: false,
|
||||||
autoTags: '',
|
autoTags: '',
|
||||||
|
conversionHistory: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ConversionOptions {
|
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);
|
color: var(--text-normal);
|
||||||
border-color: var(--interactive-accent);
|
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"]
|
"lib": ["DOM", "ES2020"]
|
||||||
},
|
},
|
||||||
"include": ["main.ts", "src/**/*.ts"],
|
"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