mirror of
https://github.com/langgenius/dify.git
synced 2026-06-24 13:01:16 +08:00
feat(web): show snippet publish action
This commit is contained in:
parent
01957f302f
commit
b8cc01cf10
@ -7,35 +7,17 @@ import SnippetWorkflowPanel from './workflow-panel'
|
||||
type SnippetChildrenProps = {
|
||||
snippetId: string
|
||||
fields: SnippetInputField[]
|
||||
canDiscardChanges: boolean
|
||||
canEdit?: boolean
|
||||
canSave: boolean
|
||||
hasDraftChanges: boolean
|
||||
isEditing: boolean
|
||||
isPublishing: boolean
|
||||
onCancel: () => void
|
||||
onEdit: () => void
|
||||
onExitEditing: () => void | Promise<void>
|
||||
onExitEditingWithoutSave: () => void | Promise<void>
|
||||
onPublish: () => void
|
||||
onSaveAndExitEditing: () => void | Promise<void>
|
||||
}
|
||||
|
||||
const SnippetChildren = ({
|
||||
snippetId,
|
||||
fields,
|
||||
canDiscardChanges,
|
||||
canEdit = true,
|
||||
canSave,
|
||||
hasDraftChanges,
|
||||
isEditing,
|
||||
isPublishing,
|
||||
onCancel,
|
||||
onEdit,
|
||||
onExitEditing,
|
||||
onExitEditingWithoutSave,
|
||||
onPublish,
|
||||
onSaveAndExitEditing,
|
||||
}: SnippetChildrenProps) => {
|
||||
return (
|
||||
<>
|
||||
@ -43,18 +25,9 @@ const SnippetChildren = ({
|
||||
|
||||
<SnippetHeader
|
||||
snippetId={snippetId}
|
||||
canDiscardChanges={canDiscardChanges}
|
||||
canEdit={canEdit}
|
||||
canSave={canSave}
|
||||
hasDraftChanges={hasDraftChanges}
|
||||
isEditing={isEditing}
|
||||
isPublishing={isPublishing}
|
||||
onCancel={onCancel}
|
||||
onEdit={onEdit}
|
||||
onExitEditing={onExitEditing}
|
||||
onExitEditingWithoutSave={onExitEditingWithoutSave}
|
||||
onPublish={onPublish}
|
||||
onSaveAndExitEditing={onSaveAndExitEditing}
|
||||
/>
|
||||
|
||||
<SnippetWorkflowPanel
|
||||
|
||||
@ -1,28 +1,8 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { HeaderProps } from '@/app/components/workflow/header'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { expectLoadingButton } from '@/test/button'
|
||||
import SnippetHeader from '..'
|
||||
|
||||
vi.mock('@langgenius/dify-ui/alert-dialog', () => ({
|
||||
AlertDialog: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
AlertDialogActions: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
AlertDialogCancelButton: ({ children }: { children: ReactNode }) => <button type="button">{children}</button>,
|
||||
AlertDialogConfirmButton: ({
|
||||
children,
|
||||
disabled,
|
||||
onClick,
|
||||
}: {
|
||||
children: ReactNode
|
||||
disabled?: boolean
|
||||
onClick?: () => void
|
||||
}) => <button type="button" disabled={disabled} onClick={onClick}>{children}</button>,
|
||||
AlertDialogContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
AlertDialogDescription: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
AlertDialogTitle: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
AlertDialogTrigger: ({ children, render }: { children?: ReactNode, render?: ReactNode }) => render ?? <button type="button">{children}</button>,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/header', () => ({
|
||||
default: (props: HeaderProps) => {
|
||||
return (
|
||||
@ -44,242 +24,71 @@ vi.mock('@/app/components/workflow/header', () => ({
|
||||
}))
|
||||
|
||||
describe('SnippetHeader', () => {
|
||||
const mockCancel = vi.fn()
|
||||
const mockEdit = vi.fn()
|
||||
const mockExitEditing = vi.fn()
|
||||
const mockExitEditingWithoutSave = vi.fn()
|
||||
const mockPublish = vi.fn()
|
||||
const mockSaveAndExit = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// Verifies the wrapper passes the expected workflow header configuration.
|
||||
describe('Rendering', () => {
|
||||
it('should configure workflow header slots and hide workflow-only controls', () => {
|
||||
render(
|
||||
<SnippetHeader
|
||||
snippetId="snippet-1"
|
||||
canDiscardChanges
|
||||
canSave
|
||||
hasDraftChanges={false}
|
||||
isEditing={false}
|
||||
isPublishing={false}
|
||||
onCancel={mockCancel}
|
||||
onEdit={mockEdit}
|
||||
onExitEditing={mockExitEditing}
|
||||
onExitEditingWithoutSave={mockExitEditingWithoutSave}
|
||||
onPublish={mockPublish}
|
||||
onSaveAndExitEditing={mockSaveAndExit}
|
||||
/>,
|
||||
)
|
||||
it('should configure workflow header slots and hide workflow-only controls', () => {
|
||||
render(
|
||||
<SnippetHeader
|
||||
snippetId="snippet-1"
|
||||
canSave
|
||||
isPublishing={false}
|
||||
onPublish={mockPublish}
|
||||
/>,
|
||||
)
|
||||
|
||||
const header = screen.getByTestId('workflow-header')
|
||||
expect(header).toHaveAttribute('data-show-env', 'false')
|
||||
expect(header).toHaveAttribute('data-show-global-variable', 'false')
|
||||
expect(header).toHaveAttribute('data-history-url', '/snippets/snippet-1/workflow-runs')
|
||||
expect(screen.getByText('snippet.viewOnly')).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: /snippet\.edit/i })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: /snippet\.testRunButton/i })).toBeInTheDocument()
|
||||
})
|
||||
const header = screen.getByTestId('workflow-header')
|
||||
expect(header).toHaveAttribute('data-show-env', 'false')
|
||||
expect(header).toHaveAttribute('data-show-global-variable', 'false')
|
||||
expect(header).toHaveAttribute('data-history-url', '/snippets/snippet-1/workflow-runs')
|
||||
expect(screen.getByRole('button', { name: /snippet\.publishButton/i })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: /snippet\.testRunButton/i })).toBeInTheDocument()
|
||||
expect(screen.queryByText('snippet.viewOnly')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: /snippet\.edit/i })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: /snippet\.exitEditing/i })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Verifies forwarded callbacks still drive the snippet-specific controls.
|
||||
describe('User Interactions', () => {
|
||||
it('should invoke the snippet callbacks when save and discard are clicked in editing mode', () => {
|
||||
render(
|
||||
<SnippetHeader
|
||||
snippetId="snippet-1"
|
||||
canDiscardChanges
|
||||
canSave
|
||||
hasDraftChanges
|
||||
isEditing
|
||||
isPublishing={false}
|
||||
onCancel={mockCancel}
|
||||
onEdit={mockEdit}
|
||||
onExitEditing={mockExitEditing}
|
||||
onExitEditingWithoutSave={mockExitEditingWithoutSave}
|
||||
onPublish={mockPublish}
|
||||
onSaveAndExitEditing={mockSaveAndExit}
|
||||
/>,
|
||||
)
|
||||
it('should publish from the primary header action', () => {
|
||||
render(
|
||||
<SnippetHeader
|
||||
snippetId="snippet-1"
|
||||
canSave
|
||||
isPublishing={false}
|
||||
onPublish={mockPublish}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /^snippet\.save$/i }))
|
||||
fireEvent.click(screen.getByRole('button', { name: /snippet\.discardChanges/i }))
|
||||
fireEvent.click(screen.getByRole('button', { name: /snippet\.publishButton/i }))
|
||||
|
||||
expect(mockPublish).toHaveBeenCalledTimes(1)
|
||||
expect(mockCancel).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
expect(mockPublish).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should disable save actions when the current graph has no nodes', () => {
|
||||
render(
|
||||
<SnippetHeader
|
||||
snippetId="snippet-1"
|
||||
canDiscardChanges
|
||||
canSave={false}
|
||||
hasDraftChanges
|
||||
isEditing
|
||||
isPublishing={false}
|
||||
onCancel={mockCancel}
|
||||
onEdit={mockEdit}
|
||||
onExitEditing={mockExitEditing}
|
||||
onExitEditingWithoutSave={mockExitEditingWithoutSave}
|
||||
onPublish={mockPublish}
|
||||
onSaveAndExitEditing={mockSaveAndExit}
|
||||
/>,
|
||||
)
|
||||
it('should disable publish when the current graph has no nodes', () => {
|
||||
render(
|
||||
<SnippetHeader
|
||||
snippetId="snippet-1"
|
||||
canSave={false}
|
||||
isPublishing={false}
|
||||
onPublish={mockPublish}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: /^snippet\.save$/i })).toBeDisabled()
|
||||
expect(screen.getByRole('button', { name: /snippet\.saveAndExit/i })).toBeDisabled()
|
||||
expect(screen.getByRole('button', { name: /snippet\.doNotSave/i })).not.toBeDisabled()
|
||||
})
|
||||
expect(screen.getByRole('button', { name: /snippet\.publishButton/i })).toBeDisabled()
|
||||
})
|
||||
|
||||
it('should hide the discard draft action when there is no published workflow', () => {
|
||||
render(
|
||||
<SnippetHeader
|
||||
snippetId="snippet-1"
|
||||
canDiscardChanges={false}
|
||||
canSave
|
||||
hasDraftChanges
|
||||
isEditing
|
||||
isPublishing={false}
|
||||
onCancel={mockCancel}
|
||||
onEdit={mockEdit}
|
||||
onExitEditing={mockExitEditing}
|
||||
onExitEditingWithoutSave={mockExitEditingWithoutSave}
|
||||
onPublish={mockPublish}
|
||||
onSaveAndExitEditing={mockSaveAndExit}
|
||||
/>,
|
||||
)
|
||||
it('should show publish loading state while publishing', () => {
|
||||
render(
|
||||
<SnippetHeader
|
||||
snippetId="snippet-1"
|
||||
canSave
|
||||
isPublishing
|
||||
onPublish={mockPublish}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByText('snippet.discardDraft')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('snippet.editingDraft')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should enter editing mode from the readonly header action', () => {
|
||||
render(
|
||||
<SnippetHeader
|
||||
snippetId="snippet-1"
|
||||
canDiscardChanges
|
||||
canSave
|
||||
hasDraftChanges={false}
|
||||
isEditing={false}
|
||||
isPublishing={false}
|
||||
onCancel={mockCancel}
|
||||
onEdit={mockEdit}
|
||||
onExitEditing={mockExitEditing}
|
||||
onExitEditingWithoutSave={mockExitEditingWithoutSave}
|
||||
onPublish={mockPublish}
|
||||
onSaveAndExitEditing={mockSaveAndExit}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'snippet.edit' }))
|
||||
|
||||
expect(mockEdit).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should exit editing immediately when there are no draft changes', () => {
|
||||
render(
|
||||
<SnippetHeader
|
||||
snippetId="snippet-1"
|
||||
canDiscardChanges
|
||||
canSave
|
||||
hasDraftChanges={false}
|
||||
isEditing
|
||||
isPublishing={false}
|
||||
onCancel={mockCancel}
|
||||
onEdit={mockEdit}
|
||||
onExitEditing={mockExitEditing}
|
||||
onExitEditingWithoutSave={mockExitEditingWithoutSave}
|
||||
onPublish={mockPublish}
|
||||
onSaveAndExitEditing={mockSaveAndExit}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'snippet.exitEditing' }))
|
||||
|
||||
expect(mockExitEditing).toHaveBeenCalledTimes(1)
|
||||
expect(mockExitEditingWithoutSave).not.toHaveBeenCalled()
|
||||
expect(mockSaveAndExit).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should disable edit actions while publishing', () => {
|
||||
render(
|
||||
<SnippetHeader
|
||||
snippetId="snippet-1"
|
||||
canDiscardChanges
|
||||
canSave
|
||||
hasDraftChanges
|
||||
isEditing
|
||||
isPublishing
|
||||
onCancel={mockCancel}
|
||||
onEdit={mockEdit}
|
||||
onExitEditing={mockExitEditing}
|
||||
onExitEditingWithoutSave={mockExitEditingWithoutSave}
|
||||
onPublish={mockPublish}
|
||||
onSaveAndExitEditing={mockSaveAndExit}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: 'snippet.exitEditing' })).toBeDisabled()
|
||||
expectLoadingButton(screen.getByRole('button', { name: /^snippet\.save$/i }))
|
||||
expect(screen.getByRole('button', { name: 'snippet.doNotSave' })).toBeDisabled()
|
||||
})
|
||||
|
||||
it('should discard changes from the exit confirmation dialog', async () => {
|
||||
render(
|
||||
<SnippetHeader
|
||||
snippetId="snippet-1"
|
||||
canDiscardChanges
|
||||
canSave
|
||||
hasDraftChanges
|
||||
isEditing
|
||||
isPublishing={false}
|
||||
onCancel={mockCancel}
|
||||
onEdit={mockEdit}
|
||||
onExitEditing={mockExitEditing}
|
||||
onExitEditingWithoutSave={mockExitEditingWithoutSave}
|
||||
onPublish={mockPublish}
|
||||
onSaveAndExitEditing={mockSaveAndExit}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'snippet.exitEditing' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'snippet.doNotSave' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockExitEditingWithoutSave).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
expect(mockSaveAndExit).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should save and exit from the exit confirmation dialog', async () => {
|
||||
render(
|
||||
<SnippetHeader
|
||||
snippetId="snippet-1"
|
||||
canDiscardChanges
|
||||
canSave
|
||||
hasDraftChanges
|
||||
isEditing
|
||||
isPublishing={false}
|
||||
onCancel={mockCancel}
|
||||
onEdit={mockEdit}
|
||||
onExitEditing={mockExitEditing}
|
||||
onExitEditingWithoutSave={mockExitEditingWithoutSave}
|
||||
onPublish={mockPublish}
|
||||
onSaveAndExitEditing={mockSaveAndExit}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'snippet.exitEditing' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'snippet.saveAndExit' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSaveAndExit).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
expect(mockExitEditingWithoutSave).not.toHaveBeenCalled()
|
||||
})
|
||||
expectLoadingButton(screen.getByRole('button', { name: /snippet\.publishButton/i }))
|
||||
})
|
||||
})
|
||||
|
||||
@ -5,128 +5,42 @@ import { Button } from '@langgenius/dify-ui/button'
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Header from '@/app/components/workflow/header'
|
||||
import SaveBeforeLeavingDialog from '../save-before-leaving-dialog'
|
||||
import CancelChanges from './cancel-changes'
|
||||
import RunMode from './run-mode'
|
||||
|
||||
type SnippetHeaderProps = {
|
||||
snippetId: string
|
||||
canDiscardChanges: boolean
|
||||
canEdit?: boolean
|
||||
canSave: boolean
|
||||
hasDraftChanges: boolean
|
||||
isEditing: boolean
|
||||
isPublishing: boolean
|
||||
onCancel: () => void
|
||||
onEdit: () => void
|
||||
onExitEditing: () => void | Promise<void>
|
||||
onExitEditingWithoutSave: () => void | Promise<void>
|
||||
onPublish: () => void
|
||||
onSaveAndExitEditing: () => void | Promise<void>
|
||||
}
|
||||
|
||||
const ViewOnlyBadge = () => {
|
||||
const { t } = useTranslation('snippet')
|
||||
|
||||
return (
|
||||
<div className="rounded-md border border-components-badge-status-light-normal-border-inner bg-components-badge-bg-blue-light-soft px-1.5 py-0.5 system-xs-semibold-uppercase text-text-accent">
|
||||
{t('viewOnly')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const EditActions = ({
|
||||
canEdit = true,
|
||||
const PublishAction = ({
|
||||
canSave,
|
||||
hasDraftChanges,
|
||||
isEditing,
|
||||
isPublishing,
|
||||
onEdit,
|
||||
onExitEditing,
|
||||
onExitEditingWithoutSave,
|
||||
onPublish,
|
||||
onSaveAndExitEditing,
|
||||
}: Pick<SnippetHeaderProps, 'canEdit' | 'canSave' | 'hasDraftChanges' | 'isEditing' | 'isPublishing' | 'onEdit' | 'onExitEditing' | 'onExitEditingWithoutSave' | 'onPublish' | 'onSaveAndExitEditing'>) => {
|
||||
}: Pick<SnippetHeaderProps, 'canSave' | 'isPublishing' | 'onPublish'>) => {
|
||||
const { t } = useTranslation('snippet')
|
||||
const [exitConfirmOpen, setExitConfirmOpen] = useState(false)
|
||||
|
||||
if (!isEditing) {
|
||||
if (!canEdit)
|
||||
return null
|
||||
|
||||
return (
|
||||
<Button variant="primary" onClick={onEdit}>
|
||||
{t('edit')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SaveBeforeLeavingDialog
|
||||
open={exitConfirmOpen}
|
||||
onOpenChange={setExitConfirmOpen}
|
||||
trigger={(
|
||||
<Button
|
||||
disabled={isPublishing || !canEdit}
|
||||
onClick={(event) => {
|
||||
if (!canEdit)
|
||||
return
|
||||
|
||||
if (!hasDraftChanges) {
|
||||
event.preventDefault()
|
||||
void onExitEditing()
|
||||
return
|
||||
}
|
||||
|
||||
setExitConfirmOpen(true)
|
||||
}}
|
||||
>
|
||||
{t('exitEditing')}
|
||||
</Button>
|
||||
)}
|
||||
disabled={isPublishing || !canEdit}
|
||||
saveDisabled={!canEdit || !canSave}
|
||||
loading={isPublishing}
|
||||
onDiscard={async () => {
|
||||
await onExitEditingWithoutSave()
|
||||
setExitConfirmOpen(false)
|
||||
}}
|
||||
onSave={async () => {
|
||||
await onSaveAndExitEditing()
|
||||
setExitConfirmOpen(false)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
loading={isPublishing}
|
||||
disabled={isPublishing || !canEdit || !canSave}
|
||||
onClick={onPublish}
|
||||
>
|
||||
{t('save')}
|
||||
</Button>
|
||||
</>
|
||||
<Button
|
||||
variant="primary"
|
||||
loading={isPublishing}
|
||||
disabled={isPublishing || !canSave}
|
||||
onClick={onPublish}
|
||||
>
|
||||
{t('publishButton')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
const SnippetHeader = ({
|
||||
snippetId,
|
||||
canDiscardChanges,
|
||||
canEdit = true,
|
||||
canSave,
|
||||
hasDraftChanges,
|
||||
isEditing,
|
||||
isPublishing,
|
||||
onCancel,
|
||||
onEdit,
|
||||
onExitEditing,
|
||||
onExitEditingWithoutSave,
|
||||
onPublish,
|
||||
onSaveAndExitEditing,
|
||||
}: SnippetHeaderProps) => {
|
||||
const { t } = useTranslation('snippet')
|
||||
const viewHistoryProps = useMemo(() => {
|
||||
@ -139,21 +53,11 @@ const SnippetHeader = ({
|
||||
return {
|
||||
normal: {
|
||||
components: {
|
||||
title: isEditing
|
||||
? (hasDraftChanges ? <CancelChanges canDiscardChanges={canDiscardChanges} onCancel={onCancel} /> : <></>)
|
||||
: <ViewOnlyBadge />,
|
||||
left: (
|
||||
<EditActions
|
||||
canEdit={canEdit}
|
||||
<PublishAction
|
||||
canSave={canSave}
|
||||
hasDraftChanges={hasDraftChanges}
|
||||
isEditing={isEditing}
|
||||
isPublishing={isPublishing}
|
||||
onEdit={onEdit}
|
||||
onExitEditing={onExitEditing}
|
||||
onExitEditingWithoutSave={onExitEditingWithoutSave}
|
||||
onPublish={onPublish}
|
||||
onSaveAndExitEditing={onSaveAndExitEditing}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -174,7 +78,7 @@ const SnippetHeader = ({
|
||||
viewHistoryProps,
|
||||
},
|
||||
}
|
||||
}, [canDiscardChanges, canEdit, canSave, hasDraftChanges, isEditing, isPublishing, onCancel, onEdit, onExitEditing, onExitEditingWithoutSave, onPublish, onSaveAndExitEditing, t, viewHistoryProps])
|
||||
}, [canSave, isPublishing, onPublish, t, viewHistoryProps])
|
||||
|
||||
return <Header {...headerProps} />
|
||||
}
|
||||
|
||||
@ -93,19 +93,12 @@ const hasSnippetDraftNodes = (payload?: Omit<SnippetDraftSyncPayload, 'hash'> |
|
||||
const SnippetMainContent = ({
|
||||
snippetId,
|
||||
fields,
|
||||
canDiscardChanges,
|
||||
canEdit,
|
||||
canSave,
|
||||
hasDraftChanges,
|
||||
isEditing,
|
||||
onBeforePublish,
|
||||
onCancel,
|
||||
onDiscardRoute,
|
||||
onEdit,
|
||||
onExitEditing,
|
||||
onExitEditingWithoutSave,
|
||||
onSaved,
|
||||
onSavedAndExitEditing,
|
||||
}: SnippetMainContentProps) => {
|
||||
const { push } = useRouter()
|
||||
const { t } = useTranslation('snippet')
|
||||
@ -134,12 +127,6 @@ const SnippetMainContent = ({
|
||||
return didSave
|
||||
}, [handlePublish, onBeforePublish, onSaved, t])
|
||||
|
||||
const handleSaveAndExitEditing = useCallback(async () => {
|
||||
const didSave = await handlePublishSnippet()
|
||||
if (didSave)
|
||||
onSavedAndExitEditing()
|
||||
}, [handlePublishSnippet, onSavedAndExitEditing])
|
||||
|
||||
const navigateToPendingHref = useCallback((href: string) => {
|
||||
const url = new URL(href, window.location.href)
|
||||
if (url.origin === window.location.origin)
|
||||
@ -226,18 +213,9 @@ const SnippetMainContent = ({
|
||||
<SnippetChildren
|
||||
snippetId={snippetId}
|
||||
fields={fields}
|
||||
canDiscardChanges={canDiscardChanges}
|
||||
canEdit={canEdit}
|
||||
canSave={canSave}
|
||||
hasDraftChanges={hasDraftChanges}
|
||||
isEditing={isEditing}
|
||||
isPublishing={isPublishing}
|
||||
onCancel={onCancel}
|
||||
onEdit={onEdit}
|
||||
onExitEditing={onExitEditing}
|
||||
onExitEditingWithoutSave={onExitEditingWithoutSave}
|
||||
onPublish={handlePublishSnippet}
|
||||
onSaveAndExitEditing={handleSaveAndExitEditing}
|
||||
/>
|
||||
<SaveBeforeLeavingDialog
|
||||
open={!!pendingHref}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user