diff --git a/packages/editor/core/src/styles/editor.css b/packages/editor/core/src/styles/editor.css index d602a1ddf..ae0155eec 100644 --- a/packages/editor/core/src/styles/editor.css +++ b/packages/editor/core/src/styles/editor.css @@ -1,3 +1,14 @@ +.ProseMirror { + --font-size-h1: 1.5rem; + --font-size-h2: 1.3125rem; + --font-size-h3: 1.125rem; + --font-size-h4: 0.9375rem; + --font-size-h5: 0.8125rem; + --font-size-h6: 0.75rem; + --font-size-regular: 0.9375rem; + --font-size-list: var(--font-size-regular); +} + .ProseMirror p.is-editor-empty:first-child::before { content: attr(data-placeholder); float: left; @@ -56,7 +67,7 @@ /* to-do list */ ul[data-type="taskList"] li { - font-size: 1rem; + font-size: var(--font-size-list); line-height: 1.5; } @@ -162,7 +173,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p { cursor: text; line-height: 1.2; font-family: inherit; - font-size: 14px; + font-size: var(--font-size-regular); color: inherit; -moz-box-sizing: border-box; box-sizing: border-box; @@ -310,15 +321,15 @@ ul[data-type="taskList"] ul[data-type="taskList"] { .prose :where(h1):not(:where([class~="not-prose"], [class~="not-prose"] *)) { margin-top: 2rem; margin-bottom: 4px; - font-size: 1.875rem; - font-weight: 700; + font-size: var(--font-size-h1); + font-weight: 600; line-height: 1.3; } .prose :where(h2):not(:where([class~="not-prose"], [class~="not-prose"] *)) { margin-top: 1.4rem; margin-bottom: 1px; - font-size: 1.5rem; + font-size: var(--font-size-h2); font-weight: 600; line-height: 1.3; } @@ -326,21 +337,23 @@ ul[data-type="taskList"] ul[data-type="taskList"] { .prose :where(h3):not(:where([class~="not-prose"], [class~="not-prose"] *)) { margin-top: 1rem; margin-bottom: 1px; - font-size: 1.25rem; + font-size: var(--font-size-h3); + font-weight: 600; line-height: 1.3; } .prose :where(h4):not(:where([class~="not-prose"], [class~="not-prose"] *)) { margin-top: 1rem; margin-bottom: 1px; - font-size: 1rem; + font-size: var(--font-size-h4); + font-weight: 600; line-height: 1.5; } .prose :where(h5):not(:where([class~="not-prose"], [class~="not-prose"] *)) { margin-top: 1rem; margin-bottom: 1px; - font-size: 0.9rem; + font-size: var(--font-size-h5); font-weight: 600; line-height: 1.5; } @@ -348,7 +361,7 @@ ul[data-type="taskList"] ul[data-type="taskList"] { .prose :where(h6):not(:where([class~="not-prose"], [class~="not-prose"] *)) { margin-top: 1rem; margin-bottom: 1px; - font-size: 0.83rem; + font-size: var(--font-size-h6); font-weight: 600; line-height: 1.5; } @@ -356,14 +369,14 @@ ul[data-type="taskList"] ul[data-type="taskList"] { .prose :where(p):not(:where([class~="not-prose"], [class~="not-prose"] *)) { margin-top: 0.25rem; margin-bottom: 1px; - padding: 3px 2px; - font-size: 1rem; + padding: 3px 0; + font-size: var(--font-size-regular); line-height: 1.5; } .prose :where(ol):not(:where([class~="not-prose"], [class~="not-prose"] *)) li p, .prose :where(ul):not(:where([class~="not-prose"], [class~="not-prose"] *)) li p { - font-size: 1rem; + font-size: var(--font-size-list); line-height: 1.5; } diff --git a/space/components/editor/rich-text-read-only-editor.tsx b/space/components/editor/rich-text-read-only-editor.tsx index 562e63581..56694e91f 100644 --- a/space/components/editor/rich-text-read-only-editor.tsx +++ b/space/components/editor/rich-text-read-only-editor.tsx @@ -18,7 +18,7 @@ export const RichTextReadOnlyEditor = React.forwardRef ); } diff --git a/web/components/editor/rich-text-editor/rich-text-editor.tsx b/web/components/editor/rich-text-editor/rich-text-editor.tsx index e2173445d..5b9df4bec 100644 --- a/web/components/editor/rich-text-editor/rich-text-editor.tsx +++ b/web/components/editor/rich-text-editor/rich-text-editor.tsx @@ -51,7 +51,7 @@ export const RichTextEditor = forwardRef ); }); diff --git a/web/components/inbox/content/issue-properties.tsx b/web/components/inbox/content/issue-properties.tsx index 2adf4bdc7..89ddbdef7 100644 --- a/web/components/inbox/content/issue-properties.tsx +++ b/web/components/inbox/content/issue-properties.tsx @@ -33,7 +33,7 @@ export const InboxIssueContentProperties: React.FC = observer((props) => if (!issue || !issue?.id) return <>; return (
-
+
Properties
diff --git a/web/components/inbox/content/issue-root.tsx b/web/components/inbox/content/issue-root.tsx index d0f682447..72a5e833d 100644 --- a/web/components/inbox/content/issue-root.tsx +++ b/web/components/inbox/content/issue-root.tsx @@ -114,7 +114,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => { return ( <> -
+
= observer((props) => { issueOperations={issueOperations} disabled={!isEditable} value={issue.name} + containerClassName="-ml-3" /> {loader === "issue-loading" ? ( @@ -140,6 +141,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => { disabled={!isEditable} issueOperations={issueOperations} setIsSubmitting={(value) => setIsSubmitting(value)} + containerClassName="-ml-3 border-none" /> )} @@ -152,12 +154,15 @@ export const InboxIssueMainContent: React.FC = observer((props) => { /> )}
- + +
+ +
= observer((props) => { duplicateIssueDetails={inboxIssue?.duplicate_issue_detail} /> -
+
diff --git a/web/components/inbox/content/root.tsx b/web/components/inbox/content/root.tsx index 719b1c19f..7874b74a5 100644 --- a/web/components/inbox/content/root.tsx +++ b/web/components/inbox/content/root.tsx @@ -52,7 +52,7 @@ export const InboxContentRoot: FC = observer((props) => { isSubmitting={isSubmitting} />
-
+
= observer((props) data={formData} handleData={handleFormData} editorRef={descriptionEditorRef} + containerClassName="border-[0.5px] border-custom-border-200 py-3" />
diff --git a/web/components/inbox/modals/create-edit-modal/edit-root.tsx b/web/components/inbox/modals/create-edit-modal/edit-root.tsx index 6ffb6efee..fb5da771a 100644 --- a/web/components/inbox/modals/create-edit-modal/edit-root.tsx +++ b/web/components/inbox/modals/create-edit-modal/edit-root.tsx @@ -138,6 +138,7 @@ export const InboxIssueEditRoot: FC = observer((props) => { data={formData} handleData={handleFormData} editorRef={descriptionEditorRef} + containerClassName="border-[0.5px] border-custom-border-200 py-3" />
diff --git a/web/components/inbox/modals/create-edit-modal/issue-description.tsx b/web/components/inbox/modals/create-edit-modal/issue-description.tsx index 74c62f97c..98d2b8efa 100644 --- a/web/components/inbox/modals/create-edit-modal/issue-description.tsx +++ b/web/components/inbox/modals/create-edit-modal/issue-description.tsx @@ -11,6 +11,7 @@ import { getDescriptionPlaceholder } from "@/helpers/issue.helper"; import { useProjectInbox } from "@/hooks/store"; type TInboxIssueDescription = { + containerClassName?: string; workspaceSlug: string; projectId: string; workspaceId: string; @@ -21,7 +22,7 @@ type TInboxIssueDescription = { // TODO: have to implement GPT Assistance export const InboxIssueDescription: FC = observer((props) => { - const { workspaceSlug, projectId, workspaceId, data, handleData, editorRef } = props; + const {containerClassName, workspaceSlug, projectId, workspaceId, data, handleData, editorRef } = props; // hooks const { loader } = useProjectInbox(); @@ -42,6 +43,7 @@ export const InboxIssueDescription: FC = observer((props dragDropEnabled={false} onChange={(_description: object, description_html: string) => handleData("description_html", description_html)} placeholder={getDescriptionPlaceholder} + containerClassName={containerClassName} />
); diff --git a/web/components/inbox/modals/create-issue-modal.tsx b/web/components/inbox/modals/create-issue-modal.tsx deleted file mode 100644 index 3cc642be8..000000000 --- a/web/components/inbox/modals/create-issue-modal.tsx +++ /dev/null @@ -1,326 +0,0 @@ -import { Fragment, useRef, useState } from "react"; -import { observer } from "mobx-react"; -import { useRouter } from "next/router"; -import { Controller, useForm } from "react-hook-form"; -import { Sparkle } from "lucide-react"; -import { Transition, Dialog } from "@headlessui/react"; -import { EditorRefApi } from "@plane/rich-text-editor"; -// types -import { TIssue } from "@plane/types"; -// ui -import { Button, Input, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui"; -// components -import { GptAssistantPopover } from "@/components/core"; -import { PriorityDropdown } from "@/components/dropdowns"; -import { RichTextEditor } from "@/components/editor/rich-text-editor/rich-text-editor"; -import { ISSUE_CREATED } from "@/constants/event-tracker"; -import { useApplication, useEventTracker, useWorkspace, useProjectInbox } from "@/hooks/store"; -// services -import { AIService } from "@/services/ai.service"; -// components -// ui -// types -// constants - -type Props = { - isOpen: boolean; - onClose: () => void; -}; - -const defaultValues: Partial = { - name: "", - description_html: "

", - priority: "none", -}; - -// services -const aiService = new AIService(); - -export const CreateInboxIssueModal: React.FC = observer((props) => { - const { isOpen, onClose } = props; - // router - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; - if (!workspaceSlug || !projectId) return null; - // states - const [createMore, setCreateMore] = useState(false); - const [gptAssistantModal, setGptAssistantModal] = useState(false); - const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false); - // refs - const editorRef = useRef(null); - // hooks - const workspaceStore = useWorkspace(); - const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug.toString() as string)?.id.toString() as string; - - // store hooks - const { createInboxIssue } = useProjectInbox(); - const { - config: { envConfig }, - } = useApplication(); - const { captureIssueEvent } = useEventTracker(); - // form info - const { - control, - formState: { errors, isSubmitting }, - handleSubmit, - reset, - watch, - getValues, - } = useForm>({ defaultValues }); - const issueName = watch("name"); - - const handleClose = () => { - onClose(); - reset(defaultValues); - editorRef?.current?.clearEditor(); - }; - - const handleFormSubmit = async (formData: Partial) => { - if (!workspaceSlug || !projectId) return; - await createInboxIssue(workspaceSlug.toString(), projectId.toString(), formData) - .then((res) => { - if (!createMore) { - router.push(`/${workspaceSlug}/projects/${projectId}/inbox/?currentTab=open&inboxIssueId=${res?.issue?.id}`); - handleClose(); - } else { - reset(defaultValues); - editorRef?.current?.clearEditor(); - } - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { - ...formData, - state: "SUCCESS", - element: "Inbox page", - }, - path: router.pathname, - }); - }) - .catch((error) => { - console.error(error); - captureIssueEvent({ - eventName: ISSUE_CREATED, - payload: { - ...formData, - state: "FAILED", - element: "Inbox page", - }, - path: router.pathname, - }); - }); - }; - - const handleAiAssistance = async (response: string) => { - if (!workspaceSlug || !projectId) return; - editorRef.current?.setEditorValueAtCursorPosition(response); - }; - - const handleAutoGenerateDescription = async () => { - const issueName = getValues("name"); - if (!workspaceSlug || !projectId || !issueName) return; - - setIAmFeelingLucky(true); - - aiService - .createGptTask(workspaceSlug as string, projectId as string, { - prompt: issueName, - task: "Generate a proper description for this issue.", - }) - .then((res) => { - if (res.response === "") - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: - "Issue title isn't informative enough to generate the description. Please try with a different title.", - }); - else handleAiAssistance(res.response_html); - }) - .catch((err) => { - const error = err?.data?.error; - - if (err.status === 429) - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: error || "You have reached the maximum number of requests of 50 requests per month per user.", - }); - else - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: error || "Some error occurred. Please try again.", - }); - }) - .finally(() => setIAmFeelingLucky(false)); - }; - - return ( - - - -
- - -
-
- - -
-
-

Create Inbox Issue

-
-
-
- ( - - )} - /> -
-
-
- {watch("name") && issueName !== "" && ( - - )} - - {envConfig?.has_openai_configured && ( - { - setGptAssistantModal((prevData) => !prevData); - // this is done so that the title do not reset after gpt popover closed - reset(getValues()); - }} - onResponse={(response) => { - handleAiAssistance(response); - }} - button={ - - } - className="!min-w-[38rem]" - placement="top-end" - /> - )} -
- ( -

" : value} - ref={editorRef} - workspaceSlug={workspaceSlug.toString()} - workspaceId={workspaceId} - projectId={projectId.toString()} - dragDropEnabled={false} - onChange={(_description: object, description_html: string) => { - onChange(description_html); - }} - /> - )} - /> -
- -
- ( -
- -
- )} - /> -
-
-
-
-
-
setCreateMore((prevData) => !prevData)} - > - Create more - {}} size="md" /> -
-
- - -
-
-
-
-
-
-
-
-
- ); -}); diff --git a/web/components/issues/description-input.tsx b/web/components/issues/description-input.tsx index b8cfbd582..538f5444e 100644 --- a/web/components/issues/description-input.tsx +++ b/web/components/issues/description-input.tsx @@ -15,6 +15,7 @@ import { getDescriptionPlaceholder } from "@/helpers/issue.helper"; import { useWorkspace } from "@/hooks/store"; export type IssueDescriptionInputProps = { + containerClassName?: string; workspaceSlug: string; projectId: string; issueId: string; @@ -28,6 +29,7 @@ export type IssueDescriptionInputProps = { export const IssueDescriptionInput: FC = observer((props) => { const { + containerClassName, workspaceSlug, projectId, issueId, @@ -110,11 +112,12 @@ export const IssueDescriptionInput: FC = observer((p placeholder={ placeholder ? placeholder : (isFocused, value) => getDescriptionPlaceholder(isFocused, value) } + containerClassName={containerClassName} /> ) : ( ) } diff --git a/web/components/issues/issue-detail/main-content.tsx b/web/components/issues/issue-detail/main-content.tsx index 541dd1f3e..309f86bcf 100644 --- a/web/components/issues/issue-detail/main-content.tsx +++ b/web/components/issues/issue-detail/main-content.tsx @@ -54,7 +54,7 @@ export const IssueMainContent: React.FC = observer((props) => { return ( <> -
+
{issue.parent_id && ( = observer((props) => { issueOperations={issueOperations} disabled={!isEditable} value={issue.name} + containerClassName="-ml-3" /> {/* {issue?.description_html === issueDescription && ( */} @@ -97,6 +98,7 @@ export const IssueMainContent: React.FC = observer((props) => { disabled={!isEditable} issueOperations={issueOperations} setIsSubmitting={(value) => setIsSubmitting(value)} + containerClassName="-ml-3 border-none" /> {/* )} */} @@ -121,14 +123,18 @@ export const IssueMainContent: React.FC = observer((props) => { )}
- +
+ +
- +
+ +
); }); diff --git a/web/components/issues/issue-detail/root.tsx b/web/components/issues/issue-detail/root.tsx index f9e3c83f7..c061c6a63 100644 --- a/web/components/issues/issue-detail/root.tsx +++ b/web/components/issues/issue-detail/root.tsx @@ -357,7 +357,7 @@ export const IssueDetailRoot: FC = observer((props) => { /> ) : (
-
+
= observer((props) => { ref={editorRef} tabIndex={getTabIndex("description_html")} placeholder={getDescriptionPlaceholder} + containerClassName="border-[0.5px] border-custom-border-200 py-3" /> )} /> diff --git a/web/components/issues/peek-overview/issue-detail.tsx b/web/components/issues/peek-overview/issue-detail.tsx index 29b83f788..d7b880266 100644 --- a/web/components/issues/peek-overview/issue-detail.tsx +++ b/web/components/issues/peek-overview/issue-detail.tsx @@ -69,6 +69,7 @@ export const PeekOverviewIssueDetails: FC = observer( issueOperations={issueOperations} disabled={disabled} value={issue.name} + containerClassName="-ml-3" /> = observer( disabled={disabled} issueOperations={issueOperations} setIsSubmitting={(value) => setIsSubmitting(value)} + containerClassName="-ml-3 border-none" /> {currentUser && ( diff --git a/web/components/issues/title-input.tsx b/web/components/issues/title-input.tsx index 11a82e623..db9a4199a 100644 --- a/web/components/issues/title-input.tsx +++ b/web/components/issues/title-input.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react"; // components import { TextArea } from "@plane/ui"; // types +import { cn } from "@/helpers/common.helper"; import useDebounce from "@/hooks/use-debounce"; import { TIssueOperations } from "./issue-detail"; // hooks @@ -16,12 +17,26 @@ export type IssueTitleInputProps = { issueOperations: TIssueOperations; projectId: string; issueId: string; + className?: string; + containerClassName?: string; }; export const IssueTitleInput: FC = observer((props) => { - const { disabled, value, workspaceSlug, isSubmitting, setIsSubmitting, issueId, issueOperations, projectId } = props; + const { + disabled, + value, + workspaceSlug, + isSubmitting, + setIsSubmitting, + issueId, + issueOperations, + projectId, + className, + containerClassName, + } = props; // states const [title, setTitle] = useState(""); + const [isLengthVisible, setIsLengthVisible] = useState(false); // hooks const debouncedValue = useDebounce(title, 1500); @@ -76,19 +91,32 @@ export const IssueTitleInput: FC = observer((props) => { if (disabled) return
{title}
; return ( -
+