Convert Obsidian Image Links to Nunjucks Shortcodes in Eleventy

Posted:

In Eleventy, I use Nunjucks shortcodes to interface with the image plugin. But, I don't want to use the shortcodes within Obsidian - I want images visible there as well. So, I wrote some basic code that runs in the pre-build stage to convert Obsidian images in their usual format to a Nunjucks shortcode that looks like:

ø

{% image "/Users/amy/Code/amykhar-dev-build/images/sample.png", " " %}

And, this will render as

I do not yet process alt text, as I have been otherwise occupied.

The meat of the code lives in a file named obsidian-image-convert.js


const parseContent = (content) => {  
    const regexp = /\!\[\[(.+)\]\]/g;  
    let matches = [...content.matchAll(regexp)];  
    return matches;  
}  
  
const convertToShortcode = (findString, imageFilename, content, base ) => {  
    const imageDir = base + '/images/';  
    const shortcodeStart = '{% image ';  
    const shortcodeEnd = ' %}';  
    let replaceString = shortcodeStart;  
    if (imageFilename.includes('http')) {  
       replaceString += '"' + imageFilename + '", "' + ' "' ;  
    } else {  
         replaceString += '"' + imageDir + imageFilename + '","' + ' "' ;  
    }  
    replaceString += shortcodeEnd;  
    console.log('Replacing ' + findString + ' with ' + replaceString);  
    content = content.replace(findString, replaceString);  
    return content;  
}  
  
const obsidianImageConvert = (content, base) => {  
    console.log('Converting Obsidian images to shortcodes');  
    let convertedContent = content;  
    const imageMatches = parseContent(content);  
    imageMatches.forEach(match => {  
        convertedContent = convertToShortcode(match[0], match[1], convertedContent, base);  
    });  
    return convertedContent;  
}  
  
module.exports = obsidianImageConvert;

In my eleventy.js file, I import the obsidianImageConvert function:

const obsidianImageToShortcode = require('./obsidian-image-convert.js');

In my return function, I added the 'base' directory for my eleventy environment:

return {  
    dir: {  
        base: "/Users/amy/Code/amykhar-dev-build",  
        input:  
            "/Users/amy/Code/amykhar-dev-build/content",  
        output: "/Users/amy/Code/amykhar.dev",  
        imageOutput: "/Users/amy/Code/amykhar.dev/images",  
        layouts: "../layouts",  
        includes: "../includes",  
    },  
    markdownTemplateEngine: "njk"  
};

Then, I have an eleventy.before handler containing a call to the image link conversion function.

eleventyConfig.on("eleventy.before", async ({dir, runMode, outputMode}) => {
	const mdfiles = await glob(dir.input + '**/*.md');
	
	mdfiles.forEach(file => {
		let content = fs.readFileSync(file).toString();
		let shouldWrite = false;
		const converted = obsidianImageToShortcode(content, dir.base);  
		if (converted !== content) {  
		    shouldWrite = true;  
		    content = converted;  
		}  
  
		if (shouldWrite) {  
		    console.log('Links changed updating the file ' + file);  
		    fs.writeFile(file, content, 'utf8', function (err) {  
		        if (err) return console.log(err);  
		    });  
		}
	});

});

I also have a bit of logic in my version of the eleventy.before handler that checks to see if the content file was changed since the last build date, and skips parsing it if it has not.

Known Issues: