feat: shorts
This commit is contained in:
parent
9776bbf7e1
commit
215bd8fbc0
|
|
@ -1,5 +1,6 @@
|
|||
import PortfolioEditor from "./portfolio-editor"
|
||||
import PostEditor from "./post-editor"
|
||||
import ShortEditor from "./short-editor"
|
||||
|
||||
export default function SubdomainEditor({
|
||||
searchParams,
|
||||
|
|
@ -10,6 +11,8 @@ export default function SubdomainEditor({
|
|||
}) {
|
||||
if (searchParams?.type === "portfolio") {
|
||||
return <PortfolioEditor />
|
||||
} else if (searchParams?.type === "short") {
|
||||
return <ShortEditor />
|
||||
} else {
|
||||
return <PostEditor />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,332 @@
|
|||
"use client"
|
||||
|
||||
import { nanoid } from "nanoid"
|
||||
import { useParams, useRouter, useSearchParams } from "next/navigation"
|
||||
import { memo, useCallback, useEffect, useState } from "react"
|
||||
import toast from "react-hot-toast"
|
||||
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
|
||||
import { DashboardMain } from "~/components/dashboard/DashboardMain"
|
||||
import { PublishButton } from "~/components/dashboard/PublishButton"
|
||||
import PublishedModal from "~/components/dashboard/PublishedModal"
|
||||
import EditorContent from "~/components/dashboard/editor-properties/EditorContent"
|
||||
import EditorImages from "~/components/dashboard/editor-properties/EditorImages"
|
||||
import EditorPublishAt from "~/components/dashboard/editor-properties/EditorPublishAt"
|
||||
import EditorTitle from "~/components/dashboard/editor-properties/EditorTitle"
|
||||
import { useModalStack } from "~/components/ui/ModalStack"
|
||||
import {
|
||||
Values,
|
||||
initialEditorState,
|
||||
useEditorState,
|
||||
} from "~/hooks/useEditorState"
|
||||
import { useGetState } from "~/hooks/useGetState"
|
||||
import { useBeforeMounted } from "~/hooks/useSyncOnce"
|
||||
import { showConfetti } from "~/lib/confetti"
|
||||
import { CSB_SCAN } from "~/lib/env"
|
||||
import { getTwitterShareUrl } from "~/lib/helpers"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
import { getPageVisibility } from "~/lib/page-helpers"
|
||||
import { delStorage, setStorage } from "~/lib/storage"
|
||||
import { PageVisibilityEnum } from "~/lib/types"
|
||||
import { cn } from "~/lib/utils"
|
||||
import {
|
||||
useCreatePage,
|
||||
useDeletePage,
|
||||
useGetPage,
|
||||
useUpdatePage,
|
||||
} from "~/queries/page"
|
||||
import { useGetSite } from "~/queries/site"
|
||||
|
||||
export default function ShortEditor() {
|
||||
const router = useRouter()
|
||||
const queryClient = useQueryClient()
|
||||
const { t } = useTranslation("dashboard")
|
||||
const params = useParams()
|
||||
const subdomain = params?.subdomain as string
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
let pageId = searchParams?.get("id") as string | undefined
|
||||
const type = "short"
|
||||
|
||||
const site = useGetSite(subdomain)
|
||||
|
||||
const [draftKey, setDraftKey] = useState<string>("")
|
||||
useEffect(() => {
|
||||
if (subdomain) {
|
||||
let key
|
||||
if (!pageId) {
|
||||
const randomId = nanoid()
|
||||
key = `draft-${site.data?.characterId}-!local-${randomId}`
|
||||
queryClient.invalidateQueries([
|
||||
"getPagesBySite",
|
||||
site.data?.characterId,
|
||||
])
|
||||
router.replace(
|
||||
`/dashboard/${subdomain}/editor?id=!local-${randomId}&type=${type}`,
|
||||
)
|
||||
} else {
|
||||
key = `draft-${site.data?.characterId}-${pageId}`
|
||||
}
|
||||
setDraftKey(key)
|
||||
}
|
||||
}, [
|
||||
subdomain,
|
||||
pageId,
|
||||
queryClient,
|
||||
router,
|
||||
site.data?.characterId,
|
||||
searchParams,
|
||||
])
|
||||
|
||||
const page = useGetPage({
|
||||
characterId: site.data?.characterId,
|
||||
noteId: pageId && /\d+/.test(pageId) ? +pageId : undefined,
|
||||
slug: pageId || draftKey.replace(`draft-${site.data?.characterId}-`, ""),
|
||||
handle: subdomain,
|
||||
})
|
||||
|
||||
const [visibility, setVisibility] = useState<PageVisibilityEnum>()
|
||||
|
||||
useEffect(() => {
|
||||
if (page.isSuccess) {
|
||||
setVisibility(getPageVisibility(page.data || undefined))
|
||||
}
|
||||
}, [page.isSuccess, page.data])
|
||||
|
||||
// reset editor state when page changes
|
||||
useBeforeMounted(() => {
|
||||
useEditorState.setState({
|
||||
...initialEditorState,
|
||||
})
|
||||
})
|
||||
|
||||
const values = useEditorState()
|
||||
|
||||
const getValues = useGetState(values)
|
||||
const updateValue = useCallback(
|
||||
(val: Partial<Values>) => {
|
||||
if (visibility !== PageVisibilityEnum.Draft) {
|
||||
setVisibility(PageVisibilityEnum.Modified)
|
||||
}
|
||||
|
||||
const values = getValues()
|
||||
const newValues = { ...values, ...val }
|
||||
if (draftKey) {
|
||||
setStorage(draftKey, {
|
||||
date: +new Date(),
|
||||
values: newValues,
|
||||
type,
|
||||
})
|
||||
queryClient.invalidateQueries([
|
||||
"getPagesBySite",
|
||||
site.data?.characterId,
|
||||
])
|
||||
}
|
||||
useEditorState.setState(newValues)
|
||||
},
|
||||
[visibility],
|
||||
)
|
||||
|
||||
// Save
|
||||
const createPage = useCreatePage()
|
||||
const updatePage = useUpdatePage()
|
||||
|
||||
const savePage = async () => {
|
||||
const baseValues = {
|
||||
...values,
|
||||
characterId: site.data?.characterId,
|
||||
}
|
||||
if (visibility === PageVisibilityEnum.Draft) {
|
||||
createPage.mutate({
|
||||
...baseValues,
|
||||
type,
|
||||
})
|
||||
} else {
|
||||
updatePage.mutate({
|
||||
...baseValues,
|
||||
noteId: page?.data?.noteId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const { present } = useModalStack()
|
||||
|
||||
useEffect(() => {
|
||||
if (createPage.isSuccess || updatePage.isSuccess) {
|
||||
if (draftKey) {
|
||||
delStorage(draftKey)
|
||||
queryClient.invalidateQueries([
|
||||
"getPagesBySite",
|
||||
site.data?.characterId,
|
||||
])
|
||||
queryClient.invalidateQueries([
|
||||
"getPage",
|
||||
draftKey.replace(`draft-${site.data?.characterId}-`, ""),
|
||||
])
|
||||
} else {
|
||||
queryClient.invalidateQueries(["getPage", pageId])
|
||||
}
|
||||
|
||||
if (createPage.data?.noteId) {
|
||||
router.replace(
|
||||
`/dashboard/${subdomain}/editor?id=${createPage.data?.noteId}&type=${type}`,
|
||||
)
|
||||
}
|
||||
|
||||
const transactionUrl = `${CSB_SCAN}/tx/${
|
||||
page.data?.updatedTransactionHash || page.data?.transactionHash // TODO
|
||||
}`
|
||||
|
||||
const modalId = "publish-modal"
|
||||
present({
|
||||
title: `🎉 ${t("Published!")}`,
|
||||
id: modalId,
|
||||
content: (props) => (
|
||||
<PublishedModal transactionUrl={transactionUrl} {...props} />
|
||||
),
|
||||
})
|
||||
|
||||
showConfetti()
|
||||
|
||||
createPage.reset()
|
||||
updatePage.reset()
|
||||
}
|
||||
}, [createPage.isSuccess, updatePage.isSuccess])
|
||||
|
||||
useEffect(() => {
|
||||
if (createPage.isError || updatePage.isError) {
|
||||
toast.error("Error: " + (createPage.error || updatePage.error))
|
||||
createPage.reset()
|
||||
updatePage.reset()
|
||||
}
|
||||
}, [createPage.isError, updatePage.isSuccess])
|
||||
|
||||
// Delete
|
||||
const deleteP = useDeletePage()
|
||||
const deletePage = async () => {
|
||||
if (page.data) {
|
||||
if (!page.data?.noteId) {
|
||||
// Is draft
|
||||
delStorage(`draft-${page.data.characterId}-${page.data.draftKey}`)
|
||||
} else {
|
||||
// Is Note
|
||||
return deleteP.mutate({
|
||||
noteId: page.data.noteId,
|
||||
characterId: page.data.characterId,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (deleteP.isSuccess) {
|
||||
toast.success(t("Deleted!"))
|
||||
deleteP.reset()
|
||||
router.push(`/dashboard/${subdomain}/${type}s`)
|
||||
}
|
||||
}, [deleteP.isSuccess])
|
||||
|
||||
// Init
|
||||
useEffect(() => {
|
||||
if (!page.data?.metadata || !draftKey) return
|
||||
useEditorState.setState({
|
||||
published: !!page.data.noteId,
|
||||
title: page.data.metadata?.content?.title || "",
|
||||
publishedAt: page.data.metadata?.content?.date_published,
|
||||
content: page.data.metadata?.content?.content || "",
|
||||
images:
|
||||
page.data.metadata?.content?.attachments?.filter(
|
||||
(attachment) => attachment.name === "image",
|
||||
) || [],
|
||||
})
|
||||
}, [page.data, subdomain, draftKey, site.data?.characterId])
|
||||
|
||||
// Reset
|
||||
const discardChanges = useCallback(() => {
|
||||
if (draftKey) {
|
||||
delStorage(draftKey)
|
||||
queryClient.invalidateQueries(["getPagesBySite", site.data?.characterId])
|
||||
page.remove()
|
||||
page.refetch()
|
||||
}
|
||||
}, [draftKey, site.data?.characterId])
|
||||
|
||||
return (
|
||||
<>
|
||||
<DashboardMain className="max-w-screen-lg" title="Edit short">
|
||||
{page.isLoading ? (
|
||||
<div className="flex justify-center items-center min-h-[300px]">
|
||||
{t("Loading")}...
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<EditorExtraProperties updateValue={updateValue} />
|
||||
<div className="flex justify-between h-14 items-center text-sm mt-8">
|
||||
<div className="flex items-center space-x-3 flex-shrink-0">
|
||||
<PublishButton
|
||||
savePage={savePage}
|
||||
deletePage={deletePage}
|
||||
twitterShareUrl={
|
||||
page.data && site.data
|
||||
? getTwitterShareUrl({
|
||||
page: page.data,
|
||||
site: site.data,
|
||||
t,
|
||||
})
|
||||
: ""
|
||||
}
|
||||
published={visibility !== PageVisibilityEnum.Draft}
|
||||
isSaving={
|
||||
createPage.isLoading ||
|
||||
updatePage.isLoading ||
|
||||
deleteP.isLoading
|
||||
}
|
||||
isDisabled={false}
|
||||
type={type}
|
||||
isModified={visibility === PageVisibilityEnum.Modified}
|
||||
discardChanges={discardChanges}
|
||||
placement="top-start"
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
`text-sm capitalize`,
|
||||
visibility === PageVisibilityEnum.Draft
|
||||
? `text-zinc-300`
|
||||
: visibility === PageVisibilityEnum.Modified
|
||||
? "text-orange-600"
|
||||
: "text-green-600",
|
||||
)}
|
||||
>
|
||||
{t(visibility as string)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DashboardMain>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const EditorExtraProperties = memo(
|
||||
({ updateValue }: { updateValue: (val: Partial<Values>) => void }) => {
|
||||
const { t } = useTranslation("dashboard")
|
||||
|
||||
return (
|
||||
<div className="w-full space-y-5">
|
||||
<EditorImages updateValue={updateValue} />
|
||||
<EditorTitle updateValue={updateValue} />
|
||||
<EditorContent updateValue={updateValue} />
|
||||
<EditorPublishAt
|
||||
updateValue={updateValue}
|
||||
prompt={t(
|
||||
"This short will be accessible from this time. Leave blank to use the current time.",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
EditorExtraProperties.displayName = "EditorExtraProperties"
|
||||
|
|
@ -120,6 +120,13 @@ export default function DashboardLayout({
|
|||
text: "Pages",
|
||||
lever: 2,
|
||||
},
|
||||
{
|
||||
href: `/dashboard/${subdomain}/shorts`,
|
||||
isActive: ({ href, pathname }) => href === pathname,
|
||||
icon: "icon-[mingcute--ins-line]",
|
||||
text: "Shorts",
|
||||
lever: 2,
|
||||
},
|
||||
{
|
||||
href: `/dashboard/${subdomain}/portfolios`,
|
||||
isActive: ({ href, pathname }) => href === pathname,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import { PagesManager } from "~/components/dashboard/PagesManager"
|
||||
|
||||
export default function SubdomainPages() {
|
||||
return <PagesManager type="short" />
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { ChangeEvent } from "react"
|
||||
|
||||
import { Input } from "~/components/ui/Input"
|
||||
import { Values, useEditorState } from "~/hooks/useEditorState"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
|
||||
export default function EditorContent({
|
||||
updateValue,
|
||||
prompt,
|
||||
}: {
|
||||
updateValue: (val: Partial<Values>) => void
|
||||
prompt?: string
|
||||
}) {
|
||||
const { t } = useTranslation("dashboard")
|
||||
const value = useEditorState((state) => state.content)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
label={t("Content") || ""}
|
||||
isBlock
|
||||
name="content"
|
||||
id="content"
|
||||
value={value}
|
||||
multiline
|
||||
rows={4}
|
||||
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
updateValue({
|
||||
content: e.target.value,
|
||||
})
|
||||
}}
|
||||
help={prompt}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import { useState } from "react"
|
||||
|
||||
import { FieldLabel } from "~/components/ui/FieldLabel"
|
||||
import { ImageUploader } from "~/components/ui/ImageUploader"
|
||||
import { Values, useEditorState } from "~/hooks/useEditorState"
|
||||
import { useTranslation } from "~/lib/i18n/client"
|
||||
|
||||
export default function EditorImages({
|
||||
updateValue,
|
||||
prompt,
|
||||
}: {
|
||||
updateValue: (val: Partial<Values>) => void
|
||||
prompt?: string
|
||||
}) {
|
||||
const { t } = useTranslation("dashboard")
|
||||
const value = useEditorState((state) => state.images)
|
||||
const [extraValue, setExtraValue] = useState(undefined)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FieldLabel label={t("Images")} />
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{value.map((image, index) => (
|
||||
<ImageUploader
|
||||
key={image.address}
|
||||
className="aspect-video rounded-lg"
|
||||
value={image}
|
||||
hasClose={true}
|
||||
withMimeType={true}
|
||||
uploadEnd={(key) => {
|
||||
const tmpValue = [...value]
|
||||
if (key) {
|
||||
tmpValue[index] = key
|
||||
} else {
|
||||
tmpValue.splice(index, 1)
|
||||
}
|
||||
updateValue({
|
||||
images: tmpValue,
|
||||
})
|
||||
}}
|
||||
accept="image/*"
|
||||
/>
|
||||
))}
|
||||
<ImageUploader
|
||||
key={"image"}
|
||||
className="aspect-video rounded-lg"
|
||||
withMimeType={true}
|
||||
value={extraValue}
|
||||
disablePreview={true}
|
||||
uploadEnd={(key) => {
|
||||
if (key) {
|
||||
const tmpValue = [
|
||||
...value,
|
||||
{
|
||||
address: key.address,
|
||||
mime_type: key.mime_type,
|
||||
},
|
||||
]
|
||||
updateValue({
|
||||
images: tmpValue,
|
||||
})
|
||||
}
|
||||
setExtraValue(undefined)
|
||||
}}
|
||||
accept="image/*"
|
||||
/>
|
||||
</div>
|
||||
{prompt && <div className="text-xs text-gray-400 mt-1">{prompt}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ export const ImageUploader = forwardRef(function ImageUploader(
|
|||
withMimeType,
|
||||
hasClose,
|
||||
accept,
|
||||
disablePreview,
|
||||
...inputProps
|
||||
}: {
|
||||
className?: string
|
||||
|
|
@ -30,6 +31,7 @@ export const ImageUploader = forwardRef(function ImageUploader(
|
|||
uploadStart?: () => void
|
||||
hasClose?: boolean
|
||||
accept?: string
|
||||
disablePreview?: boolean
|
||||
} & (
|
||||
| {
|
||||
withMimeType?: false
|
||||
|
|
@ -55,9 +57,11 @@ export const ImageUploader = forwardRef(function ImageUploader(
|
|||
|
||||
const handleChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files?.[0]) {
|
||||
getBase64(event.target.files[0], (url) => {
|
||||
setImageUrl(url)
|
||||
})
|
||||
if (!disablePreview) {
|
||||
getBase64(event.target.files[0], (url) => {
|
||||
setImageUrl(url)
|
||||
})
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
uploadStart?.()
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ export const initialEditorState = {
|
|||
},
|
||||
disableAISummary: false,
|
||||
externalUrl: "",
|
||||
images: [],
|
||||
}
|
||||
|
||||
export type Values = {
|
||||
title: string
|
||||
publishedAt: string
|
||||
|
|
@ -29,6 +31,10 @@ export type Values = {
|
|||
}
|
||||
disableAISummary: boolean
|
||||
externalUrl: string
|
||||
images: {
|
||||
address?: string
|
||||
mime_type?: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export const useEditorState = create<
|
||||
|
|
|
|||
|
|
@ -63,14 +63,25 @@ export const expandCrossbellNote = async ({
|
|||
)?.address || rendered?.cover
|
||||
|
||||
expandedNote.metadata.content.images = []
|
||||
|
||||
const cover = expandedNote.metadata?.content?.attachments?.find(
|
||||
(attachment) => attachment.name === "cover",
|
||||
)?.address
|
||||
if (cover) {
|
||||
expandedNote.metadata.content.images.push(cover)
|
||||
}
|
||||
|
||||
const attachmentsImages = expandedNote.metadata?.content?.attachments
|
||||
?.filter(
|
||||
(attachment) => attachment.name === "image" && attachment.address,
|
||||
)
|
||||
.map((attachment) => attachment.address!)
|
||||
expandedNote.metadata.content.images =
|
||||
expandedNote.metadata.content.images.concat(attachmentsImages || [])
|
||||
|
||||
expandedNote.metadata.content.images =
|
||||
expandedNote.metadata.content.images.concat(rendered?.images || [])
|
||||
|
||||
expandedNote.metadata.content.images = [
|
||||
...new Set(expandedNote.metadata.content.images),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -124,4 +124,4 @@ export type ExpandedCharacter = CharacterEntity & {
|
|||
|
||||
export type ColorScheme = "dark" | "light"
|
||||
|
||||
export type NoteType = "post" | "page" | "portfolio"
|
||||
export type NoteType = "post" | "page" | "portfolio" | "short"
|
||||
|
|
|
|||
|
|
@ -143,6 +143,13 @@ const getLocalPages = (input: {
|
|||
},
|
||||
]
|
||||
: []),
|
||||
...(page.values.images?.length
|
||||
? page.values.images?.map((image: any) => ({
|
||||
name: "image",
|
||||
address: image?.address,
|
||||
mime_type: image?.mime_type,
|
||||
}))
|
||||
: []),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
useQueryClient,
|
||||
} from "@tanstack/react-query"
|
||||
|
||||
import { type Values } from "~/hooks/useEditorState"
|
||||
import createSearchParams from "~/lib/search-params"
|
||||
import { NoteType } from "~/lib/types"
|
||||
import * as pageModel from "~/models/page.model"
|
||||
|
|
@ -214,22 +215,12 @@ export function useCreatePage() {
|
|||
const { mutateAsync: _, ...postNote } = usePostNote()
|
||||
|
||||
const mutate = useRefCallback(
|
||||
(input: {
|
||||
characterId?: number
|
||||
slug?: string
|
||||
tags?: string
|
||||
title?: string
|
||||
content?: string
|
||||
publishedAt?: string
|
||||
excerpt?: string
|
||||
type?: NoteType
|
||||
cover?: {
|
||||
address?: string
|
||||
mime_type?: string
|
||||
}
|
||||
disableAISummary?: boolean
|
||||
externalUrl?: string
|
||||
}) => {
|
||||
(
|
||||
input: {
|
||||
characterId?: number
|
||||
type?: NoteType
|
||||
} & Values,
|
||||
) => {
|
||||
if (!input.characterId) {
|
||||
throw new Error("characterId is required")
|
||||
}
|
||||
|
|
@ -274,6 +265,13 @@ export function useCreatePage() {
|
|||
},
|
||||
]
|
||||
: []),
|
||||
...(input.images?.length
|
||||
? input.images?.map((image) => ({
|
||||
name: "image",
|
||||
address: image.address,
|
||||
mime_type: image.mime_type,
|
||||
}))
|
||||
: []),
|
||||
],
|
||||
external_urls: [input.externalUrl],
|
||||
} as NoteMetadata & {
|
||||
|
|
@ -301,23 +299,13 @@ export function useUpdatePage() {
|
|||
const { mutateAsync: _, ...updateNote } = useUpdateNote()
|
||||
|
||||
const mutate = useRefCallback(
|
||||
(input: {
|
||||
noteId?: number
|
||||
characterId?: number
|
||||
slug?: string
|
||||
tags?: string
|
||||
title?: string
|
||||
content?: string
|
||||
publishedAt?: string
|
||||
excerpt?: string
|
||||
type?: NoteType
|
||||
cover?: {
|
||||
address?: string
|
||||
mime_type?: string
|
||||
}
|
||||
disableAISummary?: boolean
|
||||
externalUrl?: string
|
||||
}) => {
|
||||
(
|
||||
input: {
|
||||
noteId?: number
|
||||
characterId?: number
|
||||
type?: NoteType
|
||||
} & Values,
|
||||
) => {
|
||||
if (!input.characterId || !input.noteId) {
|
||||
throw new Error("characterId and noteId are required")
|
||||
}
|
||||
|
|
@ -412,6 +400,21 @@ export function useUpdatePage() {
|
|||
}
|
||||
}
|
||||
|
||||
if (input.images?.length) {
|
||||
if (!metadataDraft.attachments) {
|
||||
metadataDraft.attachments = []
|
||||
}
|
||||
metadataDraft.attachments = metadataDraft.attachments
|
||||
?.filter((attr) => attr.name !== "image")
|
||||
.concat(
|
||||
input.images?.map((image) => ({
|
||||
name: "image",
|
||||
address: image.address,
|
||||
mime_type: image.mime_type,
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
if (input.externalUrl) {
|
||||
if (!metadataDraft.external_urls) {
|
||||
metadataDraft.external_urls = []
|
||||
|
|
|
|||
Loading…
Reference in New Issue