diff --git a/packages/client/components/ParabolScopingSearchResultItem.tsx b/packages/client/components/ParabolScopingSearchResultItem.tsx index f3e449fc02e..4701e7f6562 100644 --- a/packages/client/components/ParabolScopingSearchResultItem.tsx +++ b/packages/client/components/ParabolScopingSearchResultItem.tsx @@ -68,10 +68,14 @@ const ParabolScopingSearchResultItem = (props: Props) => { const disabled = !isSelected && usedServiceTaskIds.size >= Threshold.MAX_POKER_STORIES const atmosphere = useAtmosphere() const {onCompleted, onError, submitMutation, submitting} = useMutationProps() - const {editor, linkState, setLinkState} = useTipTapTaskEditor(content, {atmosphere, teamId}) + const isEditingThisItem = !plaintextContent + const {editor, linkState, setLinkState} = useTipTapTaskEditor(content, { + atmosphere, + teamId, + readOnly: !isEditingThisItem + }) const {useTaskChild, addTaskChild, removeTaskChild, isTaskFocused} = useTaskChildFocus(serviceTaskId) - const isEditingThisItem = !plaintextContent const updatePokerScope = () => { if (submitting || disabled) return @@ -96,7 +100,8 @@ const ParabolScopingSearchResultItem = (props: Props) => { const handleTaskUpdate = () => { if (!editor) return const isFocused = isTaskFocused() - if (editor.isEmpty && !isFocused) { + if (isFocused) return + if (editor.isEmpty) { DeleteTaskMutation(atmosphere, {taskId: serviceTaskId}) return } diff --git a/packages/client/components/ParabolScopingSearchResults.tsx b/packages/client/components/ParabolScopingSearchResults.tsx index 6817e51e344..630d790a602 100644 --- a/packages/client/components/ParabolScopingSearchResults.tsx +++ b/packages/client/components/ParabolScopingSearchResults.tsx @@ -1,4 +1,3 @@ -import styled from '@emotion/styled' import graphql from 'babel-plugin-relay/macro' import {useEffect, useState} from 'react' import {PreloadedQuery, useFragment, usePaginationFragment, usePreloadedQuery} from 'react-relay' @@ -17,9 +16,6 @@ import NewIntegrationRecordButton from './NewIntegrationRecordButton' import ParabolScopingSearchResultItem from './ParabolScopingSearchResultItem' import ParabolScopingSelectAllTasks from './ParabolScopingSelectAllTasks' -const ResultScroller = styled('div')({ - overflow: 'auto' -}) interface Props { queryRef: PreloadedQuery meetingRef: ParabolScopingSearchResults_meeting$key @@ -145,7 +141,7 @@ const ParabolScopingSearchResults = (props: Props) => { tasks={edges} meetingId={meetingId} /> - +
{edges.map(({node}) => { return ( { ) })} {lastItem} - +
{!isEditing && ( )} diff --git a/packages/client/components/promptResponse/TipTapEditor.tsx b/packages/client/components/promptResponse/TipTapEditor.tsx index cec1ec0a347..a7a231cfa01 100644 --- a/packages/client/components/promptResponse/TipTapEditor.tsx +++ b/packages/client/components/promptResponse/TipTapEditor.tsx @@ -12,7 +12,7 @@ interface Props extends React.ButtonHTMLAttributes { export const TipTapEditor = (props: Props) => { const {editor, linkState, setLinkState, showBubbleMenu, useLinkEditor} = props return ( -
+
{showBubbleMenu && setLinkState && ( )} diff --git a/packages/client/components/promptResponse/TipTapLinkMenu.tsx b/packages/client/components/promptResponse/TipTapLinkMenu.tsx index 8ad39cd0c72..113aaacde33 100644 --- a/packages/client/components/promptResponse/TipTapLinkMenu.tsx +++ b/packages/client/components/promptResponse/TipTapLinkMenu.tsx @@ -43,10 +43,11 @@ export const TipTapLinkMenu = (props: Props) => { return {link: '', text} } }) - - const handleEdit = useCallback(() => { + const oldLinkStateRef = useRef(null) + const handleEdit = () => { setLinkState('edit') - }, []) + oldLinkStateRef.current = 'preview' + } const onSetLink = useCallback( ({text, url}: {text: string; url: string}) => { @@ -104,7 +105,16 @@ export const TipTapLinkMenu = (props: Props) => { setLinkState(null) }, [editor]) const onOpenChange = (willOpen: boolean) => { - setLinkState(willOpen ? (editor.isActive('link') ? 'preview' : 'edit') : null) + const isLinkActive = editor.isActive('link') + if (willOpen) { + setLinkState(isLinkActive ? 'preview' : 'edit') + } else { + // special case when switching from preview to edit radix-ui triggers onOpenChange(false) + if (!(oldLinkStateRef.current === 'preview' && linkState === 'edit')) { + setLinkState(null) + } + oldLinkStateRef.current = null + } } const transformRef = useRef(undefined) const getTransform = () => { @@ -122,7 +132,7 @@ export const TipTapLinkMenu = (props: Props) => { { - // necessary for link preview to preview focusing the first button + // necessary for link preview to prevent focusing the first button e.preventDefault() }} > diff --git a/packages/client/modules/outcomeCard/components/OutcomeCard/OutcomeCard.tsx b/packages/client/modules/outcomeCard/components/OutcomeCard/OutcomeCard.tsx index e714856a2d2..b9a7f0fb2b8 100644 --- a/packages/client/modules/outcomeCard/components/OutcomeCard/OutcomeCard.tsx +++ b/packages/client/modules/outcomeCard/components/OutcomeCard/OutcomeCard.tsx @@ -13,7 +13,7 @@ import {LinkMenuState} from '../../../../components/promptResponse/TipTapLinkMen import TaskIntegrationLink from '../../../../components/TaskIntegrationLink' import TaskWatermark from '../../../../components/TaskWatermark' import useAtmosphere from '../../../../hooks/useAtmosphere' -import useTaskChildFocus, {UseTaskChild} from '../../../../hooks/useTaskChildFocus' +import {UseTaskChild} from '../../../../hooks/useTaskChildFocus' import UpdateTaskMutation from '../../../../mutations/UpdateTaskMutation' import {cardFocusShadow, cardHoverShadow, cardShadow, Elevation} from '../../../../styles/elevation' import cardRootStyles from '../../../../styles/helpers/cardRootStyles' @@ -55,8 +55,6 @@ const StatusIndicatorBlock = styled('div')({ display: 'flex' }) -const TaskEditorWrapper = styled('div')() - interface Props { area: AreaEnum isTaskFocused: boolean @@ -70,10 +68,14 @@ interface Props { task: OutcomeCard_task$key useTaskChild: UseTaskChild dataCy: string + addTaskChild(name: string): void + removeTaskChild(name: string): void } const OutcomeCard = memo((props: Props) => { const { + addTaskChild, + removeTaskChild, area, isTaskFocused, isTaskHovered, @@ -140,7 +142,6 @@ const OutcomeCard = memo((props: Props) => { const {viewerId} = atmosphere const otherEditors = editors.filter((editor) => editor.userId !== viewerId) const isEditing = editors.length > otherEditors.length - const {addTaskChild, removeTaskChild} = useTaskChildFocus(taskId) const type = integration?.__typename const statusTitle = `Card status: ${taskStatusLabels[status]}` const privateTitle = ', marked as #private' @@ -171,7 +172,8 @@ const OutcomeCard = memo((props: Props) => { {!type && ( - { removeTaskChild('root') setTimeout(handleCardUpdate) @@ -182,9 +184,11 @@ const OutcomeCard = memo((props: Props) => { editor={editor} linkState={linkState} setLinkState={setLinkState} - useLinkEditor={() => useTaskChild('editor-link-changer')} + useLinkEditor={() => { + useTaskChild('editor-link-changer') + }} /> - +
)} { const ref = useRef(null) const [isTaskHovered, setIsTaskHovered] = useState(false) - const {useTaskChild, isTaskFocused} = useTaskChildFocus(taskId) + const {useTaskChild, isTaskFocused, addTaskChild, removeTaskChild} = useTaskChildFocus(taskId) const isHighlighted = isTaskHovered || !!isDraggingOver useEffect(() => { @@ -81,12 +82,13 @@ const OutcomeCardContainer = memo((props: Props) => { const handleCardUpdate = () => { const isFocused = isTaskFocused() + if (isFocused) return if (editor.isEmpty && !isFocused) { DeleteTaskMutation(atmosphere, {taskId}) return } const nextContent = JSON.stringify(editor.getJSON()) - if (content === nextContent) return + if (areEqual(JSON.parse(content), editor.getJSON())) return const updatedTask = { id: taskId, content: nextContent @@ -118,6 +120,8 @@ const OutcomeCardContainer = memo((props: Props) => { isDraggingOver={isDraggingOver} task={task} useTaskChild={useTaskChild} + addTaskChild={addTaskChild} + removeTaskChild={removeTaskChild} /> ) diff --git a/packages/client/mutations/handlers/handleUpsertTasks.ts b/packages/client/mutations/handlers/handleUpsertTasks.ts index 7f7a59d020d..c564bee5e8c 100644 --- a/packages/client/mutations/handlers/handleUpsertTasks.ts +++ b/packages/client/mutations/handlers/handleUpsertTasks.ts @@ -4,6 +4,7 @@ import isTaskPrivate from '~/utils/isTaskPrivate' import {parseQueryParams} from '~/utils/useQueryParameterParser' import addNodeToArray from '../../utils/relay/addNodeToArray' import safeRemoveNodeFromConn from '../../utils/relay/safeRemoveNodeFromConn' +import safeRemoveNodeFromUnknownConn from '../../utils/relay/safeRemoveNodeFromUnknownConn' import getArchivedTasksConn from '../connections/getArchivedTasksConn' import getScopingTasksConn from '../connections/getScopingTasksConn' import getTeamTasksConn from '../connections/getTeamTasksConn' @@ -53,12 +54,18 @@ const handleUpsertTask = (task: Task | null, store: RecordSourceSelectorProxy safePutNodeInConn(archiveConn, task, store)) } else { archiveConns.forEach((archiveConn) => safeRemoveNodeFromConn(taskId, archiveConn)) safePutNodeInConn(teamConn, task, store) safePutNodeInConn(threadConn, task, store, 'threadSortOrder', true) addNodeToArray(task, meeting, 'tasks', 'createdAt') + /* updates parabol search query if task is created from a sprint poker meeting + * should also implement updating parabol search query if task is created elsewhere? + */ + const scopingTasksConn = getScopingTasksConn(store, meetingId, viewer, [teamId]) + safePutNodeInConn(scopingTasksConn, task, store, 'updatedAt', false) if (userConn) { const isPrivate = isTaskPrivate(tags) const ownedByViewer = task.getValue('userId') === viewerId @@ -69,11 +76,6 @@ const handleUpsertTask = (task: Task | null, store: RecordSourceSelectorProxy