authorly-editor 0.1.0 → 0.1.3

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.
Files changed (2) hide show
  1. package/README.md +544 -544
  2. package/package.json +9 -3
package/README.md CHANGED
@@ -1,544 +1,544 @@
1
- <p align="center">
2
- <img src="https://raw.githubusercontent.com/lucide-icons/lucide/main/icons/pen-line.svg" width="64" height="64" alt="Authorly" />
3
- </p>
4
-
5
- <h1 align="center">Authorly — Rich Text Editor for Blogs & Publishing</h1>
6
-
7
- <p align="center">
8
- <strong>A rich text editor for authors, blogs, and documentation</strong><br>
9
- Clean, publish-ready HTML output. Zero bloat.
10
- </p>
11
-
12
- <p align="center">
13
- <a href="#installation">Installation</a> •
14
- <a href="#quick-start">Quick Start</a> •
15
- <a href="#components">Components</a> •
16
- <a href="#api-reference">API</a> •
17
- <a href="#examples">Examples</a>
18
- </p>
19
-
20
- <p align="center">
21
- <img src="https://img.shields.io/badge/React-17%2B-61dafb?style=flat-square&logo=react" alt="React 17+" />
22
- <img src="https://img.shields.io/badge/TypeScript-Ready-3178c6?style=flat-square&logo=typescript" alt="TypeScript" />
23
- <img src="https://img.shields.io/badge/Size-~30kb-green?style=flat-square" alt="Bundle Size" />
24
- <img src="https://img.shields.io/badge/License-MIT-yellow?style=flat-square" alt="MIT License" />
25
- </p>
26
-
27
- ---
28
-
29
- ## Why Authorly?
30
-
31
- | Feature | Authorly | Other Editors |
32
- |---------|----------|---------------|
33
- | **Output** | Pure semantic HTML | JSON AST / Custom format |
34
- | **Dependencies** | React + Lucide icons | Heavy frameworks |
35
- | **Bundle size** | ~30kb gzipped | 100kb+ |
36
- | **Learning curve** | Minutes | Hours/Days |
37
- | **Database storage** | Just HTML string | Complex serialization |
38
-
39
- ```html
40
- <!-- What you get: Clean, portable HTML ready to publish -->
41
- <h1>My Article</h1>
42
- <p>A paragraph with <strong>bold</strong> and <em>italic</em> text.</p>
43
- <ul>
44
- <li>Simple</li>
45
- <li>Clean</li>
46
- <li>Works everywhere</li>
47
- </ul>
48
- ```
49
-
50
- ---
51
-
52
- ## Installation
53
-
54
- ```bash
55
- npm install authorly
56
- ```
57
-
58
- ```bash
59
- yarn add authorly
60
- ```
61
-
62
- ```bash
63
- pnpm add authorly
64
- ```
65
-
66
- ---
67
-
68
- ## Quick Start
69
-
70
- ```tsx
71
- import { ContentBlocksEditor } from 'authorly';
72
-
73
- function App() {
74
- const [content, setContent] = useState('<p>Hello World!</p>');
75
-
76
- return (
77
- <ContentBlocksEditor
78
- initialContent={content}
79
- onChange={setContent}
80
- />
81
- );
82
- }
83
- ```
84
-
85
- That's it. No configuration needed.
86
-
87
- ---
88
-
89
- ## Components
90
-
91
- ### 1. ContentBlocksEditor
92
-
93
- The main editor component for creating and editing content.
94
-
95
- ```tsx
96
- import { ContentBlocksEditor } from 'authorly';
97
-
98
- <ContentBlocksEditor
99
- initialContent="<p>Start writing...</p>"
100
- onChange={(html) => console.log(html)}
101
- onSave={(html) => saveToDatabase(html)}
102
- darkMode={false}
103
- showToolbar={true}
104
- placeholder="Type '/' for commands..."
105
- />
106
- ```
107
-
108
- ### 2. ContentBlocksRenderer
109
-
110
- Display saved HTML content with beautiful styling. No editor overhead.
111
-
112
- ```tsx
113
- import { ContentBlocksRenderer } from 'authorly';
114
-
115
- <ContentBlocksRenderer
116
- html={savedContent}
117
- darkMode={false}
118
- enableCodeCopy={true}
119
- />
120
- ```
121
-
122
- ### 3. TableOfContents
123
-
124
- Auto-generate navigation from your content headings.
125
-
126
- ```tsx
127
- import { TableOfContents, ContentBlocksRenderer } from 'authorly';
128
-
129
- <div style={{ display: 'flex' }}>
130
- <aside style={{ width: 200 }}>
131
- <TableOfContents html={content} title="Contents" />
132
- </aside>
133
- <main>
134
- <ContentBlocksRenderer html={content} enableHeadingIds={true} />
135
- </main>
136
- </div>
137
- ```
138
-
139
- ---
140
-
141
- ## Block Types
142
-
143
- | Block | Description | HTML Output |
144
- |-------|-------------|-------------|
145
- | **Paragraph** | Basic text | `<p>` |
146
- | **Heading 1-6** | Section headings | `<h1>` - `<h6>` |
147
- | **Bullet List** | Unordered list | `<ul><li>` |
148
- | **Numbered List** | Ordered list | `<ol><li>` |
149
- | **Checklist** | Todo items | `<ul><li><input type="checkbox">` |
150
- | **Quote** | Blockquote | `<blockquote>` |
151
- | **Code** | Code block | `<pre><code>` |
152
- | **Image** | Image with caption | `<figure><img><figcaption>` |
153
- | **Video** | YouTube/Vimeo/MP4 | `<figure><iframe>` |
154
- | **Table** | Data table | `<table>` |
155
- | **Divider** | Horizontal rule | `<hr>` |
156
- | **Callout** | Info/Warning/Error | `<aside>` |
157
- | **Accordion** | Collapsible section | `<details><summary>` |
158
-
159
- ---
160
-
161
- ## Keyboard Shortcuts
162
-
163
- | Shortcut | Action |
164
- |----------|--------|
165
- | `Ctrl/Cmd + B` | Bold |
166
- | `Ctrl/Cmd + I` | Italic |
167
- | `Ctrl/Cmd + U` | Underline |
168
- | `Ctrl/Cmd + S` | Save (triggers `onSave`) |
169
- | `Ctrl/Cmd + Z` | Undo |
170
- | `Ctrl/Cmd + Y` | Redo |
171
- | `Ctrl/Cmd + 1/2/3` | Heading 1/2/3 |
172
- | `/` | Open block menu |
173
- | `Enter` | New block / New list item |
174
- | `Backspace` | Delete empty block / Merge |
175
- | `Tab` | Indent list / Navigate table |
176
- | `↑ / ↓` | Navigate between blocks |
177
-
178
- ---
179
-
180
- ## API Reference
181
-
182
- ### ContentBlocksEditor Props
183
-
184
- | Prop | Type | Default | Description |
185
- |------|------|---------|-------------|
186
- | `initialContent` | `string` | `''` | Initial HTML content |
187
- | `onChange` | `(html: string) => void` | - | Called on content change |
188
- | `onSave` | `(html: string) => void` | - | Called on Ctrl+S |
189
- | `onFocus` | `() => void` | - | Called when editor gains focus |
190
- | `onBlur` | `() => void` | - | Called when editor loses focus |
191
- | `onReady` | `(editor: EditorInstance) => void` | - | Called when editor is ready |
192
- | `darkMode` | `boolean` | `false` | Enable dark theme |
193
- | `showToolbar` | `boolean` | `true` | Show formatting toolbar |
194
- | `toolbarPosition` | `'top' \| 'bottom'` | `'top'` | Toolbar position |
195
- | `placeholder` | `string` | `'Type "/" for commands...'` | Placeholder text |
196
- | `readOnly` | `boolean` | `false` | Disable editing |
197
- | `autoFocus` | `boolean` | `false` | Focus on mount |
198
- | `spellCheck` | `boolean` | `true` | Enable spell check |
199
- | `className` | `string` | `''` | Custom class name |
200
- | `style` | `CSSProperties` | - | Custom styles |
201
-
202
- ### EditorRef Methods
203
-
204
- Access editor methods using a ref:
205
-
206
- ```tsx
207
- import { useRef } from 'react';
208
- import { ContentBlocksEditor, EditorRef } from 'authorly';
209
-
210
- function MyEditor() {
211
- const editorRef = useRef<EditorRef>(null);
212
-
213
- return (
214
- <>
215
- <ContentBlocksEditor ref={editorRef} />
216
- <button onClick={() => console.log(editorRef.current?.getHTML())}>
217
- Get HTML
218
- </button>
219
- </>
220
- );
221
- }
222
- ```
223
-
224
- | Method | Description |
225
- |--------|-------------|
226
- | `getHTML()` | Returns the current HTML content |
227
- | `setHTML(html: string)` | Sets the editor content |
228
- | `getText()` | Returns plain text content |
229
- | `focus()` | Focuses the editor |
230
- | `blur()` | Blurs the editor |
231
- | `insertBlock(type, data?)` | Inserts a new block |
232
- | `getEditor()` | Returns the full editor instance |
233
-
234
- ### ContentBlocksRenderer Props
235
-
236
- | Prop | Type | Default | Description |
237
- |------|------|---------|-------------|
238
- | `html` | `string` | `''` | HTML content to render |
239
- | `darkMode` | `boolean` | `false` | Enable dark theme |
240
- | `enableCodeCopy` | `boolean` | `true` | Add copy button to code blocks |
241
- | `enableHeadingIds` | `boolean` | `true` | Add IDs to headings |
242
- | `enableChecklistStyles` | `boolean` | `true` | Strikethrough checked items |
243
- | `className` | `string` | `''` | Custom class name |
244
- | `style` | `CSSProperties` | - | Custom styles |
245
-
246
- ### TableOfContents Props
247
-
248
- | Prop | Type | Default | Description |
249
- |------|------|---------|-------------|
250
- | `html` | `string` | `''` | HTML to extract headings from |
251
- | `darkMode` | `boolean` | `false` | Enable dark theme |
252
- | `title` | `string` | `'Table of Contents'` | Title text |
253
- | `minLevel` | `number` | `1` | Min heading level (1-6) |
254
- | `maxLevel` | `number` | `6` | Max heading level (1-6) |
255
- | `onNavigate` | `(id, item) => void` | - | Custom navigation handler |
256
- | `smoothScroll` | `boolean` | `true` | Smooth scroll to heading |
257
- | `collapsible` | `boolean` | `false` | Make TOC collapsible |
258
-
259
- ---
260
-
261
- ## Examples
262
-
263
- ### Blog Editor with Preview
264
-
265
- ```tsx
266
- import { useState, useRef } from 'react';
267
- import {
268
- ContentBlocksEditor,
269
- ContentBlocksRenderer,
270
- EditorRef
271
- } from 'authorly';
272
-
273
- function BlogEditor() {
274
- const editorRef = useRef<EditorRef>(null);
275
- const [content, setContent] = useState('<p>Write your post...</p>');
276
- const [showPreview, setShowPreview] = useState(false);
277
-
278
- const handleSave = async (html: string) => {
279
- await fetch('/api/posts', {
280
- method: 'POST',
281
- body: JSON.stringify({ content: html }),
282
- });
283
- };
284
-
285
- return (
286
- <div>
287
- <button onClick={() => setShowPreview(!showPreview)}>
288
- {showPreview ? 'Edit' : 'Preview'}
289
- </button>
290
-
291
- {showPreview ? (
292
- <ContentBlocksRenderer html={content} />
293
- ) : (
294
- <ContentBlocksEditor
295
- ref={editorRef}
296
- initialContent={content}
297
- onChange={setContent}
298
- onSave={handleSave}
299
- />
300
- )}
301
- </div>
302
- );
303
- }
304
- ```
305
-
306
- ### Documentation Page with TOC
307
-
308
- ```tsx
309
- import {
310
- ContentBlocksRenderer,
311
- TableOfContents
312
- } from 'authorly';
313
-
314
- function DocsPage({ content }) {
315
- return (
316
- <div style={{ display: 'grid', gridTemplateColumns: '250px 1fr', gap: '2rem' }}>
317
- <aside style={{ position: 'sticky', top: '1rem', height: 'fit-content' }}>
318
- <TableOfContents
319
- html={content}
320
- title="On this page"
321
- maxLevel={3}
322
- />
323
- </aside>
324
- <main>
325
- <ContentBlocksRenderer
326
- html={content}
327
- enableHeadingIds={true}
328
- enableCodeCopy={true}
329
- />
330
- </main>
331
- </div>
332
- );
333
- }
334
- ```
335
-
336
- ### Dark Mode Support
337
-
338
- ```tsx
339
- import { useState } from 'react';
340
- import { ContentBlocksEditor } from 'authorly';
341
-
342
- function ThemedEditor() {
343
- const [darkMode, setDarkMode] = useState(false);
344
-
345
- return (
346
- <div style={{
347
- background: darkMode ? '#0f172a' : '#ffffff',
348
- minHeight: '100vh',
349
- padding: '2rem'
350
- }}>
351
- <button onClick={() => setDarkMode(!darkMode)}>
352
- Toggle Theme
353
- </button>
354
- <ContentBlocksEditor darkMode={darkMode} />
355
- </div>
356
- );
357
- }
358
- ```
359
-
360
- ---
361
-
362
- ## Customization
363
-
364
- ### CSS Variables
365
-
366
- Override these CSS variables to customize the editor appearance:
367
-
368
- ```css
369
- .cb-editor {
370
- /* Colors */
371
- --cb-primary: #3b82f6;
372
- --cb-primary-hover: #2563eb;
373
- --cb-bg: #ffffff;
374
- --cb-bg-secondary: #f9fafb;
375
- --cb-bg-tertiary: #f3f4f6;
376
- --cb-text: #111827;
377
- --cb-text-secondary: #6b7280;
378
- --cb-border: #e5e7eb;
379
- --cb-border-focus: #3b82f6;
380
-
381
- /* Spacing */
382
- --cb-spacing-xs: 0.25rem;
383
- --cb-spacing-sm: 0.5rem;
384
- --cb-spacing-md: 1rem;
385
- --cb-spacing-lg: 1.5rem;
386
-
387
- /* Border radius */
388
- --cb-radius-sm: 0.25rem;
389
- --cb-radius-md: 0.375rem;
390
- --cb-radius-lg: 0.5rem;
391
-
392
- /* Typography */
393
- --cb-font-family: system-ui, -apple-system, sans-serif;
394
- --cb-font-mono: 'SF Mono', Monaco, Consolas, monospace;
395
- }
396
- ```
397
-
398
- ### Custom Blocks
399
-
400
- Register your own block types:
401
-
402
- ```tsx
403
- import { blockRegistry, BlockDefinition } from 'authorly';
404
-
405
- const myCustomBlock: BlockDefinition = {
406
- name: 'custom',
407
- tag: 'div',
408
- editable: true,
409
- allowedChildren: ['text', 'inline'],
410
- label: 'Custom Block',
411
- icon: 'box',
412
- create: (data) => {
413
- const el = document.createElement('div');
414
- el.className = 'my-custom-block';
415
- el.contentEditable = 'true';
416
- el.innerHTML = data?.content || '';
417
- return el;
418
- },
419
- getData: (el) => ({ content: el.innerHTML }),
420
- update: (el, data) => { el.innerHTML = data.content; },
421
- };
422
-
423
- blockRegistry.register(myCustomBlock);
424
- ```
425
-
426
- ---
427
-
428
- ## Browser Support
429
-
430
- | Browser | Version |
431
- |---------|---------|
432
- | Chrome | 90+ |
433
- | Firefox | 90+ |
434
- | Safari | 14+ |
435
- | Edge | 90+ |
436
-
437
- ---
438
-
439
- ## TypeScript
440
-
441
- Full TypeScript support with exported types:
442
-
443
- ```tsx
444
- import type {
445
- EditorRef,
446
- EditorInstance,
447
- BlockType,
448
- BlockData,
449
- ContentBlocksEditorProps,
450
- ContentBlocksRendererProps,
451
- TableOfContentsProps,
452
- TocItem,
453
- } from 'authorly';
454
- ```
455
-
456
- ---
457
-
458
- ## FAQ
459
-
460
- <details>
461
- <summary><strong>How do I save content to a database?</strong></summary>
462
-
463
- The editor outputs plain HTML strings. Save it directly:
464
-
465
- ```tsx
466
- const handleSave = async (html: string) => {
467
- await db.posts.create({ content: html });
468
- };
469
-
470
- <ContentBlocksEditor onSave={handleSave} />
471
- ```
472
- </details>
473
-
474
- <details>
475
- <summary><strong>How do I display saved content?</strong></summary>
476
-
477
- Use the `ContentBlocksRenderer` component:
478
-
479
- ```tsx
480
- const post = await db.posts.findOne(id);
481
-
482
- <ContentBlocksRenderer html={post.content} />
483
- ```
484
- </details>
485
-
486
- <details>
487
- <summary><strong>Can I use it without React?</strong></summary>
488
-
489
- Currently, Authorly is React-only. The output HTML can be used anywhere, but the editor component requires React 17+.
490
- </details>
491
-
492
- <details>
493
- <summary><strong>Does it support collaborative editing?</strong></summary>
494
-
495
- Not built-in. For real-time collaboration, you'd need to integrate with a service like Yjs or Liveblocks on top of this editor.
496
- </details>
497
-
498
- <details>
499
- <summary><strong>How do I handle image uploads?</strong></summary>
500
-
501
- The editor supports pasting images (as base64) and entering URLs. For server uploads, handle it in your app:
502
-
503
- ```tsx
504
- const handleImageUpload = async (file: File) => {
505
- const url = await uploadToS3(file);
506
- editorRef.current?.insertBlock('image', { src: url });
507
- };
508
- ```
509
- </details>
510
-
511
- ---
512
-
513
- ## Contributing
514
-
515
- Contributions are welcome! Please read our contributing guidelines first.
516
-
517
- ```bash
518
- # Clone the repo
519
- git clone https://github.com/your-username/authorly.git
520
-
521
- # Install dependencies
522
- npm install
523
-
524
- # Start dev server
525
- npm run dev
526
-
527
- # Run tests
528
- npm test
529
-
530
- # Build
531
- npm run build
532
- ```
533
-
534
- ---
535
-
536
- ## License
537
- MIT © Aaditya Hasabnis
538
-
539
-
540
- ---
541
-
542
- <p align="center">
543
- <strong>Authorly</strong> — Made for writers who want their words to shine, not fight with formatting.
544
- </p>
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/lucide-icons/lucide/main/icons/pen-line.svg" width="64" height="64" alt="Authorly" />
3
+ </p>
4
+
5
+ <h1 align="center">Authorly — Rich Text Editor for Blogs & Publishing</h1>
6
+
7
+ <p align="center">
8
+ <strong>A rich text editor for authors, blogs, and documentation</strong><br>
9
+ Clean, publish-ready HTML output. Zero bloat.
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="#installation">Installation</a> •
14
+ <a href="#quick-start">Quick Start</a> •
15
+ <a href="#components">Components</a> •
16
+ <a href="#api-reference">API</a> •
17
+ <a href="#examples">Examples</a>
18
+ </p>
19
+
20
+ <p align="center">
21
+ <img src="https://img.shields.io/badge/React-17%2B-61dafb?style=flat-square&logo=react" alt="React 17+" />
22
+ <img src="https://img.shields.io/badge/TypeScript-Ready-3178c6?style=flat-square&logo=typescript" alt="TypeScript" />
23
+ <img src="https://img.shields.io/badge/Size-~30kb-green?style=flat-square" alt="Bundle Size" />
24
+ <img src="https://img.shields.io/badge/License-MIT-yellow?style=flat-square" alt="MIT License" />
25
+ </p>
26
+
27
+ ---
28
+
29
+ ## Why Authorly?
30
+
31
+ | Feature | Authorly | Other Editors |
32
+ |---------|----------|---------------|
33
+ | **Output** | Pure semantic HTML | JSON AST / Custom format |
34
+ | **Dependencies** | React + Lucide icons | Heavy frameworks |
35
+ | **Bundle size** | ~30kb gzipped | 100kb+ |
36
+ | **Learning curve** | Minutes | Hours/Days |
37
+ | **Database storage** | Just HTML string | Complex serialization |
38
+
39
+ ```html
40
+ <!-- What you get: Clean, portable HTML ready to publish -->
41
+ <h1>My Article</h1>
42
+ <p>A paragraph with <strong>bold</strong> and <em>italic</em> text.</p>
43
+ <ul>
44
+ <li>Simple</li>
45
+ <li>Clean</li>
46
+ <li>Works everywhere</li>
47
+ </ul>
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ npm install authorly-editor
56
+ ```
57
+
58
+ ```bash
59
+ yarn add authorly-editor
60
+ ```
61
+
62
+ ```bash
63
+ pnpm add authorly-editor
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Quick Start
69
+
70
+ ```tsx
71
+ import { ContentBlocksEditor } from 'authorly-editor';
72
+
73
+ function App() {
74
+ const [content, setContent] = useState('<p>Hello World!</p>');
75
+
76
+ return (
77
+ <ContentBlocksEditor
78
+ initialContent={content}
79
+ onChange={setContent}
80
+ />
81
+ );
82
+ }
83
+ ```
84
+
85
+ That's it. No configuration needed.
86
+
87
+ ---
88
+
89
+ ## Components
90
+
91
+ ### 1. ContentBlocksEditor
92
+
93
+ The main editor component for creating and editing content.
94
+
95
+ ```tsx
96
+ import { ContentBlocksEditor } from 'authorly-editor';
97
+
98
+ <ContentBlocksEditor
99
+ initialContent="<p>Start writing...</p>"
100
+ onChange={(html) => console.log(html)}
101
+ onSave={(html) => saveToDatabase(html)}
102
+ darkMode={false}
103
+ showToolbar={true}
104
+ placeholder="Type '/' for commands..."
105
+ />
106
+ ```
107
+
108
+ ### 2. ContentBlocksRenderer
109
+
110
+ Display saved HTML content with beautiful styling. No editor overhead.
111
+
112
+ ```tsx
113
+ import { ContentBlocksRenderer } from 'authorly-editor';
114
+
115
+ <ContentBlocksRenderer
116
+ html={savedContent}
117
+ darkMode={false}
118
+ enableCodeCopy={true}
119
+ />
120
+ ```
121
+
122
+ ### 3. TableOfContents
123
+
124
+ Auto-generate navigation from your content headings.
125
+
126
+ ```tsx
127
+ import { TableOfContents, ContentBlocksRenderer } from 'authorly-editor';
128
+
129
+ <div style={{ display: 'flex' }}>
130
+ <aside style={{ width: 200 }}>
131
+ <TableOfContents html={content} title="Contents" />
132
+ </aside>
133
+ <main>
134
+ <ContentBlocksRenderer html={content} enableHeadingIds={true} />
135
+ </main>
136
+ </div>
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Block Types
142
+
143
+ | Block | Description | HTML Output |
144
+ |-------|-------------|-------------|
145
+ | **Paragraph** | Basic text | `<p>` |
146
+ | **Heading 1-6** | Section headings | `<h1>` - `<h6>` |
147
+ | **Bullet List** | Unordered list | `<ul><li>` |
148
+ | **Numbered List** | Ordered list | `<ol><li>` |
149
+ | **Checklist** | Todo items | `<ul><li><input type="checkbox">` |
150
+ | **Quote** | Blockquote | `<blockquote>` |
151
+ | **Code** | Code block | `<pre><code>` |
152
+ | **Image** | Image with caption | `<figure><img><figcaption>` |
153
+ | **Video** | YouTube/Vimeo/MP4 | `<figure><iframe>` |
154
+ | **Table** | Data table | `<table>` |
155
+ | **Divider** | Horizontal rule | `<hr>` |
156
+ | **Callout** | Info/Warning/Error | `<aside>` |
157
+ | **Accordion** | Collapsible section | `<details><summary>` |
158
+
159
+ ---
160
+
161
+ ## Keyboard Shortcuts
162
+
163
+ | Shortcut | Action |
164
+ |----------|--------|
165
+ | `Ctrl/Cmd + B` | Bold |
166
+ | `Ctrl/Cmd + I` | Italic |
167
+ | `Ctrl/Cmd + U` | Underline |
168
+ | `Ctrl/Cmd + S` | Save (triggers `onSave`) |
169
+ | `Ctrl/Cmd + Z` | Undo |
170
+ | `Ctrl/Cmd + Y` | Redo |
171
+ | `Ctrl/Cmd + 1/2/3` | Heading 1/2/3 |
172
+ | `/` | Open block menu |
173
+ | `Enter` | New block / New list item |
174
+ | `Backspace` | Delete empty block / Merge |
175
+ | `Tab` | Indent list / Navigate table |
176
+ | `↑ / ↓` | Navigate between blocks |
177
+
178
+ ---
179
+
180
+ ## API Reference
181
+
182
+ ### ContentBlocksEditor Props
183
+
184
+ | Prop | Type | Default | Description |
185
+ |------|------|---------|-------------|
186
+ | `initialContent` | `string` | `''` | Initial HTML content |
187
+ | `onChange` | `(html: string) => void` | - | Called on content change |
188
+ | `onSave` | `(html: string) => void` | - | Called on Ctrl+S |
189
+ | `onFocus` | `() => void` | - | Called when editor gains focus |
190
+ | `onBlur` | `() => void` | - | Called when editor loses focus |
191
+ | `onReady` | `(editor: EditorInstance) => void` | - | Called when editor is ready |
192
+ | `darkMode` | `boolean` | `false` | Enable dark theme |
193
+ | `showToolbar` | `boolean` | `true` | Show formatting toolbar |
194
+ | `toolbarPosition` | `'top' \| 'bottom'` | `'top'` | Toolbar position |
195
+ | `placeholder` | `string` | `'Type "/" for commands...'` | Placeholder text |
196
+ | `readOnly` | `boolean` | `false` | Disable editing |
197
+ | `autoFocus` | `boolean` | `false` | Focus on mount |
198
+ | `spellCheck` | `boolean` | `true` | Enable spell check |
199
+ | `className` | `string` | `''` | Custom class name |
200
+ | `style` | `CSSProperties` | - | Custom styles |
201
+
202
+ ### EditorRef Methods
203
+
204
+ Access editor methods using a ref:
205
+
206
+ ```tsx
207
+ import { useRef } from 'react';
208
+ import { ContentBlocksEditor, EditorRef } from 'authorly-editor';
209
+
210
+ function MyEditor() {
211
+ const editorRef = useRef<EditorRef>(null);
212
+
213
+ return (
214
+ <>
215
+ <ContentBlocksEditor ref={editorRef} />
216
+ <button onClick={() => console.log(editorRef.current?.getHTML())}>
217
+ Get HTML
218
+ </button>
219
+ </>
220
+ );
221
+ }
222
+ ```
223
+
224
+ | Method | Description |
225
+ |--------|-------------|
226
+ | `getHTML()` | Returns the current HTML content |
227
+ | `setHTML(html: string)` | Sets the editor content |
228
+ | `getText()` | Returns plain text content |
229
+ | `focus()` | Focuses the editor |
230
+ | `blur()` | Blurs the editor |
231
+ | `insertBlock(type, data?)` | Inserts a new block |
232
+ | `getEditor()` | Returns the full editor instance |
233
+
234
+ ### ContentBlocksRenderer Props
235
+
236
+ | Prop | Type | Default | Description |
237
+ |------|------|---------|-------------|
238
+ | `html` | `string` | `''` | HTML content to render |
239
+ | `darkMode` | `boolean` | `false` | Enable dark theme |
240
+ | `enableCodeCopy` | `boolean` | `true` | Add copy button to code blocks |
241
+ | `enableHeadingIds` | `boolean` | `true` | Add IDs to headings |
242
+ | `enableChecklistStyles` | `boolean` | `true` | Strikethrough checked items |
243
+ | `className` | `string` | `''` | Custom class name |
244
+ | `style` | `CSSProperties` | - | Custom styles |
245
+
246
+ ### TableOfContents Props
247
+
248
+ | Prop | Type | Default | Description |
249
+ |------|------|---------|-------------|
250
+ | `html` | `string` | `''` | HTML to extract headings from |
251
+ | `darkMode` | `boolean` | `false` | Enable dark theme |
252
+ | `title` | `string` | `'Table of Contents'` | Title text |
253
+ | `minLevel` | `number` | `1` | Min heading level (1-6) |
254
+ | `maxLevel` | `number` | `6` | Max heading level (1-6) |
255
+ | `onNavigate` | `(id, item) => void` | - | Custom navigation handler |
256
+ | `smoothScroll` | `boolean` | `true` | Smooth scroll to heading |
257
+ | `collapsible` | `boolean` | `false` | Make TOC collapsible |
258
+
259
+ ---
260
+
261
+ ## Examples
262
+
263
+ ### Blog Editor with Preview
264
+
265
+ ```tsx
266
+ import { useState, useRef } from 'react';
267
+ import {
268
+ ContentBlocksEditor,
269
+ ContentBlocksRenderer,
270
+ EditorRef
271
+ } from 'authorly-editor';
272
+
273
+ function BlogEditor() {
274
+ const editorRef = useRef<EditorRef>(null);
275
+ const [content, setContent] = useState('<p>Write your post...</p>');
276
+ const [showPreview, setShowPreview] = useState(false);
277
+
278
+ const handleSave = async (html: string) => {
279
+ await fetch('/api/posts', {
280
+ method: 'POST',
281
+ body: JSON.stringify({ content: html }),
282
+ });
283
+ };
284
+
285
+ return (
286
+ <div>
287
+ <button onClick={() => setShowPreview(!showPreview)}>
288
+ {showPreview ? 'Edit' : 'Preview'}
289
+ </button>
290
+
291
+ {showPreview ? (
292
+ <ContentBlocksRenderer html={content} />
293
+ ) : (
294
+ <ContentBlocksEditor
295
+ ref={editorRef}
296
+ initialContent={content}
297
+ onChange={setContent}
298
+ onSave={handleSave}
299
+ />
300
+ )}
301
+ </div>
302
+ );
303
+ }
304
+ ```
305
+
306
+ ### Documentation Page with TOC
307
+
308
+ ```tsx
309
+ import {
310
+ ContentBlocksRenderer,
311
+ TableOfContents
312
+ } from 'authorly-editor';
313
+
314
+ function DocsPage({ content }) {
315
+ return (
316
+ <div style={{ display: 'grid', gridTemplateColumns: '250px 1fr', gap: '2rem' }}>
317
+ <aside style={{ position: 'sticky', top: '1rem', height: 'fit-content' }}>
318
+ <TableOfContents
319
+ html={content}
320
+ title="On this page"
321
+ maxLevel={3}
322
+ />
323
+ </aside>
324
+ <main>
325
+ <ContentBlocksRenderer
326
+ html={content}
327
+ enableHeadingIds={true}
328
+ enableCodeCopy={true}
329
+ />
330
+ </main>
331
+ </div>
332
+ );
333
+ }
334
+ ```
335
+
336
+ ### Dark Mode Support
337
+
338
+ ```tsx
339
+ import { useState } from 'react';
340
+ import { ContentBlocksEditor } from 'authorly-editor';
341
+
342
+ function ThemedEditor() {
343
+ const [darkMode, setDarkMode] = useState(false);
344
+
345
+ return (
346
+ <div style={{
347
+ background: darkMode ? '#0f172a' : '#ffffff',
348
+ minHeight: '100vh',
349
+ padding: '2rem'
350
+ }}>
351
+ <button onClick={() => setDarkMode(!darkMode)}>
352
+ Toggle Theme
353
+ </button>
354
+ <ContentBlocksEditor darkMode={darkMode} />
355
+ </div>
356
+ );
357
+ }
358
+ ```
359
+
360
+ ---
361
+
362
+ ## Customization
363
+
364
+ ### CSS Variables
365
+
366
+ Override these CSS variables to customize the editor appearance:
367
+
368
+ ```css
369
+ .cb-editor {
370
+ /* Colors */
371
+ --cb-primary: #3b82f6;
372
+ --cb-primary-hover: #2563eb;
373
+ --cb-bg: #ffffff;
374
+ --cb-bg-secondary: #f9fafb;
375
+ --cb-bg-tertiary: #f3f4f6;
376
+ --cb-text: #111827;
377
+ --cb-text-secondary: #6b7280;
378
+ --cb-border: #e5e7eb;
379
+ --cb-border-focus: #3b82f6;
380
+
381
+ /* Spacing */
382
+ --cb-spacing-xs: 0.25rem;
383
+ --cb-spacing-sm: 0.5rem;
384
+ --cb-spacing-md: 1rem;
385
+ --cb-spacing-lg: 1.5rem;
386
+
387
+ /* Border radius */
388
+ --cb-radius-sm: 0.25rem;
389
+ --cb-radius-md: 0.375rem;
390
+ --cb-radius-lg: 0.5rem;
391
+
392
+ /* Typography */
393
+ --cb-font-family: system-ui, -apple-system, sans-serif;
394
+ --cb-font-mono: 'SF Mono', Monaco, Consolas, monospace;
395
+ }
396
+ ```
397
+
398
+ ### Custom Blocks
399
+
400
+ Register your own block types:
401
+
402
+ ```tsx
403
+ import { blockRegistry, BlockDefinition } from 'authorly-editor';
404
+
405
+ const myCustomBlock: BlockDefinition = {
406
+ name: 'custom',
407
+ tag: 'div',
408
+ editable: true,
409
+ allowedChildren: ['text', 'inline'],
410
+ label: 'Custom Block',
411
+ icon: 'box',
412
+ create: (data) => {
413
+ const el = document.createElement('div');
414
+ el.className = 'my-custom-block';
415
+ el.contentEditable = 'true';
416
+ el.innerHTML = data?.content || '';
417
+ return el;
418
+ },
419
+ getData: (el) => ({ content: el.innerHTML }),
420
+ update: (el, data) => { el.innerHTML = data.content; },
421
+ };
422
+
423
+ blockRegistry.register(myCustomBlock);
424
+ ```
425
+
426
+ ---
427
+
428
+ ## Browser Support
429
+
430
+ | Browser | Version |
431
+ |---------|---------|
432
+ | Chrome | 90+ |
433
+ | Firefox | 90+ |
434
+ | Safari | 14+ |
435
+ | Edge | 90+ |
436
+
437
+ ---
438
+
439
+ ## TypeScript
440
+
441
+ Full TypeScript support with exported types:
442
+
443
+ ```tsx
444
+ import type {
445
+ EditorRef,
446
+ EditorInstance,
447
+ BlockType,
448
+ BlockData,
449
+ ContentBlocksEditorProps,
450
+ ContentBlocksRendererProps,
451
+ TableOfContentsProps,
452
+ TocItem,
453
+ } from 'authorly-editor';
454
+ ```
455
+
456
+ ---
457
+
458
+ ## FAQ
459
+
460
+ <details>
461
+ <summary><strong>How do I save content to a database?</strong></summary>
462
+
463
+ The editor outputs plain HTML strings. Save it directly:
464
+
465
+ ```tsx
466
+ const handleSave = async (html: string) => {
467
+ await db.posts.create({ content: html });
468
+ };
469
+
470
+ <ContentBlocksEditor onSave={handleSave} />
471
+ ```
472
+ </details>
473
+
474
+ <details>
475
+ <summary><strong>How do I display saved content?</strong></summary>
476
+
477
+ Use the `ContentBlocksRenderer` component:
478
+
479
+ ```tsx
480
+ const post = await db.posts.findOne(id);
481
+
482
+ <ContentBlocksRenderer html={post.content} />
483
+ ```
484
+ </details>
485
+
486
+ <details>
487
+ <summary><strong>Can I use it without React?</strong></summary>
488
+
489
+ Currently, Authorly is React-only. The output HTML can be used anywhere, but the editor component requires React 17+.
490
+ </details>
491
+
492
+ <details>
493
+ <summary><strong>Does it support collaborative editing?</strong></summary>
494
+
495
+ Not built-in. For real-time collaboration, you'd need to integrate with a service like Yjs or Liveblocks on top of this editor.
496
+ </details>
497
+
498
+ <details>
499
+ <summary><strong>How do I handle image uploads?</strong></summary>
500
+
501
+ The editor supports pasting images (as base64) and entering URLs. For server uploads, handle it in your app:
502
+
503
+ ```tsx
504
+ const handleImageUpload = async (file: File) => {
505
+ const url = await uploadToS3(file);
506
+ editorRef.current?.insertBlock('image', { src: url });
507
+ };
508
+ ```
509
+ </details>
510
+
511
+ ---
512
+
513
+ ## Contributing
514
+
515
+ Contributions are welcome! Please read our contributing guidelines first.
516
+
517
+ ```bash
518
+ # Clone the repo
519
+ git clone https://github.com/your-username/authorly.git
520
+
521
+ # Install dependencies
522
+ npm install
523
+
524
+ # Start dev server
525
+ npm run dev
526
+
527
+ # Run tests
528
+ npm test
529
+
530
+ # Build
531
+ npm run build
532
+ ```
533
+
534
+ ---
535
+
536
+ ## License
537
+ MIT © Aaditya Hasabnis
538
+
539
+
540
+ ---
541
+
542
+ <p align="center">
543
+ <strong>Authorly</strong> — Made for writers who want their words to shine, not fight with formatting.
544
+ </p>
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "authorly-editor",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "A rich text editor for authors, blogs, and documentation with clean, publish-ready output.",
5
+ "publishConfig": {
6
+ "access": "public",
7
+ "registry": "https://npm.pkg.github.com"
8
+ },
5
9
  "main": "dist/index.cjs.js",
6
10
  "module": "dist/index.esm.js",
7
11
  "types": "dist/index.d.ts",
@@ -22,7 +26,9 @@
22
26
  "preview": "vite preview",
23
27
  "lint": "eslint src --ext ts,tsx",
24
28
  "test": "vitest",
25
- "prepublishOnly": "npm run build"
29
+ "prepublishOnly": "npm run build",
30
+ "publish:github": "npm version patch && npm publish",
31
+ "publish:npm": "npm run build && npm pkg set name=authorly-editor && npm publish --registry=https://registry.npmjs.org && npm pkg set name=@aadityahasabnis/authorly"
26
32
  },
27
33
  "peerDependencies": {
28
34
  "react": ">=17.0.0",
@@ -70,4 +76,4 @@
70
76
  "type": "git",
71
77
  "url": "https://github.com/aadityahasabnis/Authorly.git"
72
78
  }
73
- }
79
+ }