@0m0g1/griot 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,179 +1,374 @@
1
1
  # Griot
2
2
 
3
- A self-contained block-based rich text editor and renderer.
4
- Built for structured historical document authoring — works standalone or embedded inside a larger app.
3
+ A lightweight, extensible block editor and viewer for the web. Inspired by Notion, but built with plain JavaScript and zero dependencies. Griot provides a rich editing experience with:
4
+
5
+ - **Block-based editing** – paragraphs, headings, lists, callouts, code blocks, images, video, audio, tables, dividers, timeline references, book citations, and more.
6
+ - **Inline formatting** – bold, italic, underline, strikethrough, inline code, highlights, colored text, links, images, and custom event/cite chips.
7
+ - **Slash commands** – type `/` to insert any block.
8
+ - **Floating format toolbar** – appears when you select text.
9
+ - **Undo/redo** – with a built‑in history stack.
10
+ - **Read‑only viewer** – render the same document without editing controls.
11
+ - **Immutable document operations** – every change produces a new document object.
12
+ - **Schema‑driven** – all block types are defined in a single schema; easy to extend.
5
13
 
6
14
  ---
7
15
 
8
- ## Install
16
+ ## Installation
9
17
 
10
18
  ```bash
11
- # Copy src/ into your project, or:
12
- npm install griot # (once published)
19
+ npm install griot
13
20
  ```
14
21
 
15
- ```js
16
- import '@0m0g1/griot/css'; // styles
17
- import { Editor, Viewer, createDocument } from 'griot';
22
+ Or include it directly via ES module:
23
+
24
+ ```html
25
+ <script type="module">
26
+ import { Editor, Viewer } from './path/to/griot.js';
27
+ // ...
28
+ </script>
18
29
  ```
19
30
 
20
31
  ---
21
32
 
22
- ## Quick start
33
+ ## Quick Start
23
34
 
24
35
  ### Editor
25
36
 
26
- ```js
27
- import { Editor, createDocument } from 'griot';
28
- import '@0m0g1/griot/css';
29
-
30
- const doc = createDocument('My Article');
31
-
32
- const editor = new Editor(document.querySelector('#editor'), {
33
- doc,
34
- books: [], // optional: parsed books for citations
35
- onChange(updatedDoc) {
36
- localStorage.setItem('draft', JSON.stringify(updatedDoc));
37
- },
38
- onEventClick(eventId) {
39
- // e.g. AppShell.handleSelectItemById(eventId)
40
- console.log('Open timeline event:', eventId);
41
- },
42
- onCiteClick(blockId) {
43
- // scroll viewer to that block
44
- viewer.setHighlight(blockId);
45
- },
46
- onRequestBookPicker(blockId, callback) {
47
- // Open your SourcePicker UI, then call:
48
- // callback({ bookId, unitId, quote, note })
49
- },
50
- });
37
+ ```html
38
+ <div id="editor-container"></div>
39
+ <script type="module">
40
+ import { Editor, createDocument } from 'griot';
41
+
42
+ const container = document.getElementById('editor-container');
43
+ const doc = createDocument([
44
+ { id: 'b1', type: 'heading', text: 'Hello World', meta: { level: 1 } },
45
+ { id: 'b2', type: 'paragraph', text: 'This is **editable** content.' },
46
+ ]);
47
+
48
+ const editor = new Editor(container, {
49
+ doc,
50
+ books: [], // optional, for book citations
51
+ onChange: (newDoc) => {
52
+ console.log('Document changed:', newDoc);
53
+ },
54
+ onEventClick: (eventId) => {
55
+ console.log('Event clicked:', eventId);
56
+ },
57
+ onCiteClick: (blockId) => {
58
+ console.log('Citation clicked:', blockId);
59
+ },
60
+ onRequestBookPicker: (blockId, callback) => {
61
+ // Open your own book picker UI, then call callback with selection
62
+ callback({ bookId: 'book1', unitId: 'unit1', quote: '', note: '' });
63
+ }
64
+ });
65
+
66
+ // Later, if you need to replace the document:
67
+ editor.setDoc(newDoc);
68
+ </script>
51
69
  ```
52
70
 
53
71
  ### Viewer
54
72
 
