Callout boxes in Stati Markdown

GitHub's > [!NOTE] syntax as a small markdown-it rule.

SNP 075 #3A1 Filed stati markdown

javascript
// helpers/alerts.js: a blockquote that starts with [!NOTE] gets class="alert alert-note"
export function alerts(md) {
  md.core.ruler.push('alerts', (state) => {
    const tokens = state.tokens;
    for (let i = 0; i < tokens.length; i++) {
      if (tokens[i].type !== 'blockquote_open') continue;
      // blockquote_open, paragraph_open, then the paragraph's text
      const inline = tokens[i + 2];
      const match = inline?.type === 'inline' && inline.content.match(/^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]/);
      if (!match) continue;
      tokens[i].attrJoin('class', `alert alert-${match[1].toLowerCase()}`);
      // Remove the marker and the line break after it
      const children = inline.children ?? [];
      if (children[0]?.type === 'text') {
        children[0].content = children[0].content.slice(match[0].length).trimStart();
        if (!children[0].content) children.splice(0, children[1]?.type === 'softbreak' ? 2 : 1);
      }
    }
  });
}
typescript
// stati.config.ts
import { alerts } from './helpers/alerts.js';

export default defineConfig({
  // site and the rest…
  markdown: {
    configure(md) {
      md.use(alerts);
    },
  },
});
markdown
> [!WARNING]
> `stati build --clean` deletes `dist/` and `.stati/`.

That becomes a <blockquote class="alert alert-warning"> without the marker, and the same Markdown still reads right on GitHub. An ordinary quote stays an ordinary quote. The colours, from Sweetie 16:

css
.alert { border-left: 4px solid; padding: 0.25rem 1rem; }
.alert-note { border-color: #41a6f6; }
.alert-tip { border-color: #38b764; }
.alert-important { border-color: #5d275d; }
.alert-warning { border-color: #ef7d57; }
.alert-caution { border-color: #b13e53; }

In the Stati docs: Creating a plugin and Custom configuration.