tiptop-editor 1.0.18 → 1.2.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/README.md +223 -43
- package/dist/components/editor/TableSelectionMenu.d.ts +4 -0
- package/dist/components/editor/TiptopEditor.d.ts +3 -0
- package/dist/components/editor/TiptopEditor.stories.d.ts +16 -0
- package/dist/components/{ColorButton.d.ts → menus/ColorButton.d.ts} +1 -1
- package/dist/components/menus/TableButtonMenu.d.ts +7 -0
- package/dist/components/{TextSelectionMenu.d.ts → menus/TextSelectionMenu.d.ts} +1 -1
- package/dist/components/{EditorButton.d.ts → ui/EditorButton.d.ts} +1 -1
- package/dist/extensions/slashCommand/SlashCommand.d.ts +6 -1
- package/dist/extensions/slashCommand/SlashCommandSuggestion.d.ts +3 -29
- package/dist/helpers.d.ts +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/tiptop-editor.css +1 -1
- package/dist/tiptop-editor.es.js +5284 -4288
- package/dist/tiptop-editor.umd.js +30 -11
- package/dist/types.d.ts +38 -15
- package/package.json +12 -2
- package/dist/components/TiptopEditor.d.ts +0 -7
- /package/dist/components/{DragHandleColorList.d.ts → editor/DragHandleColorList.d.ts} +0 -0
- /package/dist/components/{TiptopDragHandle.d.ts → editor/TiptopDragHandle.d.ts} +0 -0
- /package/dist/components/{TransformIntoIcon.d.ts → editor/TransformIntoIcon.d.ts} +0 -0
- /package/dist/components/{ColorButtonMenu.d.ts → menus/ColorButtonMenu.d.ts} +0 -0
- /package/dist/components/{LinkButtonMenu.d.ts → menus/LinkButtonMenu.d.ts} +0 -0
- /package/dist/components/{MoreOptionsButtonMenu.d.ts → menus/MoreOptionsButtonMenu.d.ts} +0 -0
- /package/dist/components/{TransformIntoButtonMenu.d.ts → menus/TransformIntoButtonMenu.d.ts} +0 -0
- /package/dist/components/{CloseIcon.d.ts → ui/CloseIcon.d.ts} +0 -0
- /package/dist/components/{ColorIcon.d.ts → ui/ColorIcon.d.ts} +0 -0
- /package/dist/components/{Icon.d.ts → ui/Icon.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,69 +1,249 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Tiptop Editor
|
|
2
2
|
|
|
3
|
-
A Notion-like rich text editor built with [Tiptap v3](https://tiptap.dev/), [HeroUI](https://heroui.
|
|
4
|
-
|
|
3
|
+
A Notion-like rich text editor built with [Tiptap v3](https://tiptap.dev/), [HeroUI](https://www.heroui.com/), and Tailwind CSS, packaged as a plug-and-play React component.
|
|
4
|
+
|
|
5
|
+
Inspired by Tiptap's Notion-like editor template:
|
|
6
|
+
https://tiptap.dev/docs/ui-components/templates/notion-like-editor
|
|
5
7
|
|
|
6
8
|

|
|
7
9
|

|
|
8
10
|

|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
## Features
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
- Tiptap v3 editor with a ready-to-use Notion-like UI
|
|
15
|
+
- Slash commands for inserting blocks
|
|
16
|
+
- Table support with row, column, header, split, and merge actions
|
|
17
|
+
- Emoji suggestions triggered with `:`
|
|
18
|
+
- Built-in image uploader block
|
|
19
|
+
- Text formatting, lists, code blocks, highlights, alignment, subscript, and superscript
|
|
20
|
+
- TypeScript support
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
- Styled with **HeroUI** + **Tailwind**
|
|
16
|
-
- Fully typed with **TypeScript**
|
|
17
|
-
- Ready to embed in any React app
|
|
18
|
-
- Designed for **Notion-like UX**
|
|
22
|
+
## Installation
|
|
19
23
|
|
|
24
|
+
```bash
|
|
25
|
+
npm install tiptop-editor
|
|
26
|
+
```
|
|
20
27
|
|
|
21
|
-
|
|
28
|
+
## Basic Usage
|
|
22
29
|
|
|
30
|
+
```tsx
|
|
31
|
+
import { TiptopEditor } from 'tiptop-editor'
|
|
32
|
+
import 'tiptop-editor/dist/tiptop-editor.css'
|
|
33
|
+
|
|
34
|
+
export function Editor() {
|
|
35
|
+
return (
|
|
36
|
+
<TiptopEditor
|
|
37
|
+
editorOptions={{
|
|
38
|
+
content: '<p>I am the Tiptop Editor</p>',
|
|
39
|
+
immediatelyRender: false,
|
|
40
|
+
}}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
```
|
|
23
45
|
|
|
24
|
-
|
|
46
|
+
`editorOptions` accepts the same options as `useEditor` from `@tiptap/react`, except `extensions`, which is managed internally by the package.
|
|
25
47
|
|
|
26
|
-
##
|
|
27
|
-
|
|
28
|
-
|
|
48
|
+
## Editor Ref and Events
|
|
49
|
+
|
|
50
|
+
You can access the editor instance and bind Tiptap runtime event listeners through the component ref.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { useEffect, useRef } from 'react'
|
|
54
|
+
import { TiptopEditor, type TiptopEditorHandle } from 'tiptop-editor'
|
|
55
|
+
|
|
56
|
+
export function EditorWithEvents() {
|
|
57
|
+
const editorRef = useRef<TiptopEditorHandle>(null)
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const handleUpdate = ({ editor }: { editor: NonNullable<ReturnType<TiptopEditorHandle['getEditor']>> }) => {
|
|
61
|
+
console.log(editor.getHTML())
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
editorRef.current?.on('update', handleUpdate)
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
editorRef.current?.off('update', handleUpdate)
|
|
68
|
+
}
|
|
69
|
+
}, [])
|
|
70
|
+
|
|
71
|
+
return <TiptopEditor ref={editorRef} />
|
|
72
|
+
}
|
|
29
73
|
```
|
|
30
74
|
|
|
31
|
-
|
|
75
|
+
Available ref methods:
|
|
32
76
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
77
|
+
- `getEditor()`
|
|
78
|
+
- `on(event, callback)`
|
|
79
|
+
- `off(event, callback?)`
|
|
80
|
+
- `once(event, callback)`
|
|
81
|
+
|
|
82
|
+
## Custom Editor UI Options
|
|
83
|
+
|
|
84
|
+
`TiptopEditor` also supports a few package-specific options inside `editorOptions`:
|
|
36
85
|
|
|
37
|
-
<TiptopEditor />
|
|
38
|
-
```
|
|
39
|
-
**Add the CSS code to your app**
|
|
40
|
-
For the package to behave like it should, you have to import the compiled CSS file. Add this line in your main css file, or import it directly in the component file that's going to host the **TiptopEditor**.
|
|
41
|
-
- In your main css file
|
|
42
|
-
```css
|
|
43
|
-
@import '../node_modules/tiptop-editor/dist/tiptop-editor.css';
|
|
44
|
-
- In any component file
|
|
45
|
-
```tsx
|
|
46
|
-
import 'tiptop-editor/dist/tiptop-editor.css'
|
|
47
|
-
## 🎨 Example
|
|
48
|
-
The Tiptop component takes as props all the props from the `UseEditorOptions` from [*@tiptap/react*](https://www.npmjs.com/package/@tiptap/react), except the `extensions` prop.
|
|
49
|
-
*Why only that prop, you ask ? Well, since this package is intended to *replicate* the Notion-like style with all their blocks/extensions and plug-and-play, as of now, I have not allowed users to pass their own extensions. But that can change in the future, just not now.*
|
|
50
|
-
Anyway, to use the package, just pass your props to `editorOptions` and you're good to go. Customize the Tiptop component will the props you want, as if you were using *EditorContent and passing props to the editor*.
|
|
51
86
|
```tsx
|
|
52
|
-
<TiptopEditor
|
|
53
|
-
|
|
54
|
-
content: '<p>
|
|
55
|
-
|
|
87
|
+
<TiptopEditor
|
|
88
|
+
editorOptions={{
|
|
89
|
+
content: '<p>Custom layout</p>',
|
|
90
|
+
disableDefaultContainer: true,
|
|
91
|
+
showDragHandle: false,
|
|
56
92
|
}}
|
|
57
93
|
/>
|
|
58
94
|
```
|
|
59
95
|
|
|
96
|
+
- `disableDefaultContainer`
|
|
97
|
+
Disables the default HeroUI `Card` wrapper and removes the editor's built-in padding. Use this when you want the editor to live inside your own container/layout.
|
|
98
|
+
- `showDragHandle`
|
|
99
|
+
Controls whether the block drag handle is rendered. Default: `true`.
|
|
100
|
+
|
|
101
|
+
## Built-in Extensions
|
|
102
|
+
|
|
103
|
+
The package ships with these extensions enabled out of the box:
|
|
104
|
+
|
|
105
|
+
- `StarterKit`
|
|
106
|
+
- `ListKit`
|
|
107
|
+
- `Placeholder`
|
|
108
|
+
- custom slash command menu
|
|
109
|
+
- custom code block
|
|
110
|
+
- custom horizontal rule
|
|
111
|
+
- `TextStyle` and `Color`
|
|
112
|
+
- `Highlight`
|
|
113
|
+
- `TextAlign`
|
|
114
|
+
- `Subscript`
|
|
115
|
+
- `Superscript`
|
|
116
|
+
- emoji suggestions
|
|
117
|
+
- `TableKit`
|
|
118
|
+
- image uploader block and upload handler
|
|
119
|
+
|
|
120
|
+
### Tables
|
|
121
|
+
|
|
122
|
+
Type `/table` to insert a table.
|
|
123
|
+
|
|
124
|
+
Inside a table you can:
|
|
125
|
+
|
|
126
|
+
- add or remove rows
|
|
127
|
+
- add or remove columns
|
|
128
|
+
- toggle header row or header column
|
|
129
|
+
- split a merged cell
|
|
130
|
+
- merge adjacent selected cells
|
|
131
|
+
|
|
132
|
+
To merge cells, drag across adjacent cells first, then use the table controls.
|
|
133
|
+
|
|
134
|
+
### Emoji
|
|
135
|
+
|
|
136
|
+
Type `:` followed by an emoji name to open emoji suggestions.
|
|
137
|
+
|
|
138
|
+
## Image Extension
|
|
139
|
+
|
|
140
|
+
The image feature is built around an `imageUploader` block.
|
|
141
|
+
|
|
142
|
+
### How to insert an image block
|
|
143
|
+
|
|
144
|
+
- Type `/image`
|
|
145
|
+
- or use the slash menu and select `Image`
|
|
146
|
+
|
|
147
|
+
Once inserted, the block lets the user click to upload or drag and drop an image.
|
|
148
|
+
|
|
149
|
+
### Supported files
|
|
150
|
+
|
|
151
|
+
- `image/png`
|
|
152
|
+
- `image/jpeg`
|
|
153
|
+
- `image/jpg`
|
|
154
|
+
- max size: `5MB`
|
|
155
|
+
|
|
156
|
+
### Demo mode with no backend
|
|
157
|
+
|
|
158
|
+
If you do not provide upload options, the editor simulates an upload and displays the image using a local object URL. This is useful for local demos and prototypes.
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<TiptopEditor
|
|
162
|
+
editorOptions={{
|
|
163
|
+
content: '<p>Upload demo</p>',
|
|
164
|
+
}}
|
|
165
|
+
/>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Real upload mode
|
|
169
|
+
|
|
170
|
+
To upload files to your backend, set both `imgUploadUrl` and `imgUploadResponseKey`.
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
<TiptopEditor
|
|
174
|
+
editorOptions={{
|
|
175
|
+
content: '<p>Upload to my API</p>',
|
|
176
|
+
imgUploadUrl: '/api/upload',
|
|
177
|
+
imgUploadResponseKey: 'url',
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The editor sends a `POST` request with `multipart/form-data` and the file under the `file` field.
|
|
183
|
+
|
|
184
|
+
`imgUploadResponseKey` is flexible. It supports:
|
|
185
|
+
|
|
186
|
+
- a top-level key like `'url'`
|
|
187
|
+
Example:
|
|
188
|
+
```tsx
|
|
189
|
+
<TiptopEditor
|
|
190
|
+
editorOptions={{
|
|
191
|
+
imgUploadUrl: '/api/upload',
|
|
192
|
+
imgUploadResponseKey: 'url',
|
|
193
|
+
}}
|
|
194
|
+
/>
|
|
195
|
+
```
|
|
196
|
+
- a nested path like `'data.url'`
|
|
197
|
+
Example:
|
|
198
|
+
```tsx
|
|
199
|
+
<TiptopEditor
|
|
200
|
+
editorOptions={{
|
|
201
|
+
imgUploadUrl: '/api/upload',
|
|
202
|
+
imgUploadResponseKey: 'data.url',
|
|
203
|
+
}}
|
|
204
|
+
/>
|
|
205
|
+
```
|
|
206
|
+
- a path array like `['data', 'url']`
|
|
207
|
+
Example:
|
|
208
|
+
```tsx
|
|
209
|
+
<TiptopEditor
|
|
210
|
+
editorOptions={{
|
|
211
|
+
imgUploadUrl: '/api/upload',
|
|
212
|
+
imgUploadResponseKey: ['data', 'url'],
|
|
213
|
+
}}
|
|
214
|
+
/>
|
|
215
|
+
```
|
|
216
|
+
- a resolver function
|
|
217
|
+
Example:
|
|
218
|
+
```tsx
|
|
219
|
+
<TiptopEditor
|
|
220
|
+
editorOptions={{
|
|
221
|
+
imgUploadUrl: '/api/upload',
|
|
222
|
+
imgUploadResponseKey: (response) => {
|
|
223
|
+
const asset = response.asset as { cdnUrl?: string } | undefined
|
|
224
|
+
return asset?.cdnUrl
|
|
225
|
+
},
|
|
226
|
+
}}
|
|
227
|
+
/>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Your server response must include the uploaded image URL at the location you describe with `imgUploadResponseKey`.
|
|
231
|
+
|
|
232
|
+
Example:
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"data": {
|
|
237
|
+
"url": "https://cdn.example.com/uploads/image-123.jpg"
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
60
241
|
|
|
61
|
-
|
|
62
|
-
##### Emoji Extension, Image extension, and more coming in next updates 🏃♂ ...
|
|
242
|
+
## Notes
|
|
63
243
|
|
|
64
|
-
|
|
244
|
+
- If you use SSR, keep `immediatelyRender: false`.
|
|
245
|
+
- The package manages the editor extensions internally, so custom `extensions` are intentionally not accepted yet.
|
|
65
246
|
|
|
66
|
-
|
|
67
|
-
*I also encourage you to open up *Issues* if you find releveant bugs inside the package.*
|
|
247
|
+
## Feedback
|
|
68
248
|
|
|
69
|
-
|
|
249
|
+
Issues and pull requests are welcome.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { StoryObj } from '@storybook/react-vite';
|
|
2
|
+
declare const meta: {
|
|
3
|
+
title: string;
|
|
4
|
+
component: import('react').ForwardRefExoticComponent<Omit<import('../..').TiptopEditorProps, "ref"> & import('react').RefAttributes<import('../..').TiptopEditorHandle>>;
|
|
5
|
+
tags: string[];
|
|
6
|
+
args: {
|
|
7
|
+
editorOptions: {
|
|
8
|
+
content: string;
|
|
9
|
+
immediatelyRender: false;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export default meta;
|
|
14
|
+
type Story = StoryObj<typeof meta>;
|
|
15
|
+
export declare const Default: Story;
|
|
16
|
+
export declare const Frameless: Story;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
import { ColorButtonProps } from '
|
|
2
|
+
import { ColorButtonProps } from '../../types';
|
|
3
3
|
declare const _default: React.MemoExoticComponent<({ editor, buttonType, hsl, color, bgColor, tooltipText, tooltipDisabled, }: ColorButtonProps) => import("react/jsx-runtime").JSX.Element>;
|
|
4
4
|
export default _default;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { Editor } from '@tiptap/react';
|
|
3
|
+
interface TableButtonMenuProps {
|
|
4
|
+
editor: Editor;
|
|
5
|
+
}
|
|
6
|
+
declare const _default: React.MemoExoticComponent<({ editor }: TableButtonMenuProps) => import("react/jsx-runtime").JSX.Element | null>;
|
|
7
|
+
export default _default;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
import { TextSelectionMenuProps } from '
|
|
2
|
+
import { TextSelectionMenuProps } from '../../types';
|
|
3
3
|
declare const _default: React.MemoExoticComponent<({ editor, prepend, append }: TextSelectionMenuProps) => import("react/jsx-runtime").JSX.Element>;
|
|
4
4
|
export default _default;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EditorButtonProps } from '
|
|
1
|
+
import { EditorButtonProps } from '../../types';
|
|
2
2
|
import { default as React } from 'react';
|
|
3
3
|
declare const _default: React.MemoExoticComponent<({ editor, buttonKey, tooltipText, isIconOnly, color, variant, isDisabled, icon, iconClass, text, withActive, onPressed, }: EditorButtonProps) => import("react/jsx-runtime").JSX.Element>;
|
|
4
4
|
export default _default;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { Extension } from '@tiptap/core';
|
|
2
|
-
|
|
2
|
+
import { SuggestionOptions } from '@tiptap/suggestion';
|
|
3
|
+
import { SlashCommandGroupCommandsProps, SlashCommandGroupProps } from '../../types';
|
|
4
|
+
export type SlashCommandSuggestionOptions = Omit<SuggestionOptions<SlashCommandGroupProps, SlashCommandGroupCommandsProps>, 'editor'>;
|
|
5
|
+
declare const _default: Extension<{
|
|
6
|
+
suggestion: SlashCommandSuggestionOptions;
|
|
7
|
+
}, any>;
|
|
3
8
|
export default _default;
|
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
declare const _default: {
|
|
5
|
-
pluginKey: PluginKey<any>;
|
|
6
|
-
items: ({ query }: {
|
|
7
|
-
query: string;
|
|
8
|
-
}) => {
|
|
9
|
-
commands: {
|
|
10
|
-
key: string;
|
|
11
|
-
title: string;
|
|
12
|
-
icon: string;
|
|
13
|
-
description: string;
|
|
14
|
-
command: ({ editor, range }: {
|
|
15
|
-
editor: Editor;
|
|
16
|
-
range: import('@tiptap/core').Range;
|
|
17
|
-
}) => void;
|
|
18
|
-
}[];
|
|
19
|
-
key: string;
|
|
20
|
-
title: string;
|
|
21
|
-
}[];
|
|
22
|
-
render: () => {
|
|
23
|
-
onStart: (props: SuggestionProps) => void;
|
|
24
|
-
onUpdate(props: SuggestionProps): void;
|
|
25
|
-
onKeyDown(props: SuggestionKeyDownProps): boolean | undefined;
|
|
26
|
-
onExit(): void;
|
|
27
|
-
};
|
|
28
|
-
};
|
|
29
|
-
export default _default;
|
|
1
|
+
import { SlashCommandSuggestionOptions } from './SlashCommand';
|
|
2
|
+
declare const SlashCommandSuggestion: SlashCommandSuggestionOptions;
|
|
3
|
+
export default SlashCommandSuggestion;
|
package/dist/helpers.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { SlashCommandGroupCommandsProps } from './types';
|
|
|
4
4
|
import { Node } from '@tiptap/pm/model';
|
|
5
5
|
export declare const isTextSelected: (editor: Editor) => boolean;
|
|
6
6
|
export declare const hasTextNodeInSelection: (editor: Editor) => boolean;
|
|
7
|
+
export declare const isTableCellSelection: (editor: Editor) => boolean;
|
|
7
8
|
export declare const isForbiddenNodeSelected: (editor: Editor) => boolean;
|
|
8
9
|
export declare const canShowColorTransform: (editor: Editor) => string | false | undefined;
|
|
9
10
|
export declare const canShowNodeTransform: (editor: Editor) => boolean | undefined;
|
|
@@ -23,9 +24,8 @@ export declare const uploadWithProgress: ({ file, url, onProgress, signal }: {
|
|
|
23
24
|
url: string;
|
|
24
25
|
onProgress: (percent: number) => boolean | void;
|
|
25
26
|
signal?: AbortSignal;
|
|
26
|
-
}) => Promise<
|
|
27
|
-
|
|
28
|
-
}>;
|
|
27
|
+
}) => Promise<Record<string, unknown>>;
|
|
28
|
+
export declare const getValueAtPath: (source: Record<string, unknown>, path: string | string[]) => unknown;
|
|
29
29
|
export declare const generateUniqueId: () => string;
|
|
30
30
|
export declare const updateNodeByPos: (editor: Editor, find: {
|
|
31
31
|
id?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { default as TiptopEditor } from './components/TiptopEditor';
|
|
1
|
+
export { default as TiptopEditor } from './components/editor/TiptopEditor';
|
|
2
2
|
export * from './types';
|