[PE-68] fix: markdown transformation of mention and custom image components (#5864)
* fix: markdown content for mention and custom image extensions * style: update issue embed upgrade card * chore: added string escapes
This commit is contained in:
parent
7bf4620bc1
commit
4c20be6cf2
4 changed files with 61 additions and 23 deletions
|
|
@ -1,9 +1,11 @@
|
||||||
import { Editor, mergeAttributes } from "@tiptap/core";
|
import { Editor, mergeAttributes } from "@tiptap/core";
|
||||||
import { Image } from "@tiptap/extension-image";
|
import { Image } from "@tiptap/extension-image";
|
||||||
|
import { MarkdownSerializerState } from "@tiptap/pm/markdown";
|
||||||
|
import { Node } from "@tiptap/pm/model";
|
||||||
import { ReactNodeViewRenderer } from "@tiptap/react";
|
import { ReactNodeViewRenderer } from "@tiptap/react";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
// extensions
|
// extensions
|
||||||
import { CustomImageNode } from "@/extensions/custom-image";
|
import { CustomImageNode, ImageAttributes } from "@/extensions/custom-image";
|
||||||
// plugins
|
// plugins
|
||||||
import { TrackImageDeletionPlugin, TrackImageRestorationPlugin, isFileValid } from "@/plugins/image";
|
import { TrackImageDeletionPlugin, TrackImageRestorationPlugin, isFileValid } from "@/plugins/image";
|
||||||
// types
|
// types
|
||||||
|
|
@ -124,6 +126,15 @@ export const CustomImageExtension = (props: TFileHandler) => {
|
||||||
deletedImageSet: new Map<string, boolean>(),
|
deletedImageSet: new Map<string, boolean>(),
|
||||||
uploadInProgress: false,
|
uploadInProgress: false,
|
||||||
maxFileSize,
|
maxFileSize,
|
||||||
|
markdown: {
|
||||||
|
serialize(state: MarkdownSerializerState, node: Node) {
|
||||||
|
const attrs = node.attrs as ImageAttributes;
|
||||||
|
const imageSource = state.esc(this?.editor?.commands?.getImageSource?.(attrs.src) || attrs.src);
|
||||||
|
const imageWidth = state.esc(attrs.width?.toString());
|
||||||
|
state.write(`<img src="${state.esc(imageSource)}" width="${imageWidth}" />`);
|
||||||
|
state.closeBlock(node);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { mergeAttributes } from "@tiptap/core";
|
import { mergeAttributes } from "@tiptap/core";
|
||||||
import { Image } from "@tiptap/extension-image";
|
import { Image } from "@tiptap/extension-image";
|
||||||
|
import { MarkdownSerializerState } from "@tiptap/pm/markdown";
|
||||||
|
import { Node } from "@tiptap/pm/model";
|
||||||
import { ReactNodeViewRenderer } from "@tiptap/react";
|
import { ReactNodeViewRenderer } from "@tiptap/react";
|
||||||
// components
|
// components
|
||||||
import { CustomImageNode, UploadImageExtensionStorage } from "@/extensions/custom-image";
|
import { CustomImageNode, ImageAttributes, UploadImageExtensionStorage } from "@/extensions/custom-image";
|
||||||
// types
|
// types
|
||||||
import { TFileHandler } from "@/types";
|
import { TFileHandler } from "@/types";
|
||||||
|
|
||||||
|
|
@ -52,6 +54,15 @@ export const CustomReadOnlyImageExtension = (props: Pick<TFileHandler, "getAsset
|
||||||
addStorage() {
|
addStorage() {
|
||||||
return {
|
return {
|
||||||
fileMap: new Map(),
|
fileMap: new Map(),
|
||||||
|
markdown: {
|
||||||
|
serialize(state: MarkdownSerializerState, node: Node) {
|
||||||
|
const attrs = node.attrs as ImageAttributes;
|
||||||
|
const imageSource = state.esc(this?.editor?.commands?.getImageSource?.(attrs.src) || attrs.src);
|
||||||
|
const imageWidth = state.esc(attrs.width?.toString());
|
||||||
|
state.write(`<img src="${state.esc(imageSource)}" width="${imageWidth}" />`);
|
||||||
|
state.closeBlock(node);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { Editor, mergeAttributes } from "@tiptap/core";
|
import { Editor, mergeAttributes } from "@tiptap/core";
|
||||||
import Mention, { MentionOptions } from "@tiptap/extension-mention";
|
import Mention, { MentionOptions } from "@tiptap/extension-mention";
|
||||||
|
import { MarkdownSerializerState } from "@tiptap/pm/markdown";
|
||||||
|
import { Node } from "@tiptap/pm/model";
|
||||||
import { ReactNodeViewRenderer, ReactRenderer } from "@tiptap/react";
|
import { ReactNodeViewRenderer, ReactRenderer } from "@tiptap/react";
|
||||||
import tippy from "tippy.js";
|
import tippy from "tippy.js";
|
||||||
// extensions
|
// extensions
|
||||||
|
|
@ -25,8 +27,19 @@ export const CustomMention = ({
|
||||||
addStorage(this) {
|
addStorage(this) {
|
||||||
return {
|
return {
|
||||||
mentionsOpen: false,
|
mentionsOpen: false,
|
||||||
|
markdown: {
|
||||||
|
serialize(state: MarkdownSerializerState, node: Node) {
|
||||||
|
const { attrs } = node;
|
||||||
|
const label = `@${state.esc(attrs.label)}`;
|
||||||
|
const originUrl = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
||||||
|
const safeRedirectionPath = state.esc(attrs.redirect_uri);
|
||||||
|
const url = `${originUrl}${safeRedirectionPath}`;
|
||||||
|
state.write(`[${label}](${url})`);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
id: {
|
id: {
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,34 @@
|
||||||
// ui
|
// plane ui
|
||||||
import { Button } from "@plane/ui";
|
import { getButtonStyling } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ProIcon } from "@/components/common";
|
import { ProIcon } from "@/components/common";
|
||||||
|
// helpers
|
||||||
|
import { cn } from "@/helpers/common.helper";
|
||||||
|
|
||||||
export const IssueEmbedUpgradeCard: React.FC<any> = (props) => (
|
export const IssueEmbedUpgradeCard: React.FC<any> = (props) => (
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={cn(
|
||||||
props.selected ? "border-custom-primary-200 border-[2px]" : ""
|
"w-full h-20 bg-custom-background-80 rounded-md border-[0.5px] border-custom-border-200 shadow-custom-shadow-2xs",
|
||||||
} w-full h-[100px] cursor-pointer space-y-2 rounded-md border-[0.5px] border-custom-border-200 shadow-custom-shadow-2xs`}
|
{
|
||||||
|
"border-2": props.selected,
|
||||||
|
}
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<h5 className="h-[20%] text-xs text-custom-text-300 p-2">
|
<div className="flex items-center justify-between gap-5 mt-2.5 pl-4 pr-5 py-3 w-full max-md:max-w-full max-md:flex-wrap rounded-md">
|
||||||
{props.node?.attrs?.project_identifier}-{props?.node?.attrs?.sequence_id}
|
<div className="flex items-center gap-4">
|
||||||
</h5>
|
<ProIcon className="flex-shrink-0 size-4" />
|
||||||
<div className="relative h-[71%]">
|
<p className="text-custom-text !text-base">
|
||||||
<div className="h-full backdrop-filter backdrop-blur-[30px] bg-custom-background-80 bg-opacity-30 flex items-center w-full justify-between gap-5 mt-2.5 pl-4 pr-5 py-3 max-md:max-w-full max-md:flex-wrap relative">
|
Embed and access issues in pages seamlessly, upgrade to Plane Pro now.
|
||||||
<div className="flex gap-2 items-center">
|
</p>
|
||||||
<div className="rounded">
|
|
||||||
<ProIcon className="m-2 w-4 h-4" />
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-custom-text text-sm">
|
<a
|
||||||
Embed and access issues in pages seamlessly, upgrade to plane pro now.
|
href="https://plane.so/pro"
|
||||||
</div>
|
target="_blank"
|
||||||
</div>
|
rel="noopener noreferrer"
|
||||||
<a href="https://plane.so/pricing" target="_blank" rel="noopener noreferrer">
|
className={cn(getButtonStyling("primary", "md"), "no-underline")}
|
||||||
<Button>Upgrade</Button>
|
>
|
||||||
|
Upgrade
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue