inkora 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tiptap-reach-editor contributors
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,509 @@
1
+ # Inkora
2
+
3
+ > Plug-and-play rich text editor for React — built on TipTap v3 and ProseMirror.
4
+
5
+ Inkora gives you a production-ready WYSIWYG editor in one import. No configuration required to get started, fully customisable when you need it. Ships with a full menu-bar editor, a lightweight toolbar editor, and a read-only viewer — all styled with CSS variables so they adapt to any design system.
6
+
7
+ ---
8
+
9
+ ## Table of contents
10
+
11
+ - [Features](#features)
12
+ - [Installation](#installation)
13
+ - [Quick start](#quick-start)
14
+ - [Components](#components)
15
+ - [InkoraEditor](#inkoraeditor)
16
+ - [InkoraBasicEditor](#inkorabasiceditor)
17
+ - [InkoraViewer](#inkoraviewer)
18
+ - [Props reference](#props-reference)
19
+ - [Media uploads](#media-uploads)
20
+ - [@ Mentions](#-mentions)
21
+ - [Theming with CSS variables](#theming-with-css-variables)
22
+ - [Dark mode](#dark-mode)
23
+ - [Using your own editor setup](#using-your-own-editor-setup)
24
+ - [Keyboard shortcuts](#keyboard-shortcuts)
25
+ - [Built-in extensions](#built-in-extensions)
26
+ - [Framework notes](#framework-notes)
27
+ - [Contributing](#contributing)
28
+ - [License](#license)
29
+
30
+ ---
31
+
32
+ ## Features
33
+
34
+ **Formatting**
35
+ - Bold, Italic, Underline, Strikethrough, Inline code
36
+ - Superscript, Subscript
37
+ - Font family, font size (8 – 96 pt)
38
+ - Text colour, highlight colour (multi-colour)
39
+ - Text alignment (left, centre, right, justify)
40
+ - Line height
41
+ - Clear all formatting
42
+
43
+ **Structure**
44
+ - Headings H1 – H6
45
+ - Bullet list, ordered list, task list (checkboxes)
46
+ - Nested indentation
47
+ - Blockquote
48
+ - Horizontal rule
49
+ - Code block with syntax highlighting (100+ languages via `lowlight`)
50
+ - Callout blocks (info, success, warning, danger) with emoji icons
51
+
52
+ **Media**
53
+ - Image insert — file upload or paste URL; resize, align, shape (rect / circle), frame, filters
54
+ - Video insert — file upload or URL
55
+ - Audio insert — file upload
56
+ - YouTube / Vimeo embed
57
+ - GIF picker (curated + URL paste)
58
+ - SVG shape inserter (circle, square, star, arrows …)
59
+
60
+ **Tables**
61
+ - Insert via grid picker (up to 10 × 10)
62
+ - Add / delete rows and columns
63
+ - Merge and split cells
64
+ - Resizable columns
65
+
66
+ **Advanced**
67
+ - LaTeX math formulas (KaTeX, optional peer dep)
68
+ - @ Mentions with custom data source
69
+ - `#` Hashtags
70
+ - Emoji shortcodes (`:heart:` → ❤️, `:smile:` → 😊, `:rocket:` → 🚀, `:fire:` → 🔥)
71
+ - Hyperlinks with modal dialog
72
+ - Global drag-handle for reordering blocks
73
+ - Bubble menu (formatting pop-up on text selection)
74
+ - Character / word count
75
+ - Auto-save with debounce
76
+ - Fullscreen mode
77
+ - HTML source editor
78
+ - Undo / Redo
79
+
80
+ **Themes**
81
+ - Light and dark mode — switch at runtime
82
+ - All colours exposed as CSS variables (`--rte-*`) for easy overriding
83
+ - Zero Tailwind dependency — safe in any project
84
+
85
+ ---
86
+
87
+ ## Installation
88
+
89
+ Install Inkora and its peer dependencies.
90
+
91
+ ```bash
92
+ npm install inkora \
93
+ @tiptap/core @tiptap/pm @tiptap/react @tiptap/starter-kit \
94
+ @tiptap/extension-character-count \
95
+ @tiptap/extension-code-block-lowlight \
96
+ @tiptap/extension-color \
97
+ @tiptap/extension-dropcursor \
98
+ @tiptap/extension-font-family \
99
+ @tiptap/extension-gapcursor \
100
+ @tiptap/extension-highlight \
101
+ @tiptap/extension-image \
102
+ @tiptap/extension-link \
103
+ @tiptap/extension-mention \
104
+ @tiptap/extension-placeholder \
105
+ @tiptap/extension-subscript \
106
+ @tiptap/extension-superscript \
107
+ @tiptap/extension-table \
108
+ @tiptap/extension-task-item \
109
+ @tiptap/extension-task-list \
110
+ @tiptap/extension-text-align \
111
+ @tiptap/extension-text-style \
112
+ @tiptap/extension-underline \
113
+ @tiptap/extension-youtube \
114
+ react react-dom
115
+ ```
116
+
117
+ **Math formulas (optional)**
118
+
119
+ If you want KaTeX math blocks, install the optional peer dependency and import the KaTeX stylesheet once in your app root:
120
+
121
+ ```bash
122
+ npm install katex
123
+ ```
124
+
125
+ ```js
126
+ import 'katex/dist/katex.min.css';
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Quick start
132
+
133
+ ```jsx
134
+ import { InkoraEditor } from 'inkora';
135
+
136
+ export default function App() {
137
+ return (
138
+ <InkoraEditor
139
+ onChange={(json) => console.log(json)}
140
+ />
141
+ );
142
+ }
143
+ ```
144
+
145
+ That is all. The editor mounts with a full toolbar, dark-mode toggle, and every extension enabled.
146
+
147
+ ---
148
+
149
+ ## Components
150
+
151
+ ### InkoraEditor
152
+
153
+ Full-featured editor with menu bar, format bar, bubble menu, media support, tables, math, and more.
154
+
155
+ ```jsx
156
+ import { InkoraEditor } from 'inkora';
157
+
158
+ <InkoraEditor
159
+ initialContent={myDocumentJson}
160
+ theme="light"
161
+ width="100%"
162
+ minHeight={500}
163
+ onChange={(json) => setContent(json)}
164
+ onSave={(json) => saveToDatabase(json)}
165
+ onUpload={async (file) => {
166
+ const url = await uploadToStorage(file);
167
+ return { src: url };
168
+ }}
169
+ resolveMediaUrl={(src) => `https://cdn.example.com/${src}`}
170
+ onToggleTheme={() => setTheme(t => t === 'light' ? 'dark' : 'light')}
171
+ />
172
+ ```
173
+
174
+ ---
175
+
176
+ ### InkoraBasicEditor
177
+
178
+ Lightweight editor with a single compact toolbar. Ideal for comments, short-form inputs, and forms.
179
+
180
+ ```jsx
181
+ import { InkoraBasicEditor } from 'inkora';
182
+
183
+ <InkoraBasicEditor
184
+ value={content}
185
+ onChange={setContent}
186
+ minHeight={150}
187
+ theme="light"
188
+ />
189
+ ```
190
+
191
+ Render saved content read-only by passing `readOnly`:
192
+
193
+ ```jsx
194
+ <InkoraBasicEditor value={content} readOnly />
195
+ ```
196
+
197
+ ---
198
+
199
+ ### InkoraViewer
200
+
201
+ Read-only renderer for TipTap JSON. Renders the same visual styles as the editor with no editing controls. Shows a shimmer skeleton during SSR / before hydration.
202
+
203
+ ```jsx
204
+ import { InkoraViewer } from 'inkora';
205
+
206
+ <InkoraViewer content={savedDocumentJson} theme="light" />
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Props reference
212
+
213
+ ### InkoraEditor
214
+
215
+ | Prop | Type | Default | Description |
216
+ |------|------|---------|-------------|
217
+ | `initialContent` | `Object` | `undefined` | TipTap JSON document to pre-populate the editor. |
218
+ | `onChange` | `(json: Object) => void` | — | Fired on every keystroke with the current TipTap JSON. |
219
+ | `onSave` | `(json: Object) => void` | — | Auto-save callback. Fires after 1 s of idle typing. |
220
+ | `onUpload` | `(file: File) => Promise<{ src: string }>` | — | Media upload handler. Must return a promise resolving to `{ src }`. |
221
+ | `resolveMediaUrl` | `(src: string) => string` | — | Maps stored `src` values to live, displayable URLs. |
222
+ | `onToggleTheme` | `() => void` | — | Called when the user clicks the dark-mode toggle button. |
223
+ | `theme` | `'light' \| 'dark'` | `'light'` | Controls the colour scheme. |
224
+ | `placeholder` | `string` | `'Start writing…'` | Empty-state placeholder text. |
225
+ | `width` | `string \| number` | `'100%'` | CSS width of the outer container. |
226
+ | `height` | `string \| number` | `undefined` | Fixed height for the scrollable content area. |
227
+ | `minHeight` | `number` | `420` | Minimum height (px) of the scrollable content area. |
228
+ | `mentionOptions` | `Object` | `{}` | Forwarded to the TipTap Mention extension's `suggestion` config. |
229
+
230
+ ### InkoraBasicEditor
231
+
232
+ | Prop | Type | Default | Description |
233
+ |------|------|---------|-------------|
234
+ | `value` | `Object` | `undefined` | TipTap JSON document. |
235
+ | `onChange` | `(json: Object) => void` | — | Fired on every change with the current TipTap JSON. |
236
+ | `onSave` | `(json: Object) => void` | — | Auto-save callback (debounced 1 s). |
237
+ | `placeholder` | `string` | `'Start writing…'` | Empty-state placeholder text. |
238
+ | `theme` | `'light' \| 'dark'` | `'light'` | Controls the colour scheme. |
239
+ | `width` | `string \| number` | `'100%'` | CSS width of the outer container. |
240
+ | `height` | `string \| number` | `undefined` | Fixed height for the content area. |
241
+ | `minHeight` | `number` | `150` | Minimum height (px) of the content area. |
242
+ | `readOnly` | `boolean` | `false` | Hides the toolbar and disables all editing. |
243
+
244
+ ### InkoraViewer
245
+
246
+ | Prop | Type | Default | Description |
247
+ |------|------|---------|-------------|
248
+ | `content` | `Object` | `undefined` | TipTap JSON document to display. |
249
+ | `theme` | `'light' \| 'dark'` | `'light'` | Controls the colour scheme. |
250
+
251
+ ---
252
+
253
+ ## Media uploads
254
+
255
+ Inkora does not upload files itself — you provide the upload function. The editor calls it with the selected `File` and expects a promise that resolves to `{ src: string }`.
256
+
257
+ ```jsx
258
+ <InkoraEditor
259
+ onUpload={async (file) => {
260
+ // Example: S3 presigned-URL upload
261
+ const { url, key } = await fetch('/api/upload-url', {
262
+ method: 'POST',
263
+ body: JSON.stringify({ name: file.name, type: file.type }),
264
+ headers: { 'Content-Type': 'application/json' },
265
+ }).then(r => r.json());
266
+
267
+ await fetch(url, { method: 'PUT', body: file });
268
+
269
+ // Return the key that gets stored in the TipTap document JSON
270
+ return { src: key };
271
+ }}
272
+ resolveMediaUrl={(src) => `https://your-bucket.s3.amazonaws.com/${src}`}
273
+ />
274
+ ```
275
+
276
+ `resolveMediaUrl` is called at render time to convert stored keys back into displayable URLs. If your `onUpload` already returns a full absolute URL, you can omit `resolveMediaUrl`.
277
+
278
+ ---
279
+
280
+ ## @ Mentions
281
+
282
+ Pass a `mentionOptions` prop with an async `items` function. The editor calls it as the user types after `@`.
283
+
284
+ ```jsx
285
+ <InkoraEditor
286
+ mentionOptions={{
287
+ suggestion: {
288
+ items: async ({ query }) => {
289
+ const users = await fetchUsers(query);
290
+ // Must return an array of objects with at least { id, label }
291
+ return users.map(u => ({ id: u.id, label: u.name }));
292
+ },
293
+ },
294
+ }}
295
+ />
296
+ ```
297
+
298
+ The mention node renders as `<span class="rte-mention">` in HTML output. Style it in your own stylesheet:
299
+
300
+ ```css
301
+ .rte-mention {
302
+ background: #d3e3fd;
303
+ color: #0b57d0;
304
+ border-radius: 4px;
305
+ padding: 1px 4px;
306
+ font-weight: 500;
307
+ }
308
+ ```
309
+
310
+ ---
311
+
312
+ ## Theming with CSS variables
313
+
314
+ All colours are driven by CSS custom properties on the wrapper element. Override any variable in your own stylesheet:
315
+
316
+ ```css
317
+ /* Example: purple accent */
318
+ .inkora-editor {
319
+ --rte-accent: #7c3aed;
320
+ --rte-accent-soft: #ede9fe;
321
+ }
322
+ ```
323
+
324
+ **Full variable reference**
325
+
326
+ | Variable | Light | Dark | Role |
327
+ |----------|-------|------|------|
328
+ | `--rte-page` | `#ffffff` | `#1f2023` | Editor content background |
329
+ | `--rte-bar` | `#ffffff` | `#26272b` | Toolbar / panel background |
330
+ | `--rte-pill` | `#f6f8fc` | `#2b2c30` | Subtle chip / button fill |
331
+ | `--rte-hover` | `rgba(60,64,67,.09)` | `rgba(255,255,255,.09)` | Button hover background |
332
+ | `--rte-border` | `#e4e7eb` | `#3c4043` | Panel and input borders |
333
+ | `--rte-ink` | `#202124` | `#e8eaed` | Primary text colour |
334
+ | `--rte-muted` | `#5f6368` | `#9aa0a6` | Secondary / disabled text |
335
+ | `--rte-accent` | `#0b57d0` | `#8ab4f8` | Links, active states, focus rings |
336
+ | `--rte-accent-soft` | `#d3e3fd` | `#1e3a5f` | Selected-node highlight tint |
337
+ | `--rte-shadow` | subtle | deeper | Outer box shadow |
338
+
339
+ ---
340
+
341
+ ## Dark mode
342
+
343
+ Pass `theme="dark"` to switch the colour scheme. The component is fully controlled — your app owns the state:
344
+
345
+ ```jsx
346
+ const [theme, setTheme] = useState('light');
347
+
348
+ <InkoraEditor
349
+ theme={theme}
350
+ onToggleTheme={() => setTheme(t => t === 'light' ? 'dark' : 'light')}
351
+ />
352
+ ```
353
+
354
+ The built-in toggle button in the toolbar calls `onToggleTheme` when clicked. Wire it up to update your `theme` state so the prop changes and the editor re-renders with the new scheme.
355
+
356
+ ---
357
+
358
+ ## Using your own editor setup
359
+
360
+ **Extension factory**
361
+
362
+ `createEditorExtensions` returns the full array of TipTap extensions used internally. Use it when building a custom `useEditor` instance:
363
+
364
+ ```jsx
365
+ import { createEditorExtensions, editorStyles } from 'inkora';
366
+ import { useEditor, EditorContent } from '@tiptap/react';
367
+ import { useMemo } from 'react';
368
+
369
+ function MyEditor({ placeholder }) {
370
+ const extensions = useMemo(
371
+ () => createEditorExtensions({ placeholder, isEditable: true }),
372
+ [placeholder]
373
+ );
374
+
375
+ const editor = useEditor({ extensions });
376
+
377
+ return (
378
+ <>
379
+ <style dangerouslySetInnerHTML={{ __html: editorStyles }} />
380
+ <EditorContent editor={editor} />
381
+ </>
382
+ );
383
+ }
384
+ ```
385
+
386
+ **`createEditorExtensions` options**
387
+
388
+ | Key | Type | Default | Description |
389
+ |-----|------|---------|-------------|
390
+ | `placeholder` | `string` | `'Start writing…'` | Placeholder shown in the empty editor. |
391
+ | `mentionOptions` | `Object` | `{}` | Forwarded into the Mention extension's `suggestion` config. |
392
+ | `isEditable` | `boolean` | `true` | Pass `false` to skip loading the drag-handle extension. |
393
+
394
+ **Styles**
395
+
396
+ `editorStyles` is a plain CSS string containing all `.rte-*` class definitions. The built-in components inject it automatically. Only import it manually if you are building a custom editor without `InkoraEditor` / `InkoraBasicEditor`.
397
+
398
+ ```jsx
399
+ import { editorStyles } from 'inkora';
400
+
401
+ <style dangerouslySetInnerHTML={{ __html: editorStyles }} />
402
+ ```
403
+
404
+ ---
405
+
406
+ ## Keyboard shortcuts
407
+
408
+ > Substitute `⌘` with `Ctrl` on Windows / Linux.
409
+
410
+ | Shortcut | Action |
411
+ |----------|--------|
412
+ | `⌘ B` | Bold |
413
+ | `⌘ I` | Italic |
414
+ | `⌘ U` | Underline |
415
+ | `⌘ Shift S` | Strikethrough |
416
+ | `⌘ K` | Insert / edit link |
417
+ | `⌘ E` | Inline code |
418
+ | `⌘ Z` | Undo |
419
+ | `⌘ Y` | Redo |
420
+ | `⌘ A` | Select all |
421
+ | `⌘ ,` | Subscript |
422
+ | `⌘ .` | Superscript |
423
+ | `⌘ \` | Clear all formatting |
424
+
425
+ ---
426
+
427
+ ## Built-in extensions
428
+
429
+ These TipTap extensions are loaded automatically. You do not need to configure them individually unless you use `createEditorExtensions` directly.
430
+
431
+ | Extension | What it provides |
432
+ |-----------|-----------------|
433
+ | `StarterKit` | Document, paragraph, headings, bold, italic, code, blockquote, HR, lists, history (undo/redo) |
434
+ | `Underline` | Underline mark |
435
+ | `Highlight` | Multi-colour highlight marks |
436
+ | `TextStyle` | Inline style container (required by Color and FontFamily) |
437
+ | `Color` | Text colour |
438
+ | `FontFamily` | Font family |
439
+ | `FontSize` *(custom)* | Font size (8 – 96 pt) |
440
+ | `LineHeight` *(custom)* | Line height per block |
441
+ | `TextAlign` | Left / centre / right / justify alignment on blocks |
442
+ | `TaskList` + `TaskItem` | Interactive checkbox lists (nested supported) |
443
+ | `Link` | Hyperlink mark — opens in new tab, `rel="noopener noreferrer"` |
444
+ | `Image` *(extended)* | Images with resize handles, shape, frame, filters, alignment |
445
+ | `Table` + rows / cells | Full table support with resizable columns |
446
+ | `Subscript` | Subscript mark |
447
+ | `Superscript` | Superscript mark |
448
+ | `Callout` *(custom)* | Coloured callout blocks (blue, green, yellow, red) |
449
+ | `Video` *(custom)* | Block video player with resize / shape / filters |
450
+ | `Audio` *(custom)* | Block audio player |
451
+ | `Math` *(custom)* | KaTeX block math formulas (click to edit) |
452
+ | `Youtube` *(extended)* | YouTube / Vimeo embeds |
453
+ | `Hashtag` *(custom)* | `#tag` input rule |
454
+ | `Mention` | `@mention` with configurable suggestion source |
455
+ | `Placeholder` | Ghost placeholder text |
456
+ | `CharacterCount` | Character and word count data |
457
+ | `Dropcursor` | Drag-and-drop position indicator |
458
+ | `Gapcursor` | Cursor in gaps between block nodes |
459
+ | `CodeBlockLowlight` *(extended)* | Fenced code blocks with syntax highlighting and collapse toggle |
460
+ | `GlobalDragHandle` | Drag handle on the left of every block node |
461
+ | `EmojiInputRules` *(custom)* | `:heart:` → ❤️ `:smile:` → 😊 `:rocket:` → 🚀 `:fire:` → 🔥 |
462
+
463
+ ---
464
+
465
+ ## Framework notes
466
+
467
+ **Next.js (App Router)**
468
+
469
+ All Inkora components include `'use client'` at the top. Import them inside a client component, or lazy-load to skip SSR:
470
+
471
+ ```jsx
472
+ 'use client';
473
+ import { InkoraEditor } from 'inkora';
474
+ ```
475
+
476
+ ```jsx
477
+ // Skip SSR entirely
478
+ import dynamic from 'next/dynamic';
479
+
480
+ const InkoraEditor = dynamic(
481
+ () => import('inkora').then(m => m.InkoraEditor),
482
+ { ssr: false }
483
+ );
484
+ ```
485
+
486
+ **Vite / Create React App**
487
+
488
+ No special configuration needed. Import and use directly.
489
+
490
+ **KaTeX stylesheet**
491
+
492
+ If you use math blocks, add the KaTeX stylesheet once at your app root:
493
+
494
+ ```js
495
+ // _app.js, layout.js, or main.jsx
496
+ import 'katex/dist/katex.min.css';
497
+ ```
498
+
499
+ ---
500
+
501
+ ## Contributing
502
+
503
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for how to set up the dev environment, submit pull requests, and report bugs.
504
+
505
+ ---
506
+
507
+ ## License
508
+
509
+ MIT © Inkora contributors