tiptop-editor 1.0.18 → 1.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/README.md CHANGED
@@ -1,69 +1,215 @@
1
- # 📝 Tiptop Editor
1
+ # Tiptop Editor
2
2
 
3
- A Notion-like rich text editor built with [Tiptap v3](https://tiptap.dev/), [HeroUI](https://heroui.dev/), [Tailwind v4](https://https://tailwindcss.com) packaged as a plug-and-play React component.
4
- Inspired from [TipTap Notion-like](https://tiptap.dev/docs/ui-components/templates/notion-like-editor).
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
  ![npm version](https://img.shields.io/npm/v/tiptop-editor.svg)
7
9
  ![bundle size](https://img.shields.io/bundlephobia/minzip/tiptop-editor)
8
10
  ![license](https://img.shields.io/npm/l/tiptop-editor)
9
11
 
10
- ---
12
+ ## Features
11
13
 
12
- ## Features
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
- - Built on **Tiptap v3** — a powerful, headless rich-text editor
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
- https://github.com/user-attachments/assets/cb7d907d-bae0-4b3b-b6e7-8493180afd75
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
- ## ⚙️ Installation
27
- ```bash
28
- npm install tiptop-editor
48
+ ## Custom Editor UI Options
49
+
50
+ `TiptopEditor` also supports a few package-specific options inside `editorOptions`:
51
+
52
+ ```tsx
53
+ <TiptopEditor
54
+ editorOptions={{
55
+ content: '<p>Custom layout</p>',
56
+ disableDefaultContainer: true,
57
+ showDragHandle: false,
58
+ }}
59
+ />
29
60
  ```
30
61
 
31
- ## 🚀 Usage
62
+ - `disableDefaultContainer`
63
+ 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.
64
+ - `showDragHandle`
65
+ Controls whether the block drag handle is rendered. Default: `true`.
32
66
 
33
- **Import the component in your app**
34
- ```tsx
35
- import { TiptopEditor } from "tiptop-editor";
67
+ ## Built-in Extensions
68
+
69
+ The package ships with these extensions enabled out of the box:
70
+
71
+ - `StarterKit`
72
+ - `ListKit`
73
+ - `Placeholder`
74
+ - custom slash command menu
75
+ - custom code block
76
+ - custom horizontal rule
77
+ - `TextStyle` and `Color`
78
+ - `Highlight`
79
+ - `TextAlign`
80
+ - `Subscript`
81
+ - `Superscript`
82
+ - emoji suggestions
83
+ - `TableKit`
84
+ - image uploader block and upload handler
85
+
86
+ ### Tables
87
+
88
+ Type `/table` to insert a table.
89
+
90
+ Inside a table you can:
91
+
92
+ - add or remove rows
93
+ - add or remove columns
94
+ - toggle header row or header column
95
+ - split a merged cell
96
+ - merge adjacent selected cells
97
+
98
+ To merge cells, drag across adjacent cells first, then use the table controls.
99
+
100
+ ### Emoji
101
+
102
+ Type `:` followed by an emoji name to open emoji suggestions.
103
+
104
+ ## Image Extension
105
+
106
+ The image feature is built around an `imageUploader` block.
107
+
108
+ ### How to insert an image block
109
+
110
+ - Type `/image`
111
+ - or use the slash menu and select `Image`
112
+
113
+ Once inserted, the block lets the user click to upload or drag and drop an image.
114
+
115
+ ### Supported files
116
+
117
+ - `image/png`
118
+ - `image/jpeg`
119
+ - `image/jpg`
120
+ - max size: `5MB`
121
+
122
+ ### Demo mode with no backend
123
+
124
+ 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.
125
+
126
+ ```tsx
127
+ <TiptopEditor
128
+ editorOptions={{
129
+ content: '<p>Upload demo</p>',
130
+ }}
131
+ />
132
+ ```
133
+
134
+ ### Real upload mode
135
+
136
+ To upload files to your backend, set both `imgUploadUrl` and `imgUploadResponseKey`.
36
137
 
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
138
  ```tsx
52
- <TiptopEditor editorOptions={{
53
- immediatelyRender: false // If using SSR (ex. a NextJS project) otherwise you can omit it
54
- content: '<p>I am the Tiptop Editor</p>'
55
- ... // Other props
139
+ <TiptopEditor
140
+ editorOptions={{
141
+ content: '<p>Upload to my API</p>',
142
+ imgUploadUrl: '/api/upload',
143
+ imgUploadResponseKey: 'url',
56
144
  }}
57
145
  />
58
146
  ```
59
147
 
148
+ The editor sends a `POST` request with `multipart/form-data` and the file under the `file` field.
149
+
150
+ `imgUploadResponseKey` is flexible. It supports:
151
+
152
+ - a top-level key like `'url'`
153
+ Example:
154
+ ```tsx
155
+ <TiptopEditor
156
+ editorOptions={{
157
+ imgUploadUrl: '/api/upload',
158
+ imgUploadResponseKey: 'url',
159
+ }}
160
+ />
161
+ ```
162
+ - a nested path like `'data.url'`
163
+ Example:
164
+ ```tsx
165
+ <TiptopEditor
166
+ editorOptions={{
167
+ imgUploadUrl: '/api/upload',
168
+ imgUploadResponseKey: 'data.url',
169
+ }}
170
+ />
171
+ ```
172
+ - a path array like `['data', 'url']`
173
+ Example:
174
+ ```tsx
175
+ <TiptopEditor
176
+ editorOptions={{
177
+ imgUploadUrl: '/api/upload',
178
+ imgUploadResponseKey: ['data', 'url'],
179
+ }}
180
+ />
181
+ ```
182
+ - a resolver function
183
+ Example:
184
+ ```tsx
185
+ <TiptopEditor
186
+ editorOptions={{
187
+ imgUploadUrl: '/api/upload',
188
+ imgUploadResponseKey: (response) => {
189
+ const asset = response.asset as { cdnUrl?: string } | undefined
190
+ return asset?.cdnUrl
191
+ },
192
+ }}
193
+ />
194
+ ```
195
+
196
+ Your server response must include the uploaded image URL at the location you describe with `imgUploadResponseKey`.
197
+
198
+ Example:
199
+
200
+ ```json
201
+ {
202
+ "data": {
203
+ "url": "https://cdn.example.com/uploads/image-123.jpg"
204
+ }
205
+ }
206
+ ```
60
207
 
61
- ##### Of course, I will continue to improve this project over time, as I have many more ideas (more extensions, more customizations, etc..)
62
- ##### Emoji Extension, Image extension, and more coming in next updates 🏃‍♂ ...
208
+ ## Notes
63
209
 
64
- I will also document the Changelogs and releases, as well as continue to update this Readme with relevant information.
210
+ - If you use SSR, keep `immediatelyRender: false`.
211
+ - The package manages the editor extensions internally, so custom `extensions` are intentionally not accepted yet.
65
212
 
66
- *If you have any suggestions/recommendations to improve this project, any feedback is much appreciated (PRs welcome) !*
67
- *I also encourage you to open up *Issues* if you find releveant bugs inside the package.*
213
+ ## Feedback
68
214
 
69
- **Thank you, and Happy Coding !**
215
+ Issues and pull requests are welcome.
@@ -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;
@@ -0,0 +1,4 @@
1
+ import { default as React } from 'react';
2
+ import { TextSelectionMenuProps } from '../types';
3
+ declare const _default: React.MemoExoticComponent<({ editor }: TextSelectionMenuProps) => import("react/jsx-runtime").JSX.Element>;
4
+ export default _default;
@@ -1,3 +1,8 @@
1
1
  import { Extension } from '@tiptap/core';
2
- declare const _default: Extension<any, any>;
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 { Editor } from '@tiptap/react';
2
- import { PluginKey } from '@tiptap/pm/state';
3
- import { SuggestionKeyDownProps, SuggestionProps } from '@tiptap/suggestion';
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
- url: string;
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;