diff --git a/README.md b/README.md index e2bc6e4..c053c50 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # DocDrop -An [Obsidian](https://obsidian.md) plugin that converts PDF files to Markdown using Microsoft's [MarkItDown](https://github.com/microsoft/markitdown) CLI tool. +An [Obsidian](https://obsidian.md) plugin that converts documents, spreadsheets, images, and more to Markdown using Microsoft's [MarkItDown](https://github.com/microsoft/markitdown) CLI tool. -Right-click any PDF in your vault and have it converted to a clean Markdown file in seconds — entirely on your machine, no cloud required (unless you opt in to the advanced AI features). +Right-click any supported file in your vault and have it converted to a clean Markdown file in seconds — entirely on your machine, no cloud required (unless you opt in to the advanced AI features). + +**Supported formats:** PDF, Word (`.docx`), PowerPoint (`.pptx`), Excel (`.xlsx`), images (`.jpg`, `.png`, `.gif`, `.webp`, `.bmp`, `.tiff`), HTML, CSV, JSON, XML, EPUB, ZIP, and audio (`.mp3`, `.wav` — requires ffmpeg). --- @@ -65,7 +67,7 @@ Then copy `main.js`, `manifest.json`, and `styles.css` into your vault's plugin ### Right-click menu -Right-click any PDF file in the Obsidian file explorer and select **Convert to Markdown with DocDrop**. The converted `.md` file is saved in the same folder as the PDF (or a custom folder — see Settings). +Right-click any supported file in the Obsidian file explorer and select **Convert to Markdown with DocDrop**. The converted `.md` file is saved in the same folder as the source file (or a custom folder — see Settings). ### Command palette diff --git a/main.js b/main.js index d3e2757..c638799 100644 --- a/main.js +++ b/main.js @@ -1,2 +1,2 @@ -var g=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var D=(a,t)=>{for(var e in t)g(a,e,{get:t[e],enumerable:!0})},I=(a,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of P(t))!k.call(a,n)&&n!==e&&g(a,n,{get:()=>t[n],enumerable:!(s=A(t,n))||s.enumerable});return a};var b=a=>I(g({},"__esModule",{value:!0}),a);var x={};D(x,{default:()=>p});module.exports=b(x);var i=require("obsidian"),f=require("child_process"),E={executablePath:"markitdown",outputMode:"same",customOutputFolder:"",keepDataUris:!1,usePlugins:!1,openAiApiKey:"",openAiBaseUrl:"",openAiModel:"gpt-4o",useDocIntel:!1,docIntelEndpoint:"",docIntelApiKey:"",mimeType:"",charset:""},h=class extends i.Modal{constructor(t,e,s,n){super(t),this.fileName=e,this.onConfirm=s,this.onCancel=n}onOpen(){let{contentEl:t}=this;t.empty(),t.createEl("h2",{text:"File already exists"}),t.createEl("p",{text:`"${this.fileName}" already exists. Overwrite it?`});let e=t.createDiv({cls:"modal-button-container"});e.createEl("button",{text:"Overwrite",cls:"mod-warning"}).addEventListener("click",()=>{this.close(),this.onConfirm()}),e.createEl("button",{text:"Cancel"}).addEventListener("click",()=>{this.close(),this.onCancel()})}onClose(){this.contentEl.empty()}},m=class extends i.PluginSettingTab{constructor(t,e){super(t,e),this.plugin=e}display(){let{containerEl:t}=this;t.empty(),t.createEl("h2",{text:"DocDrop"}),new i.Setting(t).setName("Executable path").setDesc('Full path to the markitdown binary, or just "markitdown" if it is on your PATH.').addText(e=>e.setPlaceholder("markitdown").setValue(this.plugin.settings.executablePath).onChange(async s=>{this.plugin.settings.executablePath=s.trim()||"markitdown",await this.plugin.saveSettings()})),new i.Setting(t).setName("Output location").setDesc("Where to save converted Markdown files.").addDropdown(e=>e.addOption("same","Same folder as PDF").addOption("custom","Custom folder").setValue(this.plugin.settings.outputMode).onChange(async s=>{this.plugin.settings.outputMode=s,await this.plugin.saveSettings(),this.display()})),this.plugin.settings.outputMode==="custom"&&new i.Setting(t).setName("Custom output folder").setDesc('Vault-relative path, e.g. "Converted". The folder must already exist.').addText(e=>e.setPlaceholder("Converted").setValue(this.plugin.settings.customOutputFolder).onChange(async s=>{this.plugin.settings.customOutputFolder=s.trim(),await this.plugin.saveSettings()})),t.createEl("h3",{text:"Conversion options"}),new i.Setting(t).setName("Keep images").setDesc("Preserve images embedded in the PDF as base64 data inside the Markdown file. Turning this on makes the output file much larger but keeps all visuals inline. Leave off if you only need the text.").addToggle(e=>e.setValue(this.plugin.settings.keepDataUris).onChange(async s=>{this.plugin.settings.keepDataUris=s,await this.plugin.saveSettings()})),new i.Setting(t).setName("MIME type hint").setDesc('Tells markitdown what kind of file it is receiving, e.g. "application/pdf". Normally not needed \u2014 markitdown detects the type automatically. Only set this if conversion produces wrong results and you suspect a detection failure.').addText(e=>e.setPlaceholder("application/pdf").setValue(this.plugin.settings.mimeType).onChange(async s=>{this.plugin.settings.mimeType=s.trim(),await this.plugin.saveSettings()})),new i.Setting(t).setName("Character encoding hint").setDesc('Tells markitdown what text encoding the file uses, e.g. "UTF-8" or "ISO-8859-1". Leave blank unless converted text contains garbled characters \u2014 markitdown detects encoding automatically.').addText(e=>e.setPlaceholder("UTF-8").setValue(this.plugin.settings.charset).onChange(async s=>{this.plugin.settings.charset=s.trim(),await this.plugin.saveSettings()})),t.createEl("h3",{text:"markitdown-ocr plugin (optional)"}),t.createEl("p",{text:"markitdown-ocr is a free, separately-installed plugin that uses an AI vision model (like OpenAI's GPT-4o) to read text from images inside PDFs \u2014 useful for scanned documents or PDFs that are just pictures of pages. Install it first with: pip install markitdown-ocr. You will need an OpenAI account (or a compatible service) to provide the AI.",cls:"setting-item-description"}),new i.Setting(t).setName("Enable markitdown-ocr").setDesc("Activate any installed markitdown plugins, including markitdown-ocr. Has no effect if markitdown-ocr is not installed. Install it with: pip install markitdown-ocr").addToggle(e=>e.setValue(this.plugin.settings.usePlugins).onChange(async s=>{this.plugin.settings.usePlugins=s,await this.plugin.saveSettings(),this.display()})),this.plugin.settings.usePlugins&&(new i.Setting(t).setName("OpenAI API key").setDesc("Your secret API key from OpenAI (or a compatible service). Required for markitdown-ocr to call the AI vision model. Get one at platform.openai.com \u2192 API keys. Looks like: sk-proj-...").addText(e=>{e.setPlaceholder("sk-proj-...").setValue(this.plugin.settings.openAiApiKey).onChange(async s=>{this.plugin.settings.openAiApiKey=s.trim(),await this.plugin.saveSettings()}),e.inputEl.type="password"}),new i.Setting(t).setName("AI model").setDesc('The vision-capable AI model markitdown-ocr will use to read images. Must support image/vision input. "gpt-4o" is the default and works well. Other options: gpt-4o-mini (cheaper, slightly less accurate).').addText(e=>e.setPlaceholder("gpt-4o").setValue(this.plugin.settings.openAiModel).onChange(async s=>{this.plugin.settings.openAiModel=s.trim()||"gpt-4o",await this.plugin.saveSettings()})),new i.Setting(t).setName("OpenAI API base URL (optional)").setDesc("Override the API server markitdown-ocr connects to. Leave blank to use the default OpenAI servers. Set this if you are using Azure OpenAI or a self-hosted compatible service (e.g. https://your-resource.openai.azure.com/). ").addText(e=>e.setPlaceholder("https://api.openai.com/v1").setValue(this.plugin.settings.openAiBaseUrl).onChange(async s=>{this.plugin.settings.openAiBaseUrl=s.trim(),await this.plugin.saveSettings()}))),t.createEl("h3",{text:"Azure Document Intelligence (optional)"}),t.createEl("p",{text:"Document Intelligence is a paid Microsoft Azure cloud service that uses AI to read PDFs with much higher accuracy than offline conversion \u2014 especially for scanned documents, handwriting, tables, and complex layouts. Requires an Azure account. Leave these blank to use free offline conversion instead.",cls:"setting-item-description"}),new i.Setting(t).setName("Use Document Intelligence").setDesc("Send the PDF to Microsoft Azure for conversion instead of processing it locally. Produces better results for scanned or image-based PDFs, but requires internet access and a paid Azure subscription.").addToggle(e=>e.setValue(this.plugin.settings.useDocIntel).onChange(async s=>{this.plugin.settings.useDocIntel=s,await this.plugin.saveSettings(),this.display()})),this.plugin.settings.useDocIntel&&(new i.Setting(t).setName("Endpoint URL").setDesc("The URL of your Azure Document Intelligence resource. Find it in the Azure Portal: open your Document Intelligence resource \u2192 Keys and Endpoint. Looks like: https://your-resource-name.cognitiveservices.azure.com/").addText(e=>e.setPlaceholder("https://your-resource.cognitiveservices.azure.com/").setValue(this.plugin.settings.docIntelEndpoint).onChange(async s=>{this.plugin.settings.docIntelEndpoint=s.trim(),await this.plugin.saveSettings()})),new i.Setting(t).setName("API key").setDesc("The secret key that authenticates you with Azure Document Intelligence. Find it in the Azure Portal: open your Document Intelligence resource \u2192 Keys and Endpoint \u2192 KEY 1 or KEY 2. Either key works.").addText(e=>{e.setPlaceholder("Your Azure API key").setValue(this.plugin.settings.docIntelApiKey).onChange(async s=>{this.plugin.settings.docIntelApiKey=s.trim(),await this.plugin.saveSettings()}),e.inputEl.type="password"}))}},p=class extends i.Plugin{async onload(){await this.loadSettings(),this.addSettingTab(new m(this.app,this)),this.addCommand({id:"convert-active-pdf",name:"Convert active PDF to Markdown",callback:()=>{let t=this.app.workspace.getActiveFile();if(!t){new i.Notice("No file is currently open.");return}if(t.extension.toLowerCase()!=="pdf"){new i.Notice("The active file is not a PDF.");return}this.convertPdf(t)}}),this.registerEvent(this.app.workspace.on("file-menu",(t,e)=>{e instanceof i.TFile&&e.extension.toLowerCase()==="pdf"&&t.addItem(s=>{s.setTitle("Convert to Markdown with DocDrop").setIcon("file-text").onClick(()=>this.convertPdf(e))})}))}onunload(){}async loadSettings(){this.settings=Object.assign({},E,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}async convertPdf(t){let n=`${this.app.vault.adapter.getBasePath()}/${t.path}`,o=this.resolveOutputPath(t);if(o===null){new i.Notice(`Custom output folder "${this.settings.customOutputFolder}" does not exist in the vault.`);return}let r=this.app.vault.getAbstractFileByPath(o);if(r instanceof i.TFile){new h(this.app,o,()=>this.runConversion(n,o,r),()=>new i.Notice("Conversion cancelled.")).open();return}await this.runConversion(n,o,null)}resolveOutputPath(t){var o,r;let e=t.basename;if(this.settings.outputMode==="same"){let c=(r=(o=t.parent)==null?void 0:o.path)!=null?r:"",u=c==="/"?"":c;return u?`${u}/${e}.md`:`${e}.md`}let s=this.settings.customOutputFolder.replace(/\/$/,"");return s===""?`${e}.md`:this.app.vault.getAbstractFileByPath(s)instanceof i.TFolder?`${s}/${e}.md`:null}async runConversion(t,e,s){let n=new i.Notice(`Converting "${e}"\u2026`,0);try{let o=await this.invokeMarkItDown(t);n.hide(),s?await this.app.vault.modify(s,o):await this.app.vault.create(e,o),new i.Notice(`Converted: ${e}`,5e3)}catch(o){n.hide();let r=o instanceof Error?o.message:String(o);new i.Notice(`DocDrop failed: -${r}`,8e3),console.error("[DocDrop] conversion error",o)}}invokeMarkItDown(t){var r,c;let e=["/usr/local/bin","/opt/homebrew/bin","/opt/homebrew/sbin","/Library/Frameworks/Python.framework/Versions/3.12/bin","/Library/Frameworks/Python.framework/Versions/3.11/bin","/Library/Frameworks/Python.framework/Versions/3.10/bin","/usr/bin","/bin"],s=[...(c=(r=process.env.PATH)==null?void 0:r.split(":"))!=null?c:[],...e].join(":"),n=[t];this.settings.keepDataUris&&n.push("--keep-data-uris"),this.settings.usePlugins&&n.push("--use-plugins"),this.settings.mimeType&&n.push("--mime-type",this.settings.mimeType),this.settings.charset&&n.push("--charset",this.settings.charset),this.settings.useDocIntel&&(n.push("--use-docintel"),this.settings.docIntelEndpoint&&n.push("--endpoint",this.settings.docIntelEndpoint));let o={PATH:s};return this.settings.useDocIntel&&this.settings.docIntelApiKey&&(o.AZURE_API_KEY=this.settings.docIntelApiKey),this.settings.usePlugins&&this.settings.openAiApiKey&&(o.OPENAI_API_KEY=this.settings.openAiApiKey),this.settings.usePlugins&&this.settings.openAiModel&&(o.OPENAI_MODEL=this.settings.openAiModel),this.settings.usePlugins&&this.settings.openAiBaseUrl&&(o.OPENAI_BASE_URL=this.settings.openAiBaseUrl),new Promise((u,v)=>{(0,f.execFile)(this.settings.executablePath,n,{encoding:"utf8",maxBuffer:50*1024*1024,env:{...process.env,...o}},(d,w,l)=>{if(d){let y=(l==null?void 0:l.trim())||d.message;v(new Error(`Exit code ${d.code}: ${y}`));return}l!=null&&l.trim()&&console.warn("[DocDrop] stderr:",l.trim()),u(w)})})}}; +var g=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var D=Object.prototype.hasOwnProperty;var I=(a,e)=>{for(var t in e)g(a,t,{get:e[t],enumerable:!0})},b=(a,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of k(e))!D.call(a,n)&&n!==t&&g(a,n,{get:()=>e[n],enumerable:!(s=P(e,n))||s.enumerable});return a};var x=a=>b(g({},"__esModule",{value:!0}),a);var T={};I(T,{default:()=>u});module.exports=x(T);var i=require("obsidian"),f=require("child_process"),v=new Set(["pdf","docx","doc","pptx","ppt","xlsx","xls","jpg","jpeg","png","gif","webp","bmp","tiff","html","htm","csv","json","xml","epub","zip","mp3","wav"]),E={executablePath:"markitdown",outputMode:"same",customOutputFolder:"",keepDataUris:!1,usePlugins:!1,openAiApiKey:"",openAiBaseUrl:"",openAiModel:"gpt-4o",useDocIntel:!1,docIntelEndpoint:"",docIntelApiKey:"",mimeType:"",charset:""},h=class extends i.Modal{constructor(e,t,s,n){super(e),this.fileName=t,this.onConfirm=s,this.onCancel=n}onOpen(){let{contentEl:e}=this;e.empty(),e.createEl("h2",{text:"File already exists"}),e.createEl("p",{text:`"${this.fileName}" already exists. Overwrite it?`});let t=e.createDiv({cls:"modal-button-container"});t.createEl("button",{text:"Overwrite",cls:"mod-warning"}).addEventListener("click",()=>{this.close(),this.onConfirm()}),t.createEl("button",{text:"Cancel"}).addEventListener("click",()=>{this.close(),this.onCancel()})}onClose(){this.contentEl.empty()}},m=class extends i.PluginSettingTab{constructor(e,t){super(e,t),this.plugin=t}display(){let{containerEl:e}=this;e.empty(),e.createEl("h2",{text:"DocDrop"}),new i.Setting(e).setName("Executable path").setDesc('Full path to the markitdown binary, or just "markitdown" if it is on your PATH.').addText(t=>t.setPlaceholder("markitdown").setValue(this.plugin.settings.executablePath).onChange(async s=>{this.plugin.settings.executablePath=s.trim()||"markitdown",await this.plugin.saveSettings()})),new i.Setting(e).setName("Output location").setDesc("Where to save converted Markdown files.").addDropdown(t=>t.addOption("same","Same folder as PDF").addOption("custom","Custom folder").setValue(this.plugin.settings.outputMode).onChange(async s=>{this.plugin.settings.outputMode=s,await this.plugin.saveSettings(),this.display()})),this.plugin.settings.outputMode==="custom"&&new i.Setting(e).setName("Custom output folder").setDesc('Vault-relative path, e.g. "Converted". The folder must already exist.').addText(t=>t.setPlaceholder("Converted").setValue(this.plugin.settings.customOutputFolder).onChange(async s=>{this.plugin.settings.customOutputFolder=s.trim(),await this.plugin.saveSettings()})),e.createEl("h3",{text:"Conversion options"}),new i.Setting(e).setName("Keep images").setDesc("Preserve images embedded in the PDF as base64 data inside the Markdown file. Turning this on makes the output file much larger but keeps all visuals inline. Leave off if you only need the text.").addToggle(t=>t.setValue(this.plugin.settings.keepDataUris).onChange(async s=>{this.plugin.settings.keepDataUris=s,await this.plugin.saveSettings()})),new i.Setting(e).setName("MIME type hint").setDesc('Tells markitdown what kind of file it is receiving, e.g. "application/pdf". Normally not needed \u2014 markitdown detects the type automatically. Only set this if conversion produces wrong results and you suspect a detection failure.').addText(t=>t.setPlaceholder("application/pdf").setValue(this.plugin.settings.mimeType).onChange(async s=>{this.plugin.settings.mimeType=s.trim(),await this.plugin.saveSettings()})),new i.Setting(e).setName("Character encoding hint").setDesc('Tells markitdown what text encoding the file uses, e.g. "UTF-8" or "ISO-8859-1". Leave blank unless converted text contains garbled characters \u2014 markitdown detects encoding automatically.').addText(t=>t.setPlaceholder("UTF-8").setValue(this.plugin.settings.charset).onChange(async s=>{this.plugin.settings.charset=s.trim(),await this.plugin.saveSettings()})),e.createEl("h3",{text:"markitdown-ocr plugin (optional)"}),e.createEl("p",{text:"markitdown-ocr is a free, separately-installed plugin that uses an AI vision model (like OpenAI's GPT-4o) to read text from images inside PDFs \u2014 useful for scanned documents or PDFs that are just pictures of pages. Install it first with: pip install markitdown-ocr. You will need an OpenAI account (or a compatible service) to provide the AI.",cls:"setting-item-description"}),new i.Setting(e).setName("Enable markitdown-ocr").setDesc("Activate any installed markitdown plugins, including markitdown-ocr. Has no effect if markitdown-ocr is not installed. Install it with: pip install markitdown-ocr").addToggle(t=>t.setValue(this.plugin.settings.usePlugins).onChange(async s=>{this.plugin.settings.usePlugins=s,await this.plugin.saveSettings(),this.display()})),this.plugin.settings.usePlugins&&(new i.Setting(e).setName("OpenAI API key").setDesc("Your secret API key from OpenAI (or a compatible service). Required for markitdown-ocr to call the AI vision model. Get one at platform.openai.com \u2192 API keys. Looks like: sk-proj-...").addText(t=>{t.setPlaceholder("sk-proj-...").setValue(this.plugin.settings.openAiApiKey).onChange(async s=>{this.plugin.settings.openAiApiKey=s.trim(),await this.plugin.saveSettings()}),t.inputEl.type="password"}),new i.Setting(e).setName("AI model").setDesc('The vision-capable AI model markitdown-ocr will use to read images. Must support image/vision input. "gpt-4o" is the default and works well. Other options: gpt-4o-mini (cheaper, slightly less accurate).').addText(t=>t.setPlaceholder("gpt-4o").setValue(this.plugin.settings.openAiModel).onChange(async s=>{this.plugin.settings.openAiModel=s.trim()||"gpt-4o",await this.plugin.saveSettings()})),new i.Setting(e).setName("OpenAI API base URL (optional)").setDesc("Override the API server markitdown-ocr connects to. Leave blank to use the default OpenAI servers. Set this if you are using Azure OpenAI or a self-hosted compatible service (e.g. https://your-resource.openai.azure.com/). ").addText(t=>t.setPlaceholder("https://api.openai.com/v1").setValue(this.plugin.settings.openAiBaseUrl).onChange(async s=>{this.plugin.settings.openAiBaseUrl=s.trim(),await this.plugin.saveSettings()}))),e.createEl("h3",{text:"Azure Document Intelligence (optional)"}),e.createEl("p",{text:"Document Intelligence is a paid Microsoft Azure cloud service that uses AI to read PDFs with much higher accuracy than offline conversion \u2014 especially for scanned documents, handwriting, tables, and complex layouts. Requires an Azure account. Leave these blank to use free offline conversion instead.",cls:"setting-item-description"}),new i.Setting(e).setName("Use Document Intelligence").setDesc("Send the PDF to Microsoft Azure for conversion instead of processing it locally. Produces better results for scanned or image-based PDFs, but requires internet access and a paid Azure subscription.").addToggle(t=>t.setValue(this.plugin.settings.useDocIntel).onChange(async s=>{this.plugin.settings.useDocIntel=s,await this.plugin.saveSettings(),this.display()})),this.plugin.settings.useDocIntel&&(new i.Setting(e).setName("Endpoint URL").setDesc("The URL of your Azure Document Intelligence resource. Find it in the Azure Portal: open your Document Intelligence resource \u2192 Keys and Endpoint. Looks like: https://your-resource-name.cognitiveservices.azure.com/").addText(t=>t.setPlaceholder("https://your-resource.cognitiveservices.azure.com/").setValue(this.plugin.settings.docIntelEndpoint).onChange(async s=>{this.plugin.settings.docIntelEndpoint=s.trim(),await this.plugin.saveSettings()})),new i.Setting(e).setName("API key").setDesc("The secret key that authenticates you with Azure Document Intelligence. Find it in the Azure Portal: open your Document Intelligence resource \u2192 Keys and Endpoint \u2192 KEY 1 or KEY 2. Either key works.").addText(t=>{t.setPlaceholder("Your Azure API key").setValue(this.plugin.settings.docIntelApiKey).onChange(async s=>{this.plugin.settings.docIntelApiKey=s.trim(),await this.plugin.saveSettings()}),t.inputEl.type="password"}))}},u=class extends i.Plugin{async onload(){await this.loadSettings(),this.addSettingTab(new m(this.app,this)),this.addCommand({id:"convert-active-file",name:"Convert active file to Markdown",callback:()=>{let e=this.app.workspace.getActiveFile();if(!e){new i.Notice("No file is currently open.");return}if(!v.has(e.extension.toLowerCase())){new i.Notice(`DocDrop does not support .${e.extension} files.`);return}this.convertFile(e)}}),this.registerEvent(this.app.workspace.on("file-menu",(e,t)=>{t instanceof i.TFile&&v.has(t.extension.toLowerCase())&&e.addItem(s=>{s.setTitle("Convert to Markdown with DocDrop").setIcon("file-text").onClick(()=>this.convertFile(t))})}))}onunload(){}async loadSettings(){this.settings=Object.assign({},E,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}async convertFile(e){let n=`${this.app.vault.adapter.getBasePath()}/${e.path}`,o=this.resolveOutputPath(e);if(o===null){new i.Notice(`Custom output folder "${this.settings.customOutputFolder}" does not exist in the vault.`);return}let r=this.app.vault.getAbstractFileByPath(o);if(r instanceof i.TFile){new h(this.app,o,()=>this.runConversion(n,o,r),()=>new i.Notice("Conversion cancelled.")).open();return}await this.runConversion(n,o,null)}resolveOutputPath(e){var o,r;let t=e.basename;if(this.settings.outputMode==="same"){let c=(r=(o=e.parent)==null?void 0:o.path)!=null?r:"",p=c==="/"?"":c;return p?`${p}/${t}.md`:`${t}.md`}let s=this.settings.customOutputFolder.replace(/\/$/,"");return s===""?`${t}.md`:this.app.vault.getAbstractFileByPath(s)instanceof i.TFolder?`${s}/${t}.md`:null}async runConversion(e,t,s){let n=new i.Notice(`Converting "${t}"\u2026`,0);try{let o=await this.invokeMarkItDown(e);n.hide(),s?await this.app.vault.modify(s,o):await this.app.vault.create(t,o),new i.Notice(`Converted: ${t}`,5e3)}catch(o){n.hide();let r=o instanceof Error?o.message:String(o);new i.Notice(`DocDrop failed: +${r}`,8e3),console.error("[DocDrop] conversion error",o)}}invokeMarkItDown(e){var r,c;let t=["/usr/local/bin","/opt/homebrew/bin","/opt/homebrew/sbin","/Library/Frameworks/Python.framework/Versions/3.12/bin","/Library/Frameworks/Python.framework/Versions/3.11/bin","/Library/Frameworks/Python.framework/Versions/3.10/bin","/usr/bin","/bin"],s=[...(c=(r=process.env.PATH)==null?void 0:r.split(":"))!=null?c:[],...t].join(":"),n=[e];this.settings.keepDataUris&&n.push("--keep-data-uris"),this.settings.usePlugins&&n.push("--use-plugins"),this.settings.mimeType&&n.push("--mime-type",this.settings.mimeType),this.settings.charset&&n.push("--charset",this.settings.charset),this.settings.useDocIntel&&(n.push("--use-docintel"),this.settings.docIntelEndpoint&&n.push("--endpoint",this.settings.docIntelEndpoint));let o={PATH:s};return this.settings.useDocIntel&&this.settings.docIntelApiKey&&(o.AZURE_API_KEY=this.settings.docIntelApiKey),this.settings.usePlugins&&this.settings.openAiApiKey&&(o.OPENAI_API_KEY=this.settings.openAiApiKey),this.settings.usePlugins&&this.settings.openAiModel&&(o.OPENAI_MODEL=this.settings.openAiModel),this.settings.usePlugins&&this.settings.openAiBaseUrl&&(o.OPENAI_BASE_URL=this.settings.openAiBaseUrl),new Promise((p,w)=>{(0,f.execFile)(this.settings.executablePath,n,{encoding:"utf8",maxBuffer:50*1024*1024,env:{...process.env,...o}},(d,y,l)=>{if(d){let A=(l==null?void 0:l.trim())||d.message;w(new Error(`Exit code ${d.code}: ${A}`));return}l!=null&&l.trim()&&console.warn("[DocDrop] stderr:",l.trim()),p(y)})})}}; diff --git a/manifest.json b/manifest.json index e588f96..26b4be2 100644 --- a/manifest.json +++ b/manifest.json @@ -1,9 +1,9 @@ { "id": "docdrop", "name": "DocDrop", - "version": "1.0.0", + "version": "1.1.0", "minAppVersion": "1.0.0", - "description": "Convert PDF files to Markdown using Microsofts markitdown CLI.", + "description": "Convert PDF, Word, PowerPoint, Excel, images, and more to Markdown using Microsoft's markitdown.", "author": "Rhys Gottwald", "authorUrl": "", "isDesktopOnly": true diff --git a/package.json b/package.json index 442ebb5..995ae23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "docdrop", - "version": "1.0.0", + "version": "1.1.0", "description": "Obsidian plugin: convert PDFs to Markdown via Microsoft's markitdown", "main": "main.js", "type": "module", diff --git a/src/main.ts b/src/main.ts index 32c7e15..e63fa3f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,6 +11,23 @@ import { } from "obsidian"; import { execFile } from "child_process"; +// ─── Supported file types ──────────────────────────────────────────────────── + +const SUPPORTED_EXTENSIONS = new Set([ + "pdf", + "docx", "doc", + "pptx", "ppt", + "xlsx", "xls", + "jpg", "jpeg", "png", "gif", "webp", "bmp", "tiff", + "html", "htm", + "csv", + "json", + "xml", + "epub", + "zip", + "mp3", "wav", +]); + // ─── Settings ──────────────────────────────────────────────────────────────── interface DocDropSettings { @@ -373,32 +390,32 @@ export default class DocDropPlugin extends Plugin { this.addSettingTab(new DocDropSettingTab(this.app, this)); this.addCommand({ - id: "convert-active-pdf", - name: "Convert active PDF to Markdown", + id: "convert-active-file", + name: "Convert active file to Markdown", callback: () => { const activeFile = this.app.workspace.getActiveFile(); if (!activeFile) { new Notice("No file is currently open."); return; } - if (activeFile.extension.toLowerCase() !== "pdf") { - new Notice("The active file is not a PDF."); + if (!SUPPORTED_EXTENSIONS.has(activeFile.extension.toLowerCase())) { + new Notice(`DocDrop does not support .${activeFile.extension} files.`); return; } - this.convertPdf(activeFile); + this.convertFile(activeFile); }, }); this.registerEvent( this.app.workspace.on("file-menu", (menu, file: TAbstractFile) => { if (!(file instanceof TFile)) return; - if (file.extension.toLowerCase() !== "pdf") return; + if (!SUPPORTED_EXTENSIONS.has(file.extension.toLowerCase())) return; menu.addItem((item) => { item .setTitle("Convert to Markdown with DocDrop") .setIcon("file-text") - .onClick(() => this.convertPdf(file)); + .onClick(() => this.convertFile(file)); }); }) ); @@ -414,7 +431,7 @@ export default class DocDropPlugin extends Plugin { await this.saveData(this.settings); } - private async convertPdf(pdfFile: TFile): Promise { + private async convertFile(pdfFile: TFile): Promise { const adapter = this.app.vault.adapter; const basePath = (adapter as any).getBasePath() as string; const absolutePdfPath = `${basePath}/${pdfFile.path}`;