From 78bb3085f343fbde632f00cf595b00cb30254442 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Mon, 14 Aug 2023 18:30:46 +0530 Subject: [PATCH] fix: slash command scroll posiiton --- .../core/modals/gpt-assistant-modal.tsx | 27 +++---- .../components/issues/comment/add-comment.tsx | 17 ++--- .../issues/comment/comment-card.tsx | 31 ++++---- .../components/issues/description-form.tsx | 2 +- apps/app/components/issues/form.tsx | 17 +++-- .../pages/create-update-block-inline.tsx | 31 ++++---- apps/app/components/tiptap/index.tsx | 71 +++++++++---------- .../components/tiptap/slash-command/index.tsx | 71 +++++-------------- .../projects/[projectId]/pages/[pageId].tsx | 24 ++++--- apps/app/styles/editor.css | 8 +-- 10 files changed, 137 insertions(+), 162 deletions(-) diff --git a/apps/app/components/core/modals/gpt-assistant-modal.tsx b/apps/app/components/core/modals/gpt-assistant-modal.tsx index b9bf09ace..e8fd0812c 100644 --- a/apps/app/components/core/modals/gpt-assistant-modal.tsx +++ b/apps/app/components/core/modals/gpt-assistant-modal.tsx @@ -32,10 +32,9 @@ type FormData = { task: string; }; -const TiptapEditor = React.forwardRef< - ITiptapRichTextEditor, - ITiptapRichTextEditor ->((props, ref) => ); +const TiptapEditor = React.forwardRef( + (props, ref) => +); TiptapEditor.displayName = "TiptapEditor"; @@ -141,11 +140,12 @@ export const GptAssistantModal: React.FC = ({ return (
{((content && content !== "") || (htmlContent && htmlContent !== "

")) && ( -
+
Content: ${content}

`} @@ -179,10 +179,11 @@ export const GptAssistantModal: React.FC = ({ type="text" name="task" register={register} - placeholder={`${content && content !== "" - ? "Tell AI what action to perform on this content..." - : "Ask AI anything..." - }`} + placeholder={`${ + content && content !== "" + ? "Tell AI what action to perform on this content..." + : "Ask AI anything..." + }`} autoComplete="off" />
@@ -218,8 +219,8 @@ export const GptAssistantModal: React.FC = ({ {isSubmitting ? "Generating response..." : response === "" - ? "Generate response" - : "Generate again"} + ? "Generate response" + : "Generate again"}
diff --git a/apps/app/components/issues/comment/add-comment.tsx b/apps/app/components/issues/comment/add-comment.tsx index 87fb745a7..6f49e900a 100644 --- a/apps/app/components/issues/comment/add-comment.tsx +++ b/apps/app/components/issues/comment/add-comment.tsx @@ -18,10 +18,9 @@ import type { ICurrentUserResponse, IIssueComment } from "types"; import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; import Tiptap, { ITiptapRichTextEditor } from "components/tiptap"; -const TiptapEditor = React.forwardRef< - ITiptapRichTextEditor, - ITiptapRichTextEditor ->((props, ref) => ); +const TiptapEditor = React.forwardRef( + (props, ref) => +); TiptapEditor.displayName = "TiptapEditor"; @@ -88,15 +87,17 @@ export const AddComment: React.FC = ({ issueId, user, disabled = false }) return (
-
+
+ render={({ field: { value, onChange } }) => ( = ({ issueId, user, disabled = false }) setValue("comment_json", comment_json); }} /> - } + )} /> diff --git a/apps/app/components/issues/comment/comment-card.tsx b/apps/app/components/issues/comment/comment-card.tsx index 5a340609a..169e2f833 100644 --- a/apps/app/components/issues/comment/comment-card.tsx +++ b/apps/app/components/issues/comment/comment-card.tsx @@ -15,10 +15,9 @@ import { timeAgo } from "helpers/date-time.helper"; import type { IIssueComment } from "types"; import Tiptap, { ITiptapRichTextEditor } from "components/tiptap"; -const TiptapEditor = React.forwardRef< - ITiptapRichTextEditor, - ITiptapRichTextEditor ->((props, ref) => ); +const TiptapEditor = React.forwardRef( + (props, ref) => +); TiptapEditor.displayName = "TiptapEditor"; @@ -51,7 +50,7 @@ export const CommentCard: React.FC = ({ comment, onSubmit, handleCommentD setIsEditing(false); onSubmit(formData); - console.log("watching", formData.comment_html) + console.log("watching", formData.comment_html); editorRef.current?.setEditorValue(formData.comment_html); showEditorRef.current?.setEditorValue(formData.comment_html); @@ -103,16 +102,18 @@ export const CommentCard: React.FC = ({ comment, onSubmit, handleCommentD className={`flex-col gap-2 ${isEditing ? "flex" : "hidden"}`} onSubmit={handleSubmit(onEnter)} > - { - setValue("comment_json", comment_json); - setValue("comment_html", comment_html); - }} - /> +
+ { + setValue("comment_json", comment_json); + setValue("comment_html", comment_html); + }} + /> +
{errors.name ? errors.name.message : null} -
+
= ({
)} {(fieldsToShow.includes("all") || fieldsToShow.includes("description")) && ( -
+
{issueName && issueName !== "" && (
Discard @@ -555,8 +558,8 @@ export const IssueForm: FC = ({ ? "Updating Issue..." : "Update Issue" : isSubmitting - ? "Adding Issue..." - : "Add Issue"} + ? "Adding Issue..." + : "Add Issue"}
diff --git a/apps/app/components/pages/create-update-block-inline.tsx b/apps/app/components/pages/create-update-block-inline.tsx index da7384921..18ff92579 100644 --- a/apps/app/components/pages/create-update-block-inline.tsx +++ b/apps/app/components/pages/create-update-block-inline.tsx @@ -39,10 +39,9 @@ const defaultValues = { description_html: null, }; -const TiptapEditor = React.forwardRef< - ITiptapRichTextEditor, - ITiptapRichTextEditor ->((props, ref) => ); +const TiptapEditor = React.forwardRef( + (props, ref) => +); TiptapEditor.displayName = "TiptapEditor"; @@ -232,9 +231,9 @@ export const CreateUpdateBlockInline: React.FC = ({ description: !data.description || data.description === "" ? { - type: "doc", - content: [{ type: "paragraph" }], - } + type: "doc", + content: [{ type: "paragraph" }], + } : data.description, description_html: data.description_html ?? "

", }); @@ -285,7 +284,10 @@ export const CreateUpdateBlockInline: React.FC = ({ maxLength={255} />
-
+
= ({ value && value !== "" && Object.keys(value).length > 0 ? value : watch("description_html") && watch("description_html") !== "" - ? watch("description_html") - : { type: "doc", content: [{ type: "paragraph" }] } + ? watch("description_html") + : { type: "doc", content: [{ type: "paragraph" }] } } debouncedUpdatesEnabled={false} customClassName="text-sm" @@ -335,8 +337,9 @@ export const CreateUpdateBlockInline: React.FC = ({
diff --git a/apps/app/components/tiptap/index.tsx b/apps/app/components/tiptap/index.tsx index 14e77825b..8f28f834a 100644 --- a/apps/app/components/tiptap/index.tsx +++ b/apps/app/components/tiptap/index.tsx @@ -1,14 +1,14 @@ // @ts-nocheck -import { useEditor, EditorContent, Editor } from '@tiptap/react'; -import { useDebouncedCallback } from 'use-debounce'; -import { EditorBubbleMenu } from './bubble-menu'; -import { TiptapExtensions } from './extensions'; -import { TiptapEditorProps } from './props'; +import { useEditor, EditorContent, Editor } from "@tiptap/react"; +import { useDebouncedCallback } from "use-debounce"; +import { EditorBubbleMenu } from "./bubble-menu"; +import { TiptapExtensions } from "./extensions"; +import { TiptapEditorProps } from "./props"; import { Node } from "@tiptap/pm/model"; import { Editor as CoreEditor } from "@tiptap/core"; -import { useCallback, useImperativeHandle, useRef } from 'react'; -import { EditorState } from '@tiptap/pm/state'; -import fileService from 'services/file.service'; +import { useCallback, useImperativeHandle, useRef } from "react"; +import { EditorState } from "@tiptap/pm/state"; +import fileService from "services/file.service"; export interface ITiptapRichTextEditor { value: string; @@ -34,7 +34,7 @@ const Tiptap = (props: ITiptapRichTextEditor) => { value, noBorder, borderOnFocus, - customClassName + customClassName, } = props; const editor = useEditor({ @@ -45,43 +45,40 @@ const Tiptap = (props: ITiptapRichTextEditor) => { onUpdate: async ({ editor }) => { // for instant feedback loop setIsSubmitting?.(true); - checkForNodeDeletions(editor) + checkForNodeDeletions(editor); if (debouncedUpdatesEnabled) { debouncedUpdates({ onChange, editor }); } else { onChange?.(editor.getJSON(), editor.getHTML()); } - } + }, }); - const editorRef: React.MutableRefObject = useRef(null) + const editorRef: React.MutableRefObject = useRef(null); useImperativeHandle(forwardedRef, () => ({ clearEditor: () => { - console.log('clearContent') - console.log(editorRef) - editorRef.current?.commands.clearContent() + console.log("clearContent"); + console.log(editorRef); + editorRef.current?.commands.clearContent(); }, setEditorValue: (content: string) => { - console.log(editorRef, forwardedRef, content) - editorRef.current?.commands.setContent(content) - } - })) + console.log(editorRef, forwardedRef, content); + editorRef.current?.commands.setContent(content); + }, + })); const previousState = useRef(); - 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 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) => { @@ -107,7 +104,7 @@ const Tiptap = (props: ITiptapRichTextEditor) => { } } }, - [onNodeDeleted], + [onNodeDeleted] ); const debouncedUpdates = useDebouncedCallback(async ({ onChange, editor }) => { @@ -119,19 +116,19 @@ const Tiptap = (props: ITiptapRichTextEditor) => { }, 1000); const editorClassNames = `mt-2 p-3 relative focus:outline-none rounded-md focus:border-custom-border-200 - ${noBorder ? '' : 'border border-custom-border-200' - } ${borderOnFocus ? 'focus:border border-custom-border-200' : 'focus:border-0' - } ${customClassName}`; + ${noBorder ? "" : "border border-custom-border-200"} ${ + borderOnFocus ? "focus:border border-custom-border-200" : "focus:border-0" + } ${customClassName}`; - if (!editor) return null - editorRef.current = editor + if (!editor) return null; + editorRef.current = editor; return (
{ editor?.chain().focus().run(); }} - className={`tiptap-editor-container relative ${editorClassNames}`} + className={`tiptap-editor-container cursor-text relative ${editorClassNames}`} > {editor && }
diff --git a/apps/app/components/tiptap/slash-command/index.tsx b/apps/app/components/tiptap/slash-command/index.tsx index ab93945a9..94899d41c 100644 --- a/apps/app/components/tiptap/slash-command/index.tsx +++ b/apps/app/components/tiptap/slash-command/index.tsx @@ -1,11 +1,4 @@ -import React, { - useState, - useEffect, - useCallback, - ReactNode, - useRef, - useLayoutEffect, -} from "react"; +import React, { useState, useEffect, useCallback, ReactNode, useRef, useLayoutEffect } from "react"; import { Editor, Range, Extension } from "@tiptap/core"; import Suggestion from "@tiptap/suggestion"; import { ReactRenderer } from "@tiptap/react"; @@ -42,15 +35,7 @@ const Command = Extension.create({ return { suggestion: { char: "/", - command: ({ - editor, - range, - props, - }: { - editor: Editor; - range: Range; - props: any; - }) => { + command: ({ editor, range, props }: { editor: Editor; range: Range; props: any }) => { props.command({ editor, range }); }, }, @@ -74,12 +59,7 @@ const getSuggestionItems = ({ query }: { query: string }) => searchTerms: ["p", "paragraph"], icon: , command: ({ editor, range }: CommandProps) => { - editor - .chain() - .focus() - .deleteRange(range) - .toggleNode("paragraph", "paragraph") - .run(); + editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run(); }, }, { @@ -88,12 +68,7 @@ const getSuggestionItems = ({ query }: { query: string }) => searchTerms: ["title", "big", "large"], icon: , command: ({ editor, range }: CommandProps) => { - editor - .chain() - .focus() - .deleteRange(range) - .setNode("heading", { level: 1 }) - .run(); + editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run(); }, }, { @@ -102,12 +77,7 @@ const getSuggestionItems = ({ query }: { query: string }) => searchTerms: ["subtitle", "medium"], icon: , command: ({ editor, range }: CommandProps) => { - editor - .chain() - .focus() - .deleteRange(range) - .setNode("heading", { level: 2 }) - .run(); + editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run(); }, }, { @@ -116,12 +86,7 @@ const getSuggestionItems = ({ query }: { query: string }) => searchTerms: ["subtitle", "small"], icon: , command: ({ editor, range }: CommandProps) => { - editor - .chain() - .focus() - .deleteRange(range) - .setNode("heading", { level: 3 }) - .run(); + editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run(); }, }, { @@ -148,7 +113,7 @@ const getSuggestionItems = ({ query }: { query: string }) => searchTerms: ["line", "divider", "horizontal", "rule", "separate"], icon: , command: ({ editor, range }: CommandProps) => { - editor.chain().focus().deleteRange(range).setHorizontalRule().run() + editor.chain().focus().deleteRange(range).setHorizontalRule().run(); }, }, { @@ -209,12 +174,11 @@ const getSuggestionItems = ({ query }: { query: string }) => return ( item.title.toLowerCase().includes(search) || item.description.toLowerCase().includes(search) || - (item.searchTerms && - item.searchTerms.some((term: string) => term.includes(search))) + (item.searchTerms && item.searchTerms.some((term: string) => term.includes(search))) ); } return true; - });; + }); export const updateScrollView = (container: HTMLElement, item: HTMLElement) => { const containerHeight = container.offsetHeight; @@ -250,7 +214,7 @@ const CommandList = ({ command(item); } }, - [command, items], + [command, items] ); useEffect(() => { @@ -297,12 +261,13 @@ const CommandList = ({
- {items.map((item: CommandItemProps, index: number) => + {items.map((item: CommandItemProps, index: number) => (
- )} + ))}
) : null; }; @@ -320,6 +285,8 @@ const renderItems = () => { let component: ReactRenderer | null = null; let popup: any | null = null; + const container = document.querySelector("#tiptap-container") as HTMLElement; + return { onStart: (props: { editor: Editor; clientRect: DOMRect }) => { component = new ReactRenderer(CommandList, { @@ -330,7 +297,7 @@ const renderItems = () => { // @ts-ignore popup = tippy("body", { getReferenceClientRect: props.clientRect, - appendTo: () => document.body, + appendTo: () => container, content: component.element, showOnCreate: true, interactive: true, diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index ed52f447a..0502e9e87 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -629,17 +629,19 @@ const SinglePage: NextPage = () => { ref={provided.innerRef} {...provided.droppableProps} > - {pageBlocks.map((block, index) => ( - - ))} - {provided.placeholder} + <> + {pageBlocks.map((block, index) => ( + + ))} + {provided.placeholder} +
)} diff --git a/apps/app/styles/editor.css b/apps/app/styles/editor.css index f8f91ca34..f76f690af 100644 --- a/apps/app/styles/editor.css +++ b/apps/app/styles/editor.css @@ -32,18 +32,18 @@ /* Custom TODO list checkboxes – shoutout to this awesome tutorial: https://moderncss.dev/pure-css-custom-checkbox-style/ */ -ul[data-type="taskList"] li>label { +ul[data-type="taskList"] li > label { margin-right: 0.2rem; user-select: none; } @media screen and (max-width: 768px) { - ul[data-type="taskList"] li>label { + ul[data-type="taskList"] li > label { margin-right: 0.5rem; } } -ul[data-type="taskList"] li>label input[type="checkbox"] { +ul[data-type="taskList"] li > label input[type="checkbox"] { -webkit-appearance: none; appearance: none; background-color: rgb(var(--color-background-100)); @@ -81,7 +81,7 @@ ul[data-type="taskList"] li>label input[type="checkbox"] { } } -ul[data-type="taskList"] li[data-checked="true"]>div>p { +ul[data-type="taskList"] li[data-checked="true"] > div > p { color: rgb(var(--color-text-200)); text-decoration: line-through; text-decoration-thickness: 2px;