# Meowdown Plugin Guide

Meowdown supports a lightweight plugin system for both the editor and the standalone viewer. Plugins allow you to extend the functionality of the editor and the viewer by interacting with the app and the SDK.

## Editor Plugins (Community Plugins)

Community plugins are loaded directly into the editor. They are simply ES Modules that export a default asynchronous function which receives two arguments: `app` and `sdk`. You can load them from any accessible URL (e.g. GitHub Pages or a CDN like `esm.sh`), but note that dynamic imports inside plugins are restricted to `https://esm.sh/` for security purposes.

### Example: Sync Scroll Plugin
Here is an example plugin that adds a scroll-sync toggle to the editor header.

```javascript
export default async function ScrollSyncPlugin(app, sdk) {
  let syncEnabled = localStorage.getItem('meowdown_sync') === 'true';
  let scrolling = false;

  // Add a button to the top header
  const btn = sdk.addHeaderButton(syncEnabled ? 'Sync: On' : 'Sync: Off', 'toggleSync', 'Toggle Scroll Sync');

  const updateUI = () => {
    btn.textContent = syncEnabled ? 'Sync: On' : 'Sync: Off';
  };

  // Listen for the custom toggle event triggered by our button
  sdk.on('toggleSync', () => {
    syncEnabled = !syncEnabled;
    localStorage.setItem('meowdown_sync', syncEnabled);
    updateUI();
  });

  // Listen for scroll events from the editor or preview containers
  sdk.on('scroll', (e) => {
    if (!syncEnabled || scrolling) return;
    scrolling = true;
    const target = e.target.getAttribute('data-bind');
    const percent = sdk.getScroll(target);
    const other = target === 'editor' ? 'preview' : 'editor';
    sdk.setScroll(other, percent);
    setTimeout(() => scrolling = false, 50);
  });
}
```

### SDK Capabilities
The `sdk` object provides various helper methods to interact with the editor without directly manipulating the DOM:
- `sdk.addHeaderButton(label, action, title)`: Add a button to the top toolbar.
- `sdk.on(event, handler)`: Register an event listener.
- `sdk.updateText(bindName, text)`: Update the text of an element via `data-bind`.
- `sdk.getScroll(target)` and `sdk.setScroll(target, percent)`: Get or set the scroll percentage.
- `sdk.getEl(bindName)`: Get an element using its `data-bind` attribute.

## Viewer Plugins

When you share a note via the standalone viewer (accessed at `/s/[id]`), you can specify plugins to run on that specific document via the markdown frontmatter.

To enable a plugin in the viewer, add a `plugins` array to your document's frontmatter. These plugins must export an `init(contentEl, tocEl)` function, which receives the main document container (`contentEl`) and the table of contents container (`tocEl`).

### Example: Viewer Plugin in Frontmatter

```yaml
---
plugins:
  - https://your-domain.com/my-viewer-plugin.js
---
# My Document

This document loads a custom viewer plugin.
```

### Example: Viewer Plugin Source

```javascript
export function init(contentEl, tocEl) {
  console.log("Viewer plugin loaded!");

  // You can manipulate the content directly
  const headers = contentEl.querySelectorAll('h1, h2, h3');
  headers.forEach(h => {
    h.style.color = '#ff6600';
  });
}
```

## Security & Sandboxing

Because plugins execute JavaScript directly in the browser, Meowdown employs several sandboxing measures:

### Fetch Restrictions
When a plugin runs, `window.fetch` is temporarily overridden with a `restrictedFetch` function. This prevents plugins from silently exfiltrating your data or making unauthorized API calls to arbitrary endpoints.
- **Allowed Domains:** Currently, plugins are strictly limited to fetching from `https://esm.sh/` (for dynamic ES module imports) and `https://openrouter.ai/` (for AI integrations).
- If your plugin attempts to fetch from a domain not on the allowlist, the request will be blocked, and an error will be logged.

### Event Timeouts
To ensure the editor remains responsive, plugin event handlers (registered via `sdk.on`) and initialization logic are monitored.
- Asynchronous plugin execution is given a strict **100ms timeout**.
- If a plugin's logic exceeds this budget, a console warning is emitted (`Plugin event [...] took [...]ms (Limit: 100ms)`). Keep your plugin logic lightweight!

### Viewer Consent
When a document shared via the standalone viewer (`/s/[id]`) contains frontmatter plugins, the viewer will prompt the user for consent before executing any third-party code. This ensures users are aware of potential security risks before loading custom formatting scripts.
