mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: Image resize, Link selector in Modals, Delete/ sync images and so much more (#1896)
* added image-resizing support * link form removed * updated image upload logic and 35% default width on upload * removed shadow, added alert if not saved and ts errors * prevent enter key on Link selector to trigger modal submit * added workspace slug to all tiptap instances * added delete plugin with loading indicator * added better syncing of "Saved" state of editor and Image uploads * removed redundant description_html check
This commit is contained in:
parent
cebc8bdc8d
commit
d470adf262
@ -140,14 +140,14 @@ export const GptAssistantModal: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`absolute ${inset} z-20 w-full space-y-4 rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4 shadow ${
|
className={`absolute ${inset} z-20 w-full space-y-4 rounded-[10px] border border-custom-border-200 bg-custom-background-100 p-4 shadow ${isOpen ? "block" : "hidden"
|
||||||
isOpen ? "block" : "hidden"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{((content && content !== "") || (htmlContent && htmlContent !== "<p></p>")) && (
|
{((content && content !== "") || (htmlContent && htmlContent !== "<p></p>")) && (
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
Content:
|
Content:
|
||||||
<TiptapEditor
|
<TiptapEditor
|
||||||
|
workspaceSlug={workspaceSlug as string}
|
||||||
value={htmlContent ?? `<p>${content}</p>`}
|
value={htmlContent ?? `<p>${content}</p>`}
|
||||||
customClassName="-m-3"
|
customClassName="-m-3"
|
||||||
noBorder
|
noBorder
|
||||||
@ -161,6 +161,7 @@ export const GptAssistantModal: React.FC<Props> = ({
|
|||||||
<div className="page-block-section text-sm">
|
<div className="page-block-section text-sm">
|
||||||
Response:
|
Response:
|
||||||
<Tiptap
|
<Tiptap
|
||||||
|
workspaceSlug={workspaceSlug as string}
|
||||||
value={`<p>${response}</p>`}
|
value={`<p>${response}</p>`}
|
||||||
customClassName="-mx-3 -my-3"
|
customClassName="-mx-3 -my-3"
|
||||||
noBorder
|
noBorder
|
||||||
@ -179,8 +180,7 @@ export const GptAssistantModal: React.FC<Props> = ({
|
|||||||
type="text"
|
type="text"
|
||||||
name="task"
|
name="task"
|
||||||
register={register}
|
register={register}
|
||||||
placeholder={`${
|
placeholder={`${content && content !== ""
|
||||||
content && content !== ""
|
|
||||||
? "Tell AI what action to perform on this content..."
|
? "Tell AI what action to perform on this content..."
|
||||||
: "Ask AI anything..."
|
: "Ask AI anything..."
|
||||||
}`}
|
}`}
|
||||||
|
@ -293,6 +293,7 @@ export const InboxMainContent: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<IssueDescriptionForm
|
<IssueDescriptionForm
|
||||||
|
workspaceSlug={workspaceSlug as string}
|
||||||
issue={{
|
issue={{
|
||||||
name: issueDetails.name,
|
name: issueDetails.name,
|
||||||
description: issueDetails.description,
|
description: issueDetails.description,
|
||||||
|
@ -175,6 +175,7 @@ export const IssueActivitySection: React.FC<Props> = ({ issueId, user }) => {
|
|||||||
return (
|
return (
|
||||||
<div key={activityItem.id} className="mt-4">
|
<div key={activityItem.id} className="mt-4">
|
||||||
<CommentCard
|
<CommentCard
|
||||||
|
workspaceSlug={workspaceSlug as string}
|
||||||
comment={activityItem as IIssueComment}
|
comment={activityItem as IIssueComment}
|
||||||
onSubmit={handleCommentUpdate}
|
onSubmit={handleCommentUpdate}
|
||||||
handleCommentDeletion={handleCommentDelete}
|
handleCommentDeletion={handleCommentDelete}
|
||||||
|
@ -93,6 +93,7 @@ export const AddComment: React.FC<Props> = ({ issueId, user, disabled = false })
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<TiptapEditor
|
<TiptapEditor
|
||||||
|
workspaceSlug={workspaceSlug as string}
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
value={
|
value={
|
||||||
!value ||
|
!value ||
|
||||||
|
@ -22,12 +22,13 @@ const TiptapEditor = React.forwardRef<ITiptapRichTextEditor, ITiptapRichTextEdit
|
|||||||
TiptapEditor.displayName = "TiptapEditor";
|
TiptapEditor.displayName = "TiptapEditor";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
workspaceSlug: string;
|
||||||
comment: IIssueComment;
|
comment: IIssueComment;
|
||||||
onSubmit: (comment: IIssueComment) => void;
|
onSubmit: (comment: IIssueComment) => void;
|
||||||
handleCommentDeletion: (comment: string) => void;
|
handleCommentDeletion: (comment: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CommentCard: React.FC<Props> = ({ comment, onSubmit, handleCommentDeletion }) => {
|
export const CommentCard: React.FC<Props> = ({ comment, workspaceSlug, onSubmit, handleCommentDeletion }) => {
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
|
||||||
const editorRef = React.useRef<any>(null);
|
const editorRef = React.useRef<any>(null);
|
||||||
@ -109,6 +110,7 @@ export const CommentCard: React.FC<Props> = ({ comment, onSubmit, handleCommentD
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<TiptapEditor
|
<TiptapEditor
|
||||||
|
workspaceSlug={workspaceSlug as string}
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
value={watch("comment_html")}
|
value={watch("comment_html")}
|
||||||
debouncedUpdatesEnabled={false}
|
debouncedUpdatesEnabled={false}
|
||||||
@ -138,6 +140,7 @@ export const CommentCard: React.FC<Props> = ({ comment, onSubmit, handleCommentD
|
|||||||
</form>
|
</form>
|
||||||
<div className={`${isEditing ? "hidden" : ""}`}>
|
<div className={`${isEditing ? "hidden" : ""}`}>
|
||||||
<TiptapEditor
|
<TiptapEditor
|
||||||
|
workspaceSlug={workspaceSlug as string}
|
||||||
ref={showEditorRef}
|
ref={showEditorRef}
|
||||||
value={comment.comment_html}
|
value={comment.comment_html}
|
||||||
editable={false}
|
editable={false}
|
||||||
|
@ -24,6 +24,7 @@ export interface IssueDetailsProps {
|
|||||||
description: string;
|
description: string;
|
||||||
description_html: string;
|
description_html: string;
|
||||||
};
|
};
|
||||||
|
workspaceSlug: string;
|
||||||
handleFormSubmit: (value: IssueDescriptionFormValues) => Promise<void>;
|
handleFormSubmit: (value: IssueDescriptionFormValues) => Promise<void>;
|
||||||
isAllowed: boolean;
|
isAllowed: boolean;
|
||||||
}
|
}
|
||||||
@ -31,6 +32,7 @@ export interface IssueDetailsProps {
|
|||||||
export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
|
export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
|
||||||
issue,
|
issue,
|
||||||
handleFormSubmit,
|
handleFormSubmit,
|
||||||
|
workspaceSlug,
|
||||||
isAllowed,
|
isAllowed,
|
||||||
}) => {
|
}) => {
|
||||||
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
|
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
|
||||||
@ -69,11 +71,15 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSubmitting === "submitted") {
|
if (isSubmitting === "submitted") {
|
||||||
|
setShowAlert(false);
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
setIsSubmitting("saved");
|
setIsSubmitting("saved");
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
} else if (isSubmitting === "submitting") {
|
||||||
|
setShowAlert(true);
|
||||||
}
|
}
|
||||||
}, [isSubmitting]);
|
}, [isSubmitting, setShowAlert]);
|
||||||
|
|
||||||
|
|
||||||
// reset form values
|
// reset form values
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -112,8 +118,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
|
|||||||
{characterLimit && (
|
{characterLimit && (
|
||||||
<div className="pointer-events-none absolute bottom-1 right-1 z-[2] rounded bg-custom-background-100 text-custom-text-200 p-0.5 text-xs">
|
<div className="pointer-events-none absolute bottom-1 right-1 z-[2] rounded bg-custom-background-100 text-custom-text-200 p-0.5 text-xs">
|
||||||
<span
|
<span
|
||||||
className={`${
|
className={`${watch("name").length === 0 || watch("name").length > 255 ? "text-red-500" : ""
|
||||||
watch("name").length === 0 || watch("name").length > 255 ? "text-red-500" : ""
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{watch("name").length}
|
{watch("name").length}
|
||||||
@ -139,11 +144,14 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
|
|||||||
? watch("description_html")
|
? watch("description_html")
|
||||||
: value
|
: value
|
||||||
}
|
}
|
||||||
|
workspaceSlug={workspaceSlug}
|
||||||
debouncedUpdatesEnabled={true}
|
debouncedUpdatesEnabled={true}
|
||||||
|
setShouldShowAlert={setShowAlert}
|
||||||
setIsSubmitting={setIsSubmitting}
|
setIsSubmitting={setIsSubmitting}
|
||||||
customClassName="min-h-[150px] shadow-sm"
|
customClassName="min-h-[150px] shadow-sm"
|
||||||
editorContentCustomClassNames="pb-9"
|
editorContentCustomClassNames="pb-9"
|
||||||
onChange={(description: Object, description_html: string) => {
|
onChange={(description: Object, description_html: string) => {
|
||||||
|
setShowAlert(true);
|
||||||
setIsSubmitting("submitting");
|
setIsSubmitting("submitting");
|
||||||
onChange(description_html);
|
onChange(description_html);
|
||||||
setValue("description", description);
|
setValue("description", description);
|
||||||
@ -156,8 +164,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={`absolute right-5 bottom-5 text-xs text-custom-text-200 border border-custom-border-400 rounded-xl w-[6.5rem] py-1 z-10 flex items-center justify-center ${
|
className={`absolute right-5 bottom-5 text-xs text-custom-text-200 border border-custom-border-400 rounded-xl w-[6.5rem] py-1 z-10 flex items-center justify-center ${isSubmitting === "saved" ? "fadeOut" : "fadeIn"
|
||||||
isSubmitting === "saved" ? "fadeOut" : "fadeIn"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isSubmitting === "submitting" ? "Saving..." : "Saved"}
|
{isSubmitting === "submitting" ? "Saving..." : "Saved"}
|
||||||
|
@ -370,6 +370,7 @@ export const IssueForm: FC<IssueFormProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TiptapEditor
|
<TiptapEditor
|
||||||
|
workspaceSlug={workspaceSlug as string}
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
debouncedUpdatesEnabled={false}
|
debouncedUpdatesEnabled={false}
|
||||||
value={
|
value={
|
||||||
|
@ -124,6 +124,7 @@ export const IssueMainContent: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<IssueDescriptionForm
|
<IssueDescriptionForm
|
||||||
|
workspaceSlug={workspaceSlug as string}
|
||||||
issue={issueDetails}
|
issue={issueDetails}
|
||||||
handleFormSubmit={submitChanges}
|
handleFormSubmit={submitChanges}
|
||||||
isAllowed={memberRole.isMember || memberRole.isOwner || !uneditable}
|
isAllowed={memberRole.isMember || memberRole.isOwner || !uneditable}
|
||||||
|
@ -292,6 +292,7 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
|
|||||||
if (!data)
|
if (!data)
|
||||||
return (
|
return (
|
||||||
<TiptapEditor
|
<TiptapEditor
|
||||||
|
workspaceSlug={workspaceSlug as string}
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
value={"<p></p>"}
|
value={"<p></p>"}
|
||||||
debouncedUpdatesEnabled={false}
|
debouncedUpdatesEnabled={false}
|
||||||
@ -311,12 +312,11 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TiptapEditor
|
<TiptapEditor
|
||||||
|
workspaceSlug={workspaceSlug as string}
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
value={
|
value={
|
||||||
value && value !== "" && Object.keys(value).length > 0
|
value && value !== "" && Object.keys(value).length > 0
|
||||||
? value
|
? value
|
||||||
: watch("description_html") && watch("description_html") !== ""
|
|
||||||
? watch("description_html")
|
|
||||||
: { type: "doc", content: [{ type: "paragraph" }] }
|
: { type: "doc", content: [{ type: "paragraph" }] }
|
||||||
}
|
}
|
||||||
debouncedUpdatesEnabled={false}
|
debouncedUpdatesEnabled={false}
|
||||||
@ -334,8 +334,7 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
|
|||||||
<div className="m-2 mt-6 flex">
|
<div className="m-2 mt-6 flex">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-80 ${
|
className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-80 ${iAmFeelingLucky ? "cursor-wait bg-custom-background-90" : ""
|
||||||
iAmFeelingLucky ? "cursor-wait bg-custom-background-90" : ""
|
|
||||||
}`}
|
}`}
|
||||||
onClick={handleAutoGenerateDescription}
|
onClick={handleAutoGenerateDescription}
|
||||||
disabled={iAmFeelingLucky}
|
disabled={iAmFeelingLucky}
|
||||||
|
@ -456,6 +456,7 @@ export const SinglePageBlock: React.FC<Props> = ({
|
|||||||
{showBlockDetails
|
{showBlockDetails
|
||||||
? block.description_html.length > 7 && (
|
? block.description_html.length > 7 && (
|
||||||
<TiptapEditor
|
<TiptapEditor
|
||||||
|
workspaceSlug={workspaceSlug as string}
|
||||||
value={block.description_html}
|
value={block.description_html}
|
||||||
customClassName="text-sm min-h-[150px]"
|
customClassName="text-sm min-h-[150px]"
|
||||||
noBorder
|
noBorder
|
||||||
|
@ -1,17 +1,27 @@
|
|||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "@tiptap/core";
|
||||||
import { Check, Trash } from "lucide-react";
|
import { Check, Trash } from "lucide-react";
|
||||||
import { Dispatch, FC, SetStateAction, useEffect, useRef } from "react";
|
import { Dispatch, FC, SetStateAction, useCallback, useEffect, useRef } from "react";
|
||||||
import { cn } from "../utils";
|
import { cn } from "../utils";
|
||||||
|
import isValidHttpUrl from "./utils/link-validator";
|
||||||
interface LinkSelectorProps {
|
interface LinkSelectorProps {
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const LinkSelector: FC<LinkSelectorProps> = ({ editor, isOpen, setIsOpen }) => {
|
export const LinkSelector: FC<LinkSelectorProps> = ({ editor, isOpen, setIsOpen }) => {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const onLinkSubmit = useCallback(() => {
|
||||||
|
const input = inputRef.current;
|
||||||
|
const url = input?.value;
|
||||||
|
if (url && isValidHttpUrl(url)) {
|
||||||
|
editor.chain().focus().setLink({ href: url }).run();
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
}, [editor, inputRef, setIsOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
inputRef.current && inputRef.current?.focus();
|
inputRef.current && inputRef.current?.focus();
|
||||||
});
|
});
|
||||||
@ -38,15 +48,13 @@ export const LinkSelector: FC<LinkSelectorProps> = ({ editor, isOpen, setIsOpen
|
|||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<form
|
<div
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const form = e.target as HTMLFormElement;
|
|
||||||
const input = form.elements[0] as HTMLInputElement;
|
|
||||||
editor.chain().focus().setLink({ href: input.value }).run();
|
|
||||||
setIsOpen(false);
|
|
||||||
}}
|
|
||||||
className="fixed top-full z-[99999] mt-1 flex w-60 overflow-hidden rounded border border-custom-border-300 bg-custom-background-100 dow-xl animate-in fade-in slide-in-from-top-1"
|
className="fixed top-full z-[99999] mt-1 flex w-60 overflow-hidden rounded border border-custom-border-300 bg-custom-background-100 dow-xl animate-in fade-in slide-in-from-top-1"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault(); onLinkSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
@ -57,6 +65,7 @@ export const LinkSelector: FC<LinkSelectorProps> = ({ editor, isOpen, setIsOpen
|
|||||||
/>
|
/>
|
||||||
{editor.getAttributes("link").href ? (
|
{editor.getAttributes("link").href ? (
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className="flex items-center rounded-sm p-1 text-red-600 transition-all hover:bg-red-100 dark:hover:bg-red-800"
|
className="flex items-center rounded-sm p-1 text-red-600 transition-all hover:bg-red-100 dark:hover:bg-red-800"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
editor.chain().focus().unsetLink().run();
|
editor.chain().focus().unsetLink().run();
|
||||||
@ -66,11 +75,15 @@ export const LinkSelector: FC<LinkSelectorProps> = ({ editor, isOpen, setIsOpen
|
|||||||
<Trash className="h-4 w-4" />
|
<Trash className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button className="flex items-center rounded-sm p-1 text-custom-text-300 transition-all hover:bg-custom-background-90">
|
<button className="flex items-center rounded-sm p-1 text-custom-text-300 transition-all hover:bg-custom-background-90" type="button"
|
||||||
|
onClick={() => {
|
||||||
|
onLinkSubmit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Check className="h-4 w-4" />
|
<Check className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</form>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
export default function isValidHttpUrl(string: string): boolean {
|
||||||
|
let url;
|
||||||
|
|
||||||
|
try {
|
||||||
|
url = new URL(string);
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.protocol === "http:" || url.protocol === "https:";
|
||||||
|
}
|
||||||
|
|
57
apps/app/components/tiptap/extensions/image-resize.tsx
Normal file
57
apps/app/components/tiptap/extensions/image-resize.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { Editor } from "@tiptap/react";
|
||||||
|
import Moveable from "react-moveable";
|
||||||
|
|
||||||
|
export const ImageResizer = ({ editor }: { editor: Editor }) => {
|
||||||
|
const updateMediaSize = () => {
|
||||||
|
const imageInfo = document.querySelector(
|
||||||
|
".ProseMirror-selectednode",
|
||||||
|
) as HTMLImageElement;
|
||||||
|
if (imageInfo) {
|
||||||
|
const selection = editor.state.selection;
|
||||||
|
editor.commands.setImage({
|
||||||
|
src: imageInfo.src,
|
||||||
|
width: Number(imageInfo.style.width.replace("px", "")),
|
||||||
|
height: Number(imageInfo.style.height.replace("px", "")),
|
||||||
|
} as any);
|
||||||
|
editor.commands.setNodeSelection(selection.from);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Moveable
|
||||||
|
target={document.querySelector(".ProseMirror-selectednode") as any}
|
||||||
|
container={null}
|
||||||
|
origin={false}
|
||||||
|
edge={false}
|
||||||
|
throttleDrag={0}
|
||||||
|
keepRatio={true}
|
||||||
|
resizable={true}
|
||||||
|
throttleResize={0}
|
||||||
|
onResize={({
|
||||||
|
target,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
delta,
|
||||||
|
}:
|
||||||
|
any) => {
|
||||||
|
delta[0] && (target!.style.width = `${width}px`);
|
||||||
|
delta[1] && (target!.style.height = `${height}px`);
|
||||||
|
}}
|
||||||
|
onResizeEnd={() => {
|
||||||
|
updateMediaSize();
|
||||||
|
}}
|
||||||
|
scalable={true}
|
||||||
|
renderDirections={["w", "e"]}
|
||||||
|
onScale={({
|
||||||
|
target,
|
||||||
|
transform,
|
||||||
|
}:
|
||||||
|
any) => {
|
||||||
|
target!.style.transform = transform;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,6 @@
|
|||||||
import StarterKit from "@tiptap/starter-kit";
|
import StarterKit from "@tiptap/starter-kit";
|
||||||
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
||||||
import TiptapLink from "@tiptap/extension-link";
|
import TiptapLink from "@tiptap/extension-link";
|
||||||
import TiptapImage from "@tiptap/extension-image";
|
|
||||||
import Placeholder from "@tiptap/extension-placeholder";
|
import Placeholder from "@tiptap/extension-placeholder";
|
||||||
import TiptapUnderline from "@tiptap/extension-underline";
|
import TiptapUnderline from "@tiptap/extension-underline";
|
||||||
import TextStyle from "@tiptap/extension-text-style";
|
import TextStyle from "@tiptap/extension-text-style";
|
||||||
@ -18,18 +17,13 @@ import { InputRule } from "@tiptap/core";
|
|||||||
import ts from "highlight.js/lib/languages/typescript";
|
import ts from "highlight.js/lib/languages/typescript";
|
||||||
|
|
||||||
import "highlight.js/styles/github-dark.css";
|
import "highlight.js/styles/github-dark.css";
|
||||||
import UploadImagesPlugin from "../plugins/upload-image";
|
|
||||||
import UniqueID from "@tiptap-pro/extension-unique-id";
|
import UniqueID from "@tiptap-pro/extension-unique-id";
|
||||||
|
import UpdatedImage from "./updated-image";
|
||||||
|
import isValidHttpUrl from "../bubble-menu/utils/link-validator";
|
||||||
|
|
||||||
lowlight.registerLanguage("ts", ts);
|
lowlight.registerLanguage("ts", ts);
|
||||||
|
|
||||||
const CustomImage = TiptapImage.extend({
|
export const TiptapExtensions = (workspaceSlug: string, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void) => [
|
||||||
addProseMirrorPlugins() {
|
|
||||||
return [UploadImagesPlugin()];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const TiptapExtensions = [
|
|
||||||
StarterKit.configure({
|
StarterKit.configure({
|
||||||
bulletList: {
|
bulletList: {
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
@ -93,13 +87,14 @@ export const TiptapExtensions = [
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
TiptapLink.configure({
|
TiptapLink.configure({
|
||||||
|
protocols: ["http", "https"],
|
||||||
|
validate: (url) => isValidHttpUrl(url),
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class:
|
class:
|
||||||
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
|
"text-custom-primary-300 underline underline-offset-[3px] hover:text-custom-primary-500 transition-colors cursor-pointer",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
CustomImage.configure({
|
UpdatedImage.configure({
|
||||||
allowBase64: true,
|
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: "rounded-lg border border-custom-border-300",
|
class: "rounded-lg border border-custom-border-300",
|
||||||
},
|
},
|
||||||
@ -117,7 +112,7 @@ export const TiptapExtensions = [
|
|||||||
UniqueID.configure({
|
UniqueID.configure({
|
||||||
types: ["image"],
|
types: ["image"],
|
||||||
}),
|
}),
|
||||||
SlashCommand,
|
SlashCommand(workspaceSlug, setIsSubmitting),
|
||||||
TiptapUnderline,
|
TiptapUnderline,
|
||||||
TextStyle,
|
TextStyle,
|
||||||
Color,
|
Color,
|
||||||
|
22
apps/app/components/tiptap/extensions/updated-image.tsx
Normal file
22
apps/app/components/tiptap/extensions/updated-image.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import Image from "@tiptap/extension-image";
|
||||||
|
import TrackImageDeletionPlugin from "../plugins/delete-image";
|
||||||
|
import UploadImagesPlugin from "../plugins/upload-image";
|
||||||
|
|
||||||
|
const UpdatedImage = Image.extend({
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
return [UploadImagesPlugin(), TrackImageDeletionPlugin()];
|
||||||
|
},
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
...this.parent?.(),
|
||||||
|
width: {
|
||||||
|
default: '35%',
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default UpdatedImage;
|
@ -1,14 +1,10 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import { useEditor, EditorContent, Editor } from "@tiptap/react";
|
import { useEditor, EditorContent, Editor } from "@tiptap/react";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import { EditorBubbleMenu } from "./bubble-menu";
|
import { EditorBubbleMenu } from "./bubble-menu";
|
||||||
import { TiptapExtensions } from "./extensions";
|
import { TiptapExtensions } from "./extensions";
|
||||||
import { TiptapEditorProps } from "./props";
|
import { TiptapEditorProps } from "./props";
|
||||||
import { Node } from "@tiptap/pm/model";
|
import { useImperativeHandle, useRef } from "react";
|
||||||
import { Editor as CoreEditor } from "@tiptap/core";
|
import { ImageResizer } from "./extensions/image-resize";
|
||||||
import { useCallback, useImperativeHandle, useRef } from "react";
|
|
||||||
import { EditorState } from "@tiptap/pm/state";
|
|
||||||
import fileService from "services/file.service";
|
|
||||||
|
|
||||||
export interface ITiptapRichTextEditor {
|
export interface ITiptapRichTextEditor {
|
||||||
value: string;
|
value: string;
|
||||||
@ -18,6 +14,8 @@ export interface ITiptapRichTextEditor {
|
|||||||
editorContentCustomClassNames?: string;
|
editorContentCustomClassNames?: string;
|
||||||
onChange?: (json: any, html: string) => void;
|
onChange?: (json: any, html: string) => void;
|
||||||
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
|
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
|
||||||
|
setShouldShowAlert?: (showAlert: boolean) => void;
|
||||||
|
workspaceSlug: string;
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
forwardedRef?: any;
|
forwardedRef?: any;
|
||||||
debouncedUpdatesEnabled?: boolean;
|
debouncedUpdatesEnabled?: boolean;
|
||||||
@ -30,22 +28,24 @@ const Tiptap = (props: ITiptapRichTextEditor) => {
|
|||||||
forwardedRef,
|
forwardedRef,
|
||||||
editable,
|
editable,
|
||||||
setIsSubmitting,
|
setIsSubmitting,
|
||||||
|
setShouldShowAlert,
|
||||||
editorContentCustomClassNames,
|
editorContentCustomClassNames,
|
||||||
value,
|
value,
|
||||||
noBorder,
|
noBorder,
|
||||||
|
workspaceSlug,
|
||||||
borderOnFocus,
|
borderOnFocus,
|
||||||
customClassName,
|
customClassName,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
editable: editable ?? true,
|
editable: editable ?? true,
|
||||||
editorProps: TiptapEditorProps,
|
editorProps: TiptapEditorProps(workspaceSlug, setIsSubmitting),
|
||||||
extensions: TiptapExtensions,
|
extensions: TiptapExtensions(workspaceSlug, setIsSubmitting),
|
||||||
content: value,
|
content: value,
|
||||||
onUpdate: async ({ editor }) => {
|
onUpdate: async ({ editor }) => {
|
||||||
// for instant feedback loop
|
// for instant feedback loop
|
||||||
setIsSubmitting?.("submitting");
|
setIsSubmitting?.("submitting");
|
||||||
checkForNodeDeletions(editor);
|
setShouldShowAlert?.(true);
|
||||||
if (debouncedUpdatesEnabled) {
|
if (debouncedUpdatesEnabled) {
|
||||||
debouncedUpdates({ onChange, editor });
|
debouncedUpdates({ onChange, editor });
|
||||||
} else {
|
} else {
|
||||||
@ -65,45 +65,6 @@ const Tiptap = (props: ITiptapRichTextEditor) => {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const previousState = useRef<EditorState>();
|
|
||||||
|
|
||||||
const onNodeDeleted = useCallback(async (node: Node) => {
|
|
||||||
if (node.type.name === "image") {
|
|
||||||
const assetUrlWithWorkspaceId = new URL(node.attrs.src).pathname.substring(1);
|
|
||||||
const resStatus = await fileService.deleteImage(assetUrlWithWorkspaceId);
|
|
||||||
if (resStatus === 204) {
|
|
||||||
console.log("file deleted successfully");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const checkForNodeDeletions = useCallback(
|
|
||||||
(editor: CoreEditor) => {
|
|
||||||
const prevNodesById: Record<string, Node> = {};
|
|
||||||
previousState.current?.doc.forEach((node) => {
|
|
||||||
if (node.attrs.id) {
|
|
||||||
prevNodesById[node.attrs.id] = node;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const nodesById: Record<string, Node> = {};
|
|
||||||
editor.state?.doc.forEach((node) => {
|
|
||||||
if (node.attrs.id) {
|
|
||||||
nodesById[node.attrs.id] = node;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
previousState.current = editor.state;
|
|
||||||
|
|
||||||
for (const [id, node] of Object.entries(prevNodesById)) {
|
|
||||||
if (nodesById[id] === undefined) {
|
|
||||||
onNodeDeleted(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onNodeDeleted]
|
|
||||||
);
|
|
||||||
|
|
||||||
const debouncedUpdates = useDebouncedCallback(async ({ onChange, editor }) => {
|
const debouncedUpdates = useDebouncedCallback(async ({ onChange, editor }) => {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
@ -112,9 +73,8 @@ const Tiptap = (props: ITiptapRichTextEditor) => {
|
|||||||
}, 500);
|
}, 500);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
const editorClassNames = `relative w-full max-w-screen-lg mt-2 p-3 relative focus:outline-none rounded-lg
|
const editorClassNames = `relative w-full max-w-screen-lg sm:rounded-lg mt-2 p-3 relative focus:outline-none rounded-md
|
||||||
${noBorder ? "" : "border border-custom-border-200"} ${
|
${noBorder ? "" : "border border-custom-border-200"} ${borderOnFocus ? "focus:border border-custom-border-300" : "focus:border-0"
|
||||||
borderOnFocus ? "focus:border border-custom-border-300" : "focus:border-0"
|
|
||||||
} ${customClassName}`;
|
} ${customClassName}`;
|
||||||
|
|
||||||
if (!editor) return null;
|
if (!editor) return null;
|
||||||
@ -131,6 +91,7 @@ const Tiptap = (props: ITiptapRichTextEditor) => {
|
|||||||
{editor && <EditorBubbleMenu editor={editor} />}
|
{editor && <EditorBubbleMenu editor={editor} />}
|
||||||
<div className={`${editorContentCustomClassNames}`}>
|
<div className={`${editorContentCustomClassNames}`}>
|
||||||
<EditorContent editor={editor} />
|
<EditorContent editor={editor} />
|
||||||
|
{editor?.isActive("image") && <ImageResizer editor={editor} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
56
apps/app/components/tiptap/plugins/delete-image.tsx
Normal file
56
apps/app/components/tiptap/plugins/delete-image.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
||||||
|
import { Node as ProseMirrorNode } from '@tiptap/pm/model';
|
||||||
|
import fileService from "services/file.service";
|
||||||
|
|
||||||
|
const deleteKey = new PluginKey("delete-image");
|
||||||
|
|
||||||
|
const TrackImageDeletionPlugin = () =>
|
||||||
|
new Plugin({
|
||||||
|
key: deleteKey,
|
||||||
|
appendTransaction: (transactions, oldState, newState) => {
|
||||||
|
transactions.forEach((transaction) => {
|
||||||
|
if (!transaction.docChanged) return;
|
||||||
|
|
||||||
|
const removedImages: ProseMirrorNode[] = [];
|
||||||
|
|
||||||
|
oldState.doc.descendants((oldNode, oldPos) => {
|
||||||
|
if (oldNode.type.name !== 'image') return;
|
||||||
|
|
||||||
|
if (!newState.doc.resolve(oldPos).parent) return;
|
||||||
|
const newNode = newState.doc.nodeAt(oldPos);
|
||||||
|
|
||||||
|
// Check if the node has been deleted or replaced
|
||||||
|
if (!newNode || newNode.type.name !== 'image') {
|
||||||
|
// Check if the node still exists elsewhere in the document
|
||||||
|
let nodeExists = false;
|
||||||
|
newState.doc.descendants((node) => {
|
||||||
|
if (node.attrs.id === oldNode.attrs.id) {
|
||||||
|
nodeExists = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!nodeExists) {
|
||||||
|
removedImages.push(oldNode as ProseMirrorNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
removedImages.forEach((node) => {
|
||||||
|
const src = node.attrs.src;
|
||||||
|
onNodeDeleted(src);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TrackImageDeletionPlugin;
|
||||||
|
|
||||||
|
async function onNodeDeleted(src: string) {
|
||||||
|
const assetUrlWithWorkspaceId = new URL(src).pathname.substring(1);
|
||||||
|
const resStatus = await fileService.deleteImage(assetUrlWithWorkspaceId);
|
||||||
|
if (resStatus === 204) {
|
||||||
|
console.log("Image deleted successfully");
|
||||||
|
}
|
||||||
|
}
|
@ -57,7 +57,7 @@ function findPlaceholder(state: EditorState, id: {}) {
|
|||||||
return found.length ? found[0].from : null;
|
return found.length ? found[0].from : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startImageUpload(file: File, view: EditorView, pos: number) {
|
export async function startImageUpload(file: File, view: EditorView, pos: number, workspaceSlug: string, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void) {
|
||||||
if (!file.type.includes("image/")) {
|
if (!file.type.includes("image/")) {
|
||||||
return;
|
return;
|
||||||
} else if (file.size / 1024 / 1024 > 20) {
|
} else if (file.size / 1024 / 1024 > 20) {
|
||||||
@ -82,7 +82,11 @@ export async function startImageUpload(file: File, view: EditorView, pos: number
|
|||||||
view.dispatch(tr);
|
view.dispatch(tr);
|
||||||
};
|
};
|
||||||
|
|
||||||
const src = await UploadImageHandler(file);
|
if (!workspaceSlug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsSubmitting?.("submitting")
|
||||||
|
const src = await UploadImageHandler(file, workspaceSlug);
|
||||||
const { schema } = view.state;
|
const { schema } = view.state;
|
||||||
pos = findPlaceholder(view.state, id);
|
pos = findPlaceholder(view.state, id);
|
||||||
|
|
||||||
@ -96,7 +100,10 @@ export async function startImageUpload(file: File, view: EditorView, pos: number
|
|||||||
view.dispatch(transaction);
|
view.dispatch(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
const UploadImageHandler = (file: File): Promise<string> => {
|
const UploadImageHandler = (file: File, workspaceSlug: string): Promise<string> => {
|
||||||
|
if (!workspaceSlug) {
|
||||||
|
return Promise.reject("Workspace slug is missing");
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("asset", file);
|
formData.append("asset", file);
|
||||||
@ -104,7 +111,7 @@ const UploadImageHandler = (file: File): Promise<string> => {
|
|||||||
|
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const imageUrl = await fileService
|
const imageUrl = await fileService
|
||||||
.uploadFile("plane", formData)
|
.uploadFile(workspaceSlug, formData)
|
||||||
.then((response) => response.asset);
|
.then((response) => response.asset);
|
||||||
|
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { EditorProps } from "@tiptap/pm/view";
|
import { EditorProps } from "@tiptap/pm/view";
|
||||||
import { startImageUpload } from "./plugins/upload-image";
|
import { startImageUpload } from "./plugins/upload-image";
|
||||||
|
|
||||||
export const TiptapEditorProps: EditorProps = {
|
export function TiptapEditorProps(workspaceSlug: string, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void): EditorProps {
|
||||||
|
return {
|
||||||
attributes: {
|
attributes: {
|
||||||
class: `prose prose-brand max-w-full prose-headings:font-display font-default focus:outline-none`,
|
class: `prose prose-brand max-w-full prose-headings:font-display font-default focus:outline-none`,
|
||||||
},
|
},
|
||||||
@ -25,8 +26,7 @@ export const TiptapEditorProps: EditorProps = {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const file = event.clipboardData.files[0];
|
const file = event.clipboardData.files[0];
|
||||||
const pos = view.state.selection.from;
|
const pos = view.state.selection.from;
|
||||||
|
startImageUpload(file, view, pos, workspaceSlug, setIsSubmitting);
|
||||||
startImageUpload(file, view, pos);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -46,11 +46,11 @@ export const TiptapEditorProps: EditorProps = {
|
|||||||
});
|
});
|
||||||
// here we deduct 1 from the pos or else the image will create an extra node
|
// here we deduct 1 from the pos or else the image will create an extra node
|
||||||
if (coordinates) {
|
if (coordinates) {
|
||||||
startImageUpload(file, view, coordinates.pos - 1);
|
startImageUpload(file, view, coordinates.pos - 1, workspaceSlug, setIsSubmitting);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
@ -52,7 +52,7 @@ const Command = Extension.create({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSuggestionItems = ({ query }: { query: string }) =>
|
const getSuggestionItems = (workspaceSlug: string, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void) => ({ query }: { query: string }) =>
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
title: "Text",
|
title: "Text",
|
||||||
@ -163,7 +163,7 @@ const getSuggestionItems = ({ query }: { query: string }) =>
|
|||||||
if (input.files?.length) {
|
if (input.files?.length) {
|
||||||
const file = input.files[0];
|
const file = input.files[0];
|
||||||
const pos = editor.view.state.selection.from;
|
const pos = editor.view.state.selection.from;
|
||||||
startImageUpload(file, editor.view, pos);
|
startImageUpload(file, editor.view, pos, workspaceSlug, setIsSubmitting);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
input.click();
|
input.click();
|
||||||
@ -328,11 +328,12 @@ const renderItems = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const SlashCommand = Command.configure({
|
export const SlashCommand = (workspaceSlug: string, setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void) =>
|
||||||
|
Command.configure({
|
||||||
suggestion: {
|
suggestion: {
|
||||||
items: getSuggestionItems,
|
items: getSuggestionItems(workspaceSlug, setIsSubmitting),
|
||||||
render: renderItems,
|
render: renderItems,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default SlashCommand;
|
export default SlashCommand;
|
||||||
|
@ -52,10 +52,10 @@
|
|||||||
"highlight.js": "^11.8.0",
|
"highlight.js": "^11.8.0",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"mobx": "^6.10.0",
|
|
||||||
"mobx-react-lite": "^4.0.3",
|
|
||||||
"lowlight": "^2.9.0",
|
"lowlight": "^2.9.0",
|
||||||
"lucide-react": "^0.263.1",
|
"lucide-react": "^0.263.1",
|
||||||
|
"mobx": "^6.10.0",
|
||||||
|
"mobx-react-lite": "^4.0.3",
|
||||||
"next": "12.3.2",
|
"next": "12.3.2",
|
||||||
"next-pwa": "^5.6.0",
|
"next-pwa": "^5.6.0",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
@ -68,6 +68,7 @@
|
|||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-hook-form": "^7.38.0",
|
"react-hook-form": "^7.38.0",
|
||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
|
"react-moveable": "^0.54.1",
|
||||||
"sharp": "^0.32.1",
|
"sharp": "^0.32.1",
|
||||||
"sonner": "^0.6.2",
|
"sonner": "^0.6.2",
|
||||||
"swr": "^2.1.3",
|
"swr": "^2.1.3",
|
||||||
|
@ -106,6 +106,7 @@ const ProfileActivity = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="issue-comments-section p-0">
|
<div className="issue-comments-section p-0">
|
||||||
<Tiptap
|
<Tiptap
|
||||||
|
workspaceSlug={workspaceSlug as string}
|
||||||
value={
|
value={
|
||||||
activityItem?.new_value !== ""
|
activityItem?.new_value !== ""
|
||||||
? activityItem.new_value
|
? activityItem.new_value
|
||||||
|
@ -126,3 +126,27 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
|||||||
transition: opacity 0.2s ease-out;
|
transition: opacity 0.2s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.img-placeholder {
|
||||||
|
position: relative;
|
||||||
|
width: 35%;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 45%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3px solid rgba(var(--color-text-200));
|
||||||
|
border-top-color: rgba(var(--color-text-800));
|
||||||
|
animation: spinning 0.6s linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinning {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
165
yarn.lock
165
yarn.lock
@ -1006,6 +1006,40 @@
|
|||||||
react-popper "^2.3.0"
|
react-popper "^2.3.0"
|
||||||
tslib "~2.5.0"
|
tslib "~2.5.0"
|
||||||
|
|
||||||
|
"@cfcs/core@^0.0.6":
|
||||||
|
version "0.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cfcs/core/-/core-0.0.6.tgz#9f8499dcd2ad29fd96d8fa72055411cd4a249121"
|
||||||
|
integrity sha512-FxfJMwoLB8MEMConeXUCqtMGqxdtePQxRBOiGip9ULcYYam3WfCgoY6xdnMaSkYvRvmosp5iuG+TiPofm65+Pw==
|
||||||
|
dependencies:
|
||||||
|
"@egjs/component" "^3.0.2"
|
||||||
|
|
||||||
|
"@daybrush/utils@^1.1.1", "@daybrush/utils@^1.13.0", "@daybrush/utils@^1.4.0", "@daybrush/utils@^1.6.0", "@daybrush/utils@^1.7.1":
|
||||||
|
version "1.13.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@daybrush/utils/-/utils-1.13.0.tgz#ea70a60864130da476406fdd1d465e3068aea0ff"
|
||||||
|
integrity sha512-ALK12C6SQNNHw1enXK+UO8bdyQ+jaWNQ1Af7Z3FNxeAwjYhQT7do+TRE4RASAJ3ObaS2+TJ7TXR3oz2Gzbw0PQ==
|
||||||
|
|
||||||
|
"@egjs/agent@^2.2.1":
|
||||||
|
version "2.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@egjs/agent/-/agent-2.4.3.tgz#6d44e2fb1ff7bab242c07f82732fe60305ac6f06"
|
||||||
|
integrity sha512-XvksSENe8wPeFlEVouvrOhKdx8HMniJ3by7sro2uPF3M6QqWwjzVcmvwoPtdjiX8O1lfRoLhQMp1a7NGlVTdIA==
|
||||||
|
|
||||||
|
"@egjs/children-differ@^1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@egjs/children-differ/-/children-differ-1.0.1.tgz#5465fa80671d5ca3564ebe912f48b05b3e8a14fd"
|
||||||
|
integrity sha512-DRvyqMf+CPCOzAopQKHtW+X8iN6Hy6SFol+/7zCUiE5y4P/OB8JP8FtU4NxtZwtafvSL4faD5KoQYPj3JHzPFQ==
|
||||||
|
dependencies:
|
||||||
|
"@egjs/list-differ" "^1.0.0"
|
||||||
|
|
||||||
|
"@egjs/component@^3.0.2":
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@egjs/component/-/component-3.0.4.tgz#ad7b53794b2a612806179a188ad828acb9525f61"
|
||||||
|
integrity sha512-sXA7bGbIeLF2OAw/vpka66c6QBBUPcA4UUhR4WGJfnp2XWdiI8QrnJGJMr/UxpE/xnevX9tN3jvNPlW8WkHl3g==
|
||||||
|
|
||||||
|
"@egjs/list-differ@^1.0.0":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@egjs/list-differ/-/list-differ-1.0.1.tgz#5772b0f8b87973bb67827f6c7d7df8d7f64a22eb"
|
||||||
|
integrity sha512-OTFTDQcWS+1ZREOdCWuk5hCBgYO4OsD30lXcOCyVOAjXMhgL5rBRDnt/otb6Nz8CzU0L/igdcaQBDLWc4t9gvg==
|
||||||
|
|
||||||
"@emotion/babel-plugin@^11.11.0":
|
"@emotion/babel-plugin@^11.11.0":
|
||||||
version "11.11.0"
|
version "11.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c"
|
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c"
|
||||||
@ -1990,6 +2024,28 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz#16ab6c727d8c2020a5b6e4a176a243ecd88d8d69"
|
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz#16ab6c727d8c2020a5b6e4a176a243ecd88d8d69"
|
||||||
integrity sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==
|
integrity sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==
|
||||||
|
|
||||||
|
"@scena/dragscroll@^1.4.0":
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@scena/dragscroll/-/dragscroll-1.4.0.tgz#220b2430c16119cd3e70044ee533a5b9a43cffd7"
|
||||||
|
integrity sha512-3O8daaZD9VXA9CP3dra6xcgt/qrm0mg0xJCwiX6druCteQ9FFsXffkF8PrqxY4Z4VJ58fFKEa0RlKqbsi/XnRA==
|
||||||
|
dependencies:
|
||||||
|
"@daybrush/utils" "^1.6.0"
|
||||||
|
"@scena/event-emitter" "^1.0.2"
|
||||||
|
|
||||||
|
"@scena/event-emitter@^1.0.2", "@scena/event-emitter@^1.0.5":
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@scena/event-emitter/-/event-emitter-1.0.5.tgz#047e3acef93cf238d7ce3a8cc5a12ec6bd9c3bb1"
|
||||||
|
integrity sha512-AzY4OTb0+7ynefmWFQ6hxDdk0CySAq/D4efljfhtRHCOP7MBF9zUfhKG3TJiroVjASqVgkRJFdenS8ArZo6Olg==
|
||||||
|
dependencies:
|
||||||
|
"@daybrush/utils" "^1.1.1"
|
||||||
|
|
||||||
|
"@scena/matrix@^1.0.0", "@scena/matrix@^1.1.1":
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@scena/matrix/-/matrix-1.1.1.tgz#5297f71825c72e2c2c8f802f924f482ed200c43c"
|
||||||
|
integrity sha512-JVKBhN0tm2Srl+Yt+Ywqu0oLgLcdemDQlD1OxmN9jaCTwaFPZ7tY8n6dhVgMEaR9qcR7r+kAlMXnSfNyYdE+Vg==
|
||||||
|
dependencies:
|
||||||
|
"@daybrush/utils" "^1.4.0"
|
||||||
|
|
||||||
"@sentry-internal/tracing@7.63.0":
|
"@sentry-internal/tracing@7.63.0":
|
||||||
version "7.63.0"
|
version "7.63.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.63.0.tgz#58903b2205456034611cc5bc1b5b2479275f89c7"
|
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.63.0.tgz#58903b2205456034611cc5bc1b5b2479275f89c7"
|
||||||
@ -3462,6 +3518,21 @@ css-box-model@^1.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tiny-invariant "^1.0.6"
|
tiny-invariant "^1.0.6"
|
||||||
|
|
||||||
|
css-styled@^1.0.8, css-styled@~1.0.8:
|
||||||
|
version "1.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-styled/-/css-styled-1.0.8.tgz#c9c05dc4abdef5571033090bfb8cfc5e19429974"
|
||||||
|
integrity sha512-tCpP7kLRI8dI95rCh3Syl7I+v7PP+2JYOzWkl0bUEoSbJM+u8ITbutjlQVf0NC2/g4ULROJPi16sfwDIO8/84g==
|
||||||
|
dependencies:
|
||||||
|
"@daybrush/utils" "^1.13.0"
|
||||||
|
|
||||||
|
css-to-mat@^1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-to-mat/-/css-to-mat-1.1.1.tgz#0dd10dcf9ec17df15708c8ff07a74fbd0b9a3fe5"
|
||||||
|
integrity sha512-kvpxFYZb27jRd2vium35G7q5XZ2WJ9rWjDUMNT36M3Hc41qCrLXFM5iEKMGXcrPsKfXEN+8l/riB4QzwwwiEyQ==
|
||||||
|
dependencies:
|
||||||
|
"@daybrush/utils" "^1.13.0"
|
||||||
|
"@scena/matrix" "^1.0.0"
|
||||||
|
|
||||||
cssesc@^3.0.0:
|
cssesc@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||||
@ -4484,6 +4555,11 @@ fraction.js@^4.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
|
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
|
||||||
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
|
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
|
||||||
|
|
||||||
|
framework-utils@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/framework-utils/-/framework-utils-1.1.0.tgz#a3b528bce838dfd623148847dc92371b09d0da2d"
|
||||||
|
integrity sha512-KAfqli5PwpFJ8o3psRNs8svpMGyCSAe8nmGcjQ0zZBWN2H6dZDnq+ABp3N3hdUmFeMrLtjOCTXD4yplUJIWceg==
|
||||||
|
|
||||||
fs-constants@^1.0.0:
|
fs-constants@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||||
@ -4539,6 +4615,14 @@ gensync@^1.0.0-beta.2:
|
|||||||
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
||||||
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
|
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
|
||||||
|
|
||||||
|
gesto@^1.19.0, gesto@^1.19.1:
|
||||||
|
version "1.19.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/gesto/-/gesto-1.19.1.tgz#b2a29730663eecf77b248982bbff929e79d4a461"
|
||||||
|
integrity sha512-ofWVEdqmnpFm3AFf7aoclhoayseb3OkwSiXbXusKYu/99iN5HgeWP+SWqdghQ5TFlOgP5Zlz+6SY8mP2V0kFaQ==
|
||||||
|
dependencies:
|
||||||
|
"@daybrush/utils" "^1.13.0"
|
||||||
|
"@scena/event-emitter" "^1.0.2"
|
||||||
|
|
||||||
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1:
|
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82"
|
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82"
|
||||||
@ -5244,6 +5328,21 @@ jsonpointer@^5.0.0:
|
|||||||
object.assign "^4.1.4"
|
object.assign "^4.1.4"
|
||||||
object.values "^1.1.6"
|
object.values "^1.1.6"
|
||||||
|
|
||||||
|
keycode@^2.2.0:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.1.tgz#09c23b2be0611d26117ea2501c2c391a01f39eff"
|
||||||
|
integrity sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==
|
||||||
|
|
||||||
|
keycon@^1.2.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/keycon/-/keycon-1.4.0.tgz#bf2a633f3c3b659ea564045938cff33e584cebd5"
|
||||||
|
integrity sha512-p1NAIxiRMH3jYfTeXRs2uWbVJ1WpEjpi8ktzUyBJsX7/wn2qu2VRXktneBLNtKNxJmlUYxRi9gOJt1DuthXR7A==
|
||||||
|
dependencies:
|
||||||
|
"@cfcs/core" "^0.0.6"
|
||||||
|
"@daybrush/utils" "^1.7.1"
|
||||||
|
"@scena/event-emitter" "^1.0.2"
|
||||||
|
keycode "^2.2.0"
|
||||||
|
|
||||||
kleur@^4.0.3:
|
kleur@^4.0.3:
|
||||||
version "4.1.5"
|
version "4.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
|
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
|
||||||
@ -6081,6 +6180,13 @@ orderedmap@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-2.1.1.tgz#61481269c44031c449915497bf5a4ad273c512d2"
|
resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-2.1.1.tgz#61481269c44031c449915497bf5a4ad273c512d2"
|
||||||
integrity sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==
|
integrity sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==
|
||||||
|
|
||||||
|
overlap-area@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/overlap-area/-/overlap-area-1.1.0.tgz#1fcaa21bdb9cb1ace973d9aa299ae6b56557a4c2"
|
||||||
|
integrity sha512-3dlJgJCaVeXH0/eZjYVJvQiLVVrPO4U1ZGqlATtx6QGO3b5eNM6+JgUKa7oStBTdYuGTk7gVoABCW6Tp+dhRdw==
|
||||||
|
dependencies:
|
||||||
|
"@daybrush/utils" "^1.7.1"
|
||||||
|
|
||||||
p-limit@^2.2.0:
|
p-limit@^2.2.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
|
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
|
||||||
@ -6452,20 +6558,13 @@ prosemirror-menu@^1.2.1:
|
|||||||
prosemirror-history "^1.0.0"
|
prosemirror-history "^1.0.0"
|
||||||
prosemirror-state "^1.0.0"
|
prosemirror-state "^1.0.0"
|
||||||
|
|
||||||
prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.18.1, prosemirror-model@^1.8.1:
|
prosemirror-model@1.18.1, prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.18.1, prosemirror-model@^1.19.0, prosemirror-model@^1.8.1:
|
||||||
version "1.18.1"
|
version "1.18.1"
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.18.1.tgz#1d5d6b6de7b983ee67a479dc607165fdef3935bd"
|
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.18.1.tgz#1d5d6b6de7b983ee67a479dc607165fdef3935bd"
|
||||||
integrity sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw==
|
integrity sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw==
|
||||||
dependencies:
|
dependencies:
|
||||||
orderedmap "^2.0.0"
|
orderedmap "^2.0.0"
|
||||||
|
|
||||||
prosemirror-model@^1.19.0:
|
|
||||||
version "1.19.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.19.3.tgz#f0d55285487fefd962d0ac695f716f4ec6705006"
|
|
||||||
integrity sha512-tgSnwN7BS7/UM0sSARcW+IQryx2vODKX4MI7xpqY2X+iaepJdKBPc7I4aACIsDV/LTaTjt12Z56MhDr9LsyuZQ==
|
|
||||||
dependencies:
|
|
||||||
orderedmap "^2.0.0"
|
|
||||||
|
|
||||||
prosemirror-schema-basic@^1.2.0:
|
prosemirror-schema-basic@^1.2.0:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.2.tgz#6695f5175e4628aab179bf62e5568628b9cfe6c7"
|
resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.2.tgz#6695f5175e4628aab179bf62e5568628b9cfe6c7"
|
||||||
@ -6603,6 +6702,14 @@ react-color@^2.19.3:
|
|||||||
reactcss "^1.2.0"
|
reactcss "^1.2.0"
|
||||||
tinycolor2 "^1.4.1"
|
tinycolor2 "^1.4.1"
|
||||||
|
|
||||||
|
react-css-styled@^1.1.9:
|
||||||
|
version "1.1.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-css-styled/-/react-css-styled-1.1.9.tgz#a7cc948e49f72b2f7fb1393bd85416a8293afab3"
|
||||||
|
integrity sha512-M7fJZ3IWFaIHcZEkoFOnkjdiUFmwd8d+gTh2bpqMOcnxy/0Gsykw4dsL4QBiKsxcGow6tETUa4NAUcmJF+/nfw==
|
||||||
|
dependencies:
|
||||||
|
css-styled "~1.0.8"
|
||||||
|
framework-utils "^1.1.0"
|
||||||
|
|
||||||
react-datepicker@^4.8.0:
|
react-datepicker@^4.8.0:
|
||||||
version "4.16.0"
|
version "4.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.16.0.tgz#b9dd389bb5611a1acc514bba1dd944be21dd877f"
|
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.16.0.tgz#b9dd389bb5611a1acc514bba1dd944be21dd877f"
|
||||||
@ -6683,6 +6790,25 @@ react-markdown@^8.0.7:
|
|||||||
unist-util-visit "^4.0.0"
|
unist-util-visit "^4.0.0"
|
||||||
vfile "^5.0.0"
|
vfile "^5.0.0"
|
||||||
|
|
||||||
|
react-moveable@^0.54.1:
|
||||||
|
version "0.54.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-moveable/-/react-moveable-0.54.1.tgz#3c69748c444184700e6999501b0da953c934205e"
|
||||||
|
integrity sha512-Kj2ifw9nk3LZvu7ezhst8Z5WBPRr+yVv9oROwrBirFlHmwGHHZXUGk5Gaezu+JGqqNRsQJncVMW5Uf68KSSOvg==
|
||||||
|
dependencies:
|
||||||
|
"@daybrush/utils" "^1.13.0"
|
||||||
|
"@egjs/agent" "^2.2.1"
|
||||||
|
"@egjs/children-differ" "^1.0.1"
|
||||||
|
"@egjs/list-differ" "^1.0.0"
|
||||||
|
"@scena/dragscroll" "^1.4.0"
|
||||||
|
"@scena/event-emitter" "^1.0.5"
|
||||||
|
"@scena/matrix" "^1.1.1"
|
||||||
|
css-to-mat "^1.1.1"
|
||||||
|
framework-utils "^1.1.0"
|
||||||
|
gesto "^1.19.0"
|
||||||
|
overlap-area "^1.1.0"
|
||||||
|
react-css-styled "^1.1.9"
|
||||||
|
react-selecto "^1.25.0"
|
||||||
|
|
||||||
react-onclickoutside@^6.12.2:
|
react-onclickoutside@^6.12.2:
|
||||||
version "6.13.0"
|
version "6.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz#e165ea4e5157f3da94f4376a3ab3e22a565f4ffc"
|
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz#e165ea4e5157f3da94f4376a3ab3e22a565f4ffc"
|
||||||
@ -6740,6 +6866,13 @@ react-remove-scroll@2.5.4:
|
|||||||
use-callback-ref "^1.3.0"
|
use-callback-ref "^1.3.0"
|
||||||
use-sidecar "^1.1.2"
|
use-sidecar "^1.1.2"
|
||||||
|
|
||||||
|
react-selecto@^1.25.0:
|
||||||
|
version "1.26.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-selecto/-/react-selecto-1.26.0.tgz#9157ff0a732fc426602b30c08ec21b6ca0a9c472"
|
||||||
|
integrity sha512-aBTZEYA68uE+o8TytNjTb2GpIn4oKEv0U4LIow3cspJQlF/PdAnBwkq9UuiKVuFluu5kfLQ7Keu3S2Tihlmw0g==
|
||||||
|
dependencies:
|
||||||
|
selecto "~1.26.0"
|
||||||
|
|
||||||
react-style-singleton@^2.2.1:
|
react-style-singleton@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4"
|
resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4"
|
||||||
@ -7023,6 +7156,22 @@ schema-utils@^3.1.1:
|
|||||||
ajv "^6.12.5"
|
ajv "^6.12.5"
|
||||||
ajv-keywords "^3.5.2"
|
ajv-keywords "^3.5.2"
|
||||||
|
|
||||||
|
selecto@~1.26.0:
|
||||||
|
version "1.26.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/selecto/-/selecto-1.26.0.tgz#f3f04fb6409112b198243458f6c9963946d5ba2f"
|
||||||
|
integrity sha512-cEFKdv5rmkF6pf2OScQJllaNp4UJy/FvviB40ZaMSHrQCxC72X/Q6uhzW1tlb2RE+0danvUNJTs64cI9VXtUyg==
|
||||||
|
dependencies:
|
||||||
|
"@daybrush/utils" "^1.13.0"
|
||||||
|
"@egjs/children-differ" "^1.0.1"
|
||||||
|
"@scena/dragscroll" "^1.4.0"
|
||||||
|
"@scena/event-emitter" "^1.0.5"
|
||||||
|
css-styled "^1.0.8"
|
||||||
|
css-to-mat "^1.1.1"
|
||||||
|
framework-utils "^1.1.0"
|
||||||
|
gesto "^1.19.1"
|
||||||
|
keycon "^1.2.0"
|
||||||
|
overlap-area "^1.1.0"
|
||||||
|
|
||||||
semver@^6.0.0, semver@^6.3.0, semver@^6.3.1:
|
semver@^6.0.0, semver@^6.3.0, semver@^6.3.1:
|
||||||
version "6.3.1"
|
version "6.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||||
|
Loading…
Reference in New Issue
Block a user