npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

milkdown-plugin-bible

v0.1.2

Published

Bible references for Milkdown: finds references as you write, previews the passage and inserts it as a quote.

Readme

milkdown-plugin-bible

npm CI

Bible references for Milkdown. Write John 3:16 and the plugin underlines it, shows the passage when you point at it or tap it, and can drop the passage into the document as a quote.

The passage popover over a reference in a Crepe editor

It stores nothing of its own in the document. References stay the text you typed, and an inserted passage is an ordinary Markdown block quote, so the file reads the same in any other editor.

This morning I read John 3:16–17.

> ¹⁶ For God so loved the world that He gave His one and only Son, that everyone who believes in Him shall not perish but have eternal life. ¹⁷ For God did not send His Son into the world to condemn the world, but to save the world through Him.
>
> — John 3:16–17 (BSB)

What it does

  • Finds references as you type. Full names and common abbreviations (Gen 1:1, 1 Cor 13:4-7, Ps 23, II Tim 3:16, Jude 5), verse lists and ranges (John 3:16, 18-20, Rom 8:28; 12:1-2, Gen 1:1-2:3), with or without a translation after them (John 3:16 (KJV)). Every reference is checked against the real chapter and verse counts, so John 45:3 and mark 2 as done are left alone. Text inside code and links is skipped.
  • Shows the passage. Hover a reference, click or tap it, or press Mod-Enter with the cursor in it. The popover switches translation, expands to the whole chapter with the cited verses marked, and copies the text.
  • Inserts the passage. From the popover; by pressing Tab after a reference that has a line to itself; or from a search box you can open from a slash menu, which suggests books as you type and takes loose input such as jn 3 16.
  • Keeps poetry in lines. Psalms and other verse are inserted line by line.
  • Works offline for what you have read. Chapters are cached in memory, and in any storage you give it.

Install

npm install milkdown-plugin-bible @milkdown/kit

It needs @milkdown/kit 7.22 or later, which is what it is tested against, and ships as an ES module with type declarations.

Use it

import { Editor } from '@milkdown/kit/core';
import { commonmark } from '@milkdown/kit/preset/commonmark';
import { bible } from 'milkdown-plugin-bible';
import 'milkdown-plugin-bible/style.css';

await Editor.make().use(commonmark).use(bible).create();

With Crepe, add it to the underlying editor, and optionally to the slash menu:

import { Crepe } from '@milkdown/crepe';
import { bible, bibleMenuItem } from 'milkdown-plugin-bible';
import 'milkdown-plugin-bible/style.css';

const crepe = new Crepe({
  root,
  featureConfigs: {
    [Crepe.Feature.BlockEdit]: {
      buildMenu: (builder) => builder.addGroup('bible', 'Bible').addItem('passage', bibleMenuItem),
    },
  },
});
crepe.editor.use(bible);
await crepe.create();

Passages are inserted as block quotes, so the editor needs the blockquote, paragraph and (for poetry) hardbreak nodes from the commonmark preset. Without blockquote the paragraphs are inserted on their own.

Configure

import { bibleConfig } from 'milkdown-plugin-bible';

editor.config((ctx) => {
  ctx.update(bibleConfig.key, (config) => ({
    ...config,
    translation: 'KJV',
    onTranslationChange: (translation) => localStorage.setItem('bible', translation.id),
  }));
});

| Option | Default | | | --- | --- | --- | | provider | helloaoProvider() | Where passage text comes from. See below. | | translation | 'BSB' | Translation id or label. Pass a function to read it from somewhere shared, so several editors follow one setting. | | onTranslationChange | | Called when the reader picks a translation in the popover. | | hoverDelay | 350 | Milliseconds the pointer rests on a reference before the popover opens. | | maxChapters | 3 | Most chapters one passage may span. | | verseNumbers | true | Number the verses of inserted passages that have more than one. | | citation | — John 3:16 (BSB) | (reference, translation) => string for the last line of an inserted passage. A quote is recognised as a passage by this line: a dash, a reference, and optionally a translation in parentheses. | | keys | { preview: 'Mod-Enter', insert: 'Tab' } | Key bindings in ProseMirror syntax. Set one to null to turn it off. | | root | the editor's parent | Element the popover is added to. If the editor sits inside something that clips its overflow, use document.body. | | labels | English | Every piece of text the plugin shows; see defaultLabels. |

Commands

import { callCommand } from '@milkdown/kit/utils';
import { insertBiblePassageCommand, openBiblePickerCommand, showBiblePassageCommand } from 'milkdown-plugin-bible';

editor.action(callCommand(openBiblePickerCommand.key)); // search box at the cursor
editor.action(callCommand(showBiblePassageCommand.key)); // popover for the reference at the cursor
editor.action(callCommand(insertBiblePassageCommand.key, 'Romans 8:28-30'));
editor.action(callCommand(insertBiblePassageCommand.key, { reference: 'Ps 23', translation: 'KJV' }));

