npm package: @masonjames/emdash-plugin-reading-time
Description: Reading time plugin for EmDash CMS with editor blocks, theme components, and sitewide defaults
Author: Mason James
Latest version: 0.1.0

# EmDash Reading Time

Reading Time is a native EmDash plugin that gives you a polished, reusable reading-time badge for long-form content.

- **Editors** can insert a `readingTime` Portable Text block.
- **Theme authors** can import a dedicated Astro component for explicit placement near titles, heroes, or metadata rows.
- **Admins** can define sitewide reading-time defaults for words per minute, label format, and counting rules.

The package is intentionally small and operationally boring:

- no routes
- no required capabilities
- no plugin storage
- no migrations

## Status

This is a **trusted/native plugin** for EmDash today.

That is not a marketing choice—it is a current platform constraint. EmDash’s Portable Text plugin blocks and `componentsEntry` rendering are native-only, so this package **cannot be installed as a sandboxed marketplace plugin without losing core features**.

## Official distribution path

Today, the supported distribution path for this package is:

1. publish the package to npm
2. install it in an EmDash site
3. register it in `astro.config.mjs`

`emdash plugin publish` targets the EmDash marketplace flow for bundled marketplace tarballs. Current EmDash docs and runtime constraints make that path incompatible with a plugin that needs `componentsEntry` and Portable Text block types.

## Install

```bash
npm install @masonjames/emdash-plugin-reading-time
```

## Register the plugin

```ts
// astro.config.mjs
import { defineConfig } from "astro/config";
import { emdash } from "emdash";
import { readingTimePlugin } from "@masonjames/emdash-plugin-reading-time";

export default defineConfig({
	integrations: [
		emdash({
			plugins: [readingTimePlugin()],
		}),
	],
});
```

## What you get

### 1. Sitewide plugin settings

The plugin adds settings for:

- words per minute
- label template
- include headings
- include captions
- ignore code blocks
- short-content behavior

### 2. A `readingTime` Portable Text block

Editors can insert **Reading Time** from the slash menu and optionally override:

- display style
- manual minutes
- custom label
- prefix text
- words per minute
- counting rules
- short-content behavior

### 3. A theme component for explicit placement

```astro
---
import { ReadingTimeBadge } from "@masonjames/emdash-plugin-reading-time/components";

const { post } = Astro.props;
---

<ReadingTimeBadge content={post.content} variant="pill" />
```

This is the recommended path for consistent, fully automatic output.

## Theme usage examples

### Basic badge

```astro
---
import { ReadingTimeBadge } from "@masonjames/emdash-plugin-reading-time/components";

const { article } = Astro.props;
---

<ReadingTimeBadge content={article.content} />
```

### Custom label template

```astro
<ReadingTimeBadge
	content={article.content}
	labelTemplate="Approx. {minutes} minutes"
	variant="compact"
/>
```

### Manual override for unusual formats

```astro
<ReadingTimeBadge
	content={article.content}
	manualMinutes={12}
	prefixText="Estimated"
/>
```

## Editor-placed block vs theme placement

EmDash Reading Time deliberately supports **explicit placement** in two ways:

### Theme placement

Best when you want the same badge position on every article.

### Editor placement

Best when a team wants per-article control inside Portable Text.

### Important caveat

Portable Text type renderers typically only receive the current block node. Because reading-time calculation needs the surrounding article content, the plugin’s auto-wired `readingTime` block can only calculate automatically when the renderer is given article content by the theme.

In practice:

- **Theme component placement** is the most reliable fully automatic path.
- **Editor-placed blocks** are great for manual minutes or custom labels out of the box.
- If your theme passes the surrounding Portable Text array into the block renderer, editor-placed blocks can also calculate automatically.

## Defaults

The plugin ships with these safe defaults:

```ts
{
	wordsPerMinute: 200,
	labelTemplate: "{minutes} min read",
	includeHeadings: true,
	includeCaptions: false,
	ignoreCodeBlocks: true,
	shortContentBehavior: "one_min"
}
```

## Short-content behavior

For posts under one minute, choose one of:

- `one_min` → `1 min read`
- `less_than_one` → `< 1 min read`
- `hide` → render nothing

## Duplicate badge guidance

Use **editor placement or theme placement**, not both, unless you intentionally want two badges.

## Public exports

### Descriptor

```ts
import { readingTimePlugin } from "@masonjames/emdash-plugin-reading-time";
```

### Runtime entrypoint

Used internally by the descriptor:

```ts
import createPlugin from "@masonjames/emdash-plugin-reading-time/plugin";
```

### Auto-wired Astro block components

```ts
import { blockComponents } from "@masonjames/emdash-plugin-reading-time/astro";
```

### Explicit theme components

```ts
import { ReadingTimeBadge } from "@masonjames/emdash-plugin-reading-time/components";
```

## Stable EmDash note

The public `emdash` npm package does not currently expose a stable site-side helper for reading persisted plugin settings during Astro render. This package therefore:

- always works with its built-in defaults
- supports explicit `settings={...}` passed by the theme
- automatically uses persisted plugin settings when that helper is available in the host EmDash runtime

## Requirements

- EmDash `0.1.0` or newer
- Astro `6` or newer
- Trusted/native plugin installation via `plugins: []`

## Development

```bash
npm test
npm run typecheck
```

## Roadmap note

If EmDash later supports Portable Text plugin blocks and site-side components in sandboxed plugins, this package can grow a marketplace-safe variant. Until then, the native/trusted format is the honest and correct shape for full feature parity.