diff --git a/apps/web/src/components/Editor.tsx b/apps/web/src/components/Editor.tsx index 514f5d96..7d82e92a 100644 --- a/apps/web/src/components/Editor.tsx +++ b/apps/web/src/components/Editor.tsx @@ -387,46 +387,54 @@ export interface SlashNodeAttrs { label?: string | null; } -const CommandItems: SlashCommandItem[] = [ - { - title: "Heading 1", - icon: , - command: ({ editor }) => - editor.chain().focus().setHeading({ level: 1 }).run(), - }, - { - title: "Heading 2", - icon: , - command: ({ editor }) => - editor.chain().focus().setHeading({ level: 2 }).run(), - }, - { - title: "Heading 3", - icon: , - command: ({ editor }) => - editor.chain().focus().setHeading({ level: 3 }).run(), - }, - { - title: "Bullet List", - icon: , - command: ({ editor }) => editor.chain().focus().toggleBulletList().run(), - }, - { - title: "Ordered List", - icon: , - command: ({ editor }) => editor.chain().focus().toggleOrderedList().run(), - }, - { - title: "Blockquote", - icon: , - command: ({ editor }) => editor.chain().focus().toggleBlockquote().run(), - }, - { - title: "Code Block", - icon: , - command: ({ editor }) => editor.chain().focus().toggleCodeBlock().run(), - }, -]; +const getCommandItems = (disableHeadings: boolean): SlashCommandItem[] => { + const headingCommands: SlashCommandItem[] = disableHeadings + ? [] + : [ + { + title: "Heading 1", + icon: , + command: ({ editor }) => + editor.chain().focus().setHeading({ level: 1 }).run(), + }, + { + title: "Heading 2", + icon: , + command: ({ editor }) => + editor.chain().focus().setHeading({ level: 2 }).run(), + }, + { + title: "Heading 3", + icon: , + command: ({ editor }) => + editor.chain().focus().setHeading({ level: 3 }).run(), + }, + ]; + + return [ + ...headingCommands, + { + title: "Bullet List", + icon: , + command: ({ editor }) => editor.chain().focus().toggleBulletList().run(), + }, + { + title: "Ordered List", + icon: , + command: ({ editor }) => editor.chain().focus().toggleOrderedList().run(), + }, + { + title: "Blockquote", + icon: , + command: ({ editor }) => editor.chain().focus().toggleBlockquote().run(), + }, + { + title: "Code Block", + icon: , + command: ({ editor }) => editor.chain().focus().toggleCodeBlock().run(), + }, + ]; +}; export default function Editor({ content, @@ -435,6 +443,8 @@ export default function Editor({ readOnly = false, workspaceMembers, enableYouTubeEmbed = true, + placeholder, + disableHeadings = false, }: { content: string | null; onChange?: (value: string) => void; @@ -442,18 +452,23 @@ export default function Editor({ readOnly?: boolean; workspaceMembers: WorkspaceMember[]; enableYouTubeEmbed?: boolean; + placeholder?: string; + disableHeadings?: boolean; }) { const containerRef = useRef(null); const editor = useEditor( { extensions: [ - StarterKit, + StarterKit.configure({ + heading: disableHeadings ? false : undefined, + }), Markdown, Placeholder.configure({ placeholder: readOnly ? "" - : t`Add description... (type '/' to open commands or '@' to mention)`, + : placeholder ?? + t`Add description... (type '/' to open commands or '@' to mention)`, }), Link.configure({ openOnClick: true, @@ -466,10 +481,10 @@ export default function Editor({ autolink: true, }), SlashCommands.configure({ - commandItems: CommandItems, + commandItems: getCommandItems(disableHeadings), suggestion: { items: ({ query }: { query: string }) => - filterSlashCommandItems(CommandItems, query), + filterSlashCommandItems(getCommandItems(disableHeadings), query), startOfLine: true, char: "/", }, diff --git a/apps/web/src/views/card/components/Comment.tsx b/apps/web/src/views/card/components/Comment.tsx index e9587c36..6653c563 100644 --- a/apps/web/src/views/card/components/Comment.tsx +++ b/apps/web/src/views/card/components/Comment.tsx @@ -1,12 +1,13 @@ import { t } from "@lingui/core/macro"; import { formatDistanceToNow } from "date-fns"; import { useState } from "react"; -import ContentEditable from "react-contenteditable"; import { useForm } from "react-hook-form"; import { HiEllipsisHorizontal, HiPencil, HiTrash } from "react-icons/hi2"; import Avatar from "~/components/Avatar"; import Button from "~/components/Button"; +import Editor from "~/components/Editor"; +import type { WorkspaceMember } from "~/components/Editor"; import Dropdown from "~/components/Dropdown"; import { usePermissions } from "~/hooks/usePermissions"; import { useModal } from "~/providers/modal"; @@ -29,7 +30,6 @@ const Comment = ({ createdAt, comment, isAuthor, - isAdmin, isEdited = false, isViewOnly = false, }: { @@ -42,7 +42,6 @@ const Comment = ({ createdAt: string; comment: string | undefined; isAuthor: boolean; - isAdmin: boolean; isEdited: boolean; isViewOnly: boolean; }) => { @@ -57,6 +56,30 @@ const Comment = ({ }, }); + const { data: cardData } = api.card.byId.useQuery( + { + cardPublicId, + }, + { + enabled: !!cardPublicId && cardPublicId.length >= 12, + }, + ); + + const workspaceMembers: WorkspaceMember[] = + cardData?.list.board.workspace.members + .filter((member) => member.email) + .map((member) => ({ + publicId: member.publicId, + email: member.email, + user: member.user + ? { + id: member.user.id, + name: member.user.name ?? null, + image: member.user.image ?? null, + } + : null, + })) ?? []; + if (!publicId) return null; const updateCommentMutation = api.card.updateComment.useMutation({ @@ -142,21 +165,28 @@ const Comment = ({ )} {!isEditing ? ( - + + + ) : ( - setValue("comment", e.target.value)} - className="block w-full max-w-[800px] border-0 bg-transparent py-1.5 text-sm text-light-900 focus-visible:outline-none dark:text-dark-1000 sm:text-sm sm:leading-6" - /> - + + setValue("comment", value)} + workspaceMembers={workspaceMembers} + enableYouTubeEmbed={false} + placeholder={t`Add comment... (type '/' to open commands or '@' to mention)`} + disableHeadings={true} + /> + + { +const NewCommentForm = ({ + cardPublicId, + workspaceMembers, +}: { + cardPublicId: string; + workspaceMembers: WorkspaceMember[]; +}) => { const utils = api.useUtils(); const { showPopup } = usePopup(); const { canCreateComment } = usePermissions(); @@ -23,10 +30,6 @@ const NewCommentForm = ({ cardPublicId }: { cardPublicId: string }) => { }, }); - const queryParams = { - cardPublicId, - }; - const addCommentMutation = api.card.addComment.useMutation({ onError: (_error, _newList) => { showPopup({ @@ -57,18 +60,13 @@ const NewCommentForm = ({ cardPublicId }: { cardPublicId: string }) => { onSubmit={handleSubmit(onSubmit)} className="flex w-full max-w-[800px] flex-col rounded-xl border border-light-600 bg-light-100 p-4 text-light-900 focus-visible:outline-none dark:border-dark-400 dark:bg-dark-100 dark:text-dark-1000 sm:text-sm sm:leading-6" > - setValue("comment", e.target.value)} - className="block w-full border-0 bg-transparent py-1.5 text-light-900 focus-visible:outline-none dark:text-dark-1000 sm:text-sm sm:leading-6" - onKeyDown={async (e) => { - if (e.key === "Enter" && e.shiftKey) { - e.preventDefault(); - await handleSubmit(onSubmit)(); - } - }} + setValue("comment", value)} + workspaceMembers={workspaceMembers} + enableYouTubeEmbed={false} + placeholder={t`Add comment... (type '/' to open commands or '@' to mention)`} + disableHeadings={true} /> member.email) + .map((member) => ({ + publicId: member.publicId, + email: member.email, + user: member.user + ? { + id: member.user.id, + name: member.user.name ?? null, + image: member.user.image ?? null, + } + : null, + })) ?? []; + const updateCard = api.card.update.useMutation({ onError: () => { showPopup({ @@ -432,7 +447,10 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) { {!isTemplate && ( - + )}