55
- ```js
56
- import { Viewer } from 'griot';
73
+ ```html
74
+ <div id="viewer-container"></div>
75
+ <script type="module">
76
+ import { Viewer } from 'griot';
77
+
78
+ const container = document.getElementById('viewer-container');
79
+ const viewer = new Viewer(container, {
80
+ doc: myDocument,
81
+ books: myBooks,
82
+ onEventClick: (eventId) => { /* ... */ },
83
+ onCiteClick: (blockId) => { /* ... */ },
84
+ highlightBlockId: 'b2' // optional initial highlight
85
+ });
86
+
87
+ // Highlight and scroll to a block
88
+ viewer.setHighlight('b1');
89
+ </script>
90
+ ```
57
91
 
58
- const viewer = new Viewer(document.querySelector('#viewer'), {
59
- doc,
60
- books: [],
61
- onEventClick(eventId) {
62
- console.log('Open event:', eventId);
63
- },
64
- });
92
+ ---
65
93
 
66
- // Jump to a block (e.g. from a timeline citation)
67
- viewer.setHighlight('b_abc123');
94
+ ## Concepts
95
+
96
+ ### Document
97
+
98
+ A Griot document is a plain object with an `id` and an array of blocks:
99
+
100
+ ```typescript
101
+ interface Document {
102
+ id: string;
103
+ blocks: Block[];
104
+ }
68
105
  ```
69
106
 
70
- ---
107
+ ### Block
108
+
109
+ Every block has at least `id`, `type`, and optionally `text` and `meta`. The `text` field is only present for block types that contain editable text (e.g. paragraphs, headings). All other block types store their data in `meta`.
110
+
111
+ ```typescript
112
+ interface Block {
113
+ id: string;
114
+ type: string;
115
+ text?: string | null;
116
+ meta: Record<string, any>;
117
+ }
118
+ ```
119
+
120
+ ### Inline Markup
121
+
122
+ Within text blocks, you can use lightweight syntax:
123
+
124
+ | Syntax | Result |
125
+ |---|---|
126
+ | `**bold**` | **bold** |
127
+ | `*italic*` | *italic* |
128
+ | `__underline__` | underline |
129
+ | `~~strikethrough~~` | ~~strikethrough~~ |
130
+ | `` `code` `` | `code` |
131
+ | `==highlight==` | highlight |
132
+ | `{#ff0000:red text}` or `{blue:text}` | colored text |
133
+ | `[link text](https://example.com)` | link |
134
+ | `![alt text](image.jpg)` | image |
135
+ | `[[event:eventId\|label]]` | clickable chip → `onEventClick` |
136
+ | `[[cite:blockId\|label]]` | clickable chip → `onCiteClick` |
137
+
138
+ The inline parser is fully independent and can be used separately: `tokenizeInline()`, `renderInlineToDOM()`, `renderInlineToHTML()`.
139
+
140
+ ### Block Schema
141
+
142
+ All block types are defined in `BlockSchema.js`. Each definition includes category, label, icon, slash label, whether it has text, default meta, and placeholder. You can extend the schema by adding new entries.
71
143
 
72
- ## Block types
144
+ ### History
73
145
 
74
- | Type | Icon | Text field | Notes |
75
- |---|---|---|---|
76
- | `paragraph` | ¶ | ✓ | Inline syntax supported |
77
- | `heading` | H | ✓ | `meta.level` 1–6 |
78
- | `blockquote` | ❝ | ✓ | Inline syntax supported |
79
- | `callout` | 💡 | ✓ | `meta.icon` for the emoji |
80
- | `code` | </> | ✓ | No inline parsing. `meta.language` for highlight |
81
- | `divider` | — | — | Horizontal rule |
82
- | `image` | 🖼 | — | `meta.src`, `meta.alt`, `meta.caption` |
83
- | `timeline_ref` | ⏱ | — | `meta.eventId`, `meta.eventTitle`, `meta.note` |
84
- | `book_citation` | 📖 | — | `meta.bookId`, `meta.unitId`, `meta.quote`, `meta.note` |
146
+ The `History` class provides a simple linear undo/redo stack. The editor uses it internally; you can also use it standalone.
85
147
 
86
148
  ---
87
149
 
88
- ## Inline syntax
150
+ ## API Reference
89
151
 
90
- Works inside any block with `hasInline: true` (paragraph, blockquote, callout, note fields):
152
+ The public API is exposed through the main entry point (`griot.js`). Below are the most important exports.
91
153
 
92
- ```
93
- **bold**
94
- *italic*
95
- `inline code`
96
- [link text](https://example.com)
97
- [[event:rome_founding|The Founding of Rome]] → timeline event chip
98
- [[cite:b_abc123|See Chapter 2]] → citation cross-reference
154
+ ### Core
155
+
156
+ | Export | Description |
157
+ |---|---|
158
+ | `createBlock(type, overrides?)` | Create a new block with a unique id. |
159
+ | `cloneBlock(block, newId = true)` | Deep clone a block. |
160
+ | `isTextBlock(block)` | Check if a block stores a text string. |
161
+ | `isValidBlock(block)` | Minimal structural check. |
162
+ | `anchorId(blockId)` | Generate the DOM id used for a block element. |
163
+ | `scrollToBlock(blockId, behavior = 'smooth')` | Scroll to a block's element. |
164
+ | `TEXT_TYPES` | Set of block types that have a text field. |
165
+ | `ALL_TYPES` | Array of all known block type names. |
166
+
167
+ ### Document Operations
168
+
169
+ All functions are immutable – they return a new document.
170
+
171
+ | Export | Description |
172
+ |---|---|
173
+ | `createDocument(blocks?)` | Create a new document (with at least one paragraph). |
174
+ | `toJSON(doc)` / `fromJSON(json)` | Serialize / deserialize. |
175
+ | `getBlock(doc, id)` | Find a block by id. |
176
+ | `getBlockIndex(doc, id)` | Get index of a block. |
177
+ | `getBlockBefore(doc, id)` / `getBlockAfter(doc, id)` | Adjacent blocks. |
178
+ | `updateBlock(doc, id, patch)` | Update text and/or meta. |
179
+ | `insertBlockAfter(doc, afterId, newBlock)` | Insert block. |
180
+ | `insertBlockBefore(doc, beforeId, newBlock)` | Insert block. |
181
+ | `removeBlock(doc, id)` | Delete a block. |
182
+ | `moveBlock(doc, fromIdx, toIdx)` | Reorder blocks. |
183
+ | `splitBlock(doc, blockId, offset)` | Split a text block at offset. |
184
+ | `mergeBlockWithPrev(doc, blockId)` | Merge block into previous one. |
185
+
186
+ ### Inline Parsing & Rendering
187
+
188
+ | Export | Description |
189
+ |---|---|
190
+ | `tokenizeInline(text)` | Return an array of token objects. |
191
+ | `renderInlineToDOM(text, callbacks?)` | Render tokens into a DocumentFragment. |
192
+ | `renderInlineToHTML(text)` | Render tokens into an HTML string. |
193
+ | `escHtml(str)` / `escAttr(str)` | Escape helpers. |
194
+ | `TOKEN` | Enum of token types. |
195
+
196
+ ### Block Rendering (for Viewer or custom use)
197
+
198
+ | Export | Description |
199
+ |---|---|
200
+ | `renderBlock(block, options)` | Render a single block to a DOM element. Used by Viewer. |
201
+ | `getBlockDef(type)` | Get the schema definition for a block type. |
202
+ | `getAllTypes()` | All registered block type names. |
203
+ | `getTypesByCategory(category)` | Filter types by category. |
204
+ | `defaultMeta(type)` | Get default meta for a type. |
205
+ | `resolveYouTube(src)` / `resolveVimeo` / `resolveSpotify` / `resolveSoundCloud` | Extract embed URLs from various sources. |
206
+
207
+ ### Editor Classes
208
+
209
+ | Export | Description |
210
+ |---|---|
211
+ | `Editor` | Main editor class. See constructor options below. |
212
+ | `FormatToolbar` | Floating formatting toolbar (used internally, but can be used standalone). |
213
+ | `SlashMenu` | Slash command menu (used internally). |
214
+ | `DropHandler` | Handles drag & drop of files/images (not yet shown, but exported). |
215
+
216
+ **Editor constructor options:**
217
+
218
+ ```typescript
219
+ {
220
+ doc: Document; // initial document
221
+ books?: Book[]; // array of book objects for citations
222
+ onChange?: (doc: Document) => void; // called after every change (debounced)
223
+ onEventClick?: (eventId: string) => void;
224
+ onCiteClick?: (blockId: string) => void;
225
+ onRequestBookPicker?: (blockId: string, callback: (selection) => void) => void;
226
+ }
99
227
  ```
100
228
 
101
- ---
229
+ **Editor methods:**
102
230
 
103
- ## Document format (`.griot.json`)
231
+ - `setDoc(doc)` replace the document.
232
+ - `setBooks(books)` – update the book list.
233
+ - `focus(blockId)` – focus a specific block.
234
+ - `destroy()` – clean up.
104
235
 
105
- ```json
236
+ ### Viewer
237
+
238
+ | Export | Description |
239
+ |---|---|
240
+ | `Viewer` | Read‑only renderer. |
241
+
242
+ **Viewer constructor options:**
243
+
244
+ ```typescript
106
245
  {
107
- "version": 1,
108
- "id": "doc_abc",
109
- "title": "The Fall of Rome",
110
- "createdAt": "2025-01-01T00:00:00.000Z",
111
- "updatedAt": "2025-01-01T00:00:00.000Z",
112
- "blocks": [
113
- { "id": "b_1", "type": "heading", "text": "The Fall of Rome", "meta": { "level": 1 } },
114
- { "id": "b_2", "type": "paragraph", "text": "In **476 CE** the last emperor [[event:fall_of_rome|was deposed]].", "meta": {} },
115
- { "id": "b_3", "type": "book_citation", "text": null, "meta": {
116
- "bookId": "book_xyz", "unitId": "unit_abc",
117
- "quote": "The barbarians had long served in Roman armies.",
118
- "note": "Essential context for understanding the transition."
119
- }}
120
- ]
246
+ doc?: Document;
247
+ books?: Book[];
248
+ onEventClick?: (eventId: string) => void;
249
+ onCiteClick?: (blockId: string) => void;
250
+ highlightBlockId?: string; // initial highlight
121
251
  }
122
252
  ```
123
253
 
254
+ **Viewer methods:**
255
+
256
+ - `setDoc(doc)`
257
+ - `setBooks(books)`
258
+ - `setHighlight(blockId, options?)` – scroll to and briefly highlight a block.
259
+ - `destroy()`
260
+
261
+ ### Keyboard Helpers
262
+
263
+ | Export | Description |
264
+ |---|---|
265
+ | `attachKeyboardHandler(el, blockId, callbacks)` | Attach editor keyboard shortcuts to a contenteditable element. |
266
+ | `getCursorOffset(el)` / `setCursorOffset(el, offset)` | Get/set caret position by character offset. |
267
+ | `getSelectionOffsets(el)` | Get start/end offsets of current selection. |
268
+ | `focusAtEnd(el)` / `focusAtStart(el)` | Move caret to end/start. |
269
+
270
+ ### History
271
+
272
+ ```javascript
273
+ import { History } from 'griot';
274
+
275
+ const history = new History(initialDoc);
276
+ history.push(newDoc);
277
+ history.undo(); // returns previous document
278
+ history.redo();
279
+ history.current; // current document
280
+ ```
281
+
124
282
  ---
125
283
 
126
- ## Deep-link anchors
284
+ ## Styling
127
285
 
128
- Every rendered block gets `id="griot-{blockId}"` in the DOM.
286
+ Griot includes no CSS by design – you can style it to match your application. All elements have semantic class names prefixed with `griot-`. For a quick start, you can copy the example styles from the test page or browse the class names used in the source.
129
287
 
130
- ```js
131
- import { anchorId, scrollToBlock } from 'griot';
288
+ **Minimal recommended styles:**
132
289
 
133
- // Get the DOM id for a block
134
- anchorId('b_abc123') // "griot-b_abc123"
290
+ - Make `.griot-editor-block__input[contenteditable]` look like a normal block.
291
+ - Add basic spacing and borders.
292
+ - Style the floating toolbar and slash menu as floating cards.
135
293
 
136
- // Scroll to a block (viewer or editor)
137
- scrollToBlock('b_abc123');
138
- scrollToBlock('b_abc123', 'instant');
294
+ ---
295
+
296
+ ## Examples
297
+
298
+ ### Basic Editor with Slash Menu and Toolbar
299
+
300
+ The editor includes the slash menu and format toolbar automatically. Just instantiate it.
301
+
302
+ ### Using the Viewer with Highlight
303
+
304
+ ```javascript
305
+ const viewer = new Viewer(container, { doc });
306
+ viewer.setHighlight('some-block-id');
307
+ ```
308
+
309
+ ### Custom Book Picker
310
+
311
+ When a `book_citation` block is added, the editor calls `onRequestBookPicker`. Implement your own modal or dropdown:
312
+
313
+ ```javascript
314
+ onRequestBookPicker: (blockId, callback) => {
315
+ const book = prompt('Enter book ID:');
316
+ const unit = prompt('Enter unit ID:');
317
+ callback({ bookId: book, unitId: unit, quote: '', note: '' });
318
+ }
139
319
  ```
140
320
 
141
- This is the contract for timeline → article navigation:
142
- store `{ docId, blockId }` on a citation, then call `scrollToBlock(blockId)` when the timeline jumps to it.
321
+ ### Using Inline Renderer Standalone
322
+
323
+ ```javascript
324
+ import { renderInlineToDOM } from 'griot';
325
+
326
+ const text = 'This is **bold** and [a link](https://example.com).';
327
+ const fragment = renderInlineToDOM(text, {
328
+ onEventClick: (id) => console.log(id),
329
+ onCiteClick: (id) => console.log(id)
330
+ });
331
+ document.getElementById('output').appendChild(fragment);
332
+ ```
143
333
 
144
334
  ---
145
335
 
146
- ## API reference
336
+ ## Extending
147
337
 
148
- ### `Editor`
149
- | Method | Description |
150
- |---|---|
151
- | `new Editor(el, options)` | Mount editor into `el` |
152
- | `editor.doc` | Current document (read-only) |
153
- | `editor.setDoc(doc)` | Replace document |
154
- | `editor.setBooks(books)` | Update available books |
155
- | `editor.focus(blockId)` | Focus a specific block |
156
- | `editor.destroy()` | Unmount and clean up |
157
-
158
- ### `Viewer`
159
- | Method | Description |
160
- |---|---|
161
- | `new Viewer(el, options)` | Mount viewer into `el` |
162
- | `viewer.setDoc(doc)` | Replace document |
163
- | `viewer.setBooks(books)` | Update available books |
164
- | `viewer.setHighlight(blockId)` | Scroll to + briefly highlight a block |
165
- | `viewer.destroy()` | Unmount and clean up |
166
-
167
- ### Document helpers
168
- ```js
169
- createDocument(title)
170
- createBlock(type, overrides)
171
- updateBlock(doc, blockId, patch)
172
- insertBlockAfter(doc, blockId, newBlock)
173
- removeBlock(doc, blockId)
174
- splitBlock(doc, blockId, offset) // returns [newDoc, newBlockId]
175
- mergeBlockWithPrev(doc, blockId) // returns [newDoc, prevId, offset]
176
- moveBlock(doc, fromIndex, toIndex)
177
- toJSON(doc)
178
- fromJSON(jsonStringOrObject)
338
+ ### Adding a New Block Type
339
+
340
+ 1. Add an entry in `BlockSchema.js` (or patch the schema at runtime).
341
+ 2. Add a rendering case in `BlockRenderer.js`.
342
+ 3. If the block has a special editor UI, add a case in `Editor._buildSpecialBlockUI()`.
343
+ 4. (Optional) Add support in the slash menu – it reads from the schema automatically.
344
+
345
+ ### Custom Inline Syntax
346
+
347
+ Modify `InlineLexer.js` by adding a new rule. Then update `InlineRenderer.js` to render the new token type.
348
+
349
+ ---
350
+
351
+ ## Development
352
+
353
+ ```bash
354
+ git clone https://github.com/yourname/griot.git
355
+ cd griot
356
+ npm install
357
+ npm run dev # serves test page at localhost:5000 (or similar)
179
358
  ```
359
+
360
+ The source is organised as:
361
+
362
+ - `src/core/` – block primitives, document operations, history.
363
+ - `src/blocks/` – block schema and renderer.
364
+ - `src/inline/` – inline lexer and renderer.
365
+ - `src/editor/` – editor UI, keyboard handling, slash menu, toolbar.
366
+ - `src/viewer/` – read-only renderer.
367
+
368
+ All public exports are aggregated in `src/griot.js`.
369
+
370
+ ---
371
+
372
+ ## License
373
+
374
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0m0g1/griot",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "A self-contained block-based rich text editor and renderer built for historical document authoring.",
5
5
  "type": "module",
6
6
  "main": "./src/Griot.js",
package/src/Griot.js CHANGED
@@ -1,32 +1,26 @@
1
1
  // ─── Griot.js ─────────────────────────────────────────────────────────────────
2
- // Public facade. Import from here — never from internal modules directly.
2
+ // Public facade. Import from here only — never from internal modules directly.
3
3
  //
4
- // Named exports cover every public surface:
5
- //
6
- // Classes
7
- // Editor, Viewer
8
- //
9
- // Document model
10
- // createDocument, createBlock, cloneBlock
11
- // updateBlock, insertBlockAfter, insertBlockBefore,
12
- // removeBlock, splitBlock, mergeBlockWithPrev, moveBlock,
13
- // getBlock, getBlockIndex, toJSON, fromJSON
14
- //
15
- // Block helpers
16
- // anchorId, scrollToBlock, isTextBlock
17
- //
18
- // Inline
19
- // tokenizeInline, renderInlineToDOM, renderInlineToHTML, TOKEN
20
- //
21
- // Schema
22
- // getBlockDef, getAllTypes, defaultMeta, BlockSchema
4
+ // Classes Editor, Viewer, FormatToolbar, SlashMenu, DropHandler
5
+ // Document createDocument, createBlock, cloneBlock
6
+ // updateBlock, insertBlockAfter, insertBlockBefore,
7
+ // removeBlock, moveBlock, splitBlock, mergeBlockWithPrev,
8
+ // getBlock, getBlockIndex, getBlockBefore, getBlockAfter,
9
+ // toJSON, fromJSON
10
+ // Block anchorId, scrollToBlock, isTextBlock, isValidBlock
11
+ // TEXT_TYPES, ALL_TYPES
12
+ // Inline tokenizeInline, renderInlineToDOM, renderInlineToHTML,
13
+ // escHtml, escAttr, TOKEN
14
+ // Schema getBlockDef, getAllTypes, getTypesByCategory,
15
+ // defaultMeta, BlockSchema
16
+ // Keyboard attachKeyboardHandler, getCursorOffset, getSelectionOffsets,
17
+ // setCursorOffset, focusAtEnd, focusAtStart
18
+ // URL helpers resolveYouTube, resolveVimeo, resolveSpotify, resolveSoundCloud
23
19
  // ─────────────────────────────────────────────────────────────────────────────
24
20
 
25
- // Core
26
21
  export {
27
22
  createBlock, cloneBlock, isTextBlock, isValidBlock,
28
- anchorId, scrollToBlock,
29
- TEXT_TYPES, ALL_TYPES,
23
+ anchorId, scrollToBlock, TEXT_TYPES, ALL_TYPES,
30
24
  } from './core/Block.js';
31
25
 
32
26
  export {
@@ -38,17 +32,21 @@ export {
38
32
 
39
33
  export { History } from './core/History.js';
40
34
 
41
- // Inline
42
- export { tokenizeInline, TOKEN } from './inline/InlineLexer.js';
43
- export {
44
- renderInlineToDOM, renderInlineToHTML, escHtml, escAttr,
45
- } from './inline/InlineRenderer.js';
35
+ export { tokenizeInline, TOKEN } from './inline/InlineLexer.js';
36
+ export { renderInlineToDOM, renderInlineToHTML, escHtml, escAttr } from './inline/InlineRenderer.js';
46
37
 
47
- // Blocks
48
- export { getBlockDef, getAllTypes, defaultMeta } from './blocks/BlockSchema.js';
49
- export { default as BlockSchema } from './blocks/BlockSchema.js';
50
- export { renderBlock } from './blocks/BlockRenderer.js';
38
+ export { getBlockDef, getAllTypes, getTypesByCategory, defaultMeta } from './blocks/BlockSchema.js';
39
+ export { default as BlockSchema } from './blocks/BlockSchema.js';
40
+ export { renderBlock, resolveYouTube, resolveVimeo, resolveSpotify, resolveSoundCloud } from './blocks/BlockRenderer.js';
41
+
42
+ export { Editor } from './editor/Editor.js';
43
+ export { FormatToolbar } from './editor/FormatToolbar.js';
44
+ export { SlashMenu } from './editor/SlashMenu.js';
45
+ export { DropHandler } from './editor/DropHandler.js';
46
+ export {
47
+ attachKeyboardHandler,
48
+ getCursorOffset, getSelectionOffsets, setCursorOffset,
49
+ focusAtEnd, focusAtStart,
50
+ } from './editor/Keyboard.js';
51
51
 
52
- // Editor / Viewer
53
- export { Editor } from './editor/Editor.js';
54
- export { Viewer } from './viewer/Viewer.js';
52
+ export { Viewer } from './viewer/Viewer.js';