import { App, Editor, MarkdownFileInfo, MarkdownView, Notice, Setting, normalizePath, } from 'obsidian'; import type ZeroQoLModulesPlugin from '../../main'; import { BaseModule } from '../base-module'; import { locale } from '../../locales'; const MODULE_ID = 'attachment-clean-paste'; interface AttachmentCleanPasteSettings { extensions: string; } export class AttachmentCleanPasteModule extends BaseModule { id = MODULE_ID; name = locale.modules['attachment-clean-paste'].name; description = locale.modules['attachment-clean-paste'].description; private app: App; get defaultSettings(): Record { return { extensions: 'png, jpg, jpeg, gif, svg, webp, bmp, pdf', }; } onload(plugin: ZeroQoLModulesPlugin, moduleSettings: Record): void { this.app = plugin.app; const rawSettings = moduleSettings as AttachmentCleanPasteSettings; const pasteRef = plugin.app.workspace.on( 'editor-paste', this.createPasteHandler(rawSettings), ); this.registerCleanup(() => plugin.app.workspace.offref(pasteRef)); const dropRef = plugin.app.workspace.on( 'editor-drop', this.createDropHandler(rawSettings), ); this.registerCleanup(() => plugin.app.workspace.offref(dropRef)); } renderInlineSettings( containerEl: HTMLElement, settings: Record, saveSettings: () => Promise, ): void { const loc = locale.modules['attachment-clean-paste']; const extSettings = settings as unknown as AttachmentCleanPasteSettings; new Setting(containerEl) .setName(loc.extensionsLabel) .setDesc(loc.extensionsDesc) .addTextArea((textarea) => { textarea .setPlaceholder(loc.extensionsPlaceholder) .setValue(extSettings?.extensions ?? '') .onChange(async (value) => { settings.extensions = value; await saveSettings(); }); textarea.inputEl.rows = 4; textarea.inputEl.style.width = '100%'; }); } private createPasteHandler(settings: AttachmentCleanPasteSettings) { return async ( evt: ClipboardEvent, editor: Editor, _info: MarkdownView | MarkdownFileInfo, ): Promise => { const files = evt.clipboardData?.files; if (!files || files.length === 0) return; const matchingFiles = Array.from(files).filter((f) => this.shouldProcess(f.name, settings), ); if (matchingFiles.length === 0) return; evt.preventDefault(); evt.stopPropagation(); for (const file of matchingFiles) { await this.processFile(file, editor, settings); } }; } private createDropHandler(settings: AttachmentCleanPasteSettings) { return async ( evt: DragEvent, editor: Editor, _info: MarkdownView | MarkdownFileInfo, ): Promise => { const files = evt.dataTransfer?.files; if (!files || files.length === 0) return; const matchingFiles = Array.from(files).filter((f) => this.shouldProcess(f.name, settings), ); if (matchingFiles.length === 0) return; evt.preventDefault(); evt.stopPropagation(); for (const file of matchingFiles) { await this.processFile(file, editor, settings); } }; } private shouldProcess( filename: string, settings: AttachmentCleanPasteSettings, ): boolean { const dotIndex = filename.lastIndexOf('.'); if (dotIndex === -1) return false; const ext = filename.slice(dotIndex + 1).toLowerCase(); const exts = settings.extensions .split(',') .map((e) => e.trim().toLowerCase()) .filter((e) => e.length > 0); return exts.some((e) => { const cleanExt = e.startsWith('.') ? e.slice(1) : e; return cleanExt === ext; }); } private async processFile( file: File, editor: Editor, _settings: AttachmentCleanPasteSettings, ): Promise { try { const arrayBuffer = await file.arrayBuffer(); const filename = file.name; const nameWithoutExt = filename.replace(/\.[^/.]+$/, ''); let attachmentFolder = this.app.vault.getConfig( 'attachmentFolderPath', ) as string; if (!attachmentFolder) attachmentFolder = 'attachments'; const folderPath = normalizePath(attachmentFolder); if (!(await this.app.vault.adapter.exists(folderPath))) { await this.app.vault.createFolder(folderPath); } const ext = filename.slice(filename.lastIndexOf('.') + 1); const basePath = normalizePath(`${folderPath}/${nameWithoutExt}`); const availablePath = await this.app.vault.getAvailablePath( basePath, ext, ); await this.app.vault.createBinary(availablePath, arrayBuffer); const link = `[[${availablePath}|${nameWithoutExt}]]`; editor.replaceSelection(link); } catch (e) { new Notice(`Clean Paste error: ${e.message}`); console.error('Attachment Clean Paste:', e); } } }