Where the text comes from

By default the plugin reads from the Free Use Bible API, which needs no key, has no rate limit and allows requests from browsers. Four public-domain English translations are offered: the Berean Standard Bible, the World English Bible, the King James Version and the American Standard Version. Each lookup fetches one chapter; the only thing sent is which chapter.

The API serves over a thousand translations. To offer others, list them:

import { helloaoProvider } from 'milkdown-plugin-bible';

const provider = helloaoProvider({
  translations: [
    { id: 'BSB', label: 'BSB', name: 'Berean Standard Bible' },
    { id: 'spa_r09', label: 'RV1909', name: 'Reina Valera 1909' },
  ],
});

Check the licence of any translation you add; many modern ones (ESV, NIV, NLT) are not available there and need a provider of their own.

Your own provider

A provider returns one chapter at a time:

import type { BibleProvider } from 'milkdown-plugin-bible';

const provider: BibleProvider = {
  translations: [{ id: 'esv', label: 'ESV', name: 'English Standard Version' }],
  async getChapter({ book, chapter, translation, signal }) {
    const response = await fetch(`/api/bible/${translation}/${book}/${chapter}`, { signal });
    return response.json(); // { book, chapter, translation, verses: [{ number, text, paragraph?, lines? }] }
  },
};

book is a USFM code such as JHN or 1CO. Give poetry verses lines: [{ text, indent }] to keep their line breaks, and set paragraph: true on a verse that starts a new paragraph.

Keeping chapters between visits

import { cachedProvider, helloaoProvider } from 'milkdown-plugin-bible';
import { get, set } from 'idb-keyval';

const provider = cachedProvider(helloaoProvider(), {
  storage: { get: (key) => get(`bible:${key}`), set: (key, chapter) => set(`bible:${key}`, chapter) },
});

Styling

style.css takes its colours from Crepe's theme variables when they exist and from the browser's own colours otherwise, so it follows light and dark themes without setup. To restyle it, set any of these on an ancestor of the editor:

.milkdown {
  --bible-accent: #2a7628; /* a reference when pointed at, verse numbers, primary button */
  --bible-on-accent: #fff;
  --bible-surface: #fff; /* popover background */
  --bible-text: #15281a;
  --bible-muted: #647363;
  --bible-border: #e1e7d9;
  --bible-shadow: 0 8px 24px rgb(0 0 0 / 12%);
  --bible-radius: 14px;
  --bible-font-ui: 'Open Sans', sans-serif;
  --bible-font-text: 'Source Serif 4', serif; /* passage text */
  --bible-z-index: 60;
}

A reference at rest has a thin underline in the colour of the text around it. Classes in the document: .bible-ref on each reference, .bible-passage on a quoted passage, .bible-passage-cite on its citation line, .bible-verse-number on its verse numbers.

Without the editor

The reference parser and the providers don't need Milkdown:

import { findReferences, formatReference, helloaoProvider, loadPassage, parseReference } from 'milkdown-plugin-bible';

findReferences('See Rom 8:28; 12:1-2 and Ps 23.').map((match) => formatReference(match.reference));
// ['Romans 8:28; 12:1–2', 'Psalm 23']

parseReference('jn 3 16'); // { book: 'JHN', ranges: [{ chapter: 3, verse: 16, endChapter: 3, endVerse: 16 }] }

const passage = await loadPassage(helloaoProvider(), parseReference('John 3:16')!, 'BSB');

For another ProseMirror editor, createBiblePlugin({ getConfig, setTranslation }) returns the plain ProseMirror plugin.

Limits

  • References are recognised by their English book names only, whatever the translation, and only the 66 books of the Protestant canon.
  • Chapter and verse counts follow the common English numbering. A translation that numbers a passage differently may show a neighbouring verse.
  • In running text, short forms that are also ordinary words need a verse to count: Dan 3:16 is a reference, Dan 3 is not. Is, Am and So are never read as books.
  • The default provider sends each chapter request from the reader's browser to bible.helloao.org. Use your own provider, or its baseUrl option, if that doesn't suit your app.

Develop

npm install
npm run dev             # demo pages: Crepe with each of its themes, and a bare Milkdown editor
npm test                # unit tests, against a real Milkdown editor in jsdom
npm run check           # types, tests, build, and a lint of the package's exports
npm run test:consumer   # installs the packed tarball into a fresh project and builds it
npm run generate:versification   # rebuild the verse-count table

Releasing is described in RELEASING.md.

Licence

MIT. The verse counts in src/data were derived from four public-domain translations served by the Free Use Bible API; the passages in the test fixtures are from the Berean Standard Bible and the King James Version, both public domain.