Catalog npm
PACKAGE EXPLORERView in registry

@0m0g1/griot

npm

A self-contained block-based rich text editor and renderer built for historical document authoring.

blocksdocumenteditorhistoryrich-text
Versions collected20
Direct dependencies0From release 0.1.21
Last observed20 Oct, 22:12 UTC

README

Current package documentation

Griot

A lightweight, extensible block editor and viewer for the web. Inspired by Notion, built with plain JavaScript and zero dependencies. Griot ships a complete dark-themed CSS file, an immutable document model, and a fully keyboard-driven editing experience.


Features

  • 19 block types across four categories: text, media, embed, and structure
  • Inline markup — 12 token types parsed by a standalone lexer
  • Markdown shortcuts — type # , > , - , ``` etc. to convert a block on the fly
  • Floating format toolbar — appears on text selection; wraps with bold, italic, underline, strikethrough, inline code, highlight, link, or color
  • Slash command palette — type / in any empty block; searchable, keyboard-navigable, grouped by category
  • Undo / redo — linear history stack, up to 200 snapshots
  • Live inline preview — rendered below every text block that supports inline syntax
  • Read-only viewer — same document, no editing controls; supports highlight + scroll-to-block
  • Immutable document operations — every mutation returns a new document object
  • Schema-driven — all block types live in BlockSchema.js; easy to extend
  • Default CSS — ships with griot.css; dark theme with CSS variables scoped to .griot-editor / .griot-viewer
  • Zero dependencies — pure ES modules, no framework, no bundler required

Installation

npm install griot

Or via ES module directly:

<script type="module">
  import { Editor, Viewer } from './path/to/griot.js';
</script>

Styling

Griot ships a complete default stylesheet (griot.css). Import it once:

<link rel="stylesheet" href="node_modules/griot/griot.css">

All styles are scoped to .griot-editor and .griot-viewer. Every value is a CSS variable — override any of them to theme Griot to your app:

:root {
  --griot-bg:            #060918;
  --griot-surface:       rgba(255,255,255,0.03);
  --griot-surface-hover: rgba(255,255,255,0.055);
  --griot-border:        rgba(255,255,255,0.07);
  --griot-border-focus:  rgba(99,102,241,0.5);
  --griot-accent:        #6366f1;
  --griot-accent-soft:   rgba(99,102,241,0.12);
  --griot-accent-text:   #a5b4fc;
  --griot-text:          #e2e8f0;
  --griot-text-muted:    #64748b;
  --griot-text-faint:    #334155;
  --griot-code-bg:       rgba(0,0,0,0.45);
  --griot-code-color:    #a5f3fc;
  --griot-font-body:     system-ui, -apple-system, sans-serif;
  --griot-font-mono:     'Fira Code', 'Cascadia Code', monospace;
  --griot-font-serif:    'Georgia', 'Times New Roman', serif;
  --griot-radius:        8px;
}

Quick Start

Editor

<link rel="stylesheet" href="griot.css">
<div id="editor"></div>

<script type="module">
  import { Editor, createDocument } from 'griot';

  const editor = new Editor(document.getElementById('editor'), {
    doc: createDocument([
      { id: 'b1', type: 'heading', text: 'Hello World', meta: { level: 1 } },
      { id: 'b2', type: 'paragraph', text: 'Start writing…' },
    ]),
    books: [],
    onChange:             (doc)         => console.log('changed', doc),
    onEventClick:         (eventId)     => console.log('event', eventId),
    onCiteClick:          (blockId)     => console.log('cite', blockId),
    onRequestBookPicker:  (blockId, cb) => cb({ bookId: 'b1', unitId: 'u1', quote: '', note: '' }),
  });
</script>

Viewer

<div id="viewer"></div>

<script type="module">
  import { Viewer } from 'griot';

  const viewer = new Viewer(document.getElementById('viewer'), {
    doc:              myDocument,
    books:            myBooks,
    onEventClick:     (eventId) => { /* … */ },
    onCiteClick:      (blockId) => { /* … */ },
    highlightBlockId: 'b2',
  });

  viewer.setHighlight('b1');   // scroll to + 2.2s pulse-highlight
</script>

Block Types

All 19 block types are defined in BlockSchema.js.

Text (10 types)

TypeSlash labelNotes
paragraphTextSupports full inline markup; live preview strip below input
headingHeadingLevels 1–6; level picker in editor toolbar
blockquoteQuoteSupports inline markup
calloutCallout💡 Customisable icon
callout_warningWarning⚠️
callout_tipTip✅
callout_dangerDanger🚨
codeCode blockLanguage input in toolbar; pre white-space; monospace
list_ulBullet listOne item per line; Enter inserts newline
list_olNumbered listOne item per line; Enter inserts newline

Media (4 types)

