fix: slash command scroll posiiton

This commit is contained in:
Aaryan Khandelwal 2023-08-14 18:30:46 +05:30
parent bcc1131ec1
commit 78bb3085f3
10 changed files with 137 additions and 162 deletions

View File

@ -32,10 +32,9 @@ type FormData = {
task: string; task: string;
}; };
const TiptapEditor = React.forwardRef< const TiptapEditor = React.forwardRef<ITiptapRichTextEditor, ITiptapRichTextEditor>(
ITiptapRichTextEditor, (props, ref) => <Tiptap {...props} forwardedRef={ref} />
ITiptapRichTextEditor );
>((props, ref) => <Tiptap {...props} forwardedRef={ref} />);
TiptapEditor.displayName = "TiptapEditor"; TiptapEditor.displayName = "TiptapEditor";
@ -141,11 +140,12 @@ 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 ${isOpen ? "block" : "hidden" 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"
}`}
> >
{((content && content !== "") || (htmlContent && htmlContent !== "<p></p>")) && ( {((content && content !== "") || (htmlContent && htmlContent !== "<p></p>")) && (
<div className="remirror-section text-sm"> <div id="tiptap-container" className="remirror-section text-sm">
Content: Content:
<TiptapEditor <TiptapEditor
value={htmlContent ?? `<p>${content}</p>`} value={htmlContent ?? `<p>${content}</p>`}
@ -179,10 +179,11 @@ export const GptAssistantModal: React.FC<Props> = ({
type="text" type="text"
name="task" name="task"
register={register} register={register}
placeholder={`${content && content !== "" placeholder={`${
? "Tell AI what action to perform on this content..." content && content !== ""
: "Ask AI anything..." ? "Tell AI what action to perform on this content..."
}`} : "Ask AI anything..."
}`}
autoComplete="off" autoComplete="off"
/> />
<div className={`flex gap-2 ${response === "" ? "justify-end" : "justify-between"}`}> <div className={`flex gap-2 ${response === "" ? "justify-end" : "justify-between"}`}>
@ -218,8 +219,8 @@ export const GptAssistantModal: React.FC<Props> = ({
{isSubmitting {isSubmitting
? "Generating response..." ? "Generating response..."
: response === "" : response === ""
? "Generate response" ? "Generate response"
: "Generate again"} : "Generate again"}
</PrimaryButton> </PrimaryButton>
</div> </div>
</div> </div>

View File

@ -18,10 +18,9 @@ import type { ICurrentUserResponse, IIssueComment } from "types";
import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
import Tiptap, { ITiptapRichTextEditor } from "components/tiptap"; import Tiptap, { ITiptapRichTextEditor } from "components/tiptap";
const TiptapEditor = React.forwardRef< const TiptapEditor = React.forwardRef<ITiptapRichTextEditor, ITiptapRichTextEditor>(
ITiptapRichTextEditor, (props, ref) => <Tiptap {...props} forwardedRef={ref} />
ITiptapRichTextEditor );
>((props, ref) => <Tiptap {...props} forwardedRef={ref} />);
TiptapEditor.displayName = "TiptapEditor"; TiptapEditor.displayName = "TiptapEditor";
@ -88,15 +87,17 @@ export const AddComment: React.FC<Props> = ({ issueId, user, disabled = false })
return ( return (
<div> <div>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="issue-comments-section"> <div id="tiptap-container" className="issue-comments-section">
<Controller <Controller
name="comment_html" name="comment_html"
control={control} control={control}
render={({ field: { value, onChange } }) => render={({ field: { value, onChange } }) => (
<TiptapEditor <TiptapEditor
ref={editorRef} ref={editorRef}
value={ value={
!value || value === "" || (typeof value === "object" && Object.keys(value).length === 0) !value ||
value === "" ||
(typeof value === "object" && Object.keys(value).length === 0)
? watch("comment_html") ? watch("comment_html")
: value : value
} }
@ -107,7 +108,7 @@ export const AddComment: React.FC<Props> = ({ issueId, user, disabled = false })
setValue("comment_json", comment_json); setValue("comment_json", comment_json);
}} }}
/> />
} )}
/> />
<SecondaryButton type="submit" disabled={isSubmitting || disabled} className="mt-2"> <SecondaryButton type="submit" disabled={isSubmitting || disabled} className="mt-2">

View File

@ -15,10 +15,9 @@ import { timeAgo } from "helpers/date-time.helper";
import type { IIssueComment } from "types"; import type { IIssueComment } from "types";
import Tiptap, { ITiptapRichTextEditor } from "components/tiptap"; import Tiptap, { ITiptapRichTextEditor } from "components/tiptap";
const TiptapEditor = React.forwardRef< const TiptapEditor = React.forwardRef<ITiptapRichTextEditor, ITiptapRichTextEditor>(
ITiptapRichTextEditor, (props, ref) => <Tiptap {...props} forwardedRef={ref} />
ITiptapRichTextEditor );
>((props, ref) => <Tiptap {...props} forwardedRef={ref} />);
TiptapEditor.displayName = "TiptapEditor"; TiptapEditor.displayName = "TiptapEditor";
@ -51,7 +50,7 @@ export const CommentCard: React.FC<Props> = ({ comment, onSubmit, handleCommentD
setIsEditing(false); setIsEditing(false);
onSubmit(formData); onSubmit(formData);
console.log("watching", formData.comment_html) console.log("watching", formData.comment_html);
editorRef.current?.setEditorValue(formData.comment_html); editorRef.current?.setEditorValue(formData.comment_html);
showEditorRef.current?.setEditorValue(formData.comment_html); showEditorRef.current?.setEditorValue(formData.comment_html);
@ -103,16 +102,18 @@ export const CommentCard: React.FC<Props> = ({ comment, onSubmit, handleCommentD
className={`flex-col gap-2 ${isEditing ? "flex" : "hidden"}`} className={`flex-col gap-2 ${isEditing ? "flex" : "hidden"}`}
onSubmit={handleSubmit(onEnter)} onSubmit={handleSubmit(onEnter)}
> >
<TiptapEditor <div id="tiptap-container">
ref={editorRef} <TiptapEditor
value={watch("comment_html")} ref={editorRef}
debouncedUpdatesEnabled={false} value={watch("comment_html")}
customClassName="min-h-[50px] p-3" debouncedUpdatesEnabled={false}
onChange={(comment_json: Object, comment_html: string) => { customClassName="min-h-[50px] p-3"
setValue("comment_json", comment_json); onChange={(comment_json: Object, comment_html: string) => {
setValue("comment_html", comment_html); setValue("comment_json", comment_json);
}} setValue("comment_html", comment_html);
/> }}
/>
</div>
<div className="flex gap-1 self-end"> <div className="flex gap-1 self-end">
<button <button
type="submit" type="submit"

View File

@ -110,7 +110,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
)} )}
</div> </div>
<span>{errors.name ? errors.name.message : null}</span> <span>{errors.name ? errors.name.message : null}</span>
<div className="relative"> <div id="tiptap-container" className="relative">
<Controller <Controller
name="description_html" name="description_html"
control={control} control={control}

View File

@ -329,13 +329,14 @@ export const IssueForm: FC<IssueFormProps> = ({
</div> </div>
)} )}
{(fieldsToShow.includes("all") || fieldsToShow.includes("description")) && ( {(fieldsToShow.includes("all") || fieldsToShow.includes("description")) && (
<div className="relative"> <div id="tiptap-container" className="relative">
<div className="flex justify-end"> <div className="flex justify-end">
{issueName && issueName !== "" && ( {issueName && issueName !== "" && (
<button <button
type="button" type="button"
className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 ${iAmFeelingLucky ? "cursor-wait" : "" className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 ${
}`} iAmFeelingLucky ? "cursor-wait" : ""
}`}
onClick={handleAutoGenerateDescription} onClick={handleAutoGenerateDescription}
disabled={iAmFeelingLucky} disabled={iAmFeelingLucky}
> >
@ -367,7 +368,9 @@ export const IssueForm: FC<IssueFormProps> = ({
<Tiptap <Tiptap
debouncedUpdatesEnabled={false} debouncedUpdatesEnabled={false}
value={ value={
!value || value === "" || (typeof value === "object" && Object.keys(value).length === 0) !value ||
value === "" ||
(typeof value === "object" && Object.keys(value).length === 0)
? watch("description_html") ? watch("description_html")
: value : value
} }
@ -545,7 +548,7 @@ export const IssueForm: FC<IssueFormProps> = ({
onClick={() => setCreateMore((prevData) => !prevData)} onClick={() => setCreateMore((prevData) => !prevData)}
> >
<span className="text-xs">Create more</span> <span className="text-xs">Create more</span>
<ToggleSwitch value={createMore} onChange={() => { }} size="md" /> <ToggleSwitch value={createMore} onChange={() => {}} size="md" />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<SecondaryButton onClick={handleClose}>Discard</SecondaryButton> <SecondaryButton onClick={handleClose}>Discard</SecondaryButton>
@ -555,8 +558,8 @@ export const IssueForm: FC<IssueFormProps> = ({
? "Updating Issue..." ? "Updating Issue..."
: "Update Issue" : "Update Issue"
: isSubmitting : isSubmitting
? "Adding Issue..." ? "Adding Issue..."
: "Add Issue"} : "Add Issue"}
</PrimaryButton> </PrimaryButton>
</div> </div>
</div> </div>

View File

@ -39,10 +39,9 @@ const defaultValues = {
description_html: null, description_html: null,
}; };
const TiptapEditor = React.forwardRef< const TiptapEditor = React.forwardRef<ITiptapRichTextEditor, ITiptapRichTextEditor>(
ITiptapRichTextEditor, (props, ref) => <Tiptap {...props} forwardedRef={ref} />
ITiptapRichTextEditor );
>((props, ref) => <Tiptap {...props} forwardedRef={ref} />);
TiptapEditor.displayName = "TiptapEditor"; TiptapEditor.displayName = "TiptapEditor";
@ -232,9 +231,9 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
description: description:
!data.description || data.description === "" !data.description || data.description === ""
? { ? {
type: "doc", type: "doc",
content: [{ type: "paragraph" }], content: [{ type: "paragraph" }],
} }
: data.description, : data.description,
description_html: data.description_html ?? "<p></p>", description_html: data.description_html ?? "<p></p>",
}); });
@ -285,7 +284,10 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
maxLength={255} maxLength={255}
/> />
</div> </div>
<div className="page-block-section relative -mt-2 text-custom-text-200"> <div
id="tiptap-container"
className="page-block-section relative -mt-2 text-custom-text-200"
>
<Controller <Controller
name="description_html" name="description_html"
control={control} control={control}
@ -317,8 +319,8 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
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") && watch("description_html") !== ""
? watch("description_html") ? watch("description_html")
: { type: "doc", content: [{ type: "paragraph" }] } : { type: "doc", content: [{ type: "paragraph" }] }
} }
debouncedUpdatesEnabled={false} debouncedUpdatesEnabled={false}
customClassName="text-sm" customClassName="text-sm"
@ -335,8 +337,9 @@ 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 ${iAmFeelingLucky ? "cursor-wait bg-custom-background-90" : "" 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" : ""
}`}
onClick={handleAutoGenerateDescription} onClick={handleAutoGenerateDescription}
disabled={iAmFeelingLucky} disabled={iAmFeelingLucky}
> >
@ -368,8 +371,8 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
? "Updating..." ? "Updating..."
: "Update block" : "Update block"
: isSubmitting : isSubmitting
? "Adding..." ? "Adding..."
: "Add block"} : "Add block"}
</PrimaryButton> </PrimaryButton>
</div> </div>
</form> </form>

