regression: downgrade to tiptap v2 (#7982)
* chore: downgrade to tiptap v2 * fix: revert back to hocuspocus * fix: collaboration events added * fix: lock unlock issues * fix: build errors * fix: type errors * fix: graceful shutdown --------- Co-authored-by: Palanikannan M <akashmalinimurugu@gmail.com>
This commit is contained in:
parent
59022b6beb
commit
64781be7d2
48 changed files with 2123 additions and 824 deletions
|
|
@ -38,29 +38,31 @@
|
|||
"@floating-ui/dom": "^1.7.1",
|
||||
"@floating-ui/react": "^0.26.4",
|
||||
"@headlessui/react": "^1.7.3",
|
||||
"@hocuspocus/provider": "3.2.5",
|
||||
"@hocuspocus/provider": "2.15.2",
|
||||
"@plane/constants": "workspace:*",
|
||||
"@plane/hooks": "workspace:*",
|
||||
"@plane/types": "workspace:*",
|
||||
"@plane/ui": "workspace:*",
|
||||
"@plane/utils": "workspace:*",
|
||||
"@tiptap/core": "catalog:",
|
||||
"@tiptap/extension-blockquote": "^3.5.3",
|
||||
"@tiptap/extension-collaboration": "^3.5.3",
|
||||
"@tiptap/extension-emoji": "^3.5.3",
|
||||
"@tiptap/extension-image": "^3.5.3",
|
||||
"@tiptap/extension-list-item": "^3.5.3",
|
||||
"@tiptap/extension-mention": "^3.5.3",
|
||||
"@tiptap/extension-task-item": "^3.5.3",
|
||||
"@tiptap/extension-task-list": "^3.5.3",
|
||||
"@tiptap/extension-text-align": "^3.5.3",
|
||||
"@tiptap/extension-text-style": "^3.5.3",
|
||||
"@tiptap/extensions": "^3.5.3",
|
||||
"@tiptap/extension-blockquote": "^2.22.3",
|
||||
"@tiptap/extension-character-count": "^2.22.3",
|
||||
"@tiptap/extension-collaboration": "^2.22.3",
|
||||
"@tiptap/extension-emoji": "^2.22.3",
|
||||
"@tiptap/extension-image": "^2.22.3",
|
||||
"@tiptap/extension-list-item": "^2.22.3",
|
||||
"@tiptap/extension-mention": "^2.22.3",
|
||||
"@tiptap/extension-placeholder": "^2.22.3",
|
||||
"@tiptap/extension-task-item": "^2.22.3",
|
||||
"@tiptap/extension-task-list": "^2.22.3",
|
||||
"@tiptap/extension-text-align": "^2.22.3",
|
||||
"@tiptap/extension-text-style": "^2.22.3",
|
||||
"@tiptap/extension-underline": "^2.22.3",
|
||||
"@tiptap/html": "catalog:",
|
||||
"@tiptap/pm": "^3.5.3",
|
||||
"@tiptap/react": "^3.5.3",
|
||||
"@tiptap/starter-kit": "^3.5.3",
|
||||
"@tiptap/suggestion": "^3.5.3",
|
||||
"@tiptap/pm": "^2.22.3",
|
||||
"@tiptap/react": "^2.22.3",
|
||||
"@tiptap/starter-kit": "^2.22.3",
|
||||
"@tiptap/suggestion": "^2.22.3",
|
||||
"emoji-regex": "^10.3.0",
|
||||
"highlight.js": "^11.8.0",
|
||||
"is-emoji-supported": "^0.0.5",
|
||||
|
|
@ -70,7 +72,7 @@
|
|||
"lucide-react": "catalog:",
|
||||
"prosemirror-codemark": "^0.4.2",
|
||||
"tippy.js": "^6.3.7",
|
||||
"tiptap-markdown": "^0.9.0",
|
||||
"tiptap-markdown": "^0.8.10",
|
||||
"uuid": "catalog:",
|
||||
"y-indexeddb": "^9.0.12",
|
||||
"y-prosemirror": "^1.2.15",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { type Editor, isNodeSelection } from "@tiptap/core";
|
||||
import { useEditorState } from "@tiptap/react";
|
||||
import { BubbleMenu, type BubbleMenuProps } from "@tiptap/react/menus";
|
||||
import { BubbleMenu, type BubbleMenuProps, useEditorState } from "@tiptap/react";
|
||||
import { FC, useEffect, useState, useRef } from "react";
|
||||
// plane utils
|
||||
import { cn } from "@plane/utils";
|
||||
|
|
@ -119,7 +118,10 @@ export const EditorBubbleMenu: FC<Props> = (props) => {
|
|||
}
|
||||
return true;
|
||||
},
|
||||
options: {
|
||||
tippyOptions: {
|
||||
moveTransition: "transform 0.15s ease-out",
|
||||
duration: [300, 0],
|
||||
zIndex: 9,
|
||||
onShow: () => {
|
||||
if (editor.storage.link) {
|
||||
editor.storage.link.isBubbleMenuOpen = true;
|
||||
|
|
@ -134,13 +136,15 @@ export const EditorBubbleMenu: FC<Props> = (props) => {
|
|||
editor.commands.removeActiveDropbarExtension("bubble-menu");
|
||||
}, 0);
|
||||
},
|
||||
onHidden: () => {
|
||||
if (editor.storage.link) {
|
||||
editor.storage.link.isBubbleMenuOpen = false;
|
||||
}
|
||||
setTimeout(() => {
|
||||
editor.commands.removeActiveDropbarExtension("bubble-menu");
|
||||
}, 0);
|
||||
},
|
||||
},
|
||||
// TODO: Migrate these to floating UI options
|
||||
// tippyOptions: {
|
||||
// moveTransition: "transform 0.15s ease-out",
|
||||
// duration: [300, 0],
|
||||
// zIndex: 9,
|
||||
// },
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,91 @@
|
|||
import { EPageAccess } from "@plane/constants";
|
||||
import { TPage } from "@plane/types";
|
||||
import { CreatePayload, BaseActionPayload } from "@/types";
|
||||
|
||||
// Define all payload types for each event.
|
||||
export type ArchivedPayload = CreatePayload<{ archived_at: string | null }>;
|
||||
export type UnarchivedPayload = BaseActionPayload;
|
||||
export type LockedPayload = CreatePayload<{ is_locked: boolean }>;
|
||||
export type UnlockedPayload = BaseActionPayload;
|
||||
export type MadePublicPayload = CreatePayload<{ access: EPageAccess }>;
|
||||
export type MadePrivatePayload = CreatePayload<{ access: EPageAccess }>;
|
||||
export type DeletedPayload = CreatePayload<{ deleted_at: Date | null }>;
|
||||
export type DuplicatedPayload = CreatePayload<{ new_page_id: string }>;
|
||||
export type PropertyUpdatedPayload = CreatePayload<Partial<TPage>>;
|
||||
export type MovedPayload = CreatePayload<{
|
||||
new_project_id: string;
|
||||
new_page_id: string;
|
||||
}>;
|
||||
export type RestoredPayload = CreatePayload<{ deleted_page_ids?: string[] }>;
|
||||
export type ErrorPayload = CreatePayload<{
|
||||
error_message: string;
|
||||
error_type: "fetch" | "store";
|
||||
error_code?: "content_too_large" | "page_locked" | "page_archived";
|
||||
should_disconnect?: boolean;
|
||||
}>;
|
||||
|
||||
// Enhanced DocumentCollaborativeEvents with payload types.
|
||||
// Both the client name and server name are defined, and we add a "payloadType" property
|
||||
// so that we can later derive a mapping from client event to payload type.
|
||||
export const DocumentCollaborativeEvents = {
|
||||
lock: { client: "locked", server: "lock" },
|
||||
unlock: { client: "unlocked", server: "unlock" },
|
||||
archive: { client: "archived", server: "archive" },
|
||||
unarchive: { client: "unarchived", server: "unarchive" },
|
||||
"make-public": { client: "made-public", server: "make-public" },
|
||||
"make-private": { client: "made-private", server: "make-private" },
|
||||
lock: {
|
||||
client: "locked",
|
||||
server: "lock",
|
||||
payloadType: {} as LockedPayload,
|
||||
},
|
||||
unlock: {
|
||||
client: "unlocked",
|
||||
server: "unlock",
|
||||
payloadType: {} as UnlockedPayload,
|
||||
},
|
||||
archive: {
|
||||
client: "archived",
|
||||
server: "archive",
|
||||
payloadType: {} as ArchivedPayload,
|
||||
},
|
||||
unarchive: {
|
||||
client: "unarchived",
|
||||
server: "unarchive",
|
||||
payloadType: {} as UnarchivedPayload,
|
||||
},
|
||||
"make-public": {
|
||||
client: "made-public",
|
||||
server: "make-public",
|
||||
payloadType: {} as MadePublicPayload,
|
||||
},
|
||||
"make-private": {
|
||||
client: "made-private",
|
||||
server: "make-private",
|
||||
payloadType: {} as MadePrivatePayload,
|
||||
},
|
||||
delete: {
|
||||
client: "deleted",
|
||||
server: "delete",
|
||||
payloadType: {} as DeletedPayload,
|
||||
},
|
||||
move: {
|
||||
client: "moved",
|
||||
server: "move",
|
||||
payloadType: {} as MovedPayload,
|
||||
},
|
||||
duplicate: {
|
||||
client: "duplicated",
|
||||
server: "duplicate",
|
||||
payloadType: {} as DuplicatedPayload,
|
||||
},
|
||||
property_update: {
|
||||
client: "property_updated",
|
||||
server: "property_update",
|
||||
payloadType: {} as PropertyUpdatedPayload,
|
||||
},
|
||||
restore: {
|
||||
client: "restored",
|
||||
server: "restore",
|
||||
payloadType: {} as RestoredPayload,
|
||||
},
|
||||
error: {
|
||||
client: "error",
|
||||
server: "error",
|
||||
payloadType: {} as ErrorPayload,
|
||||
},
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export const CodeBlockComponent: React.FC<Props> = ({ node }) => {
|
|||
</Tooltip>
|
||||
|
||||
<pre className="bg-custom-background-90 text-custom-text-100 rounded-lg p-4 my-2">
|
||||
<NodeViewContent<"code"> as="code" className="whitespace-pre-wrap" />
|
||||
<NodeViewContent as="code" className="whitespace-pre-wrap" />
|
||||
</pre>
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import TaskItem from "@tiptap/extension-task-item";
|
||||
import TaskList from "@tiptap/extension-task-list";
|
||||
import { TextStyle } from "@tiptap/extension-text-style";
|
||||
import { Underline } from "@tiptap/extension-underline";
|
||||
// plane editor imports
|
||||
import { CoreEditorAdditionalExtensionsWithoutProps } from "@/plane-editor/extensions/core/without-props";
|
||||
// extensions
|
||||
|
|
@ -30,6 +31,7 @@ export const CoreEditorExtensionsWithoutProps = [
|
|||
CustomLinkExtension,
|
||||
ImageExtensionConfig,
|
||||
CustomImageExtensionConfig,
|
||||
Underline,
|
||||
TextStyle,
|
||||
TaskList.configure({
|
||||
HTMLAttributes: {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,32 @@ type LinkOptions = {
|
|||
};
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
interface Commands<ReturnType> {
|
||||
[CORE_EXTENSIONS.CUSTOM_LINK]: {
|
||||
/**
|
||||
* Set a link mark
|
||||
*/
|
||||
setLink: (attributes: {
|
||||
href: string;
|
||||
target?: string | null;
|
||||
rel?: string | null;
|
||||
class?: string | null;
|
||||
}) => ReturnType;
|
||||
/**
|
||||
* Toggle a link mark
|
||||
*/
|
||||
toggleLink: (attributes: {
|
||||
href: string;
|
||||
target?: string | null;
|
||||
rel?: string | null;
|
||||
class?: string | null;
|
||||
}) => ReturnType;
|
||||
/**
|
||||
* Unset a link mark
|
||||
*/
|
||||
unsetLink: () => ReturnType;
|
||||
};
|
||||
}
|
||||
interface Storage {
|
||||
[CORE_EXTENSIONS.CUSTOM_LINK]: CustomLinkStorage;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { EmojiOptions } from "@tiptap/extension-emoji";
|
||||
import type { EmojiOptions, EmojiStorage } from "@tiptap/extension-emoji";
|
||||
import { ReactRenderer, type Editor } from "@tiptap/react";
|
||||
// constants
|
||||
import { CORE_EXTENSIONS } from "@/constants/extension";
|
||||
|
|
@ -12,7 +12,7 @@ const DEFAULT_EMOJIS = ["+1", "-1", "smile", "orange_heart", "eyes"];
|
|||
|
||||
export const emojiSuggestion: EmojiOptions["suggestion"] = {
|
||||
items: ({ editor, query }: { editor: Editor; query: string }): EmojiItem[] => {
|
||||
const { emojis, isSupported } = editor.storage.emoji;
|
||||
const { emojis, isSupported } = editor.storage.emoji as EmojiStorage;
|
||||
const filteredEmojis = emojis.filter((emoji) => {
|
||||
const hasEmoji = !!emoji?.emoji;
|
||||
const hasFallbackImage = !!emoji?.fallbackImage;
|
||||
|
|
@ -79,7 +79,7 @@ export const emojiSuggestion: EmojiOptions["suggestion"] = {
|
|||
component.updateProps(props);
|
||||
if (!props.clientRect) return;
|
||||
cleanup();
|
||||
cleanup = updateFloatingUIFloaterPosition(props.editor, component.element).cleanup;
|
||||
cleanup = updateFloatingUIFloaterPosition(props.editor, component.element as HTMLElement).cleanup;
|
||||
},
|
||||
onKeyDown: ({ event }) => {
|
||||
if ([...DROPDOWN_NAVIGATION_KEYS, "Escape"].includes(event.key)) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { Extensions } from "@tiptap/core";
|
||||
import { CharacterCount } from "@tiptap/extension-character-count";
|
||||
import TaskItem from "@tiptap/extension-task-item";
|
||||
import TaskList from "@tiptap/extension-task-list";
|
||||
import { TextStyle } from "@tiptap/extension-text-style";
|
||||
import { CharacterCount } from "@tiptap/extensions";
|
||||
import { Underline } from "@tiptap/extension-underline";
|
||||
import { Markdown } from "tiptap-markdown";
|
||||
// extensions
|
||||
import {
|
||||
|
|
@ -75,6 +76,7 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
|
|||
ListKeymap({ tabIndex }),
|
||||
CustomLinkExtension,
|
||||
CustomTypographyExtension,
|
||||
Underline,
|
||||
TextStyle,
|
||||
TaskList.configure({
|
||||
HTMLAttributes: {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ export const HeadingListExtension = Extension.create<unknown, HeadingExtensionSt
|
|||
this.editor.emit("update", {
|
||||
editor: this.editor,
|
||||
transaction: newState.tr,
|
||||
appendedTransactions: [],
|
||||
});
|
||||
|
||||
return null;
|
||||
|
|
@ -61,4 +60,8 @@ export const HeadingListExtension = Extension.create<unknown, HeadingExtensionSt
|
|||
|
||||
return [plugin];
|
||||
},
|
||||
|
||||
getHeadings() {
|
||||
return this.storage.headings;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export const renderMentionsDropdown =
|
|||
component.updateProps(props);
|
||||
if (!props.clientRect) return;
|
||||
cleanup();
|
||||
cleanup = updateFloatingUIFloaterPosition(props.editor, component.element).cleanup;
|
||||
cleanup = updateFloatingUIFloaterPosition(props.editor, component.element as HTMLElement).cleanup;
|
||||
},
|
||||
onKeyDown: ({ event }) => {
|
||||
if ([...DROPDOWN_NAVIGATION_KEYS, "Escape"].includes(event.key)) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Placeholder } from "@tiptap/extensions";
|
||||
import { Placeholder } from "@tiptap/extension-placeholder";
|
||||
// constants
|
||||
import { CORE_EXTENSIONS } from "@/constants/extension";
|
||||
// types
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ export const CustomStarterKitExtension = (args: TArgs) => {
|
|||
codeBlock: false,
|
||||
horizontalRule: false,
|
||||
blockquote: false,
|
||||
link: false,
|
||||
listKeymap: false,
|
||||
paragraph: {
|
||||
HTMLAttributes: {
|
||||
class: "editor-paragraph-block",
|
||||
|
|
@ -43,6 +41,6 @@ export const CustomStarterKitExtension = (args: TArgs) => {
|
|||
class:
|
||||
"text-custom-text-300 transition-all motion-reduce:transition-none motion-reduce:hover:transform-none duration-200 ease-[cubic-bezier(0.165, 0.84, 0.44, 1)]",
|
||||
},
|
||||
...(enableHistory ? {} : { undoRedo: false }),
|
||||
...(enableHistory ? {} : { history: false }),
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -94,11 +94,8 @@ export const getEditorRefHelpers = (args: TArgs): EditorRefApi => {
|
|||
?.chain()
|
||||
.setMeta(CORE_EDITOR_META.SKIP_FILE_DELETION, true)
|
||||
.setMeta(CORE_EDITOR_META.INTENTIONAL_DELETION, true)
|
||||
.setContent(content, {
|
||||
emitUpdate,
|
||||
parseOptions: {
|
||||
preserveWhitespace: true,
|
||||
},
|
||||
.setContent(content, emitUpdate, {
|
||||
preserveWhitespace: true,
|
||||
})
|
||||
.run();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -93,11 +93,8 @@ export const useEditor = (props: TEditorHookProps) => {
|
|||
const { uploadInProgress: isUploadInProgress } = editor.storage.utility;
|
||||
if (!editor.isDestroyed && !isUploadInProgress) {
|
||||
try {
|
||||
editor.commands.setContent(value, {
|
||||
emitUpdate: false,
|
||||
parseOptions: {
|
||||
preserveWhitespace: true,
|
||||
},
|
||||
editor.commands.setContent(value, false, {
|
||||
preserveWhitespace: true,
|
||||
});
|
||||
if (editor.state.selection) {
|
||||
const docLength = editor.state.doc.content.size;
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ export const TrackFileDeletionPlugin = (editor: Editor, deleteHandler: TFileHand
|
|||
const nodeFileSetDetails = NODE_FILE_MAP[nodeType];
|
||||
if (!nodeFileSetDetails || !src) return;
|
||||
try {
|
||||
// @ts-expect-error add proper types for storage
|
||||
editor.storage[nodeType]?.[nodeFileSetDetails.fileSetName]?.set(src, true);
|
||||
// update assets list storage value
|
||||
editor.commands.updateAssetsList?.({
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ export const TrackFileRestorationPlugin = (editor: Editor, restoreHandler: TFile
|
|||
const src = node.attrs.src;
|
||||
const nodeFileSetDetails = NODE_FILE_MAP[nodeType];
|
||||
if (!nodeFileSetDetails) return;
|
||||
// @ts-expect-error add proper types for storage
|
||||
const extensionFileSetStorage = editor.storage[nodeType]?.[nodeFileSetDetails.fileSetName];
|
||||
const wasDeleted = extensionFileSetStorage?.get(src);
|
||||
if (!nodeFileSetDetails || !src) return;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ export const MarkdownClipboardPlugin = (editor: Editor): Plugin =>
|
|||
key: new PluginKey("markdownClipboard"),
|
||||
props: {
|
||||
clipboardTextSerializer: (slice) => {
|
||||
// @ts-expect-error tiptap-markdown types are not updated
|
||||
const markdownSerializer = editor.storage.markdown.serializer;
|
||||
const isTableRow = slice.content.firstChild?.type?.name === CORE_EXTENSIONS.TABLE_ROW;
|
||||
const nodeSelect = slice.openStart === 0 && slice.openEnd === 0;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,87 @@
|
|||
import { DocumentCollaborativeEvents } from "@/constants/document-collaborative-events";
|
||||
|
||||
export type TDocumentEventKey = keyof typeof DocumentCollaborativeEvents;
|
||||
export type TDocumentEventsClient = (typeof DocumentCollaborativeEvents)[TDocumentEventKey]["client"];
|
||||
export type TDocumentEventsServer = (typeof DocumentCollaborativeEvents)[TDocumentEventKey]["server"];
|
||||
// Base type for all action payloads
|
||||
export type BaseActionPayload = {
|
||||
user_id?: string;
|
||||
};
|
||||
|
||||
// Generic type for creating specific payloads
|
||||
export type CreatePayload<T = Record<string, never>> = BaseActionPayload & T;
|
||||
|
||||
export type TDocumentEventEmitter = {
|
||||
on: (event: string, callback: (message: { payload: TDocumentEventsClient }) => void) => void;
|
||||
off: (event: string, callback: (message: { payload: TDocumentEventsClient }) => void) => void;
|
||||
};
|
||||
|
||||
export type TDocumentEventKey = keyof typeof DocumentCollaborativeEvents;
|
||||
export type TDocumentEventsClient = (typeof DocumentCollaborativeEvents)[TDocumentEventKey]["client"];
|
||||
export type TDocumentEventsServer = (typeof DocumentCollaborativeEvents)[TDocumentEventKey]["server"];
|
||||
|
||||
// In this version, our union of all events (the client names) is:
|
||||
export type TAllEventTypes = TDocumentEventsClient;
|
||||
|
||||
// Create a mapping from each client event to its payload type using key remapping.
|
||||
export type EventToPayloadMap = {
|
||||
[K in keyof typeof DocumentCollaborativeEvents as (typeof DocumentCollaborativeEvents)[K]["client"]]: (typeof DocumentCollaborativeEvents)[K]["payloadType"];
|
||||
};
|
||||
|
||||
// Common fields for every realtime event
|
||||
export type CommonRealtimeFields = {
|
||||
affectedPages: {
|
||||
currentPage: string;
|
||||
parentPage: string | null;
|
||||
descendantPages: string[];
|
||||
};
|
||||
workspace_slug: string;
|
||||
project_id?: string;
|
||||
teamspace_id?: string;
|
||||
user_id: string;
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
// Helper function to create a realtime event in a type‑safe way.
|
||||
export function createRealtimeEvent<T extends keyof EventToPayloadMap>(
|
||||
opts: ApiServerPayload<T>
|
||||
): CommonRealtimeFields & BroadcastedEvent<T> {
|
||||
return {
|
||||
affectedPages: {
|
||||
currentPage: opts.page_id || "",
|
||||
parentPage: opts.parent_id || null,
|
||||
descendantPages: opts.descendants_ids || [],
|
||||
},
|
||||
workspace_slug: opts.workspace_slug,
|
||||
project_id: opts.project_id || "",
|
||||
teamspace_id: opts.teamspace_id || "",
|
||||
user_id: opts.user_id,
|
||||
timestamp: new Date().toISOString(),
|
||||
action: opts.action,
|
||||
data: opts.data,
|
||||
};
|
||||
}
|
||||
|
||||
export type ApiServerPayload<T extends keyof EventToPayloadMap> = {
|
||||
action: T;
|
||||
descendants_ids: string[];
|
||||
page_id?: string;
|
||||
parent_id?: string;
|
||||
data: EventToPayloadMap[T];
|
||||
project_id?: string;
|
||||
teamspace_id?: string;
|
||||
workspace_slug: string;
|
||||
user_id: string;
|
||||
};
|
||||
|
||||
// Create a discriminated union for broadcast payloads.
|
||||
// For every key in EventToPayloadMap, we make a union member with the common fields.
|
||||
export type BroadcastPayloadUnion = {
|
||||
[K in keyof EventToPayloadMap]: ApiServerPayload<K>;
|
||||
}[keyof EventToPayloadMap];
|
||||
|
||||
export type BroadcastedEventUnion = {
|
||||
[K in keyof EventToPayloadMap]: BroadcastedEvent<K>;
|
||||
}[keyof EventToPayloadMap];
|
||||
|
||||
export type BroadcastedEvent<T extends keyof EventToPayloadMap = keyof EventToPayloadMap> = CommonRealtimeFields & {
|
||||
action: T;
|
||||
data: EventToPayloadMap[T];
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue