From 8fcba993489bc5c53f79952dc69c82a5a7d2e31b Mon Sep 17 00:00:00 2001 From: Rhys Date: Sun, 3 May 2026 19:36:42 +0200 Subject: [PATCH] INITIAL COMMIT --- .gitignore | 1 - main.js | 2 ++ manifest.json | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 main.js diff --git a/.gitignore b/.gitignore index 7ad3282..6c4042f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ node_modules/ -main.js *.js.map diff --git a/main.js b/main.js new file mode 100644 index 0000000..d3e2757 --- /dev/null +++ b/main.js @@ -0,0 +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)})})}}; diff --git a/manifest.json b/manifest.json index 92ce311..c6571b5 100644 --- a/manifest.json +++ b/manifest.json @@ -3,8 +3,8 @@ "name": "DocDrop", "version": "1.0.0", "minAppVersion": "1.0.0", - "description": "Convert PDF files to Markdown using the markitdown CLI.", + "description": "Convert PDF files to Markdown using Microsoft's markitdown.", "author": "Rhys Gottwald", "authorUrl": "", "isDesktopOnly": true -} +} \ No newline at end of file