diff --git a/apps/web/src/components/InboxList.tsx b/apps/web/src/components/InboxList.tsx index 42ff9874..a862abbd 100644 --- a/apps/web/src/components/InboxList.tsx +++ b/apps/web/src/components/InboxList.tsx @@ -148,6 +148,8 @@ export const createVirtualItem = (props: { itemsConnectionId: string }) => { }); }; +export const isTempItemId = (id: string) => id.startsWith("Item_0."); + export const deleteVirtualItem = (props: { itemId: string; itemsConnectionId: string }) => { environment.commitUpdate((store) => { const connection = store.get(props.itemsConnectionId); diff --git a/apps/web/src/components/ItemCard.tsx b/apps/web/src/components/ItemCard.tsx index d0298d1c..393d7085 100644 --- a/apps/web/src/components/ItemCard.tsx +++ b/apps/web/src/components/ItemCard.tsx @@ -8,6 +8,8 @@ import { ItemCardUpdateItemMutation } from "../relay/__generated__/ItemCardUpdat import { ItemCardDismissFromInboxMutation } from "../relay/__generated__/ItemCardDismissFromInboxMutation.graphql"; import { RenderItemCardDetails } from "./RenderItemCardDetails"; import { RenderItemCardActions } from "./RenderItemCardActions"; +import { useEffect, useRef } from "react"; +import { isTempItemId } from "./InboxList"; type ItemCardProps = { item: ItemCard_item$key; @@ -16,9 +18,11 @@ type ItemCardProps = { }; export const ItemCard = (props: ItemCardProps) => { + const ref = useRef(null); const item = useFragment( graphql` fragment ItemCard_item on Item { + id title durationInMinutes ...ItemTitle_item @@ -30,8 +34,15 @@ export const ItemCard = (props: ItemCardProps) => { props.item, ); + useEffect(() => { + isTempItemId(item.id) && ref.current?.scrollIntoView({ block: "end" }); + }, [item.id]); + return ( -
+
diff --git a/apps/web/src/components/ItemTitle.tsx b/apps/web/src/components/ItemTitle.tsx index eee640c3..34073bd0 100644 --- a/apps/web/src/components/ItemTitle.tsx +++ b/apps/web/src/components/ItemTitle.tsx @@ -1,16 +1,16 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import { Editor, EditorContent, useEditor, Mention, MinimumKit } from "@flowdev/tiptap"; +import { Editor, EditorContent, useEditor, Mention, MinimumKit, OnEscape } from "@flowdev/tiptap"; import { CatchNewLines } from "@flowdev/tiptap"; import { graphql, useFragment, useMutation } from "@flowdev/relay"; import { ItemTitle_item$key } from "../relay/__generated__/ItemTitle_item.graphql"; import { ItemTitleUpdateItemTitleMutation } from "../relay/__generated__/ItemTitleUpdateItemTitleMutation.graphql"; import { ItemTitleCreateItemMutation } from "../relay/__generated__/ItemTitleCreateItemMutation.graphql"; -import { createVirtualItem, deleteVirtualItem } from "./InboxList"; +import { deleteVirtualItem, isTempItemId } from "./InboxList"; import "./TaskTitle.scss"; type ItemTitleProps = { item: ItemTitle_item$key; - itemsConnectionId?: string; + itemsConnectionId: string | undefined; }; export const ItemTitle = (props: ItemTitleProps) => { @@ -24,7 +24,7 @@ export const ItemTitle = (props: ItemTitleProps) => { `, props.item, ); - const isTemp = item.id.startsWith("Item_0."); + const isTemp = isTempItemId(item.id); const [createItem] = useMutation(graphql` mutation ItemTitleCreateItemMutation($input: MutationCreateItemInput!) { @@ -60,8 +60,6 @@ export const ItemTitle = (props: ItemTitleProps) => { .setValue(createdItem.inboxPoints, "inboxPoints"); }, }); - if (!props.itemsConnectionId) return; - createVirtualItem({ itemsConnectionId: props.itemsConnectionId }); } else { updateItem({ variables: { input: { id: item.id, title: title } }, @@ -128,9 +126,8 @@ export const ItemTitleInput = (props: ItemTitleInputProps) => { editorRef.current = useEditor({ extensions: [ MinimumKit, - CatchNewLines(() => { - editorRef.current!.commands.blur(); - }), + CatchNewLines(() => editorRef.current!.commands.blur()), + OnEscape(() => editorRef.current!.commands.blur()), Mention.configure({ suggestion: { char: "#", diff --git a/apps/web/src/components/TaskTitle.tsx b/apps/web/src/components/TaskTitle.tsx index e2284056..05eac7e1 100644 --- a/apps/web/src/components/TaskTitle.tsx +++ b/apps/web/src/components/TaskTitle.tsx @@ -7,6 +7,7 @@ import { Mention, CatchNewLines, MinimumKit, + OnEscape, } from "@flowdev/tiptap"; import { TaskTitle_task$key } from "@flowdev/web/relay/__generated__/TaskTitle_task.graphql"; import { TaskTitleUpdateTaskTitleMutation } from "../relay/__generated__/TaskTitleUpdateTaskTitleMutation.graphql"; @@ -142,9 +143,8 @@ export const TaskTitleInput = (props: TaskTitleInputProps) => { editorRef.current = useEditor({ extensions: [ MinimumKit, - CatchNewLines(() => { - editorRef.current!.commands.blur(); - }), + CatchNewLines(() => editorRef.current!.commands.blur()), + OnEscape(() => editorRef.current!.commands.blur()), Mention.configure({ suggestion: { char: "#", diff --git a/packages/tiptap/index.tsx b/packages/tiptap/index.tsx index 072a030b..3f4eaae4 100644 --- a/packages/tiptap/index.tsx +++ b/packages/tiptap/index.tsx @@ -22,6 +22,7 @@ import { History, HistoryOptions } from "@tiptap/extension-history"; import { Mention } from "@tiptap/extension-mention"; import { Markdown as MarkdownExtension } from "tiptap-markdown"; import { Plugin, PluginKey } from "prosemirror-state"; +import { EditorView } from "@tiptap/pm/view"; export { Document, @@ -72,19 +73,19 @@ export const CodeBlock = $CodeBlock.configure({ }, }); -export const CatchNewLines = (onNewLine?: () => void) => +export const OnKeydown = ( + onKeyDown?: (view: EditorView, event: KeyboardEvent) => boolean | undefined, + extensionName = "onKeyDown", +) => Extension.create({ - name: "catchNewLines", + name: extensionName, addProseMirrorPlugins() { return [ new Plugin({ - key: new PluginKey("eventHandler"), + key: new PluginKey(extensionName), props: { - handleKeyDown: (_view, event) => { - if (event.key === "Enter") { - onNewLine?.(); - return true; - } + handleKeyDown: (view, event) => { + return onKeyDown?.(view, event); }, }, }), @@ -92,6 +93,22 @@ export const CatchNewLines = (onNewLine?: () => void) => }, }); +export const OnEscape = (onEscape?: () => void) => + OnKeydown((_view, event) => { + if (event.key === "Escape") { + onEscape?.(); + return true; + } + }, "onEscape"); + +export const CatchNewLines = (onNewLine?: () => void) => + OnKeydown((view, event) => { + if (event.key === "Enter") { + onNewLine?.(); + return true; + } + }, "onNewLine"); + interface MinimumKitOptions { document: false; paragraph: false;