feat: Tiptap V3 migration (#1854)

* Tiptap3 migration - WIP

* fix collaboration

* remove unused code

* fix flicker

* disable duplicate extensions

* update tiptap version

* Switch to useEditorState
- Set shouldRerenderOnTransaction to false

* fix editable state

* add tippyoptions for reference

* merge main

* tiptap 3.6.1

* fix bubble menu

* fix converter

* fix menus

* fix collaboration caret css

* fix: Set `isInitialized` to force immediate react node view rendering

* feat: Migrate tippy.js menus to Floating UI

* feat: Update collaboration connection for HocusPocus v3

* fix: Connect/disconnect websocketProvider

* cleanup

* cleanup

* feat: Improved placeholder and upload handling for images

* feat: Improved placeholder and upload handling for videos

* refactor: Image node and view clean-up

* feat: Improved placeholder and upload handling for attachments

* fix: Video view styles

* fix: Transaction handling on asset upload

* fix: Use imageDimensionsFromStream

* feat: Multiple file upload, improved placeholders, local previews

* fix: Drag & drop, paste upload

* fix: Allow media as attachment

* * add skeleton pulse animation
* add translation strings
* fix attachment view responsiveness

* fix collab connection status display

* Tiptap v3.17.0

* fix suggestion menu exit bug

* fix search shortcut

* fix history editor css

* tiptap 3.17.1

---------

Co-authored-by: Arek Nawo <areknawo@areknawo.com>
This commit is contained in:
Philip Okugbe
2026-01-24 20:41:08 +00:00
committed by GitHub
parent 98f71c95fe
commit 657fdf8cb7
74 changed files with 2532 additions and 2370 deletions
@@ -1,13 +1,12 @@
import type { EditorView } from "@tiptap/pm/view";
import { uploadImageAction } from "@/features/editor/components/image/upload-image-action.tsx";
import { uploadVideoAction } from "@/features/editor/components/video/upload-video-action.tsx";
import { uploadAttachmentAction } from "../attachment/upload-attachment-action";
import { createMentionAction } from "@/features/editor/components/link/internal-link-paste.ts";
import { Slice } from "@tiptap/pm/model";
import { INTERNAL_LINK_REGEX } from "@/lib/constants.ts";
import { Editor } from "@tiptap/core";
export const handlePaste = (
view: EditorView,
editor: Editor,
event: ClipboardEvent,
pageId: string,
creatorId?: string,
@@ -18,7 +17,7 @@ export const handlePaste = (
// we have to do this validation here to allow the default link extension to takeover if needs be
event.preventDefault();
const url = clipboardData.trim();
const { from: pos, empty } = view.state.selection;
const { from: pos, empty } = editor.state.selection;
const match = INTERNAL_LINK_REGEX.exec(url);
const currentPageMatch = INTERNAL_LINK_REGEX.exec(window.location.href);
@@ -34,19 +33,27 @@ export const handlePaste = (
return false;
}
const anchorId = match[6] ? match[6].split('#')[0] : undefined;
const urlWithoutAnchor = anchorId ? url.substring(0, url.indexOf("#")) : url;
createMentionAction(urlWithoutAnchor, view, pos, creatorId, anchorId);
const anchorId = match[6] ? match[6].split("#")[0] : undefined;
const urlWithoutAnchor = anchorId
? url.substring(0, url.indexOf("#"))
: url;
createMentionAction(
urlWithoutAnchor,
editor.view,
pos,
creatorId,
anchorId,
);
return true;
}
if (event.clipboardData?.files.length) {
event.preventDefault();
for (const file of event.clipboardData.files) {
const pos = view.state.selection.from;
uploadImageAction(file, view, pos, pageId);
uploadVideoAction(file, view, pos, pageId);
uploadAttachmentAction(file, view, pos, pageId);
const pos = editor.state.selection.from;
uploadImageAction(file, editor, pos, pageId);
uploadVideoAction(file, editor, pos, pageId);
uploadAttachmentAction(file, editor, pos, pageId);
}
return true;
}
@@ -54,7 +61,7 @@ export const handlePaste = (
};
export const handleFileDrop = (
view: EditorView,
editor: Editor,
event: DragEvent,
moved: boolean,
pageId: string,
@@ -63,14 +70,14 @@ export const handleFileDrop = (
event.preventDefault();
for (const file of event.dataTransfer.files) {
const coordinates = view.posAtCoords({
const coordinates = editor.view.posAtCoords({
left: event.clientX,
top: event.clientY,
});
uploadImageAction(file, view, coordinates?.pos ?? 0 - 1, pageId);
uploadVideoAction(file, view, coordinates?.pos ?? 0 - 1, pageId);
uploadAttachmentAction(file, view, coordinates?.pos ?? 0 - 1, pageId);
uploadImageAction(file, editor, coordinates?.pos ?? 0 - 1, pageId);
uploadVideoAction(file, editor, coordinates?.pos ?? 0 - 1, pageId);
uploadAttachmentAction(file, editor, coordinates?.pos ?? 0 - 1, pageId);
}
return true;
}