markdown-it-smiles
Version:
A markdown-it plugin for rendering SMILES (Simplified Molecular Input Line Entry System) chemical structures
2 lines (1 loc) • 5.07 kB
JavaScript
!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(n="undefined"!=typeof globalThis?globalThis:n||self).markdownItSmiles=e()}(this,(function(){"use strict";function n(n,e,t,i){let s=n.bMarks[e]+n.tShift[e],r=n.eMarks[e];if(s+3>r)return!1;if("```"!==n.src.slice(s,s+3))return!1;s+=3;const a=n.src.slice(s,r).trim();if(!a.startsWith("smiles"))return!1;const o=a.match(/^smiles\s*({.*})?/);let l={};if(o&&o[1])try{l=JSON.parse(o[1])}catch(n){}if(i)return!0;let c=e+1,d=!1;for(;c<t&&(s=n.bMarks[c]+n.tShift[c],r=n.eMarks[c],!(s<r&&n.sCount[c]<n.blkIndent));){if("```"===n.src.slice(s,s+3)){d=!0;break}c++}if(!d)return!1;const f=n.getLines(e+1,c,0,!1).trim(),m=n.push("smiles_block","div",0);return m.content=f,m.info=l,m.map=[e,c+1],n.line=c+1,!0}function e(n,e){const t=n.pos,i=n.posMax;if(t+8>i)return!1;if("$smiles{"!==n.src.slice(t,t+8))return!1;let s=t+8,r=1;for(;s<i&&r>0;)"{"===n.src[s]?r++:"}"===n.src[s]&&r--,s++;if(0!==r)return!1;const a=n.src.slice(t+8,s-1);if(e)return!0;const o=n.push("smiles_inline","span",0);return o.content=a,o.markup="$smiles{",n.pos=s,!0}function t(){return"smiles-"+Math.random().toString(36).substr(2,9)}function i(n,e,i,s,r){const a=n[e],o=a.content,l=a.info||{};return`<div class="smiles-container">\n <canvas id="${t()}" width="${l.width||400}" height="${l.height||300}" data-smiles="${o}" data-options='${JSON.stringify(l)}'></canvas>\n </div>\n`}function s(n,e,i,s,r){const a=n[e].content;return`<canvas class="smiles-inline" id="${t()}" width="200" height="150" data-smiles="${a}" data-options='{"width":200,"height":150}'></canvas>`}return function(t,r){if(r=r||{},t.block.ruler.before("fence","smiles_block",n,{alt:["paragraph","reference","blockquote","list"]}),t.inline.ruler.before("emphasis","smiles_inline",e),t.renderer.rules.smiles_block=i,t.renderer.rules.smiles_inline=s,!1!==r.includeScript){const n=t.render.bind(t);t.render=function(e,t){const i=n(e,t);if(i.includes("data-smiles")){const n=r.smilesDrawerUrl||"https://unpkg.com/smiles-drawer@1.0.10/dist/smiles-drawer.min.js";return`<link href="${r.fontUrl||"https://fonts.googleapis.com/css?family=Droid+Sans:400,700"}" rel="stylesheet">\n\n<style>\n.smiles-container {\n margin: 1em 0;\n text-align: center;\n}\n\n.smiles-container canvas {\n border: 1px solid #ddd;\n border-radius: 4px;\n background: white;\n}\n\n.smiles-inline {\n vertical-align: middle;\n border: 1px solid #ddd;\n border-radius: 2px;\n background: white;\n margin: 0 2px;\n}\n\n@media (max-width: 768px) {\n .smiles-container canvas {\n max-width: 100%;\n height: auto;\n }\n}\n</style>\n`+i+"\n"+`<script src="${n}"><\/script>\n\n<script>\n(function() {\n function renderSmiles() {\n const canvases = document.querySelectorAll('canvas[data-smiles]');\n \n canvases.forEach(function(canvas) {\n const smiles = canvas.getAttribute('data-smiles');\n const options = JSON.parse(canvas.getAttribute('data-options') || '{}');\n \n // Default options\n const defaultOptions = {\n width: canvas.width,\n height: canvas.height,\n bondThickness: 0.6,\n bondLength: 15,\n atomVisualization: 'default',\n isomeric: true,\n terminalCarbons: false,\n explicitHydrogens: false,\n compactDrawing: true,\n fontSizeLarge: 5,\n fontSizeSmall: 3,\n padding: 20.0\n };\n \n const finalOptions = Object.assign(defaultOptions, options);\n \n if (typeof SmilesDrawer !== 'undefined') {\n const drawer = new SmilesDrawer.Drawer(finalOptions);\n \n SmilesDrawer.parse(smiles, function(tree) {\n drawer.draw(tree, canvas.id, 'light', false);\n }, function(err) {\n console.error('SMILES parsing error:', err);\n const ctx = canvas.getContext('2d');\n ctx.fillStyle = '#ff0000';\n ctx.font = '12px Arial';\n ctx.fillText('Error: Invalid SMILES', 10, 20);\n });\n } else {\n console.error('SmilesDrawer not loaded');\n const ctx = canvas.getContext('2d');\n ctx.fillStyle = '#ff0000';\n ctx.font = '12px Arial';\n ctx.fillText('Error: SmilesDrawer not loaded', 10, 20);\n }\n });\n }\n \n // If SmilesDrawer is already loaded, render immediately\n if (typeof SmilesDrawer !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', renderSmiles);\n } else {\n renderSmiles();\n }\n } else {\n // Wait for SmilesDrawer to load\n let checkCount = 0;\n const checkInterval = setInterval(function() {\n if (typeof SmilesDrawer !== 'undefined') {\n clearInterval(checkInterval);\n renderSmiles();\n } else if (checkCount++ > 50) { // 5 second timeout\n clearInterval(checkInterval);\n console.error('SmilesDrawer failed to load within timeout');\n }\n }, 100);\n }\n})();\n<\/script>`}return i}}}}));