@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 +322 -127
- package/package.json +1 -1
- package/src/Griot.js +33 -35
- package/src/blocks/BlockRenderer.js +240 -93
- package/src/blocks/BlockSchema.js +29 -86
- package/src/core/Block.js +42 -45
- package/src/core/Document.js +63 -98
- package/src/core/History.js +17 -42
- package/src/editor/Editor.js +405 -138
- package/src/editor/FormatToolbar.js +138 -0
- package/src/editor/Keyboard.js +92 -106
- package/src/editor/SlashMenu.js +197 -0
- package/src/inline/InlineLexer.js +69 -72
- package/src/inline/InlineRenderer.js +110 -95
package/README.md
CHANGED
|
@@ -1,179 +1,374 @@
|
|
|
1
1
|
# Griot
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
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
|
-
##
|
|
16
|
+
## Installation
|
|
9
17
|
|
|
10
18
|
```bash
|
|
11
|
-
|
|
12
|
-
npm install griot # (once published)
|
|
19
|
+
npm install griot
|
|
13
20
|
```
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
33
|
+
## Quick Start
|
|
23
34
|
|
|
24
35
|
### Editor
|
|
25
36
|
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
```
|
|
56
|
-
|
|
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
|
-
|
|
59
|
-
doc,
|
|
60
|
-
books: [],
|
|
61
|
-
onEventClick(eventId) {
|
|
62
|
-
console.log('Open event:', eventId);
|
|
63
|
-
},
|
|
64
|
-
});
|
|
92
|
+
---
|
|
65
93
|
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
| `` | 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
|
-
|
|
144
|
+
### History
|
|
73
145
|
|
|
74
|
-
|
|
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
|
-
##
|
|
150
|
+
## API Reference
|
|
89
151
|
|
|
90
|
-
|
|
152
|
+
The public API is exposed through the main entry point (`griot.js`). Below are the most important exports.
|
|
91
153
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
236
|
+
### Viewer
|
|
237
|
+
|
|
238
|
+
| Export | Description |
|
|
239
|
+
|---|---|
|
|
240
|
+
| `Viewer` | Read‑only renderer. |
|
|
241
|
+
|
|
242
|
+
**Viewer constructor options:**
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
106
245
|
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
##
|
|
284
|
+
## Styling
|
|
127
285
|
|
|
128
|
-
|
|
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
|
-
|
|
131
|
-
import { anchorId, scrollToBlock } from 'griot';
|
|
288
|
+
**Minimal recommended styles:**
|
|
132
289
|
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
##
|
|
336
|
+
## Extending
|
|
147
337
|
|
|
148
|
-
###
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
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
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
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
|
-
|
|
42
|
-
export {
|
|
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
|
-
|
|
48
|
-
export {
|
|
49
|
-
export {
|
|
50
|
-
|
|
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
|
-
|
|
53
|
-
export { Editor } from './editor/Editor.js';
|
|
54
|
-
export { Viewer } from './viewer/Viewer.js';
|
|
52
|
+
export { Viewer } from './viewer/Viewer.js';
|