diff --git a/main.js b/main.js index ffd80a1..b7bcebb 100644 --- a/main.js +++ b/main.js @@ -3,19 +3,19 @@ THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ -"use strict";var Ct=Object.create;var L=Object.defineProperty;var _t=Object.getOwnPropertyDescriptor;var Tt=Object.getOwnPropertyNames;var Dt=Object.getPrototypeOf,St=Object.prototype.hasOwnProperty;var Ft=(c,n)=>{for(var t in n)L(c,t,{get:n[t],enumerable:!0})},ct=(c,n,t,e)=>{if(n&&typeof n=="object"||typeof n=="function")for(let s of Tt(n))!St.call(c,s)&&s!==t&&L(c,s,{get:()=>n[s],enumerable:!(e=_t(n,s))||e.enumerable});return c};var y=(c,n,t)=>(t=c!=null?Ct(Dt(c)):{},ct(n||!c||!c.__esModule?L(t,"default",{value:c,enumerable:!0}):t,c)),Mt=c=>ct(L({},"__esModule",{value:!0}),c);var Ht={};Ft(Ht,{default:()=>K});module.exports=Mt(Ht);var w=require("obsidian"),x=y(require("path")),at=y(require("fs"));var X={pythonPath:"python",enablePlugins:!1,pluginArgs:[],docintelEndpoint:"",outputPath:"",imageExtractionEnabled:!1,imageSubfolderTemplate:"{filename}-images",enableBatchProgress:!0,enableContextMenu:!0,enableRecursiveConversion:!1,outputFilenameTemplate:"{filename}",enableDragDrop:!0,enableAutoFrontmatter:!1,autoTags:"",conversionHistory:[]};var M=y(require("fs"));var Q=require("child_process"),k=y(require("path")),Z=".venv",pt=["3.14","3.13","3.12","3.11","3.10","3.9"];function ut(c){let n=process.env.HOME||process.env.USERPROFILE||"",t=process.platform==="win32",e=t?";":":",s=[];t||s.push("/opt/homebrew/bin","/usr/local/bin"),n&&(s.push(k.join(n,".local","bin")),t||s.push(k.join(n,".pyenv","shims")));let o=process.env.PATH||"",a=[...s,o].filter(Boolean),l=new Set,r=a.filter(i=>l.has(i)?!1:(l.add(i),!0));return{...process.env,PYTHONUTF8:"1",PATH:r.join(e),...c}}function It(c){return process.platform==="win32"?k.join(c,Z,"Scripts","python.exe"):k.join(c,Z,"bin","python")}function $t(c,n){let t=c.trim(),e=[];t&&e.push(t),e.push(It(n));let s=process.env.HOME||process.env.USERPROFILE||"",o=process.platform==="win32",a=process.env.PIPX_HOME||(s?k.join(s,".local","pipx"):"");if(a&&(o?e.push(k.join(a,"venvs","markitdown","Scripts","python.exe")):e.push(k.join(a,"venvs","markitdown","bin","python"))),o){t==="python"?e.push("python3"):t==="python3"?e.push("python"):e.push("python","python3");let r=process.env.LOCALAPPDATA||"";if(r){for(let i of["314","313","312","311","310","39"])e.push(`${r}\\Programs\\Python\\Python${i}\\python.exe`);e.push(`${r}\\Microsoft\\WindowsApps\\python3.exe`)}}else{t==="python"?e.push("python3"):t==="python3"?e.push("python"):e.push("python3","python"),e.push("/opt/homebrew/bin/python3"),e.push("/usr/local/bin/python3");for(let r of pt)e.push(`/opt/homebrew/bin/python${r}`),e.push(`/usr/local/bin/python${r}`),e.push(`/opt/homebrew/opt/python@${r}/bin/python3`),e.push(`/usr/local/opt/python@${r}/bin/python3`);for(let r of pt)e.push(`/Library/Frameworks/Python.framework/Versions/${r}/bin/python3`);e.push("/usr/bin/python3")}let l=new Set;return e.filter(r=>!r||l.has(r)?!1:(l.add(r),!0))}function O(c,n,t,e){return new Promise((s,o)=>{let a=(0,Q.spawn)(c,[n,...t],{shell:!1,env:ut(e),stdio:["ignore","pipe","pipe"]}),l="",r="";a.stdout.on("data",i=>{l+=i.toString()}),a.stderr.on("data",i=>{r+=i.toString()}),a.on("error",i=>{o(i)}),a.on("close",i=>{s({stdout:l.trim(),stderr:r.trim(),exitCode:i??1})})})}function A(c,n){return k.join(n,"python",c)}async function tt(c,n){let t={pythonInstalled:!1,pythonVersion:null,markitdownInstalled:!1,markitdownVersion:null},e=[],s=c.trim();if(!s)return e.push({path:c||"(empty)",error:"Path is empty or whitespace-only"}),{status:t,resolvedPythonPath:c,triedPaths:e};let o=A("check_install.py",n),a=$t(s,n),l=null;for(let r of a)try{let i=await O(r,o,["--check","all"]);if(i.exitCode===0&&i.stdout){let p=JSON.parse(i.stdout),u={pythonInstalled:!0,pythonVersion:p.python_version??null,markitdownInstalled:!1,markitdownVersion:null};if(p.packages?.markitdown&&(u.markitdownInstalled=p.packages.markitdown.installed,u.markitdownVersion=p.packages.markitdown.version??null),u.markitdownInstalled)return e.push({path:r,error:""}),{status:u,resolvedPythonPath:r,triedPaths:e};e.push({path:r,error:"Python found but markitdown not installed"}),l||(l={status:u,resolvedPythonPath:r})}else{let p=i.stderr||`exit code ${i.exitCode}`;e.push({path:r,error:`Check script failed: ${p}`})}}catch(i){let p=i instanceof Error?i.message:String(i);e.push({path:r,error:p}),console.debug(`markitdown: ${r} failed:`,i);continue}return l?{...l,triedPaths:e}:{status:t,resolvedPythonPath:c,triedPaths:e}}async function dt(c,n,t,e){return c.trim()?new Promise((s,o)=>{let a=A("install_package.py",n),l=k.join(n,Z),r=(0,Q.spawn)(c,[a,"--package",t,"--venv-dir",l],{shell:!1,env:ut(),stdio:["ignore","pipe","pipe"]}),i="";r.stdout.on("data",p=>{let u=p.toString().split(` -`);for(let d of u)d.trim()&&(i=d.trim(),e?.(d.trim()))}),r.stderr.on("data",p=>{let u=p.toString().split(` -`);for(let d of u)d.trim()&&e?.(d.trim())}),r.on("error",p=>{o(p)}),r.on("close",p=>{if(p===0)try{let u=JSON.parse(i);s(u.success===!0)}catch{s(!0)}else s(!1)})}):!1}var j=["pdf","docx","pptx","xlsx","xls","html","htm","txt","csv","json","xml","jpg","jpeg","png","gif","wav","mp3","zip","epub"],gt=j.map(c=>`.${c}`).join(","),ht=[{name:"PDF files",extensions:".pdf"},{name:"Word documents",extensions:".docx"},{name:"PowerPoint presentations",extensions:".pptx"},{name:"Excel spreadsheets",extensions:".xlsx,.xls"},{name:"Web pages",extensions:".html,.htm"},{name:"Text files",extensions:".txt"},{name:"Data files",extensions:".csv,.json,.xml"},{name:"Images",extensions:".jpg,.jpeg,.png,.gif"},{name:"Audio files",extensions:".wav,.mp3"},{name:"Archives & eBooks",extensions:".zip,.epub"}];function et(c){let n=c.toLowerCase().replace(/^\./,"");return j.includes(n)}var ft=y(require("path"));function mt(c){return/[:#\[\]{}&*!|>'"`,@%\\]/.test(c)||c.trim()!==c?`"${c.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`:c}function At(c,n){let t={};if(n.enableAutoFrontmatter){let e=ft.basename(c),s=new Date,o=s.getFullYear(),a=String(s.getMonth()+1).padStart(2,"0"),l=String(s.getDate()).padStart(2,"0");t.source=e,t.converted=`${o}-${a}-${l}`,t.converter="markitdown"}if(n.autoTags.trim()){let e=n.autoTags.split(",").map(s=>s.trim()).filter(s=>s.length>0);e.length>0&&(t.tags=e)}return t}function vt(c){let n=[];for(let[t,e]of Object.entries(c))if(Array.isArray(e)){n.push(`${t}:`);for(let s of e)n.push(` - ${mt(s)}`)}else n.push(`${t}: ${mt(e)}`);return n.join(` -`)}function Nt(c){if(!c.startsWith("---"))return null;let n=c.indexOf(` ----`,3);if(n===-1)return null;let t=c.substring(4,n).trim(),e=n+4,s=c.substring(e);return{yaml:t,body:s}}function Lt(c,n){let t=new Set;for(let o of c.split(` -`)){let a=o.match(/^(\w[\w-]*)\s*:/);a&&t.add(a[1])}let e={};for(let[o,a]of Object.entries(n))t.has(o)||(e[o]=a);if(Object.keys(e).length===0)return c;let s=vt(e);return c+` -`+s}function wt(c,n,t){let e=At(n,t);if(Object.keys(e).length===0)return c;let s=Nt(c);return s?`--- +"use strict";var Ct=Object.create;var L=Object.defineProperty;var _t=Object.getOwnPropertyDescriptor;var Tt=Object.getOwnPropertyNames;var Dt=Object.getPrototypeOf,St=Object.prototype.hasOwnProperty;var Ft=(p,n)=>{for(var t in n)L(p,t,{get:n[t],enumerable:!0})},pt=(p,n,t,e)=>{if(n&&typeof n=="object"||typeof n=="function")for(let s of Tt(n))!St.call(p,s)&&s!==t&&L(p,s,{get:()=>n[s],enumerable:!(e=_t(n,s))||e.enumerable});return p};var x=(p,n,t)=>(t=p!=null?Ct(Dt(p)):{},pt(n||!p||!p.__esModule?L(t,"default",{value:p,enumerable:!0}):t,p)),Mt=p=>pt(L({},"__esModule",{value:!0}),p);var Ht={};Ft(Ht,{default:()=>K});module.exports=Mt(Ht);var y=require("obsidian"),b=x(require("path")),at=x(require("fs"));var X={pythonPath:"python",enablePlugins:!1,pluginArgs:[],docintelEndpoint:"",outputPath:"",imageExtractionEnabled:!1,imageSubfolderTemplate:"{filename}-images",enableBatchProgress:!0,enableContextMenu:!0,enableRecursiveConversion:!1,outputFilenameTemplate:"{filename}",enableDragDrop:!0,enableAutoFrontmatter:!1,autoTags:"",conversionHistory:[]};var M=x(require("fs"));var Q=require("child_process"),k=x(require("path")),Z=".venv",ct=["3.14","3.13","3.12","3.11","3.10","3.9"];function ut(p){let n=process.env.HOME||process.env.USERPROFILE||"",t=process.platform==="win32",e=t?";":":",s=[];t||s.push("/opt/homebrew/bin","/usr/local/bin"),n&&(s.push(k.join(n,".local","bin")),t||s.push(k.join(n,".pyenv","shims")));let a=process.env.PATH||"",o=[...s,a].filter(Boolean),l=new Set,r=o.filter(i=>l.has(i)?!1:(l.add(i),!0));return{...process.env,PYTHONUTF8:"1",PATH:r.join(e),...p}}function It(p){return process.platform==="win32"?k.join(p,Z,"Scripts","python.exe"):k.join(p,Z,"bin","python")}function $t(p,n){let t=p.trim(),e=[];t&&e.push(t),e.push(It(n));let s=process.env.HOME||process.env.USERPROFILE||"",a=process.platform==="win32",o=[];if(process.env.PIPX_HOME&&o.push(process.env.PIPX_HOME),s)if(o.push(k.join(s,".local","pipx")),a){let r=process.env.LOCALAPPDATA||k.join(s,"AppData","Local");o.push(k.join(r,"pipx"))}else process.platform==="darwin"?o.push(k.join(s,"Library","Application Support","pipx")):o.push(k.join(s,".local","share","pipx"));for(let r of o)a?e.push(k.join(r,"venvs","markitdown","Scripts","python.exe")):e.push(k.join(r,"venvs","markitdown","bin","python"));if(a){t==="python"?e.push("python3"):t==="python3"?e.push("python"):e.push("python","python3");let r=process.env.LOCALAPPDATA||"";if(r){for(let i of["314","313","312","311","310","39"])e.push(`${r}\\Programs\\Python\\Python${i}\\python.exe`);e.push(`${r}\\Microsoft\\WindowsApps\\python3.exe`)}}else{t==="python"?e.push("python3"):t==="python3"?e.push("python"):e.push("python3","python"),e.push("/opt/homebrew/bin/python3"),e.push("/usr/local/bin/python3");for(let r of ct)e.push(`/opt/homebrew/bin/python${r}`),e.push(`/usr/local/bin/python${r}`),e.push(`/opt/homebrew/opt/python@${r}/bin/python3`),e.push(`/usr/local/opt/python@${r}/bin/python3`);for(let r of ct)e.push(`/Library/Frameworks/Python.framework/Versions/${r}/bin/python3`);e.push("/usr/bin/python3")}let l=new Set;return e.filter(r=>!r||l.has(r)?!1:(l.add(r),!0))}function O(p,n,t,e){return new Promise((s,a)=>{let o=(0,Q.spawn)(p,[n,...t],{shell:!1,env:ut(e),stdio:["ignore","pipe","pipe"]}),l="",r="";o.stdout.on("data",i=>{l+=i.toString()}),o.stderr.on("data",i=>{r+=i.toString()}),o.on("error",i=>{a(i)}),o.on("close",i=>{s({stdout:l.trim(),stderr:r.trim(),exitCode:i??1})})})}function A(p,n){return k.join(n,"python",p)}async function tt(p,n){let t={pythonInstalled:!1,pythonVersion:null,markitdownInstalled:!1,markitdownVersion:null},e=[],s=p.trim();if(!s)return e.push({path:p||"(empty)",error:"Path is empty or whitespace-only"}),{status:t,resolvedPythonPath:p,triedPaths:e};let a=A("check_install.py",n),o=$t(s,n),l=null;for(let r of o)try{let i=await O(r,a,["--check","all"]);if(i.exitCode===0&&i.stdout){let c=JSON.parse(i.stdout),u={pythonInstalled:!0,pythonVersion:c.python_version??null,markitdownInstalled:!1,markitdownVersion:null};if(c.packages?.markitdown&&(u.markitdownInstalled=c.packages.markitdown.installed,u.markitdownVersion=c.packages.markitdown.version??null),u.markitdownInstalled)return e.push({path:r,error:""}),{status:u,resolvedPythonPath:r,triedPaths:e};e.push({path:r,error:"Python found but markitdown not installed"}),l||(l={status:u,resolvedPythonPath:r})}else{let c=i.stderr||`exit code ${i.exitCode}`;e.push({path:r,error:`Check script failed: ${c}`})}}catch(i){let c=i instanceof Error?i.message:String(i);e.push({path:r,error:c}),console.debug(`markitdown: ${r} failed:`,i);continue}return l?{...l,triedPaths:e}:{status:t,resolvedPythonPath:p,triedPaths:e}}async function dt(p,n,t,e){return p.trim()?new Promise((s,a)=>{let o=A("install_package.py",n),l=k.join(n,Z),r=(0,Q.spawn)(p,[o,"--package",t,"--venv-dir",l],{shell:!1,env:ut(),stdio:["ignore","pipe","pipe"]}),i="",c="";r.stdout.on("data",u=>{i+=u.toString();let d=i.split(` +`);i=d.pop()??"";for(let m of d)m.trim()&&(c=m.trim(),e?.(m.trim()))}),r.stderr.on("data",u=>{let d=u.toString().split(` +`);for(let m of d)m.trim()&&e?.(m.trim())}),r.on("error",u=>{a(u)}),r.on("close",u=>{let d=i.trim();if(d&&(c=d,e?.(d)),u===0)try{let m=JSON.parse(c);s(m.success===!0)}catch{s(!0)}else s(!1)})}):!1}var j=["pdf","docx","pptx","xlsx","xls","html","htm","txt","csv","json","xml","jpg","jpeg","png","gif","wav","mp3","zip","epub"],gt=j.map(p=>`.${p}`).join(","),ht=[{name:"PDF files",extensions:".pdf"},{name:"Word documents",extensions:".docx"},{name:"PowerPoint presentations",extensions:".pptx"},{name:"Excel spreadsheets",extensions:".xlsx,.xls"},{name:"Web pages",extensions:".html,.htm"},{name:"Text files",extensions:".txt"},{name:"Data files",extensions:".csv,.json,.xml"},{name:"Images",extensions:".jpg,.jpeg,.png,.gif"},{name:"Audio files",extensions:".wav,.mp3"},{name:"Archives & eBooks",extensions:".zip,.epub"}];function et(p){let n=p.toLowerCase().replace(/^\./,"");return j.includes(n)}var ft=x(require("path"));function mt(p){return/[:#\[\]{}&*!|>'"`,@%\\]/.test(p)||p.trim()!==p?`"${p.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`:p}function At(p,n){let t={};if(n.enableAutoFrontmatter){let e=ft.basename(p),s=new Date,a=s.getFullYear(),o=String(s.getMonth()+1).padStart(2,"0"),l=String(s.getDate()).padStart(2,"0");t.source=e,t.converted=`${a}-${o}-${l}`,t.converter="markitdown"}if(n.autoTags.trim()){let e=n.autoTags.split(",").map(s=>s.trim()).filter(s=>s.length>0);e.length>0&&(t.tags=e)}return t}function vt(p){let n=[];for(let[t,e]of Object.entries(p))if(Array.isArray(e)){n.push(`${t}:`);for(let s of e)n.push(` - ${mt(s)}`)}else n.push(`${t}: ${mt(e)}`);return n.join(` +`)}function Nt(p){if(!p.startsWith("---"))return null;let n=p.indexOf(` +---`,3);if(n===-1)return null;let t=p.substring(4,n).trim(),e=n+4,s=p.substring(e);return{yaml:t,body:s}}function Lt(p,n){let t=new Set;for(let a of p.split(` +`)){let o=a.match(/^(\w[\w-]*)\s*:/);o&&t.add(o[1])}let e={};for(let[a,o]of Object.entries(n))t.has(a)||(e[a]=o);if(Object.keys(e).length===0)return p;let s=vt(e);return p+` +`+s}function wt(p,n,t){let e=At(n,t);if(Object.keys(e).length===0)return p;let s=Nt(p);return s?`--- ${Lt(s.yaml,e)} ---${s.body}`:`--- ${vt(e)} --- -${c}`}var I=class{constructor(n,t){this.pythonPath=n;this.pluginDir=t}getSupportedExtensions(){return j.map(n=>`.${n}`)}canConvert(n){let t=n.startsWith(".")?n.toLowerCase():`.${n.toLowerCase()}`;return this.getSupportedExtensions().includes(t)}async convertUrl(n,t,e){let s=Date.now(),o=A("markitdown_wrapper.py",this.pluginDir),a=["--url",n,"--output",t];e?.enablePlugins&&a.push("--enable-plugins"),e?.pluginArgs&&Object.keys(e.pluginArgs).length>0&&a.push("--plugin-args",JSON.stringify(e.pluginArgs)),e?.docintelEndpoint&&a.push("--docintel-endpoint",e.docintelEndpoint);try{let l=await O(this.pythonPath,o,a);if(l.exitCode!==0){let r="Unknown error";try{r=JSON.parse(l.stderr).error||"Unknown error"}catch{r=l.stderr||"Unknown error"}return r=r.replace(/(?:\/[\w.-]+)+\//g,"\u2026/"),{success:!1,error:`Conversion failed: ${r}`,processingTime:Date.now()-s}}return M.existsSync(t)?{success:!0,outputPath:t,processingTime:Date.now()-s}:{success:!1,error:"Output file was not created",processingTime:Date.now()-s}}catch(l){return{success:!1,error:`Conversion error: ${(l instanceof Error?l.message:String(l)).replace(/(?:\/[\w.-]+)+\//g,"\u2026/")}`,processingTime:Date.now()-s}}}async convert(n,t,e){let s=Date.now(),o=A("markitdown_wrapper.py",this.pluginDir),a=["--input",n,"--output",t];e?.enablePlugins&&a.push("--enable-plugins"),e?.pluginArgs&&Object.keys(e.pluginArgs).length>0&&a.push("--plugin-args",JSON.stringify(e.pluginArgs)),e?.docintelEndpoint&&a.push("--docintel-endpoint",e.docintelEndpoint),e?.extractImages&&e?.imageDir&&a.push("--extract-images","--image-dir",e.imageDir);try{let l=await O(this.pythonPath,o,a);if(l.exitCode!==0){let i="Unknown error";try{i=JSON.parse(l.stderr).error||"Unknown error"}catch{i=l.stderr||"Unknown error"}return i=i.replace(/(?:\/[\w.-]+)+\//g,"\u2026/"),{success:!1,error:`Conversion failed: ${i}`,processingTime:Date.now()-s}}if(!M.existsSync(t))return{success:!1,error:"Output file was not created",processingTime:Date.now()-s};if(e?.postProcess)try{let i=M.readFileSync(t,"utf-8"),p=wt(i,e.postProcess.inputPath,e.postProcess.settings);p!==i&&M.writeFileSync(t,p,"utf-8")}catch(i){console.warn("markitdown: post-conversion hook error:",i)}let r=0;try{r=JSON.parse(l.stdout).images_extracted??0}catch{}return{success:!0,outputPath:t,processingTime:Date.now()-s,imagesExtracted:r}}catch(l){return{success:!1,error:`Conversion error: ${(l instanceof Error?l.message:String(l)).replace(/(?:\/[\w.-]+)+\//g,"\u2026/")}`,processingTime:Date.now()-s}}}};var yt=require("obsidian"),h=y(require("path")),R=y(require("fs"));function b(c){return c.vault.adapter instanceof yt.FileSystemAdapter?c.vault.adapter.getBasePath():null}function P(c,n){let t;n?t=h.resolve(c,n):t=h.join(c,"markitdown-output");let e=h.resolve(c);return!t.startsWith(e+h.sep)&&t!==e&&(t=h.join(c,"markitdown-output")),R.existsSync(t)||R.mkdirSync(t,{recursive:!0}),t}function $(c,n){let t=h.basename(n,h.extname(n)),e=h.extname(n).replace(/^\./,""),s=new Date,o=i=>String(i).padStart(2,"0"),a=`${s.getFullYear()}-${o(s.getMonth()+1)}-${o(s.getDate())}`,l=`${a}_${o(s.getHours())}${o(s.getMinutes())}${o(s.getSeconds())}`,r=c.replace(/\{filename\}/g,t).replace(/\{ext\}/g,e).replace(/\{date\}/g,a).replace(/\{datetime\}/g,l);return r=r.replace(/[/\\:*?"<>|]/g,""),r.trim()||(r=t),r}function xt(c,n){let t=h.dirname(c),e=h.basename(c,".md"),o=n.replace(/[/\\]/g,"-").replace("{filename}",e);return h.join(t,o)}function nt(c,n){return h.relative(n,c).replace(/\\/g,"/")}var Ot=100;function st(c,n){return c.conversionHistory=[n,...c.conversionHistory].slice(0,Ot),c.conversionHistory}var V=y(require("fs")),rt=y(require("path")),jt=`#!/usr/bin/env python3 +${p}`}var I=class{constructor(n,t){this.pythonPath=n;this.pluginDir=t}getSupportedExtensions(){return j.map(n=>`.${n}`)}canConvert(n){let t=n.startsWith(".")?n.toLowerCase():`.${n.toLowerCase()}`;return this.getSupportedExtensions().includes(t)}async convertUrl(n,t,e){let s=Date.now(),a=A("markitdown_wrapper.py",this.pluginDir),o=["--url",n,"--output",t];e?.enablePlugins&&o.push("--enable-plugins"),e?.pluginArgs&&Object.keys(e.pluginArgs).length>0&&o.push("--plugin-args",JSON.stringify(e.pluginArgs)),e?.docintelEndpoint&&o.push("--docintel-endpoint",e.docintelEndpoint);try{let l=await O(this.pythonPath,a,o);if(l.exitCode!==0){let r="Unknown error";try{r=JSON.parse(l.stderr).error||"Unknown error"}catch{r=l.stderr||"Unknown error"}return r=r.replace(/(?:\/[\w.-]+)+\//g,"\u2026/"),{success:!1,error:`Conversion failed: ${r}`,processingTime:Date.now()-s}}return M.existsSync(t)?{success:!0,outputPath:t,processingTime:Date.now()-s}:{success:!1,error:"Output file was not created",processingTime:Date.now()-s}}catch(l){return{success:!1,error:`Conversion error: ${(l instanceof Error?l.message:String(l)).replace(/(?:\/[\w.-]+)+\//g,"\u2026/")}`,processingTime:Date.now()-s}}}async convert(n,t,e){let s=Date.now(),a=A("markitdown_wrapper.py",this.pluginDir),o=["--input",n,"--output",t];e?.enablePlugins&&o.push("--enable-plugins"),e?.pluginArgs&&Object.keys(e.pluginArgs).length>0&&o.push("--plugin-args",JSON.stringify(e.pluginArgs)),e?.docintelEndpoint&&o.push("--docintel-endpoint",e.docintelEndpoint),e?.extractImages&&e?.imageDir&&o.push("--extract-images","--image-dir",e.imageDir);try{let l=await O(this.pythonPath,a,o);if(l.exitCode!==0){let i="Unknown error";try{i=JSON.parse(l.stderr).error||"Unknown error"}catch{i=l.stderr||"Unknown error"}return i=i.replace(/(?:\/[\w.-]+)+\//g,"\u2026/"),{success:!1,error:`Conversion failed: ${i}`,processingTime:Date.now()-s}}if(!M.existsSync(t))return{success:!1,error:"Output file was not created",processingTime:Date.now()-s};if(e?.postProcess)try{let i=M.readFileSync(t,"utf-8"),c=wt(i,e.postProcess.inputPath,e.postProcess.settings);c!==i&&M.writeFileSync(t,c,"utf-8")}catch(i){console.warn("markitdown: post-conversion hook error:",i)}let r=0;try{r=JSON.parse(l.stdout).images_extracted??0}catch{}return{success:!0,outputPath:t,processingTime:Date.now()-s,imagesExtracted:r}}catch(l){return{success:!1,error:`Conversion error: ${(l instanceof Error?l.message:String(l)).replace(/(?:\/[\w.-]+)+\//g,"\u2026/")}`,processingTime:Date.now()-s}}}};var yt=require("obsidian"),h=x(require("path")),R=x(require("fs"));function P(p){return p.vault.adapter instanceof yt.FileSystemAdapter?p.vault.adapter.getBasePath():null}function E(p,n){let t;n?t=h.resolve(p,n):t=h.join(p,"markitdown-output");let e=h.resolve(p);return!t.startsWith(e+h.sep)&&t!==e&&(t=h.join(p,"markitdown-output")),R.existsSync(t)||R.mkdirSync(t,{recursive:!0}),t}function $(p,n){let t=h.basename(n,h.extname(n)),e=h.extname(n).replace(/^\./,""),s=new Date,a=i=>String(i).padStart(2,"0"),o=`${s.getFullYear()}-${a(s.getMonth()+1)}-${a(s.getDate())}`,l=`${o}_${a(s.getHours())}${a(s.getMinutes())}${a(s.getSeconds())}`,r=p.replace(/\{filename\}/g,t).replace(/\{ext\}/g,e).replace(/\{date\}/g,o).replace(/\{datetime\}/g,l);return r=r.replace(/[/\\:*?"<>|]/g,""),r.trim()||(r=t),r}function xt(p,n){let t=h.dirname(p),e=h.basename(p,".md"),a=n.replace(/[/\\]/g,"-").replace("{filename}",e);return h.join(t,a)}function nt(p,n){return h.relative(n,p).replace(/\\/g,"/")}var Ot=100;function st(p,n){return p.conversionHistory=[n,...p.conversionHistory].slice(0,Ot),p.conversionHistory}var V=x(require("fs")),rt=x(require("path")),jt=`#!/usr/bin/env python3 """Check Python environment and installed packages.""" import argparse import json @@ -63,6 +63,7 @@ if __name__ == "__main__": import argparse import json import os +import shutil import subprocess import sys @@ -75,8 +76,9 @@ def run_pip(python, package): process = subprocess.Popen( [python, "-m", "pip", "install", package], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) - for line in process.stdout: - print(line, end="", flush=True) + if process.stdout is not None: + for line in process.stdout: + print(line, end="", flush=True) process.wait() return process.returncode or 0 @@ -89,6 +91,7 @@ def ensure_venv(venv_dir, bootstrap_python): [bootstrap_python, "-m", "venv", venv_dir], capture_output=True, text=True) if result.returncode != 0 or not os.path.isfile(py): + shutil.rmtree(venv_dir, ignore_errors=True) detail = (result.stderr or result.stdout or "").strip() raise RuntimeError( f"Failed to create virtual environment at {venv_dir}: {detail or f'exit {result.returncode}'}") @@ -225,6 +228,6 @@ def main(): if __name__ == "__main__": main() -`,Bt={"check_install.py":jt,"install_package.py":Rt,"markitdown_wrapper.py":Vt};async function kt(c){let n=rt.join(c,"python");try{await V.promises.mkdir(n,{recursive:!0})}catch{}for(let[t,e]of Object.entries(Bt)){let s=rt.join(n,t);try{if(await V.promises.readFile(s,"utf-8")===e)continue}catch{}await V.promises.writeFile(s,e,"utf-8")}}var g=require("obsidian");var B=class{constructor(n,t,e){this.containerEl=n;this.entries=t;this.onChange=e}render(){this.containerEl.empty();for(let t=0;t{this.entries[t].key=s.value,this.onChange(this.entries)});let o=e.createEl("input",{attr:{type:"text",placeholder:"Value"},cls:"markitdown-plugin-arg-value"});o.value=this.entries[t].value,o.addEventListener("input",()=>{this.entries[t].value=o.value,this.onChange(this.entries)}),e.createEl("button",{text:"\xD7",cls:"markitdown-plugin-arg-delete",attr:{"aria-label":"Remove argument"}}).addEventListener("click",()=>{this.entries.splice(t,1),this.onChange(this.entries),this.render()})}this.containerEl.createEl("button",{text:"+ Add argument",cls:"markitdown-plugin-arg-add"}).addEventListener("click",()=>{this.entries.push({key:"",value:""}),this.onChange(this.entries),this.render()})}};var H=class extends g.PluginSettingTab{constructor(t,e){super(t,e);this.pythonPathDebounceTimer=null;this.plugin=e}hide(){this.pythonPathDebounceTimer&&(clearTimeout(this.pythonPathDebounceTimer),this.pythonPathDebounceTimer=null,this.plugin.refreshDependencies().catch(console.error))}cancelPythonPathDebounce(){this.pythonPathDebounceTimer&&(clearTimeout(this.pythonPathDebounceTimer),this.pythonPathDebounceTimer=null)}display(){this.cancelPythonPathDebounce();let{containerEl:t}=this;t.empty(),new g.Setting(t).setName("Python").setHeading(),new g.Setting(t).setName("Python path").setDesc("Path to Python executable (e.g., python, python3, or a full path like C:\\Python311\\python.exe)").addText(r=>r.setPlaceholder("python").setValue(this.plugin.settings.pythonPath).onChange(async i=>{this.plugin.settings.pythonPath=i,await this.plugin.saveSettings(),this.pythonPathDebounceTimer&&clearTimeout(this.pythonPathDebounceTimer),this.pythonPathDebounceTimer=setTimeout(async()=>{this.pythonPathDebounceTimer=null,await this.plugin.refreshDependencies(),this.containerEl.isConnected&&this.display()},1500)}));let e=this.plugin.settings.pythonPath||"python",s=this.plugin.resolvedPythonPath;if(s&&s!==e&&t.createDiv("markitdown-resolved-path-hint").setText(`Resolved: ${s}`),new g.Setting(t).setName("Conversion").setHeading(),new g.Setting(t).setName("Output folder").setDesc('Folder for converted files (relative to vault root). Leave empty for "markitdown-output".').addText(r=>r.setPlaceholder("markitdown-output").setValue(this.plugin.settings.outputPath).onChange(async i=>{this.plugin.settings.outputPath=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Output filename template").setDesc("Template for converted filenames. Variables: {filename} (original name), {ext} (original extension), {date} (YYYY-MM-DD), {datetime} (YYYY-MM-DD_HHmmss). The .md extension is always appended automatically.").addText(r=>r.setPlaceholder("{filename}").setValue(this.plugin.settings.outputFilenameTemplate).onChange(async i=>{this.plugin.settings.outputFilenameTemplate=i||"{filename}",await this.plugin.saveSettings()})),new g.Setting(t).setName("Extract images").setDesc("Extract embedded images from PDFs and save as separate files").addToggle(r=>r.setValue(this.plugin.settings.imageExtractionEnabled).onChange(async i=>{this.plugin.settings.imageExtractionEnabled=i,await this.plugin.saveSettings(),this.display()})),this.plugin.settings.imageExtractionEnabled&&new g.Setting(t).setName("Image subfolder").setDesc("Subfolder name template for extracted images. Use {filename} as placeholder.").addText(r=>r.setPlaceholder("{filename}-images").setValue(this.plugin.settings.imageSubfolderTemplate).onChange(async i=>{this.plugin.settings.imageSubfolderTemplate=i||"{filename}-images",await this.plugin.saveSettings()})),new g.Setting(t).setName("Recursive folder conversion").setDesc("Include subfolders by default when converting a folder").addToggle(r=>r.setValue(this.plugin.settings.enableRecursiveConversion).onChange(async i=>{this.plugin.settings.enableRecursiveConversion=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Show batch progress").setDesc("Display a progress bar during batch folder conversions").addToggle(r=>r.setValue(this.plugin.settings.enableBatchProgress).onChange(async i=>{this.plugin.settings.enableBatchProgress=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Context menu").setDesc('Show "Convert to Markdown" in the file explorer right-click menu (requires restart)').addToggle(r=>r.setValue(this.plugin.settings.enableContextMenu).onChange(async i=>{this.plugin.settings.enableContextMenu=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Drag and drop").setDesc("Convert supported files automatically when dropped into the editor").addToggle(r=>r.setValue(this.plugin.settings.enableDragDrop).onChange(async i=>{this.plugin.settings.enableDragDrop=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Post-conversion").setHeading(),new g.Setting(t).setName("Auto frontmatter").setDesc("Automatically add YAML frontmatter with source filename, conversion date, and converter name").addToggle(r=>r.setValue(this.plugin.settings.enableAutoFrontmatter).onChange(async i=>{this.plugin.settings.enableAutoFrontmatter=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Auto tags").setDesc('Comma-separated tags to add to frontmatter (e.g., "converted, imported, pdf"). Requires auto frontmatter or adds a frontmatter block for tags alone.').addText(r=>r.setPlaceholder("converted, imported").setValue(this.plugin.settings.autoTags).onChange(async i=>{this.plugin.settings.autoTags=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Advanced").setHeading(),new g.Setting(t).setName("Enable Markitdown plugins").setDesc("Enable third-party Markitdown plugins (e.g., markitdown-pdf-separators)").addToggle(r=>r.setValue(this.plugin.settings.enablePlugins).onChange(async i=>{this.plugin.settings.enablePlugins=i,await this.plugin.saveSettings(),this.display()})),this.plugin.settings.enablePlugins){let r=new g.Setting(t).setName("Plugin arguments").setDesc("Key-value arguments passed to Markitdown plugins"),i=t.createDiv("markitdown-plugin-args-container");new B(i,this.plugin.settings.pluginArgs,async u=>{this.plugin.settings.pluginArgs=u,await this.plugin.saveSettings()}).render()}new g.Setting(t).setName("Azure Document Intelligence endpoint").setDesc("Optional: Azure endpoint URL for enhanced PDF conversion").addText(r=>r.setPlaceholder("https://your-resource.cognitiveservices.azure.com/").setValue(this.plugin.settings.docintelEndpoint).onChange(async i=>{this.plugin.settings.docintelEndpoint=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Status").setHeading();let o=t.createDiv("markitdown-status-container"),a=this.plugin.dependencyStatus;if(this.renderStatusItem(o,"Python",a.pythonInstalled,a.pythonVersion?`v${a.pythonVersion}`:void 0),this.renderStatusItem(o,"Markitdown",a.markitdownInstalled,a.markitdownVersion?`v${a.markitdownVersion}`:void 0),a.pythonInstalled&&o.createDiv("markitdown-resolved-path-hint").setText(`Using: ${this.plugin.resolvedPythonPath}`),a.pythonInstalled||this.renderPythonTroubleshooting(t),a.pythonInstalled&&!a.markitdownInstalled&&this.renderMarkitdownTroubleshooting(t),!a.markitdownInstalled&&a.pythonInstalled){let r=t.createEl("button",{text:"Install Markitdown",cls:"markitdown-install-button"});r.addEventListener("click",async()=>{r.disabled=!0,r.setText("Installing...");try{await this.plugin.installMarkitdown()?(new g.Notice("Markitdown installed successfully!"),await this.plugin.refreshDependencies(),this.display()):(new g.Notice("Failed to install Markitdown. Check the console for errors."),r.disabled=!1,r.setText("Try again"))}catch(i){let p=i instanceof Error?i.message:String(i);new g.Notice(`Error: ${p}`),r.disabled=!1,r.setText("Try again")}})}let l=t.createEl("button",{text:"Refresh status",cls:"markitdown-install-button"});l.addEventListener("click",async()=>{l.disabled=!0,l.setText("Checking..."),await this.plugin.refreshDependencies(),this.display()})}renderPythonTroubleshooting(t){let e=t.createEl("details",{cls:"markitdown-troubleshooting"});e.createEl("summary",{text:"Troubleshooting: Python not found"});let s=e.createDiv("markitdown-troubleshooting-content"),o=this.plugin.pythonDiscoveryLog;if(o.length>0){s.createEl("p",{text:"The following paths were checked:",cls:"markitdown-troubleshooting-label"});let i=s.createEl("ul",{cls:"markitdown-tried-paths"});for(let p of o){let u=i.createEl("li"),d=u.createEl("code",{text:p.path});p.error&&u.createSpan({text:` \u2014 ${p.error}`,cls:"markitdown-tried-path-error"})}}let a=s.createEl("p");a.createSpan({text:"Download Python from "}),a.createEl("a",{text:"python.org",href:"https://www.python.org/downloads/"});let r=process.platform==="win32"?"C:\\Python311\\python.exe":"/usr/local/bin/python3";s.createEl("p",{text:"Example path for your platform: ",cls:"markitdown-troubleshooting-label"}).createEl("code",{text:r})}renderMarkitdownTroubleshooting(t){let e=t.createEl("details",{cls:"markitdown-troubleshooting"});e.createEl("summary",{text:"Troubleshooting: markitdown not installed"});let s=e.createDiv("markitdown-troubleshooting-content");s.createEl("p",{text:'Python was found, but the markitdown package is not importable from that interpreter. Prefer the "Install Markitdown" button below \u2014 it creates an isolated plugin venv (avoids Homebrew PEP 668 / externally-managed-environment errors).'}),s.createEl("p",{text:"Already installed markitdown another way? The plugin also looks for versioned Homebrew Pythons (e.g. python3.11) and pipx at ~/.local/pipx/venvs/markitdown. Click Refresh status after installing.",cls:"markitdown-troubleshooting-hint"}),s.createEl("p",{text:"Manual alternatives:"});let o='pipx install "markitdown[all]"',a=`${this.plugin.resolvedPythonPath} -m pip install "markitdown[all]"`;for(let l of[o,a]){let r=s.createDiv("markitdown-pip-command");r.createEl("code",{text:l});let i=r.createEl("button",{text:"Copy",cls:"markitdown-copy-button"});i.addEventListener("click",()=>{navigator.clipboard.writeText(l).then(()=>{i.setText("Copied!"),setTimeout(()=>i.setText("Copy"),2e3)})})}}renderStatusItem(t,e,s,o){let a=t.createDiv("markitdown-status-item"),l=a.createSpan("markitdown-status-icon");l.addClass(s?"success":"error"),l.setText(s?"\u2713":"\u2717");let r=`${e}: ${s?"Installed":"Not installed"}`;o&&(r+=` (${o})`),a.createSpan().setText(r)}};var D=require("obsidian"),it=y(require("path")),ot=y(require("fs"));var U=class extends D.Modal{constructor(n,t){super(n),this.plugin=t}onOpen(){let{contentEl:n}=this;n.addClass("markitdown-modal"),n.createEl("h2",{text:"Convert file to Markdown"}),n.createEl("p",{text:"Select a file to convert:"});let e=n.createDiv("markitdown-file-input-container").createEl("input",{attr:{type:"file",accept:gt}}),o=n.createDiv("markitdown-button-container").createEl("button",{text:"Convert",cls:"mod-cta"});o.addEventListener("click",async()=>{if(!e.files||e.files.length===0){new D.Notice("Please select a file first");return}let a=e.files[0];o.disabled=!0,o.setText("Converting...");try{let l=b(this.app);if(!l){new D.Notice("Could not determine vault path. This plugin requires a local vault."),o.disabled=!1,o.setText("Convert");return}let r=P(l,this.plugin.settings.outputPath),i=$(this.plugin.settings.outputFilenameTemplate||"{filename}",a.name),p=it.join(r,`${i}.md`),u=a.name.replace(/[^a-zA-Z0-9._-]/g,"_"),d=it.join(r,`tmp_${Date.now()}_${u}`),E=await a.arrayBuffer();await ot.promises.writeFile(d,Buffer.from(E));try{let f=await this.plugin.convertExternalFile(d,p);if(f.success){let v=f.imagesExtracted?`Converted successfully (${f.imagesExtracted} images extracted)`:"Converted successfully";new D.Notice(v),await this.plugin.openConvertedFile(p,l),this.close()}else new D.Notice(`Conversion failed: ${f.error}`),o.disabled=!1,o.setText("Convert")}finally{await ot.promises.unlink(d).catch(()=>{})}}catch(l){let r=l instanceof Error?l.message:String(l);new D.Notice(`Error: ${r}`),o.disabled=!1,o.setText("Convert")}})}onClose(){this.contentEl.empty()}};var C=require("obsidian"),S=y(require("path")),N=y(require("fs"));var bt=require("obsidian"),W=class extends bt.Modal{constructor(n,t){super(n),this.totalFiles=t}onOpen(){let{contentEl:n}=this;n.addClass("markitdown-modal"),n.createEl("h2",{text:"Converting files"});let t=n.createDiv("markitdown-progress-bar");this.progressFill=t.createDiv("markitdown-progress-fill"),this.progressFill.style.width="0%",this.statusEl=n.createDiv("markitdown-progress-status"),this.statusEl.setText(`0 / ${this.totalFiles} files converted`),this.currentFileEl=n.createDiv("markitdown-progress-current"),this.currentFileEl.setText("Preparing...")}updateProgress(n,t,e,s){let o=this.totalFiles>0?Math.round(n/this.totalFiles*100):0;this.progressFill.style.width=`${o}%`,this.statusEl.setText(`${n} / ${this.totalFiles} files converted (${e} successful, ${s} failed)`),this.currentFileEl.setText(`Converting: ${t}`)}complete(n,t){this.progressFill.style.width="100%",this.statusEl.setText(`Conversion complete: ${n} successful, ${t} failed`),this.currentFileEl.setText("Done!"),setTimeout(()=>this.close(),2e3)}onClose(){this.contentEl.empty()}};var Y=class extends C.Modal{constructor(n,t){super(n),this.plugin=t}onOpen(){let{contentEl:n}=this;n.addClass("markitdown-modal"),n.createEl("h2",{text:"Convert folder contents to Markdown"}),n.createEl("p",{text:"Select a folder to process:"});let e=n.createDiv("markitdown-file-input-container").createEl("input",{attr:{type:"file",webkitdirectory:"",directory:""}}),s=this.plugin.settings.enableRecursiveConversion,a=n.createDiv("markitdown-setting-row").createEl("label",{cls:"markitdown-checkbox-label"}),l=a.createEl("input",{attr:{type:"checkbox"}});l.checked=s,l.addEventListener("change",()=>{s=l.checked}),a.appendText(" Include subfolders (recursive)"),n.createEl("p",{text:"Select file types to convert:"});let r=n.createDiv("markitdown-checkbox-grid"),i=[];for(let d of ht){let E=r.createEl("label",{cls:"markitdown-checkbox-label"}),f=E.createEl("input",{attr:{type:"checkbox",value:d.extensions}});f.addEventListener("change",()=>{let v=d.extensions.split(",");if(f.checked)for(let m of v)i.includes(m)||i.push(m);else for(let m of v){let T=i.indexOf(m);T>-1&&i.splice(T,1)}}),E.appendText(d.name)}n.createDiv("markitdown-button-container").createEl("button",{text:"Convert",cls:"mod-cta"}).addEventListener("click",async()=>{if(!e.files||e.files.length===0){new C.Notice("Please select a folder first");return}if(i.length===0){new C.Notice("Please select at least one file type");return}let E=e.files[0].webkitRelativePath.split("/")[0],f=[];for(let v=0;v2)continue;let Pt=lt.slice(1).join("/");f.push({file:m,absolutePath:null,name:m.name,relativePath:Pt})}if(f.length===0){new C.Notice("No matching files found in the selected folder");return}this.close(),await this.runBatchConversion(f)})}async runBatchConversion(n){let t=b(this.app);if(!t){new C.Notice("Could not determine vault path. This plugin requires a local vault.");return}let e=P(t,this.plugin.settings.outputPath),s=null;this.plugin.settings.enableBatchProgress?(s=new W(this.app,n.length),s.open()):new C.Notice(`Converting ${n.length} files...`);let o=0,a=0,l=[];for(let r=0;r{})}s?.updateProgress(r+1,i.name,o,a)}if(s)s.complete(o,a);else{let r=`Conversion complete: ${o} successful, ${a} failed`;l.length>0&&l.length<=3&&(r+=` +`,Bt={"check_install.py":jt,"install_package.py":Rt,"markitdown_wrapper.py":Vt};async function kt(p){let n=rt.join(p,"python");try{await V.promises.mkdir(n,{recursive:!0})}catch{}for(let[t,e]of Object.entries(Bt)){let s=rt.join(n,t);try{if(await V.promises.readFile(s,"utf-8")===e)continue}catch{}await V.promises.writeFile(s,e,"utf-8")}}var g=require("obsidian");var B=class{constructor(n,t,e){this.containerEl=n;this.entries=t;this.onChange=e}render(){this.containerEl.empty();for(let t=0;t{this.entries[t].key=s.value,this.onChange(this.entries)});let a=e.createEl("input",{attr:{type:"text",placeholder:"Value"},cls:"markitdown-plugin-arg-value"});a.value=this.entries[t].value,a.addEventListener("input",()=>{this.entries[t].value=a.value,this.onChange(this.entries)}),e.createEl("button",{text:"\xD7",cls:"markitdown-plugin-arg-delete",attr:{"aria-label":"Remove argument"}}).addEventListener("click",()=>{this.entries.splice(t,1),this.onChange(this.entries),this.render()})}this.containerEl.createEl("button",{text:"+ Add argument",cls:"markitdown-plugin-arg-add"}).addEventListener("click",()=>{this.entries.push({key:"",value:""}),this.onChange(this.entries),this.render()})}};var H=class extends g.PluginSettingTab{constructor(t,e){super(t,e);this.pythonPathDebounceTimer=null;this.plugin=e}hide(){this.pythonPathDebounceTimer&&(clearTimeout(this.pythonPathDebounceTimer),this.pythonPathDebounceTimer=null,this.plugin.refreshDependencies().catch(console.error))}cancelPythonPathDebounce(){this.pythonPathDebounceTimer&&(clearTimeout(this.pythonPathDebounceTimer),this.pythonPathDebounceTimer=null)}display(){this.cancelPythonPathDebounce();let{containerEl:t}=this;t.empty(),new g.Setting(t).setName("Python").setHeading(),new g.Setting(t).setName("Python path").setDesc("Path to Python executable (e.g., python, python3, or a full path like C:\\Python311\\python.exe)").addText(r=>r.setPlaceholder("python").setValue(this.plugin.settings.pythonPath).onChange(async i=>{this.plugin.settings.pythonPath=i,await this.plugin.saveSettings(),this.pythonPathDebounceTimer&&clearTimeout(this.pythonPathDebounceTimer),this.pythonPathDebounceTimer=setTimeout(async()=>{this.pythonPathDebounceTimer=null,await this.plugin.refreshDependencies(),this.containerEl.isConnected&&this.display()},1500)}));let e=this.plugin.settings.pythonPath||"python",s=this.plugin.resolvedPythonPath;if(s&&s!==e&&t.createDiv("markitdown-resolved-path-hint").setText(`Resolved: ${s}`),new g.Setting(t).setName("Conversion").setHeading(),new g.Setting(t).setName("Output folder").setDesc('Folder for converted files (relative to vault root). Leave empty for "markitdown-output".').addText(r=>r.setPlaceholder("markitdown-output").setValue(this.plugin.settings.outputPath).onChange(async i=>{this.plugin.settings.outputPath=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Output filename template").setDesc("Template for converted filenames. Variables: {filename} (original name), {ext} (original extension), {date} (YYYY-MM-DD), {datetime} (YYYY-MM-DD_HHmmss). The .md extension is always appended automatically.").addText(r=>r.setPlaceholder("{filename}").setValue(this.plugin.settings.outputFilenameTemplate).onChange(async i=>{this.plugin.settings.outputFilenameTemplate=i||"{filename}",await this.plugin.saveSettings()})),new g.Setting(t).setName("Extract images").setDesc("Extract embedded images from PDFs and save as separate files").addToggle(r=>r.setValue(this.plugin.settings.imageExtractionEnabled).onChange(async i=>{this.plugin.settings.imageExtractionEnabled=i,await this.plugin.saveSettings(),this.display()})),this.plugin.settings.imageExtractionEnabled&&new g.Setting(t).setName("Image subfolder").setDesc("Subfolder name template for extracted images. Use {filename} as placeholder.").addText(r=>r.setPlaceholder("{filename}-images").setValue(this.plugin.settings.imageSubfolderTemplate).onChange(async i=>{this.plugin.settings.imageSubfolderTemplate=i||"{filename}-images",await this.plugin.saveSettings()})),new g.Setting(t).setName("Recursive folder conversion").setDesc("Include subfolders by default when converting a folder").addToggle(r=>r.setValue(this.plugin.settings.enableRecursiveConversion).onChange(async i=>{this.plugin.settings.enableRecursiveConversion=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Show batch progress").setDesc("Display a progress bar during batch folder conversions").addToggle(r=>r.setValue(this.plugin.settings.enableBatchProgress).onChange(async i=>{this.plugin.settings.enableBatchProgress=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Context menu").setDesc('Show "Convert to Markdown" in the file explorer right-click menu (requires restart)').addToggle(r=>r.setValue(this.plugin.settings.enableContextMenu).onChange(async i=>{this.plugin.settings.enableContextMenu=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Drag and drop").setDesc("Convert supported files automatically when dropped into the editor").addToggle(r=>r.setValue(this.plugin.settings.enableDragDrop).onChange(async i=>{this.plugin.settings.enableDragDrop=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Post-conversion").setHeading(),new g.Setting(t).setName("Auto frontmatter").setDesc("Automatically add YAML frontmatter with source filename, conversion date, and converter name").addToggle(r=>r.setValue(this.plugin.settings.enableAutoFrontmatter).onChange(async i=>{this.plugin.settings.enableAutoFrontmatter=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Auto tags").setDesc('Comma-separated tags to add to frontmatter (e.g., "converted, imported, pdf"). Requires auto frontmatter or adds a frontmatter block for tags alone.').addText(r=>r.setPlaceholder("converted, imported").setValue(this.plugin.settings.autoTags).onChange(async i=>{this.plugin.settings.autoTags=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Advanced").setHeading(),new g.Setting(t).setName("Enable Markitdown plugins").setDesc("Enable third-party Markitdown plugins (e.g., markitdown-pdf-separators)").addToggle(r=>r.setValue(this.plugin.settings.enablePlugins).onChange(async i=>{this.plugin.settings.enablePlugins=i,await this.plugin.saveSettings(),this.display()})),this.plugin.settings.enablePlugins){let r=new g.Setting(t).setName("Plugin arguments").setDesc("Key-value arguments passed to Markitdown plugins"),i=t.createDiv("markitdown-plugin-args-container");new B(i,this.plugin.settings.pluginArgs,async u=>{this.plugin.settings.pluginArgs=u,await this.plugin.saveSettings()}).render()}new g.Setting(t).setName("Azure Document Intelligence endpoint").setDesc("Optional: Azure endpoint URL for enhanced PDF conversion").addText(r=>r.setPlaceholder("https://your-resource.cognitiveservices.azure.com/").setValue(this.plugin.settings.docintelEndpoint).onChange(async i=>{this.plugin.settings.docintelEndpoint=i,await this.plugin.saveSettings()})),new g.Setting(t).setName("Status").setHeading();let a=t.createDiv("markitdown-status-container"),o=this.plugin.dependencyStatus;if(this.renderStatusItem(a,"Python",o.pythonInstalled,o.pythonVersion?`v${o.pythonVersion}`:void 0),this.renderStatusItem(a,"Markitdown",o.markitdownInstalled,o.markitdownVersion?`v${o.markitdownVersion}`:void 0),o.pythonInstalled&&a.createDiv("markitdown-resolved-path-hint").setText(`Using: ${this.plugin.resolvedPythonPath}`),o.pythonInstalled||this.renderPythonTroubleshooting(t),o.pythonInstalled&&!o.markitdownInstalled&&this.renderMarkitdownTroubleshooting(t),!o.markitdownInstalled&&o.pythonInstalled){let r=t.createEl("button",{text:"Install Markitdown",cls:"markitdown-install-button"});r.addEventListener("click",async()=>{r.disabled=!0,r.setText("Installing...");try{await this.plugin.installMarkitdown()?(new g.Notice("Markitdown installed successfully!"),await this.plugin.refreshDependencies(),this.display()):(new g.Notice("Failed to install Markitdown. Check the console for errors."),r.disabled=!1,r.setText("Try again"))}catch(i){let c=i instanceof Error?i.message:String(i);new g.Notice(`Error: ${c}`),r.disabled=!1,r.setText("Try again")}})}let l=t.createEl("button",{text:"Refresh status",cls:"markitdown-install-button"});l.addEventListener("click",async()=>{l.disabled=!0,l.setText("Checking..."),await this.plugin.refreshDependencies(),this.display()})}renderPythonTroubleshooting(t){let e=t.createEl("details",{cls:"markitdown-troubleshooting"});e.createEl("summary",{text:"Troubleshooting: Python not found"});let s=e.createDiv("markitdown-troubleshooting-content"),a=this.plugin.pythonDiscoveryLog;if(a.length>0){s.createEl("p",{text:"The following paths were checked:",cls:"markitdown-troubleshooting-label"});let i=s.createEl("ul",{cls:"markitdown-tried-paths"});for(let c of a){let u=i.createEl("li"),d=u.createEl("code",{text:c.path});c.error&&u.createSpan({text:` \u2014 ${c.error}`,cls:"markitdown-tried-path-error"})}}let o=s.createEl("p");o.createSpan({text:"Download Python from "}),o.createEl("a",{text:"python.org",href:"https://www.python.org/downloads/"});let r=process.platform==="win32"?"C:\\Python311\\python.exe":"/usr/local/bin/python3";s.createEl("p",{text:"Example path for your platform: ",cls:"markitdown-troubleshooting-label"}).createEl("code",{text:r})}renderMarkitdownTroubleshooting(t){let e=t.createEl("details",{cls:"markitdown-troubleshooting"});e.createEl("summary",{text:"Troubleshooting: markitdown not installed"});let s=e.createDiv("markitdown-troubleshooting-content");s.createEl("p",{text:'Python was found, but the markitdown package is not importable from that interpreter. Prefer the "Install Markitdown" button below \u2014 it creates an isolated plugin venv (avoids Homebrew PEP 668 / externally-managed-environment errors).'}),s.createEl("p",{text:"Already installed markitdown another way? The plugin also looks for versioned Homebrew Pythons (e.g. python3.11) and pipx at ~/.local/pipx/venvs/markitdown. Click Refresh status after installing.",cls:"markitdown-troubleshooting-hint"}),s.createEl("p",{text:"Manual alternatives:"});let a='pipx install "markitdown[all]"',o=this.plugin.resolvedPythonPath,r=`${o.includes(" ")?`"${o}"`:o} -m pip install "markitdown[all]"`;for(let i of[a,r]){let c=s.createDiv("markitdown-pip-command");c.createEl("code",{text:i});let u=c.createEl("button",{text:"Copy",cls:"markitdown-copy-button"});u.addEventListener("click",()=>{navigator.clipboard.writeText(i).then(()=>{u.setText("Copied!"),setTimeout(()=>u.setText("Copy"),2e3)})})}}renderStatusItem(t,e,s,a){let o=t.createDiv("markitdown-status-item"),l=o.createSpan("markitdown-status-icon");l.addClass(s?"success":"error"),l.setText(s?"\u2713":"\u2717");let r=`${e}: ${s?"Installed":"Not installed"}`;a&&(r+=` (${a})`),o.createSpan().setText(r)}};var D=require("obsidian"),it=x(require("path")),ot=x(require("fs"));var U=class extends D.Modal{constructor(n,t){super(n),this.plugin=t}onOpen(){let{contentEl:n}=this;n.addClass("markitdown-modal"),n.createEl("h2",{text:"Convert file to Markdown"}),n.createEl("p",{text:"Select a file to convert:"});let e=n.createDiv("markitdown-file-input-container").createEl("input",{attr:{type:"file",accept:gt}}),a=n.createDiv("markitdown-button-container").createEl("button",{text:"Convert",cls:"mod-cta"});a.addEventListener("click",async()=>{if(!e.files||e.files.length===0){new D.Notice("Please select a file first");return}let o=e.files[0];a.disabled=!0,a.setText("Converting...");try{let l=P(this.app);if(!l){new D.Notice("Could not determine vault path. This plugin requires a local vault."),a.disabled=!1,a.setText("Convert");return}let r=E(l,this.plugin.settings.outputPath),i=$(this.plugin.settings.outputFilenameTemplate||"{filename}",o.name),c=it.join(r,`${i}.md`),u=o.name.replace(/[^a-zA-Z0-9._-]/g,"_"),d=it.join(r,`tmp_${Date.now()}_${u}`),m=await o.arrayBuffer();await ot.promises.writeFile(d,Buffer.from(m));try{let v=await this.plugin.convertExternalFile(d,c);if(v.success){let w=v.imagesExtracted?`Converted successfully (${v.imagesExtracted} images extracted)`:"Converted successfully";new D.Notice(w),await this.plugin.openConvertedFile(c,l),this.close()}else new D.Notice(`Conversion failed: ${v.error}`),a.disabled=!1,a.setText("Convert")}finally{await ot.promises.unlink(d).catch(()=>{})}}catch(l){let r=l instanceof Error?l.message:String(l);new D.Notice(`Error: ${r}`),a.disabled=!1,a.setText("Convert")}})}onClose(){this.contentEl.empty()}};var C=require("obsidian"),S=x(require("path")),N=x(require("fs"));var bt=require("obsidian"),W=class extends bt.Modal{constructor(n,t){super(n),this.totalFiles=t}onOpen(){let{contentEl:n}=this;n.addClass("markitdown-modal"),n.createEl("h2",{text:"Converting files"});let t=n.createDiv("markitdown-progress-bar");this.progressFill=t.createDiv("markitdown-progress-fill"),this.progressFill.style.width="0%",this.statusEl=n.createDiv("markitdown-progress-status"),this.statusEl.setText(`0 / ${this.totalFiles} files converted`),this.currentFileEl=n.createDiv("markitdown-progress-current"),this.currentFileEl.setText("Preparing...")}updateProgress(n,t,e,s){let a=this.totalFiles>0?Math.round(n/this.totalFiles*100):0;this.progressFill.style.width=`${a}%`,this.statusEl.setText(`${n} / ${this.totalFiles} files converted (${e} successful, ${s} failed)`),this.currentFileEl.setText(`Converting: ${t}`)}complete(n,t){this.progressFill.style.width="100%",this.statusEl.setText(`Conversion complete: ${n} successful, ${t} failed`),this.currentFileEl.setText("Done!"),setTimeout(()=>this.close(),2e3)}onClose(){this.contentEl.empty()}};var Y=class extends C.Modal{constructor(n,t){super(n),this.plugin=t}onOpen(){let{contentEl:n}=this;n.addClass("markitdown-modal"),n.createEl("h2",{text:"Convert folder contents to Markdown"}),n.createEl("p",{text:"Select a folder to process:"});let e=n.createDiv("markitdown-file-input-container").createEl("input",{attr:{type:"file",webkitdirectory:"",directory:""}}),s=this.plugin.settings.enableRecursiveConversion,o=n.createDiv("markitdown-setting-row").createEl("label",{cls:"markitdown-checkbox-label"}),l=o.createEl("input",{attr:{type:"checkbox"}});l.checked=s,l.addEventListener("change",()=>{s=l.checked}),o.appendText(" Include subfolders (recursive)"),n.createEl("p",{text:"Select file types to convert:"});let r=n.createDiv("markitdown-checkbox-grid"),i=[];for(let d of ht){let m=r.createEl("label",{cls:"markitdown-checkbox-label"}),v=m.createEl("input",{attr:{type:"checkbox",value:d.extensions}});v.addEventListener("change",()=>{let w=d.extensions.split(",");if(v.checked)for(let f of w)i.includes(f)||i.push(f);else for(let f of w){let T=i.indexOf(f);T>-1&&i.splice(T,1)}}),m.appendText(d.name)}n.createDiv("markitdown-button-container").createEl("button",{text:"Convert",cls:"mod-cta"}).addEventListener("click",async()=>{if(!e.files||e.files.length===0){new C.Notice("Please select a folder first");return}if(i.length===0){new C.Notice("Please select at least one file type");return}let m=e.files[0].webkitRelativePath.split("/")[0],v=[];for(let w=0;w2)continue;let Et=lt.slice(1).join("/");v.push({file:f,absolutePath:null,name:f.name,relativePath:Et})}if(v.length===0){new C.Notice("No matching files found in the selected folder");return}this.close(),await this.runBatchConversion(v)})}async runBatchConversion(n){let t=P(this.app);if(!t){new C.Notice("Could not determine vault path. This plugin requires a local vault.");return}let e=E(t,this.plugin.settings.outputPath),s=null;this.plugin.settings.enableBatchProgress?(s=new W(this.app,n.length),s.open()):new C.Notice(`Converting ${n.length} files...`);let a=0,o=0,l=[];for(let r=0;r{})}s?.updateProgress(r+1,i.name,a,o)}if(s)s.complete(a,o);else{let r=`Conversion complete: ${a} successful, ${o} failed`;l.length>0&&l.length<=3&&(r+=` `+l.join(` -`)),new C.Notice(r)}}onClose(){this.contentEl.empty()}};var _=require("obsidian"),Et=y(require("path"));var z=class extends _.Modal{constructor(n,t){super(n),this.plugin=t}onOpen(){let{contentEl:n}=this;n.addClass("markitdown-modal"),n.createEl("h2",{text:"Convert URL to Markdown"}),n.createEl("p",{text:"Enter a URL to convert (e.g. YouTube):"});let e=n.createDiv("markitdown-url-input-container").createEl("input",{attr:{type:"url",placeholder:"https://www.youtube.com/watch?v=..."}});e.style.width="100%";let o=n.createDiv("markitdown-button-container").createEl("button",{text:"Convert",cls:"mod-cta"});o.addEventListener("click",async()=>{let a=e.value.trim();if(!a){new _.Notice("Please enter a URL");return}if(!a.startsWith("http://")&&!a.startsWith("https://")){new _.Notice("URL must start with http:// or https://");return}o.disabled=!0,o.setText("Converting...");try{let l=b(this.app);if(!l){new _.Notice("Could not determine vault path. This plugin requires a local vault."),o.disabled=!1,o.setText("Convert");return}let r=P(l,this.plugin.settings.outputPath),i=this.deriveFilename(a),p=Et.join(r,`${i}.md`),u=this.plugin.buildConversionOptions(p),d=await this.plugin.converter.convertUrl(a,p,u);d.success?(new _.Notice("URL converted successfully"),this.close(),await this.plugin.openConvertedFile(p,l)):(new _.Notice(`Conversion failed: ${d.error}`),o.disabled=!1,o.setText("Convert"))}catch(l){let r=l instanceof Error?l.message:String(l);new _.Notice(`Error: ${r}`),o.disabled=!1,o.setText("Convert")}})}deriveFilename(n){try{let t=new URL(n);if(t.hostname==="www.youtube.com"||t.hostname==="youtube.com"||t.hostname==="m.youtube.com"){let s=t.searchParams.get("v");if(s)return`youtube-${s}`}if(t.hostname==="youtu.be"){let s=t.pathname.slice(1);if(s)return`youtube-${s}`}return`${t.hostname}${t.pathname}`.replace(/[^a-zA-Z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^_|_$/g,"")||"url-conversion"}catch{return`url-${Date.now()}`}}onClose(){this.contentEl.empty()}};var q=require("obsidian"),J=class extends q.Modal{constructor(n,t){super(n),this.plugin=t}onOpen(){let{contentEl:n}=this;n.addClass("markitdown-modal"),n.createEl("h2",{text:"Conversion history"});let t=this.plugin.settings.conversionHistory;if(t.length===0){n.createEl("p",{text:"No conversions have been recorded yet.",cls:"markitdown-history-empty"});return}let e=n.createDiv("markitdown-history-list");for(let a of t)this.renderEntry(e,a);n.createDiv("markitdown-button-container").createEl("button",{text:"Clear history",cls:"markitdown-history-clear-btn"}).addEventListener("click",async()=>{this.plugin.settings.conversionHistory=[],await this.plugin.saveSettings(),new q.Notice("Conversion history cleared"),this.close()})}renderEntry(n,t){let e=n.createDiv("markitdown-history-entry"),s=e.createDiv("markitdown-history-entry-header"),o=new Date(t.timestamp);s.createSpan({text:o.toLocaleString(),cls:"markitdown-history-timestamp"});let a=t.success?"markitdown-history-badge-success":"markitdown-history-badge-fail";s.createSpan({text:t.success?"Success":"Failed",cls:`markitdown-history-badge ${a}`});let l=t.inputFile.split("/").pop()?.split("\\").pop()??t.inputFile;e.createDiv({text:l,cls:"markitdown-history-filename"});let r=[];t.processingTimeMs!==void 0&&r.push(`${t.processingTimeMs}ms`),t.imagesExtracted!==void 0&&t.imagesExtracted>0&&r.push(`${t.imagesExtracted} image${t.imagesExtracted!==1?"s":""} extracted`),r.length>0&&e.createDiv({text:r.join(" \xB7 "),cls:"markitdown-history-details"}),!t.success&&t.error&&e.createDiv({text:t.error,cls:"markitdown-history-error"})}onClose(){this.contentEl.empty()}};var G=require("obsidian"),F=class extends G.Modal{constructor(n,t){super(n),this.plugin=t}onOpen(){let{contentEl:n}=this;if(n.addClass("markitdown-modal"),n.createEl("h2",{text:"Markitdown setup"}),!this.plugin.dependencyStatus.pythonInstalled){this.showPythonNotFound(n);return}if(this.plugin.dependencyStatus.markitdownInstalled){this.showAlreadyInstalled(n);return}this.showInstallPrompt(n)}showPythonNotFound(n){n.createEl("p",{text:"Python is not installed or not found at the configured path."});let t=n.createEl("div",{cls:"markitdown-setup-details"});t.createEl("p",{text:"To get started:"});let e=t.createEl("ol");e.createEl("li",{text:"Install Python 3.8+ from python.org"}),e.createEl("li",{text:"Set the correct Python path in plugin settings"}),e.createEl("li",{text:"Restart Obsidian or re-run this setup"}),n.createDiv("markitdown-button-container").createEl("button",{text:"Open settings"}).addEventListener("click",()=>{this.close(),this.app.setting?.open(),this.app.setting?.openTabById(this.plugin.manifest.id)})}showAlreadyInstalled(n){let t=this.plugin.dependencyStatus.markitdownVersion;n.createEl("p",{text:`Markitdown is installed${t?` (v${t})`:""}. You're ready to convert files.`}),n.createDiv("markitdown-button-container").createEl("button",{text:"Close"}).addEventListener("click",()=>this.close())}showInstallPrompt(n){n.createEl("p",{text:"Markitdown is not installed. Would you like to install it now?"}),n.createEl("p",{text:"This installs markitdown into an isolated plugin virtual environment (safe with Homebrew Python).",cls:"setting-item-description"});let t=n.createDiv("markitdown-setup-log");t.style.display="none";let e=n.createDiv("markitdown-button-container"),s=e.createEl("button",{text:"Cancel"});s.addEventListener("click",()=>this.close());let o=e.createEl("button",{text:"Install Markitdown",cls:"mod-cta"});o.addEventListener("click",async()=>{o.disabled=!0,o.setText("Installing..."),s.disabled=!0,t.style.display="block",t.empty();try{await this.plugin.installMarkitdown(l=>{t.createEl("div",{text:l,cls:"markitdown-setup-log-line"}).scrollIntoView()})?(new G.Notice("Markitdown installed successfully!"),await this.plugin.refreshDependencies(),this.close()):(t.createEl("div",{text:"Installation failed. Check the log above for details.",cls:"markitdown-setup-log-error"}),o.disabled=!1,o.setText("Try again"),s.disabled=!1)}catch(a){let l=a instanceof Error?a.message:String(a);t.createEl("div",{text:`Error: ${l}`,cls:"markitdown-setup-log-error"}),o.disabled=!1,o.setText("Try again"),s.disabled=!1}})}onClose(){this.contentEl.empty()}};var K=class extends w.Plugin{constructor(){super(...arguments);this.settings=X;this.dependencyStatus={pythonInstalled:!1,pythonVersion:null,markitdownInstalled:!1,markitdownVersion:null};this.converter=new I("python",".");this.pythonDiscoveryLog=[];this._resolvedPythonPath="python"}get resolvedPythonPath(){return this._resolvedPythonPath}async onload(){await this.loadSettings();let t=this.getPluginDir();await kt(t);let e=await tt(this.settings.pythonPath,t);this.dependencyStatus=e.status,this.pythonDiscoveryLog=e.triedPaths,this._resolvedPythonPath=e.resolvedPythonPath,this.converter=new I(this.resolvedPythonPath,t),this.addRibbonIcon("file-text","Convert to Markdown",()=>{this.openConvertModal()}),this.addCommand({id:"convert-file",name:"Convert file to Markdown",callback:()=>this.openConvertModal()}),this.addCommand({id:"convert-folder",name:"Convert folder to Markdown",callback:()=>this.openFolderModal()}),this.addCommand({id:"convert-url",name:"Convert URL to Markdown",callback:()=>this.openUrlModal()}),this.addCommand({id:"view-conversion-history",name:"View conversion history",callback:()=>new J(this.app,this).open()}),this.settings.enableContextMenu&&this.registerFileMenu(),this.settings.enableDragDrop&&this.registerDropHandler(),this.addSettingTab(new H(this.app,this))}onunload(){}openConvertModal(){if(!this.dependencyStatus.markitdownInstalled){new F(this.app,this).open();return}new U(this.app,this).open()}openFolderModal(){if(!this.dependencyStatus.markitdownInstalled){new F(this.app,this).open();return}new Y(this.app,this).open()}openUrlModal(){if(!this.dependencyStatus.markitdownInstalled){new F(this.app,this).open();return}new z(this.app,this).open()}registerFileMenu(){this.registerEvent(this.app.workspace.on("file-menu",(t,e)=>{e instanceof w.TFile&&et(e.extension)&&t.addItem(s=>{s.setTitle("Convert to Markdown").setIcon("file-text").onClick(()=>this.convertVaultFile(e))})}))}registerDropHandler(){this.registerEvent(this.app.workspace.on("editor-drop",(t,e,s)=>{let o=t.dataTransfer?.files;if(!o||o.length===0)return;let a=[];for(let l=0;l{let i=r instanceof Error?r.message:String(r);new w.Notice(`Drop conversion error: ${i}`)})}}))}async handleDroppedFile(t,e){let s=b(this.app);if(!s){new w.Notice("Could not determine vault path. This plugin requires a local vault.");return}if(!this.dependencyStatus.markitdownInstalled){new F(this.app,this).open();return}new w.Notice(`Converting dropped file: ${t.name}...`);let o=P(s,this.settings.outputPath),a=x.basename(t.name,x.extname(t.name)),l=x.join(o,`${a}.md`),r=t.name.replace(/[^a-zA-Z0-9._-]/g,"_"),i=x.join(o,`tmp_${Date.now()}_${r}`),p=await t.arrayBuffer();await at.promises.writeFile(i,Buffer.from(p));try{let u=await this.convertExternalFile(i,l);if(u.success){let E=`[[${nt(l,s).replace(/\.md$/,"")}]]`,f=e.getCursor();e.replaceRange(E,f);let v=u.imagesExtracted?`Converted ${t.name} (${u.imagesExtracted} images extracted)`:`Converted ${t.name} successfully`;new w.Notice(v)}else new w.Notice(`Conversion failed for ${t.name}: ${u.error}`)}finally{await at.promises.unlink(i).catch(()=>{})}}async convertVaultFile(t){let e=b(this.app);if(!e){new w.Notice("Could not determine vault path. This plugin requires a local vault.");return}if(!this.dependencyStatus.markitdownInstalled){new F(this.app,this).open();return}let s=x.join(e,t.path),o=P(e,this.settings.outputPath),a=$(this.settings.outputFilenameTemplate||"{filename}",s),l=x.join(o,`${a}.md`),r=this.buildConversionOptions(l,s);new w.Notice("Converting file...");let i=Date.now(),p=await this.converter.convert(s,l,r),u=Date.now()-i;if(st(this.settings,{inputFile:s,outputFile:l,timestamp:new Date().toISOString(),success:p.success,error:p.error,processingTimeMs:u,imagesExtracted:p.imagesExtracted}),await this.saveSettings(),p.success){let d=p.imagesExtracted?`Converted successfully (${p.imagesExtracted} images extracted)`:"Converted successfully";new w.Notice(d),await this.openConvertedFile(l,e)}else new w.Notice(`Conversion failed: ${p.error}`)}async convertExternalFile(t,e){let s=this.buildConversionOptions(e,t),o=Date.now(),a=await this.converter.convert(t,e,s),l=Date.now()-o;return st(this.settings,{inputFile:t,outputFile:e,timestamp:new Date().toISOString(),success:a.success,error:a.error,processingTimeMs:l,imagesExtracted:a.imagesExtracted}),await this.saveSettings(),a}buildConversionOptions(t,e){let s={enablePlugins:this.settings.enablePlugins,docintelEndpoint:this.settings.docintelEndpoint||void 0};return this.settings.pluginArgs.length>0&&(s.pluginArgs=this.pluginArgsToRecord(this.settings.pluginArgs)),this.settings.imageExtractionEnabled&&(s.extractImages=!0,s.imageDir=xt(t,this.settings.imageSubfolderTemplate)),e&&(this.settings.enableAutoFrontmatter||this.settings.autoTags.trim())&&(s.postProcess={settings:this.settings,inputPath:e}),s}pluginArgsToRecord(t){let e=Object.create(null);for(let s of t)if(s.key.trim()&&!(s.key==="__proto__"||s.key==="constructor"||s.key==="prototype"))try{e[s.key]=JSON.parse(s.value)}catch{e[s.key]=s.value}return e}async openConvertedFile(t,e){let s=nt(t,e),o=this.app.vault.getAbstractFileByPath(s);o instanceof w.TFile&&await this.app.workspace.getLeaf().openFile(o)}getPluginDir(){let t=b(this.app);return t&&this.manifest.dir?x.join(t,this.manifest.dir):(console.warn("markitdown: Could not resolve vault base path. Plugin features may not work correctly."),x.resolve(this.manifest.dir??"."))}async installMarkitdown(t){let e=this.getPluginDir(),s=await dt(this._resolvedPythonPath,e,"markitdown[all]",t);return s&&(this.dependencyStatus.markitdownInstalled=!0),s}async refreshDependencies(){let t=this.getPluginDir(),e=await tt(this.settings.pythonPath,t);this.dependencyStatus=e.status,this.pythonDiscoveryLog=e.triedPaths,this._resolvedPythonPath=e.resolvedPythonPath,this.converter=new I(this.resolvedPythonPath,t)}async loadSettings(){this.settings=Object.assign({},X,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}}; +`)),new C.Notice(r)}}onClose(){this.contentEl.empty()}};var _=require("obsidian"),Pt=x(require("path"));var q=class extends _.Modal{constructor(n,t){super(n),this.plugin=t}onOpen(){let{contentEl:n}=this;n.addClass("markitdown-modal"),n.createEl("h2",{text:"Convert URL to Markdown"}),n.createEl("p",{text:"Enter a URL to convert (e.g. YouTube):"});let e=n.createDiv("markitdown-url-input-container").createEl("input",{attr:{type:"url",placeholder:"https://www.youtube.com/watch?v=..."}});e.style.width="100%";let a=n.createDiv("markitdown-button-container").createEl("button",{text:"Convert",cls:"mod-cta"});a.addEventListener("click",async()=>{let o=e.value.trim();if(!o){new _.Notice("Please enter a URL");return}if(!o.startsWith("http://")&&!o.startsWith("https://")){new _.Notice("URL must start with http:// or https://");return}a.disabled=!0,a.setText("Converting...");try{let l=P(this.app);if(!l){new _.Notice("Could not determine vault path. This plugin requires a local vault."),a.disabled=!1,a.setText("Convert");return}let r=E(l,this.plugin.settings.outputPath),i=this.deriveFilename(o),c=Pt.join(r,`${i}.md`),u=this.plugin.buildConversionOptions(c),d=await this.plugin.converter.convertUrl(o,c,u);d.success?(new _.Notice("URL converted successfully"),this.close(),await this.plugin.openConvertedFile(c,l)):(new _.Notice(`Conversion failed: ${d.error}`),a.disabled=!1,a.setText("Convert"))}catch(l){let r=l instanceof Error?l.message:String(l);new _.Notice(`Error: ${r}`),a.disabled=!1,a.setText("Convert")}})}deriveFilename(n){try{let t=new URL(n);if(t.hostname==="www.youtube.com"||t.hostname==="youtube.com"||t.hostname==="m.youtube.com"){let s=t.searchParams.get("v");if(s)return`youtube-${s}`}if(t.hostname==="youtu.be"){let s=t.pathname.slice(1);if(s)return`youtube-${s}`}return`${t.hostname}${t.pathname}`.replace(/[^a-zA-Z0-9._-]/g,"_").replace(/_+/g,"_").replace(/^_|_$/g,"")||"url-conversion"}catch{return`url-${Date.now()}`}}onClose(){this.contentEl.empty()}};var J=require("obsidian"),z=class extends J.Modal{constructor(n,t){super(n),this.plugin=t}onOpen(){let{contentEl:n}=this;n.addClass("markitdown-modal"),n.createEl("h2",{text:"Conversion history"});let t=this.plugin.settings.conversionHistory;if(t.length===0){n.createEl("p",{text:"No conversions have been recorded yet.",cls:"markitdown-history-empty"});return}let e=n.createDiv("markitdown-history-list");for(let o of t)this.renderEntry(e,o);n.createDiv("markitdown-button-container").createEl("button",{text:"Clear history",cls:"markitdown-history-clear-btn"}).addEventListener("click",async()=>{this.plugin.settings.conversionHistory=[],await this.plugin.saveSettings(),new J.Notice("Conversion history cleared"),this.close()})}renderEntry(n,t){let e=n.createDiv("markitdown-history-entry"),s=e.createDiv("markitdown-history-entry-header"),a=new Date(t.timestamp);s.createSpan({text:a.toLocaleString(),cls:"markitdown-history-timestamp"});let o=t.success?"markitdown-history-badge-success":"markitdown-history-badge-fail";s.createSpan({text:t.success?"Success":"Failed",cls:`markitdown-history-badge ${o}`});let l=t.inputFile.split("/").pop()?.split("\\").pop()??t.inputFile;e.createDiv({text:l,cls:"markitdown-history-filename"});let r=[];t.processingTimeMs!==void 0&&r.push(`${t.processingTimeMs}ms`),t.imagesExtracted!==void 0&&t.imagesExtracted>0&&r.push(`${t.imagesExtracted} image${t.imagesExtracted!==1?"s":""} extracted`),r.length>0&&e.createDiv({text:r.join(" \xB7 "),cls:"markitdown-history-details"}),!t.success&&t.error&&e.createDiv({text:t.error,cls:"markitdown-history-error"})}onClose(){this.contentEl.empty()}};var G=require("obsidian"),F=class extends G.Modal{constructor(n,t){super(n),this.plugin=t}onOpen(){let{contentEl:n}=this;if(n.addClass("markitdown-modal"),n.createEl("h2",{text:"Markitdown setup"}),!this.plugin.dependencyStatus.pythonInstalled){this.showPythonNotFound(n);return}if(this.plugin.dependencyStatus.markitdownInstalled){this.showAlreadyInstalled(n);return}this.showInstallPrompt(n)}showPythonNotFound(n){n.createEl("p",{text:"Python is not installed or not found at the configured path."});let t=n.createEl("div",{cls:"markitdown-setup-details"});t.createEl("p",{text:"To get started:"});let e=t.createEl("ol");e.createEl("li",{text:"Install Python 3.8+ from python.org"}),e.createEl("li",{text:"Set the correct Python path in plugin settings"}),e.createEl("li",{text:"Restart Obsidian or re-run this setup"}),n.createDiv("markitdown-button-container").createEl("button",{text:"Open settings"}).addEventListener("click",()=>{this.close(),this.app.setting?.open(),this.app.setting?.openTabById(this.plugin.manifest.id)})}showAlreadyInstalled(n){let t=this.plugin.dependencyStatus.markitdownVersion;n.createEl("p",{text:`Markitdown is installed${t?` (v${t})`:""}. You're ready to convert files.`}),n.createDiv("markitdown-button-container").createEl("button",{text:"Close"}).addEventListener("click",()=>this.close())}showInstallPrompt(n){n.createEl("p",{text:"Markitdown is not installed. Would you like to install it now?"}),n.createEl("p",{text:"This installs markitdown into an isolated plugin virtual environment (safe with Homebrew Python).",cls:"setting-item-description"});let t=n.createDiv("markitdown-setup-log");t.style.display="none";let e=n.createDiv("markitdown-button-container"),s=e.createEl("button",{text:"Cancel"});s.addEventListener("click",()=>this.close());let a=e.createEl("button",{text:"Install Markitdown",cls:"mod-cta"});a.addEventListener("click",async()=>{a.disabled=!0,a.setText("Installing..."),s.disabled=!0,t.style.display="block",t.empty();try{await this.plugin.installMarkitdown(l=>{t.createEl("div",{text:l,cls:"markitdown-setup-log-line"}).scrollIntoView()})?(new G.Notice("Markitdown installed successfully!"),await this.plugin.refreshDependencies(),this.close()):(t.createEl("div",{text:"Installation failed. Check the log above for details.",cls:"markitdown-setup-log-error"}),a.disabled=!1,a.setText("Try again"),s.disabled=!1)}catch(o){let l=o instanceof Error?o.message:String(o);t.createEl("div",{text:`Error: ${l}`,cls:"markitdown-setup-log-error"}),a.disabled=!1,a.setText("Try again"),s.disabled=!1}})}onClose(){this.contentEl.empty()}};var K=class extends y.Plugin{constructor(){super(...arguments);this.settings=X;this.dependencyStatus={pythonInstalled:!1,pythonVersion:null,markitdownInstalled:!1,markitdownVersion:null};this.converter=new I("python",".");this.pythonDiscoveryLog=[];this._resolvedPythonPath="python"}get resolvedPythonPath(){return this._resolvedPythonPath}async onload(){await this.loadSettings();let t=this.getPluginDir();await kt(t);let e=await tt(this.settings.pythonPath,t);this.dependencyStatus=e.status,this.pythonDiscoveryLog=e.triedPaths,this._resolvedPythonPath=e.resolvedPythonPath,this.converter=new I(this.resolvedPythonPath,t),this.addRibbonIcon("file-text","Convert to Markdown",()=>{this.openConvertModal()}),this.addCommand({id:"convert-file",name:"Convert file to Markdown",callback:()=>this.openConvertModal()}),this.addCommand({id:"convert-folder",name:"Convert folder to Markdown",callback:()=>this.openFolderModal()}),this.addCommand({id:"convert-url",name:"Convert URL to Markdown",callback:()=>this.openUrlModal()}),this.addCommand({id:"view-conversion-history",name:"View conversion history",callback:()=>new z(this.app,this).open()}),this.settings.enableContextMenu&&this.registerFileMenu(),this.settings.enableDragDrop&&this.registerDropHandler(),this.addSettingTab(new H(this.app,this))}onunload(){}openConvertModal(){if(!this.dependencyStatus.markitdownInstalled){new F(this.app,this).open();return}new U(this.app,this).open()}openFolderModal(){if(!this.dependencyStatus.markitdownInstalled){new F(this.app,this).open();return}new Y(this.app,this).open()}openUrlModal(){if(!this.dependencyStatus.markitdownInstalled){new F(this.app,this).open();return}new q(this.app,this).open()}registerFileMenu(){this.registerEvent(this.app.workspace.on("file-menu",(t,e)=>{e instanceof y.TFile&&et(e.extension)&&t.addItem(s=>{s.setTitle("Convert to Markdown").setIcon("file-text").onClick(()=>this.convertVaultFile(e))})}))}registerDropHandler(){this.registerEvent(this.app.workspace.on("editor-drop",(t,e,s)=>{let a=t.dataTransfer?.files;if(!a||a.length===0)return;let o=[];for(let l=0;l{let i=r instanceof Error?r.message:String(r);new y.Notice(`Drop conversion error: ${i}`)})}}))}async handleDroppedFile(t,e){let s=P(this.app);if(!s){new y.Notice("Could not determine vault path. This plugin requires a local vault.");return}if(!this.dependencyStatus.markitdownInstalled){new F(this.app,this).open();return}new y.Notice(`Converting dropped file: ${t.name}...`);let a=E(s,this.settings.outputPath),o=b.basename(t.name,b.extname(t.name)),l=b.join(a,`${o}.md`),r=t.name.replace(/[^a-zA-Z0-9._-]/g,"_"),i=b.join(a,`tmp_${Date.now()}_${r}`),c=await t.arrayBuffer();await at.promises.writeFile(i,Buffer.from(c));try{let u=await this.convertExternalFile(i,l);if(u.success){let m=`[[${nt(l,s).replace(/\.md$/,"")}]]`,v=e.getCursor();e.replaceRange(m,v);let w=u.imagesExtracted?`Converted ${t.name} (${u.imagesExtracted} images extracted)`:`Converted ${t.name} successfully`;new y.Notice(w)}else new y.Notice(`Conversion failed for ${t.name}: ${u.error}`)}finally{await at.promises.unlink(i).catch(()=>{})}}async convertVaultFile(t){let e=P(this.app);if(!e){new y.Notice("Could not determine vault path. This plugin requires a local vault.");return}if(!this.dependencyStatus.markitdownInstalled){new F(this.app,this).open();return}let s=b.join(e,t.path),a=E(e,this.settings.outputPath),o=$(this.settings.outputFilenameTemplate||"{filename}",s),l=b.join(a,`${o}.md`),r=this.buildConversionOptions(l,s);new y.Notice("Converting file...");let i=Date.now(),c=await this.converter.convert(s,l,r),u=Date.now()-i;if(st(this.settings,{inputFile:s,outputFile:l,timestamp:new Date().toISOString(),success:c.success,error:c.error,processingTimeMs:u,imagesExtracted:c.imagesExtracted}),await this.saveSettings(),c.success){let d=c.imagesExtracted?`Converted successfully (${c.imagesExtracted} images extracted)`:"Converted successfully";new y.Notice(d),await this.openConvertedFile(l,e)}else new y.Notice(`Conversion failed: ${c.error}`)}async convertExternalFile(t,e){let s=this.buildConversionOptions(e,t),a=Date.now(),o=await this.converter.convert(t,e,s),l=Date.now()-a;return st(this.settings,{inputFile:t,outputFile:e,timestamp:new Date().toISOString(),success:o.success,error:o.error,processingTimeMs:l,imagesExtracted:o.imagesExtracted}),await this.saveSettings(),o}buildConversionOptions(t,e){let s={enablePlugins:this.settings.enablePlugins,docintelEndpoint:this.settings.docintelEndpoint||void 0};return this.settings.pluginArgs.length>0&&(s.pluginArgs=this.pluginArgsToRecord(this.settings.pluginArgs)),this.settings.imageExtractionEnabled&&(s.extractImages=!0,s.imageDir=xt(t,this.settings.imageSubfolderTemplate)),e&&(this.settings.enableAutoFrontmatter||this.settings.autoTags.trim())&&(s.postProcess={settings:this.settings,inputPath:e}),s}pluginArgsToRecord(t){let e=Object.create(null);for(let s of t)if(s.key.trim()&&!(s.key==="__proto__"||s.key==="constructor"||s.key==="prototype"))try{e[s.key]=JSON.parse(s.value)}catch{e[s.key]=s.value}return e}async openConvertedFile(t,e){let s=nt(t,e),a=this.app.vault.getAbstractFileByPath(s);a instanceof y.TFile&&await this.app.workspace.getLeaf().openFile(a)}getPluginDir(){let t=P(this.app);return t&&this.manifest.dir?b.join(t,this.manifest.dir):(console.warn("markitdown: Could not resolve vault base path. Plugin features may not work correctly."),b.resolve(this.manifest.dir??"."))}async installMarkitdown(t){let e=this.getPluginDir(),s=await dt(this._resolvedPythonPath,e,"markitdown[all]",t);return s&&(this.dependencyStatus.markitdownInstalled=!0),s}async refreshDependencies(){let t=this.getPluginDir(),e=await tt(this.settings.pythonPath,t);this.dependencyStatus=e.status,this.pythonDiscoveryLog=e.triedPaths,this._resolvedPythonPath=e.resolvedPythonPath,this.converter=new I(this.resolvedPythonPath,t)}async loadSettings(){this.settings=Object.assign({},X,await this.loadData())}async saveSettings(){await this.saveData(this.settings)}}; diff --git a/python/install_package.py b/python/install_package.py index 462a5ec..62a948d 100644 --- a/python/install_package.py +++ b/python/install_package.py @@ -16,6 +16,7 @@ Output: import argparse import json import os +import shutil import subprocess import sys @@ -35,9 +36,9 @@ def run_pip(python: str, package: str) -> int: bufsize=1, ) - assert process.stdout is not None - for line in process.stdout: - print(line, end="", flush=True) + if process.stdout is not None: + for line in process.stdout: + print(line, end="", flush=True) process.wait() return process.returncode or 0 @@ -55,6 +56,8 @@ def ensure_venv(venv_dir: str, bootstrap_python: str) -> str: text=True, ) if result.returncode != 0 or not os.path.isfile(py): + # Partial/failed create can leave a non-empty dir that blocks retries + shutil.rmtree(venv_dir, ignore_errors=True) detail = (result.stderr or result.stdout or "").strip() raise RuntimeError( f"Failed to create virtual environment at {venv_dir}: {detail or f'exit {result.returncode}'}" diff --git a/src/settings/SettingsTab.ts b/src/settings/SettingsTab.ts index 174cd71..7b1eb64 100644 --- a/src/settings/SettingsTab.ts +++ b/src/settings/SettingsTab.ts @@ -386,7 +386,10 @@ export class SettingsTab extends PluginSettingTab { }); const pipxCmd = 'pipx install "markitdown[all]"'; - const pipCmd = `${this.plugin.resolvedPythonPath} -m pip install "markitdown[all]"`; + const pythonPath = this.plugin.resolvedPythonPath; + const quotedPython = + pythonPath.includes(' ') ? `"${pythonPath}"` : pythonPath; + const pipCmd = `${quotedPython} -m pip install "markitdown[all]"`; for (const cmd of [pipxCmd, pipCmd]) { const cmdContainer = content.createDiv('markitdown-pip-command'); diff --git a/src/utils/__tests__/python.test.ts b/src/utils/__tests__/python.test.ts index b81a658..b1d233c 100644 --- a/src/utils/__tests__/python.test.ts +++ b/src/utils/__tests__/python.test.ts @@ -95,26 +95,35 @@ describe('buildPythonCandidates', () => { const pluginDir = '/plugins/markitdown'; const originalPlatform = process.platform; const originalHome = process.env.HOME; + const originalUserProfile = process.env.USERPROFILE; + const originalLocalAppData = process.env.LOCALAPPDATA; + const originalPipxHome = process.env.PIPX_HOME; afterEach(() => { Object.defineProperty(process, 'platform', { value: originalPlatform }); - if (originalHome === undefined) { - delete process.env.HOME; - } else { - process.env.HOME = originalHome; - } - delete process.env.PIPX_HOME; + const restore = (key: string, value: string | undefined) => { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + }; + restore('HOME', originalHome); + restore('USERPROFILE', originalUserProfile); + restore('LOCALAPPDATA', originalLocalAppData); + restore('PIPX_HOME', originalPipxHome); }); it('includes plugin venv and pipx python on darwin', () => { Object.defineProperty(process, 'platform', { value: 'darwin' }); process.env.HOME = '/Users/test'; + delete process.env.PIPX_HOME; const paths = buildPythonCandidates('python', pluginDir); expect(paths[0]).toBe('python'); expect(paths).toContain(getPluginVenvPython(pluginDir)); expect(paths).toContain('/Users/test/.local/pipx/venvs/markitdown/bin/python'); + expect(paths).toContain( + '/Users/test/Library/Application Support/pipx/venvs/markitdown/bin/python' + ); expect(paths).toContain('/opt/homebrew/bin/python3'); expect(paths).toContain('/opt/homebrew/bin/python3.11'); expect(paths).toContain('/opt/homebrew/opt/python@3.11/bin/python3'); @@ -124,6 +133,7 @@ describe('buildPythonCandidates', () => { it('still adds fallbacks when configured path is absolute', () => { Object.defineProperty(process, 'platform', { value: 'darwin' }); process.env.HOME = '/Users/test'; + delete process.env.PIPX_HOME; const paths = buildPythonCandidates('/opt/homebrew/bin/python3', pluginDir); @@ -146,6 +156,7 @@ describe('buildPythonCandidates', () => { process.env.HOME = 'C:\\Users\\test'; process.env.USERPROFILE = 'C:\\Users\\test'; process.env.LOCALAPPDATA = 'C:\\Users\\test\\AppData\\Local'; + delete process.env.PIPX_HOME; const paths = buildPythonCandidates('python', pluginDir); diff --git a/src/utils/bundledScripts.ts b/src/utils/bundledScripts.ts index b2659a2..f868f7d 100644 --- a/src/utils/bundledScripts.ts +++ b/src/utils/bundledScripts.ts @@ -57,6 +57,7 @@ const INSTALL_PACKAGE_PY = `#!/usr/bin/env python3 import argparse import json import os +import shutil import subprocess import sys @@ -69,8 +70,9 @@ def run_pip(python, package): process = subprocess.Popen( [python, "-m", "pip", "install", package], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) - for line in process.stdout: - print(line, end="", flush=True) + if process.stdout is not None: + for line in process.stdout: + print(line, end="", flush=True) process.wait() return process.returncode or 0 @@ -83,6 +85,7 @@ def ensure_venv(venv_dir, bootstrap_python): [bootstrap_python, "-m", "venv", venv_dir], capture_output=True, text=True) if result.returncode != 0 or not os.path.isfile(py): + shutil.rmtree(venv_dir, ignore_errors=True) detail = (result.stderr or result.stdout or "").strip() raise RuntimeError( f"Failed to create virtual environment at {venv_dir}: {detail or f'exit {result.returncode}'}") diff --git a/src/utils/python.ts b/src/utils/python.ts index 3f1a222..84f2e2c 100644 --- a/src/utils/python.ts +++ b/src/utils/python.ts @@ -79,9 +79,24 @@ export function buildPythonCandidates(pythonPath: string, pluginDir: string): st const home = process.env.HOME || process.env.USERPROFILE || ''; const isWin = process.platform === 'win32'; - // pipx-managed markitdown (isolated venv; system python3 cannot import it) - const pipxHome = process.env.PIPX_HOME || (home ? path.join(home, '.local', 'pipx') : ''); - if (pipxHome) { + // pipx-managed markitdown (isolated venv; system python3 cannot import it). + // Probe PIPX_HOME plus legacy (~/.local/pipx) and current platformdirs defaults. + const pipxHomes: string[] = []; + if (process.env.PIPX_HOME) { + pipxHomes.push(process.env.PIPX_HOME); + } + if (home) { + pipxHomes.push(path.join(home, '.local', 'pipx')); + if (isWin) { + const localAppData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local'); + pipxHomes.push(path.join(localAppData, 'pipx')); + } else if (process.platform === 'darwin') { + pipxHomes.push(path.join(home, 'Library', 'Application Support', 'pipx')); + } else { + pipxHomes.push(path.join(home, '.local', 'share', 'pipx')); + } + } + for (const pipxHome of pipxHomes) { if (isWin) { pathsToTry.push(path.join(pipxHome, 'venvs', 'markitdown', 'Scripts', 'python.exe')); } else { @@ -295,13 +310,16 @@ export async function installPackage( } ); - let lastLine = ''; + let stdoutBuf = ''; + let lastCompleteLine = ''; child.stdout.on('data', (data: Buffer) => { - const lines = data.toString().split('\n'); + stdoutBuf += data.toString(); + const lines = stdoutBuf.split('\n'); + stdoutBuf = lines.pop() ?? ''; for (const line of lines) { if (line.trim()) { - lastLine = line.trim(); + lastCompleteLine = line.trim(); onProgress?.(line.trim()); } } @@ -321,9 +339,14 @@ export async function installPackage( }); child.on('close', (code: number | null) => { + const trailing = stdoutBuf.trim(); + if (trailing) { + lastCompleteLine = trailing; + onProgress?.(trailing); + } if (code === 0) { try { - const result = JSON.parse(lastLine); + const result = JSON.parse(lastCompleteLine); resolve(result.success === true); } catch { resolve(true);