mirror of
https://github.com/makeplane/plane.git
synced 2025-12-16 20:07:56 +01:00
fix: rendering node views reliably
This commit is contained in:
@@ -46,7 +46,6 @@ export const useEditorMention = (args: TArgs) => {
|
||||
name={user.member__display_name}
|
||||
/>
|
||||
),
|
||||
id: user.member__id,
|
||||
entity_identifier: user.member__id,
|
||||
entity_name: "user_mention",
|
||||
title: user.member__display_name,
|
||||
|
||||
@@ -69,3 +69,5 @@ export const BLOCK_NODE_TYPES = [
|
||||
CORE_EXTENSIONS.CALLOUT,
|
||||
CORE_EXTENSIONS.WORK_ITEM_EMBED,
|
||||
];
|
||||
|
||||
export const INLINE_NODE_TYPES = [CORE_EXTENSIONS.MENTION];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NodeViewProps } from "@tiptap/react";
|
||||
import { NodeViewContent, NodeViewWrapper } from "@tiptap/react";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
// constants
|
||||
import { COLORS_LIST } from "@/constants/common";
|
||||
// local components
|
||||
@@ -33,6 +33,7 @@ export function CustomCalloutBlock(props: CustomCalloutNodeViewProps) {
|
||||
style={{
|
||||
backgroundColor: activeBackgroundColor,
|
||||
}}
|
||||
key={`callout-block-${node.attrs.id}`}
|
||||
>
|
||||
<CalloutBlockLogoSelector
|
||||
blockAttributes={node.attrs}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Node as ProseMirrorNode } from "@tiptap/core";
|
||||
import type { TBlockNodeBaseAttributes } from "../unique-id/types";
|
||||
|
||||
export enum ECalloutAttributeNames {
|
||||
ICON_COLOR = "data-icon-color",
|
||||
@@ -20,7 +21,7 @@ export type TCalloutBlockEmojiAttributes = {
|
||||
[ECalloutAttributeNames.EMOJI_URL]: string | undefined;
|
||||
};
|
||||
|
||||
export type TCalloutBlockAttributes = {
|
||||
export type TCalloutBlockAttributes = TBlockNodeBaseAttributes & {
|
||||
[ECalloutAttributeNames.LOGO_IN_USE]: "emoji" | "icon";
|
||||
[ECalloutAttributeNames.BACKGROUND]: string | undefined;
|
||||
[ECalloutAttributeNames.BLOCK_TYPE]: "callout-component";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
||||
import type { NodeViewProps } from "@tiptap/react";
|
||||
import { NodeViewWrapper, NodeViewContent } from "@tiptap/react";
|
||||
import ts from "highlight.js/lib/languages/typescript";
|
||||
import { common, createLowlight } from "lowlight";
|
||||
@@ -8,16 +8,20 @@ import { useState } from "react";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// plane utils
|
||||
import { cn } from "@plane/utils";
|
||||
// types
|
||||
import type { TCodeBlockAttributes } from "./types";
|
||||
|
||||
// we just have ts support for now
|
||||
const lowlight = createLowlight(common);
|
||||
lowlight.register("ts", ts);
|
||||
|
||||
type Props = {
|
||||
node: ProseMirrorNode;
|
||||
export type CodeBlockNodeViewProps = NodeViewProps & {
|
||||
node: NodeViewProps["node"] & {
|
||||
attrs: TCodeBlockAttributes;
|
||||
};
|
||||
};
|
||||
|
||||
export function CodeBlockComponent({ node }: Props) {
|
||||
export function CodeBlockComponent({ node }: CodeBlockNodeViewProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copyToClipboard = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
@@ -33,7 +37,7 @@ export function CodeBlockComponent({ node }: Props) {
|
||||
};
|
||||
|
||||
return (
|
||||
<NodeViewWrapper className="code-block relative group/code">
|
||||
<NodeViewWrapper className="code-block relative group/code" key={`code-block-${node.attrs.id}`}>
|
||||
<Tooltip tooltipContent="Copy code">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -5,13 +5,16 @@ import { common, createLowlight } from "lowlight";
|
||||
// components
|
||||
import { CodeBlockLowlight } from "./code-block-lowlight";
|
||||
import { CodeBlockComponent } from "./code-block-node-view";
|
||||
import type { CodeBlockNodeViewProps } from "./code-block-node-view";
|
||||
|
||||
const lowlight = createLowlight(common);
|
||||
lowlight.register("ts", ts);
|
||||
|
||||
export const CustomCodeBlockExtension = CodeBlockLowlight.extend({
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer(CodeBlockComponent);
|
||||
return ReactNodeViewRenderer((props) => (
|
||||
<CodeBlockComponent {...props} node={props.node as CodeBlockNodeViewProps["node"]} />
|
||||
));
|
||||
},
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
|
||||
5
packages/editor/src/core/extensions/code/types.ts
Normal file
5
packages/editor/src/core/extensions/code/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { TBlockNodeBaseAttributes } from "../unique-id/types";
|
||||
|
||||
export type TCodeBlockAttributes = TBlockNodeBaseAttributes & {
|
||||
language: string | null;
|
||||
};
|
||||
@@ -122,7 +122,7 @@ export function CustomImageNodeView(props: CustomImageNodeViewProps) {
|
||||
const shouldShowBlock = (isUploaded || imageFromFileSystem) && !failedToLoadImage;
|
||||
|
||||
return (
|
||||
<NodeViewWrapper>
|
||||
<NodeViewWrapper key={`image-block-${node.attrs.id}`}>
|
||||
<div className="p-0 mx-0 my-2" data-drag-handle ref={imageComponentRef}>
|
||||
{shouldShowBlock && !hasDuplicationFailed ? (
|
||||
<CustomImageBlock
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Node } from "@tiptap/core";
|
||||
// types
|
||||
import type { TFileHandler } from "@/types";
|
||||
import type { TBlockNodeBaseAttributes } from "../unique-id/types";
|
||||
|
||||
export enum ECustomImageAttributeNames {
|
||||
ID = "id",
|
||||
@@ -32,8 +33,7 @@ export enum ECustomImageStatus {
|
||||
DUPLICATION_FAILED = "duplication-failed",
|
||||
}
|
||||
|
||||
export type TCustomImageAttributes = {
|
||||
[ECustomImageAttributeNames.ID]: string | null;
|
||||
export type TCustomImageAttributes = TBlockNodeBaseAttributes & {
|
||||
[ECustomImageAttributeNames.WIDTH]: PixelAttribute<"35%" | number> | null;
|
||||
[ECustomImageAttributeNames.HEIGHT]: PixelAttribute<"auto" | number> | null;
|
||||
[ECustomImageAttributeNames.ASPECT_RATIO]: number | null;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { NodeViewProps } from "@tiptap/react";
|
||||
import { NodeViewWrapper } from "@tiptap/react";
|
||||
import { useMemo } from "react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
// extension config
|
||||
import type { TMentionExtensionOptions } from "./extension-config";
|
||||
// extension types
|
||||
@@ -19,7 +21,7 @@ export function MentionNodeView(props: MentionNodeViewProps) {
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<NodeViewWrapper className="mention-component inline w-fit">
|
||||
<NodeViewWrapper className="mention-component inline w-fit" key={`mention-${attrs.id}`}>
|
||||
{(extension.options as TMentionExtensionOptions).renderComponent({
|
||||
entity_identifier: attrs[EMentionComponentAttributeNames.ENTITY_IDENTIFIER] ?? "",
|
||||
entity_name: attrs[EMentionComponentAttributeNames.ENTITY_NAME] ?? "user_mention",
|
||||
|
||||
@@ -31,11 +31,9 @@ export const MentionsListDropdown = forwardRef(function MentionsListDropdown(pro
|
||||
(sectionIndex: number, itemIndex: number) => {
|
||||
try {
|
||||
const item = sections?.[sectionIndex]?.items?.[itemIndex];
|
||||
const transactionId = uuidv4();
|
||||
if (item) {
|
||||
command({
|
||||
...item,
|
||||
id: transactionId,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -4,13 +4,12 @@ import type { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
||||
import type { Transaction } from "@tiptap/pm/state";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
// constants
|
||||
import { CORE_EXTENSIONS, BLOCK_NODE_TYPES } from "@/constants/extension";
|
||||
import { CORE_EXTENSIONS, BLOCK_NODE_TYPES, INLINE_NODE_TYPES } from "@/constants/extension";
|
||||
import { ADDITIONAL_BLOCK_NODE_TYPES } from "@/plane-editor/constants/extensions";
|
||||
import { createUniqueIDPlugin } from "./plugin";
|
||||
import { createIdsForView } from "./utils";
|
||||
// plane imports
|
||||
|
||||
const COMBINED_BLOCK_NODE_TYPES = [...BLOCK_NODE_TYPES, ...ADDITIONAL_BLOCK_NODE_TYPES];
|
||||
const COMBINED_BLOCK_NODE_TYPES = [...INLINE_NODE_TYPES, ...BLOCK_NODE_TYPES, ...ADDITIONAL_BLOCK_NODE_TYPES];
|
||||
export type UniqueIDGenerationContext = {
|
||||
node: ProseMirrorNode;
|
||||
pos: number;
|
||||
|
||||
7
packages/editor/src/core/extensions/unique-id/types.ts
Normal file
7
packages/editor/src/core/extensions/unique-id/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Base attributes for all block nodes that have the unique-id extension.
|
||||
* All block node attribute types should extend this.
|
||||
*/
|
||||
export interface TBlockNodeBaseAttributes {
|
||||
id?: string | null;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { ReactNodeViewRenderer, NodeViewWrapper } from "@tiptap/react";
|
||||
import type { NodeViewProps } from "@tiptap/react";
|
||||
// local imports
|
||||
import { WorkItemEmbedExtensionConfig } from "./extension-config";
|
||||
import type { TWorkItemEmbedAttributes } from "./types";
|
||||
|
||||
type Props = {
|
||||
widgetCallback: ({
|
||||
@@ -18,15 +19,18 @@ type Props = {
|
||||
export function WorkItemEmbedExtension(props: Props) {
|
||||
return WorkItemEmbedExtensionConfig.extend({
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer((issueProps: NodeViewProps) => (
|
||||
<NodeViewWrapper>
|
||||
return ReactNodeViewRenderer((issueProps: NodeViewProps) => {
|
||||
const attrs = issueProps.node.attrs as TWorkItemEmbedAttributes;
|
||||
return (
|
||||
<NodeViewWrapper key={`work-item-embed-${attrs.id}`}>
|
||||
{props.widgetCallback({
|
||||
issueId: issueProps.node.attrs.entity_identifier,
|
||||
projectId: issueProps.node.attrs.project_identifier,
|
||||
workspaceSlug: issueProps.node.attrs.workspace_identifier,
|
||||
issueId: attrs.entity_identifier!,
|
||||
projectId: attrs.project_identifier,
|
||||
workspaceSlug: attrs.workspace_identifier,
|
||||
})}
|
||||
</NodeViewWrapper>
|
||||
));
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { TBlockNodeBaseAttributes } from "../unique-id/types";
|
||||
|
||||
export type TWorkItemEmbedAttributes = TBlockNodeBaseAttributes & {
|
||||
entity_identifier: string | undefined;
|
||||
project_identifier: string | undefined;
|
||||
workspace_identifier: string | undefined;
|
||||
entity_name: string | undefined;
|
||||
};
|
||||
@@ -5,7 +5,7 @@ export type TMentionSuggestion = {
|
||||
entity_identifier: string;
|
||||
entity_name: TSearchEntities;
|
||||
icon: React.ReactNode;
|
||||
id: string;
|
||||
id?: string | null;
|
||||
subTitle?: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user