TypeSlash labelNotes
imageImagesrc, alt, caption, width (full etc.)
videoVideoAuto-embeds YouTube (incl. Shorts) and Vimeo; falls back to native <video>
audioAudioAuto-embeds Spotify (track/album/playlist/episode) and SoundCloud; falls back to native <audio>
galleryGalleryMultiple items; layout: grid, masonry, carousel, or strip

Embed (1 type)

TypeSlash labelNotes
embedEmbed / iframeGeneric <iframe> with configurable height and optional caption

Structure (4 types)

TypeSlash labelNotes
tableTableFull WYSIWYG editor with add/remove rows and columns; inline markup in cells
dividerDivider<hr>
timeline_refTimeline eventeventId, eventTitle, note; clickable in viewer → onEventClick
book_citationBook citationbookId, unitId, quote, note; triggers onRequestBookPicker

Inline Markup

The inline parser (InlineLexer.js) is fully independent and can be used standalone. Twelve token types are supported, evaluated in priority order:

SyntaxTokenRenders as
**bold**BOLD<strong>
*italic*ITALIC<em>
__underline__UNDERLINE<u>
~~strikethrough~~STRIKE<s>
`code`CODE<code class="griot-inline-code">
==highlight==HIGHLIGHT<mark class="griot-highlight">
{#f00:red} or {tomato:text}COLOR_MARK<span style="color:…">
[label](url)LINK<a class="griot-link" target="_blank">
![alt](url)IMAGE<img class="griot-inline-img">
[[event:id|label]]EVENT_REFClickable chip → onEventClick
[[cite:id|label]]CITE_REFClickable chip → onCiteClick

Markdown Block Shortcuts

Typing these at the start of a block converts it instantly:

PatternConverts to
# Heading H1
## through ###### Heading H2–H6
> Blockquote
- or * Bullet list
1. Numbered list
--- Divider (text cleared)
``` or ``` + spaceCode block

Editor Keyboard Shortcuts

KeyAction
EnterSplit block at cursor (newline in list blocks)
Backspace at offset 0Merge block with previous; cursor placed at merge point
Delete at endMerge next block into current
↑ on first visual lineMove focus to previous block
↓ on last visual lineMove focus to next block
Ctrl/Cmd+ZUndo
Ctrl/Cmd+Y or Ctrl/Cmd+Shift+ZRedo
Ctrl/Cmd+BWrap selection in **…**
Ctrl/Cmd+IWrap selection in *…*
Ctrl/Cmd+UWrap selection in __…__

Concepts

Document

interface Document {
  id: string;
  blocks: Block[];
}

Block

interface Block {
  id: string;
  type: string;
  text: string | null;   // only present when hasText: true in schema
  meta: Record<string, any>;
}

API Reference

Core

ExportDescription
createBlock(type, overrides?)New block with unique id
cloneBlock(block, newId?)Deep clone; newId defaults to true
isTextBlock(block)true if block has a text field
isValidBlock(block)Minimal structural check
anchorId(blockId)DOM id string for a block element
scrollToBlock(blockId, behavior?)scrollIntoView wrapper
TEXT_TYPESSet<string> of types that carry a text field
ALL_TYPESstring[] of all known types

Document Operations

All functions are immutable — they return a new document object.

ExportDescription
createDocument(blocks?)New document; falls back to a single empty paragraph
toJSON(doc) / fromJSON(json)Serialize / deserialize
getBlock(doc, id)Find a block by id
getBlockIndex(doc, id)Index of a block
getBlockBefore(doc, id) / getBlockAfter(doc, id)Adjacent blocks
updateBlock(doc, id, patch)Patch text, type, and/or meta (meta is shallow-merged)
insertBlockAfter(doc, afterId, block)Insert a block
insertBlockBefore(doc, beforeId, block)Insert a block
removeBlock(doc, id)Delete a block
moveBlock(doc, fromIdx, toIdx)Reorder blocks
splitBlock(doc, blockId, offset)Split at cursor offset; headings become paragraphs. Returns [newDoc, newBlockId]
mergeBlockWithPrev(doc, blockId)Concatenate text with previous block. Returns [newDoc, prevId, mergeOffset]

Inline Parsing & Rendering

ExportDescription
tokenizeInline(text)Returns Token[]
renderInlineToDOM(text, callbacks?)Returns a DocumentFragment
renderInlineToHTML(text)Returns an HTML string
escHtml(str) / escAttr(str)Escape helpers
TOKENFrozen enum of all token type strings

Block Rendering

ExportDescription
renderBlock(block, options)Renders a single block to a DOM element
getBlockDef(type)Schema definition for a type
getAllTypes()All registered type names
getTypesByCategory(category)Types filtered by 'text', 'media', 'embed', or 'structure'
defaultMeta(type)Default meta object for a type
resolveYouTube(src)Extracts YouTube embed URL
resolveVimeo(src)Extracts Vimeo embed URL
resolveSpotify(src)Extracts Spotify embed URL
resolveSoundCloud(src)Builds SoundCloud player URL

Editor

new Editor(container: HTMLElement, options: {
  doc: Document;
  books?: Book[];
  onChange?: (doc: Document) => void;       // debounced 400 ms while typing
  onEventClick?: (eventId: string) => void;
  onCiteClick?: (blockId: string) => void;
  onRequestBookPicker?: (
    blockId: string,
    callback: (selection: { bookId: string; unitId: string; quote: string; note: string }) => void
  ) => void;
})

Methods: setDoc(doc), setBooks(books), focus(blockId), destroy()

Per-block toolbar: type switcher (all 19 types), heading level selector (H1–H6), code language input, move up/down, add below, delete.

onChange debouncing: while the user types, intermediate state is captured via history.replace(). A new undo point is committed 400 ms after the last keystroke.

Viewer

new Viewer(container: HTMLElement, options: {
  doc?: Document;
  books?: Book[];
  onEventClick?: (eventId: string) => void;
  onCiteClick?: (blockId: string) => void;
  highlightBlockId?: string;
})

Methods: setDoc(doc), setBooks(books), setHighlight(blockId, options?), destroy()

setHighlight scrolls to the block and applies a 2.2-second CSS pulse animation, then removes the highlight class automatically.

History

import { History } from 'griot';

const history = new History(initialDoc);  // max 200 snapshots
history.push(doc);      // new undo point (discards redo future)
history.replace(doc);   // overwrite current snapshot without a new undo point
history.undo();         // returns previous document
history.redo();         // returns next document
history.current;        // current document
history.canUndo();      // boolean
history.canRedo();      // boolean

Keyboard Helpers

ExportDescription
attachKeyboardHandler(el, blockId, callbacks)Attach all editor key bindings to a contenteditable
getCursorOffset(el)Character offset of caret
setCursorOffset(el, offset)Move caret to character offset
getSelectionOffsets(el){ start, end } of current selection
focusAtEnd(el) / focusAtStart(el)Move caret to end / start

Multimedia

{ type: 'gallery', meta: { items: [{ src, alt, caption }, …], layout: 'grid' } }
// layout: 'grid' | 'masonry' | 'carousel' | 'strip'

Auto-embed detection

Setting meta.src on a video or audio block automatically produces the right embed:

URL patternResult
youtube.com/watch?v=…, youtu.be/…, /shorts/…YouTube iframe
vimeo.com/…Vimeo iframe
open.spotify.com/track/… (album/playlist/episode too)Spotify iframe
soundcloud.com/…SoundCloud widget
Anything elseNative <video> / <audio>

Extending

Adding a Block Type

  1. Add an entry to BlockSchema.js (or patch the schema at runtime).
  2. Add a rendering case to BlockRenderer.js.
  3. If it needs an editor UI, add a case to Editor._buildSpecialBlockUI().
  4. The slash menu reads from the schema — no changes needed there.

Custom Inline Syntax

Add a rule to InlineLexer.js, then handle the new token type in both InlineRenderer._toNode() (DOM) and InlineRenderer._toHTML() (HTML string).


Project Structure

src/
  core/
    Block.js          — block primitives, TEXT_TYPES, uid, anchorId
    Document.js       — immutable document operations
    History.js        — undo/redo stack (max 200)
  blocks/
    BlockSchema.js    — single source of truth for all 19 block types
    BlockRenderer.js  — block → DOM element (used by Viewer)
  inline/
    InlineLexer.js    — tokenizer (12 token types)
    InlineRenderer.js — tokens → DOM fragment or HTML string
  editor/
    Editor.js         — full editing lifecycle
    FormatToolbar.js  — floating selection toolbar
    SlashMenu.js      — slash command palette
    Keyboard.js       — key bindings and cursor helpers
  viewer/
    Viewer.js         — read-only renderer
  griot.js            — public entry point
griot.css             — default dark theme (CSS variables)

Development

git clone https://github.com/yourname/griot.git
cd griot
npm install
npm run dev   # dev server at localhost:5000

License

MIT

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 20

Collected releases
VersionPublishedDirect dependenciesOpen version
0.1.21Registry latest11 May 20260
0.1.2011 May 20260
0.1.1910 May 20260
0.1.1610 May 20260
0.1.154 Apr 20260
0.1.1419 Mar 20260
0.1.1319 Mar 20260
0.1.1219 Mar 20260
0.1.1119 Mar 20260
0.1.1013 Mar 20260
0.1.913 Mar 20260
0.1.813 Mar 20260
0.1.713 Mar 20260
0.1.613 Mar 20260
0.1.513 Mar 20260
0.1.413 Mar 20260
0.1.311 Mar 20260
0.1.211 Mar 20260
0.1.111 Mar 20260
0.1.011 Mar 20260

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 22:12 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:07f5fa01cce2e090600a615d95209bf368450fbef8989064c251c4035922f71a