Merge branch 'worktree-agent-af6650b1'

# Conflicts:
#	src/modals/FileConvertModal.ts
#	styles.css
This commit is contained in:
Ethan Troy 2026-03-17 20:44:51 -04:00
commit 733e4dca8f
4 changed files with 198 additions and 10 deletions

30
main.ts
View file

@ -25,6 +25,7 @@ import { FileConvertModal } from './src/modals/FileConvertModal';
import { FolderConvertModal } from './src/modals/FolderConvertModal';
import { UrlConvertModal } from './src/modals/UrlConvertModal';
import { SetupModal } from './src/modals/SetupModal';
import { PreviewModal } from './src/modals/PreviewModal';
export default class MarkitdownPlugin extends Plugin {
settings: MarkitdownSettings = DEFAULT_SETTINGS;
@ -245,11 +246,30 @@ export default class MarkitdownPlugin extends Plugin {
const result = await this.converter.convert(inputPath, outputPath, options);
if (result.success) {
const msg = result.imagesExtracted
? `Converted successfully (${result.imagesExtracted} images extracted)`
: 'Converted successfully';
new Notice(msg);
await this.openConvertedFile(outputPath, vaultPath);
let content: string;
try {
content = await fs.promises.readFile(outputPath, 'utf-8');
} catch {
new Notice('Conversion succeeded but could not read output file');
return;
}
new PreviewModal(this.app, {
content,
outputPath,
processingTime: result.processingTime ?? 0,
onSave: async () => {
const msg = result.imagesExtracted
? `Converted successfully (${result.imagesExtracted} images extracted)`
: 'Converted successfully';
new Notice(msg);
await this.openConvertedFile(outputPath, vaultPath);
},
onCancel: async () => {
await fs.promises.unlink(outputPath).catch(() => {});
new Notice('Conversion discarded');
},
}).open();
} else {
new Notice(`Conversion failed: ${result.error}`);
}

View file

@ -4,6 +4,7 @@ import * as fs from 'fs';
import type MarkitdownPlugin from '../../main';
import { FILE_INPUT_ACCEPT } from '../utils/fileTypes';
import { getVaultBasePath, resolveOutputFolder, resolveFilenameTemplate, toVaultRelative } from '../utils/paths';
import { PreviewModal } from './PreviewModal';
export class FileConvertModal extends Modal {
private plugin: MarkitdownPlugin;
@ -67,12 +68,36 @@ export class FileConvertModal extends Modal {
const result = await this.plugin.convertExternalFile(tempFilePath, outputPath);
if (result.success) {
const msg = result.imagesExtracted
? `Converted successfully (${result.imagesExtracted} images extracted)`
: 'Converted successfully';
new Notice(msg);
// Read the converted output for preview
let content: string;
try {
content = await fs.promises.readFile(outputPath, 'utf-8');
} catch {
new Notice('Conversion succeeded but could not read output file');
convertButton.disabled = false;
convertButton.setText('Convert');
return;
}
this.close();
await this.plugin.openConvertedFile(outputPath, vaultPath);
new PreviewModal(this.app, {
content,
outputPath,
processingTime: result.processingTime ?? 0,
onSave: async () => {
const msg = result.imagesExtracted
? `Converted successfully (${result.imagesExtracted} images extracted)`
: 'Converted successfully';
new Notice(msg);
await this.plugin.openConvertedFile(outputPath, vaultPath);
},
onCancel: async () => {
// Discard the converted output file
await fs.promises.unlink(outputPath).catch(() => {});
new Notice('Conversion discarded');
},
}).open();
} else {
new Notice(`Conversion failed: ${result.error}`);
convertButton.disabled = false;

View file

@ -0,0 +1,97 @@
import { App, Modal } from 'obsidian';
const MAX_PREVIEW_LINES = 500;
export class PreviewModal extends Modal {
private content: string;
private outputPath: string;
private processingTime: number;
private onSave: () => Promise<void> | void;
private onCancel: () => Promise<void> | void;
constructor(
app: App,
opts: {
content: string;
outputPath: string;
processingTime: number;
onSave: () => Promise<void> | void;
onCancel: () => Promise<void> | void;
}
) {
super(app);
this.content = opts.content;
this.outputPath = opts.outputPath;
this.processingTime = opts.processingTime;
this.onSave = opts.onSave;
this.onCancel = opts.onCancel;
}
onOpen() {
const { contentEl } = this;
contentEl.addClass('markitdown-modal');
contentEl.addClass('markitdown-preview-modal');
contentEl.createEl('h2', { text: 'Conversion preview' });
// Metadata section
const metaEl = contentEl.createDiv('markitdown-preview-meta');
metaEl.createEl('div', {
text: `Output: ${this.outputPath}`,
cls: 'markitdown-preview-meta-item',
});
metaEl.createEl('div', {
text: `Processing time: ${(this.processingTime / 1000).toFixed(1)}s`,
cls: 'markitdown-preview-meta-item',
});
metaEl.createEl('div', {
text: `Content length: ${this.content.length.toLocaleString()} characters`,
cls: 'markitdown-preview-meta-item',
});
// Determine if content should be truncated
const lines = this.content.split('\n');
const truncated = lines.length > MAX_PREVIEW_LINES;
const displayContent = truncated
? lines.slice(0, MAX_PREVIEW_LINES).join('\n')
: this.content;
if (truncated) {
contentEl.createEl('div', {
text: `Showing first ${MAX_PREVIEW_LINES} of ${lines.length} lines`,
cls: 'markitdown-preview-truncation-note',
});
}
// Scrollable preview area
const previewContainer = contentEl.createDiv('markitdown-preview-content');
previewContainer.createEl('pre', { text: displayContent });
// Buttons
const buttonContainer = contentEl.createDiv('markitdown-button-container');
const cancelBtn = buttonContainer.createEl('button', { text: 'Cancel' });
cancelBtn.addEventListener('click', async () => {
await this.onCancel();
this.close();
});
const saveBtn = buttonContainer.createEl('button', {
text: 'Save',
cls: 'mod-cta',
});
saveBtn.addEventListener('click', async () => {
saveBtn.disabled = true;
saveBtn.setText('Saving...');
cancelBtn.disabled = true;
try {
await this.onSave();
} finally {
this.close();
}
});
}
onClose() {
this.contentEl.empty();
}
}

View file

@ -176,6 +176,7 @@ Styles for the Markitdown Obsidian plugin
font-weight: 500;
}
<<<<<<< HEAD
/* Resolved path hint */
.markitdown-resolved-path-hint {
font-size: 0.85em;
@ -285,6 +286,51 @@ Styles for the Markitdown Obsidian plugin
color: var(--text-faint);
}
/* Preview modal */
.markitdown-preview-modal {
width: 80vw;
max-width: 900px;
}
.markitdown-preview-meta {
background-color: var(--background-secondary);
padding: 10px 14px;
border-radius: 6px;
margin-bottom: 12px;
}
.markitdown-preview-meta-item {
font-size: 0.85em;
color: var(--text-muted);
padding: 2px 0;
}
.markitdown-preview-truncation-note {
font-size: 0.85em;
color: var(--text-accent);
margin-bottom: 8px;
font-style: italic;
}
.markitdown-preview-content {
max-height: 50vh;
overflow-y: auto;
border: 1px solid var(--background-modifier-border);
border-radius: 6px;
background-color: var(--background-primary-alt);
padding: 12px;
margin-bottom: 16px;
}
.markitdown-preview-content pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
font-family: var(--font-monospace);
font-size: 0.85em;
color: var(--text-normal);
}
/* Plugin args editor */
.markitdown-plugin-args-container {
margin: 8px 0 16px;