View File

@ -1,14 +1,14 @@
// @ts-nocheck // @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 { Node } from "@tiptap/pm/model";
import { Editor as CoreEditor } from "@tiptap/core"; import { Editor as CoreEditor } from "@tiptap/core";
import { useCallback, useImperativeHandle, useRef } from 'react'; import { useCallback, useImperativeHandle, useRef } from "react";
import { EditorState } from '@tiptap/pm/state'; import { EditorState } from "@tiptap/pm/state";
import fileService from 'services/file.service'; import fileService from "services/file.service";
export interface ITiptapRichTextEditor { export interface ITiptapRichTextEditor {
value: string; value: string;
@ -34,7 +34,7 @@ const Tiptap = (props: ITiptapRichTextEditor) => {
value, value,
noBorder, noBorder,
borderOnFocus, borderOnFocus,
customClassName customClassName,
} = props; } = props;
const editor = useEditor({ const editor = useEditor({
@ -45,43 +45,40 @@ const Tiptap = (props: ITiptapRichTextEditor) => {
onUpdate: async ({ editor }) => { onUpdate: async ({ editor }) => {
// for instant feedback loop // for instant feedback loop
setIsSubmitting?.(true); setIsSubmitting?.(true);
checkForNodeDeletions(editor) checkForNodeDeletions(editor);
if (debouncedUpdatesEnabled) { if (debouncedUpdatesEnabled) {
debouncedUpdates({ onChange, editor }); debouncedUpdates({ onChange, editor });
} else { } else {
onChange?.(editor.getJSON(), editor.getHTML()); onChange?.(editor.getJSON(), editor.getHTML());
} }
} },
}); });
const editorRef: React.MutableRefObject<Editor | null> = useRef(null) const editorRef: React.MutableRefObject<Editor | null> = useRef(null);
useImperativeHandle(forwardedRef, () => ({ useImperativeHandle(forwardedRef, () => ({
clearEditor: () => { clearEditor: () => {
console.log('clearContent') console.log("clearContent");
console.log(editorRef) console.log(editorRef);
editorRef.current?.commands.clearContent() editorRef.current?.commands.clearContent();
}, },
setEditorValue: (content: string) => { setEditorValue: (content: string) => {
console.log(editorRef, forwardedRef, content) console.log(editorRef, forwardedRef, content);
editorRef.current?.commands.setContent(content) editorRef.current?.commands.setContent(content);
} },
})) }));
const previousState = useRef<EditorState>(); const previousState = useRef<EditorState>();
const onNodeDeleted = useCallback( const onNodeDeleted = useCallback(async (node: Node) => {
async (node: Node) => { if (node.type.name === "image") {
if (node.type.name === 'image') { const assetUrlWithWorkspaceId = new URL(node.attrs.src).pathname.substring(1);
const assetUrlWithWorkspaceId = new URL(node.attrs.src).pathname.substring(1); const resStatus = await fileService.deleteImage(assetUrlWithWorkspaceId);
const resStatus = await fileService.deleteImage(assetUrlWithWorkspaceId); if (resStatus === 204) {
if (resStatus === 204) { console.log("file deleted successfully");
console.log("file deleted successfully");
}
} }
}, }
[], }, []);
);
const checkForNodeDeletions = useCallback( const checkForNodeDeletions = useCallback(
(editor: CoreEditor) => { (editor: CoreEditor) => {
@ -107,7 +104,7 @@ const Tiptap = (props: ITiptapRichTextEditor) => {
} }
} }
}, },
[onNodeDeleted], [onNodeDeleted]
); );
const debouncedUpdates = useDebouncedCallback(async ({ onChange, editor }) => { const debouncedUpdates = useDebouncedCallback(async ({ onChange, editor }) => {
@ -119,19 +116,19 @@ const Tiptap = (props: ITiptapRichTextEditor) => {
}, 1000); }, 1000);
const editorClassNames = `mt-2 p-3 relative focus:outline-none rounded-md focus:border-custom-border-200 const editorClassNames = `mt-2 p-3 relative focus:outline-none rounded-md focus:border-custom-border-200
${noBorder ? '' : 'border border-custom-border-200' ${noBorder ? "" : "border border-custom-border-200"} ${
} ${borderOnFocus ? 'focus:border border-custom-border-200' : 'focus:border-0' borderOnFocus ? "focus:border border-custom-border-200" : "focus:border-0"
} ${customClassName}`; } ${customClassName}`;
if (!editor) return null if (!editor) return null;
editorRef.current = editor editorRef.current = editor;
return ( return (
<div <div
onClick={() => { onClick={() => {
editor?.chain().focus().run(); editor?.chain().focus().run();
}} }}
className={`tiptap-editor-container relative ${editorClassNames}`} className={`tiptap-editor-container cursor-text relative ${editorClassNames}`}
> >
{editor && <EditorBubbleMenu editor={editor} />} {editor && <EditorBubbleMenu editor={editor} />}
<div className={`${editorContentCustomClassNames}`}> <div className={`${editorContentCustomClassNames}`}>

View File

@ -1,11 +1,4 @@
import React, { import React, { useState, useEffect, useCallback, ReactNode, useRef, useLayoutEffect } from "react";
useState,
useEffect,
useCallback,
ReactNode,
useRef,
useLayoutEffect,
} from "react";
import { Editor, Range, Extension } from "@tiptap/core"; import { Editor, Range, Extension } from "@tiptap/core";
import Suggestion from "@tiptap/suggestion"; import Suggestion from "@tiptap/suggestion";
import { ReactRenderer } from "@tiptap/react"; import { ReactRenderer } from "@tiptap/react";
@ -42,15 +35,7 @@ const Command = Extension.create({
return { return {
suggestion: { suggestion: {
char: "/", char: "/",
command: ({ command: ({ editor, range, props }: { editor: Editor; range: Range; props: any }) => {
editor,
range,
props,
}: {
editor: Editor;
range: Range;
props: any;
}) => {
props.command({ editor, range }); props.command({ editor, range });
}, },
}, },
@ -74,12 +59,7 @@ const getSuggestionItems = ({ query }: { query: string }) =>
searchTerms: ["p", "paragraph"], searchTerms: ["p", "paragraph"],
icon: <Text size={18} />, icon: <Text size={18} />,
command: ({ editor, range }: CommandProps) => { command: ({ editor, range }: CommandProps) => {
editor editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
.chain()
.focus()
.deleteRange(range)
.toggleNode("paragraph", "paragraph")
.run();
}, },
}, },
{ {
@ -88,12 +68,7 @@ const getSuggestionItems = ({ query }: { query: string }) =>
searchTerms: ["title", "big", "large"], searchTerms: ["title", "big", "large"],
icon: <Heading1 size={18} />, icon: <Heading1 size={18} />,
command: ({ editor, range }: CommandProps) => { command: ({ editor, range }: CommandProps) => {
editor editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
.chain()
.focus()
.deleteRange(range)
.setNode("heading", { level: 1 })
.run();
}, },
}, },
{ {
@ -102,12 +77,7 @@ const getSuggestionItems = ({ query }: { query: string }) =>
searchTerms: ["subtitle", "medium"], searchTerms: ["subtitle", "medium"],
icon: <Heading2 size={18} />, icon: <Heading2 size={18} />,
command: ({ editor, range }: CommandProps) => { command: ({ editor, range }: CommandProps) => {
editor editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
.chain()
.focus()
.deleteRange(range)
.setNode("heading", { level: 2 })
.run();
}, },
}, },
{ {
@ -116,12 +86,7 @@ const getSuggestionItems = ({ query }: { query: string }) =>
searchTerms: ["subtitle", "small"], searchTerms: ["subtitle", "small"],
icon: <Heading3 size={18} />, icon: <Heading3 size={18} />,
command: ({ editor, range }: CommandProps) => { command: ({ editor, range }: CommandProps) => {
editor editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
.chain()
.focus()
.deleteRange(range)
.setNode("heading", { level: 3 })
.run();
}, },
}, },
{ {
@ -148,7 +113,7 @@ const getSuggestionItems = ({ query }: { query: string }) =>
searchTerms: ["line", "divider", "horizontal", "rule", "separate"], searchTerms: ["line", "divider", "horizontal", "rule", "separate"],
icon: <MinusSquare size={18} />, icon: <MinusSquare size={18} />,
command: ({ editor, range }: CommandProps) => { 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 ( return (
item.title.toLowerCase().includes(search) || item.title.toLowerCase().includes(search) ||
item.description.toLowerCase().includes(search) || item.description.toLowerCase().includes(search) ||
(item.searchTerms && (item.searchTerms && item.searchTerms.some((term: string) => term.includes(search)))
item.searchTerms.some((term: string) => term.includes(search)))
); );
} }
return true; return true;
});; });
export const updateScrollView = (container: HTMLElement, item: HTMLElement) => { export const updateScrollView = (container: HTMLElement, item: HTMLElement) => {
const containerHeight = container.offsetHeight; const containerHeight = container.offsetHeight;
@ -250,7 +214,7 @@ const CommandList = ({
command(item); command(item);
} }
}, },
[command, items], [command, items]
); );
useEffect(() => { useEffect(() => {
@ -297,12 +261,13 @@ const CommandList = ({
<div <div
id="slash-command" id="slash-command"
ref={commandListContainer} ref={commandListContainer}
className="z-50 h-auto max-h-[330px] w-72 overflow-y-auto rounded-md border border-custom-border-200 bg-custom-background-100 px-1 py-2 shadow-md transition-all" className="z-20 h-auto max-h-[330px] w-72 overflow-y-auto rounded-md border border-custom-border-200 bg-custom-background-100 px-1 py-2 shadow-md transition-all"
> >
{items.map((item: CommandItemProps, index: number) => {items.map((item: CommandItemProps, index: number) => (
<button <button
className={`flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm text-custom-text-90 hover:text-custom-text-100 ${index === selectedIndex ? "bg-gray-800 text-custom-text-90" : "" className={`flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm text-custom-text-90 hover:text-custom-text-100 ${
}`} index === selectedIndex ? "bg-gray-800 text-custom-text-90" : ""
}`}
key={index} key={index}
onClick={() => selectItem(index)} onClick={() => selectItem(index)}
> >
@ -311,7 +276,7 @@ const CommandList = ({
<p className="text-xs text-stone-500">{item.description}</p> <p className="text-xs text-stone-500">{item.description}</p>
</div> </div>
</button> </button>
)} ))}
</div> </div>
) : null; ) : null;
}; };
@ -320,6 +285,8 @@ const renderItems = () => {
let component: ReactRenderer | null = null; let component: ReactRenderer | null = null;
let popup: any | null = null; let popup: any | null = null;
const container = document.querySelector("#tiptap-container") as HTMLElement;
return { return {
onStart: (props: { editor: Editor; clientRect: DOMRect }) => { onStart: (props: { editor: Editor; clientRect: DOMRect }) => {
component = new ReactRenderer(CommandList, { component = new ReactRenderer(CommandList, {
@ -330,7 +297,7 @@ const renderItems = () => {
// @ts-ignore // @ts-ignore
popup = tippy("body", { popup = tippy("body", {
getReferenceClientRect: props.clientRect, getReferenceClientRect: props.clientRect,
appendTo: () => document.body, appendTo: () => container,
content: component.element, content: component.element,
showOnCreate: true, showOnCreate: true,
interactive: true, interactive: true,

View File

@ -629,17 +629,19 @@ const SinglePage: NextPage = () => {
ref={provided.innerRef} ref={provided.innerRef}
{...provided.droppableProps} {...provided.droppableProps}
> >
{pageBlocks.map((block, index) => ( <>
<SinglePageBlock {pageBlocks.map((block, index) => (
key={block.id} <SinglePageBlock
block={block} key={block.id}
projectDetails={projectDetails} block={block}
showBlockDetails={showBlock} projectDetails={projectDetails}
index={index} showBlockDetails={showBlock}
user={user} index={index}
/> user={user}
))} />
{provided.placeholder} ))}
{provided.placeholder}
</>
</div> </div>
)} )}
</StrictModeDroppable> </StrictModeDroppable>

View File

@ -32,18 +32,18 @@
/* Custom TODO list checkboxes shoutout to this awesome tutorial: https://moderncss.dev/pure-css-custom-checkbox-style/ */ /* 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; margin-right: 0.2rem;
user-select: none; user-select: none;
} }
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {
ul[data-type="taskList"] li>label { ul[data-type="taskList"] li > label {
margin-right: 0.5rem; 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; -webkit-appearance: none;
appearance: none; appearance: none;
background-color: rgb(var(--color-background-100)); 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)); color: rgb(var(--color-text-200));
text-decoration: line-through; text-decoration: line-through;
text-decoration-thickness: 2px; text-decoration-thickness: 2px;