authorly-editor 0.1.0
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/LICENSE +21 -0
- package/README.md +544 -0
- package/dist/blocks/accordion.d.ts +11 -0
- package/dist/blocks/callout.d.ts +11 -0
- package/dist/blocks/code.d.ts +84 -0
- package/dist/blocks/divider.d.ts +7 -0
- package/dist/blocks/heading.d.ts +9 -0
- package/dist/blocks/image.d.ts +28 -0
- package/dist/blocks/index.d.ts +31 -0
- package/dist/blocks/list.d.ts +25 -0
- package/dist/blocks/paragraph.d.ts +3 -0
- package/dist/blocks/quote.d.ts +11 -0
- package/dist/blocks/table.d.ts +34 -0
- package/dist/blocks/video.d.ts +11 -0
- package/dist/components/BlockMenu.d.ts +4 -0
- package/dist/components/BlockWrapper.d.ts +18 -0
- package/dist/components/Editor.d.ts +13 -0
- package/dist/components/Renderer.d.ts +34 -0
- package/dist/components/TableOfContents.d.ts +59 -0
- package/dist/components/Toolbar.d.ts +4 -0
- package/dist/components/index.d.ts +9 -0
- package/dist/core/block-registry.d.ts +60 -0
- package/dist/core/commands.d.ts +123 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/selection.d.ts +88 -0
- package/dist/core/types.d.ts +325 -0
- package/dist/drag/drag-handle.d.ts +31 -0
- package/dist/icons/index.d.ts +12 -0
- package/dist/index.cjs.js +877 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.esm.js +5321 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/paste/sanitize.d.ts +40 -0
- package/dist/style.css +1 -0
- package/dist/utils/helpers.d.ts +149 -0
- package/dist/utils/index.d.ts +4 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aaditya Hasabnis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +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>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BlockDefinition } from '../core/types';
|
|
2
|
+
|
|
3
|
+
export declare const accordionBlock: BlockDefinition;
|
|
4
|
+
/**
|
|
5
|
+
* Toggle accordion open/closed
|
|
6
|
+
*/
|
|
7
|
+
export declare function toggleAccordion(element: HTMLElement): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Set accordion open state
|
|
10
|
+
*/
|
|
11
|
+
export declare function setAccordionOpen(element: HTMLElement, open: boolean): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BlockDefinition, CalloutType } from '../core/types';
|
|
2
|
+
|
|
3
|
+
export declare const calloutBlock: BlockDefinition;
|
|
4
|
+
/**
|
|
5
|
+
* Set callout type
|
|
6
|
+
*/
|
|
7
|
+
export declare function setCalloutType(element: HTMLElement, type: CalloutType): void;
|
|
8
|
+
/**
|
|
9
|
+
* Add title to callout
|
|
10
|
+
*/
|
|
11
|
+
export declare function addCalloutTitle(element: HTMLElement, text: string): void;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { BlockDefinition } from '../core/types';
|
|
2
|
+
|
|
3
|
+
export declare const LANGUAGES: readonly [{
|
|
4
|
+
readonly value: "plaintext";
|
|
5
|
+
readonly label: "Plain Text";
|
|
6
|
+
}, {
|
|
7
|
+
readonly value: "javascript";
|
|
8
|
+
readonly label: "JavaScript";
|
|
9
|
+
}, {
|
|
10
|
+
readonly value: "typescript";
|
|
11
|
+
readonly label: "TypeScript";
|
|
12
|
+
}, {
|
|
13
|
+
readonly value: "python";
|
|
14
|
+
readonly label: "Python";
|
|
15
|
+
}, {
|
|
16
|
+
readonly value: "java";
|
|
17
|
+
readonly label: "Java";
|
|
18
|
+
}, {
|
|
19
|
+
readonly value: "csharp";
|
|
20
|
+
readonly label: "C#";
|
|
21
|
+
}, {
|
|
22
|
+
readonly value: "cpp";
|
|
23
|
+
readonly label: "C++";
|
|
24
|
+
}, {
|
|
25
|
+
readonly value: "go";
|
|
26
|
+
readonly label: "Go";
|
|
27
|
+
}, {
|
|
28
|
+
readonly value: "rust";
|
|
29
|
+
readonly label: "Rust";
|
|
30
|
+
}, {
|
|
31
|
+
readonly value: "php";
|
|
32
|
+
readonly label: "PHP";
|
|
33
|
+
}, {
|
|
34
|
+
readonly value: "ruby";
|
|
35
|
+
readonly label: "Ruby";
|
|
36
|
+
}, {
|
|
37
|
+
readonly value: "swift";
|
|
38
|
+
readonly label: "Swift";
|
|
39
|
+
}, {
|
|
40
|
+
readonly value: "kotlin";
|
|
41
|
+
readonly label: "Kotlin";
|
|
42
|
+
}, {
|
|
43
|
+
readonly value: "html";
|
|
44
|
+
readonly label: "HTML";
|
|
45
|
+
}, {
|
|
46
|
+
readonly value: "css";
|
|
47
|
+
readonly label: "CSS";
|
|
48
|
+
}, {
|
|
49
|
+
readonly value: "scss";
|
|
50
|
+
readonly label: "SCSS";
|
|
51
|
+
}, {
|
|
52
|
+
readonly value: "json";
|
|
53
|
+
readonly label: "JSON";
|
|
54
|
+
}, {
|
|
55
|
+
readonly value: "yaml";
|
|
56
|
+
readonly label: "YAML";
|
|
57
|
+
}, {
|
|
58
|
+
readonly value: "xml";
|
|
59
|
+
readonly label: "XML";
|
|
60
|
+
}, {
|
|
61
|
+
readonly value: "markdown";
|
|
62
|
+
readonly label: "Markdown";
|
|
63
|
+
}, {
|
|
64
|
+
readonly value: "sql";
|
|
65
|
+
readonly label: "SQL";
|
|
66
|
+
}, {
|
|
67
|
+
readonly value: "bash";
|
|
68
|
+
readonly label: "Bash";
|
|
69
|
+
}, {
|
|
70
|
+
readonly value: "shell";
|
|
71
|
+
readonly label: "Shell";
|
|
72
|
+
}, {
|
|
73
|
+
readonly value: "dockerfile";
|
|
74
|
+
readonly label: "Dockerfile";
|
|
75
|
+
}];
|
|
76
|
+
export declare const codeBlock: BlockDefinition;
|
|
77
|
+
/**
|
|
78
|
+
* Set language for code block
|
|
79
|
+
*/
|
|
80
|
+
export declare function setLanguage(codeWrapper: HTMLElement, language: string): void;
|
|
81
|
+
/**
|
|
82
|
+
* Get code content
|
|
83
|
+
*/
|
|
84
|
+
export declare function getCode(codeWrapper: HTMLElement): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BlockDefinition, HeadingData, HeadingLevel } from '../core/types';
|
|
2
|
+
|
|
3
|
+
export declare const heading1Block: BlockDefinition;
|
|
4
|
+
export declare const heading2Block: BlockDefinition;
|
|
5
|
+
export declare const heading3Block: BlockDefinition;
|
|
6
|
+
export declare const heading4Block: BlockDefinition;
|
|
7
|
+
export declare const heading5Block: BlockDefinition;
|
|
8
|
+
export declare const heading6Block: BlockDefinition;
|
|
9
|
+
export declare function createHeading(level: HeadingLevel, data?: Partial<HeadingData>): HTMLElement;
|