|
|
@ -1,5 +1,5 @@
|
|
|
|
import React from "react"
|
|
|
|
import React from "react"
|
|
|
|
import { Box, Heading, HStack, Image, Tag, Text, useMediaQuery, VStack } from "@chakra-ui/react"
|
|
|
|
import { Box, Flex, Heading, HStack, Image, Tag, Text, useMediaQuery, VStack, AlertDialog, AlertDialogOverlay, AlertDialogContent, AlertDialogHeader, AlertDialogBody, AlertDialogFooter, Button } from "@chakra-ui/react"
|
|
|
|
import { Story } from "src/types/story"
|
|
|
|
import { Story } from "src/types/story"
|
|
|
|
import StoryAuthor from "./story-author"
|
|
|
|
import StoryAuthor from "./story-author"
|
|
|
|
import Link from "next/link"
|
|
|
|
import Link from "next/link"
|
|
|
@ -9,7 +9,6 @@ import { getSvgIcon } from "components/svg-icon"
|
|
|
|
import Count from "components/count"
|
|
|
|
import Count from "components/count"
|
|
|
|
import Highlighter from 'react-highlight-words';
|
|
|
|
import Highlighter from 'react-highlight-words';
|
|
|
|
import { IDType } from "src/types/id"
|
|
|
|
import { IDType } from "src/types/id"
|
|
|
|
import { ReserveUrls } from "src/data/reserve-urls"
|
|
|
|
|
|
|
|
import { getCommentsUrl, getStoryUrl } from "utils/story"
|
|
|
|
import { getCommentsUrl, getStoryUrl } from "utils/story"
|
|
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
interface Props {
|
|
|
@ -17,6 +16,7 @@ interface Props {
|
|
|
|
type?: string
|
|
|
|
type?: string
|
|
|
|
highlight?: string
|
|
|
|
highlight?: string
|
|
|
|
showOrg?: boolean
|
|
|
|
showOrg?: boolean
|
|
|
|
|
|
|
|
onRemove?: any
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -25,47 +25,85 @@ export const StoryCard = (props: Props) => {
|
|
|
|
const [isLargeScreen] = useMediaQuery("(min-width: 768px)")
|
|
|
|
const [isLargeScreen] = useMediaQuery("(min-width: 768px)")
|
|
|
|
const Layout = isLargeScreen ? HStack : VStack
|
|
|
|
const Layout = isLargeScreen ? HStack : VStack
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [isOpen, setIsOpen] = React.useState(false)
|
|
|
|
|
|
|
|
const onClose = () => setIsOpen(false)
|
|
|
|
|
|
|
|
const cancelRef = React.useRef()
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<VStack alignItems="left" spacing={type === "classic" ? 4 : 2} p="2">
|
|
|
|
<>
|
|
|
|
<StoryAuthor story={story} showFooter={false} size="md" showOrg={props.showOrg}/>
|
|
|
|
<VStack alignItems="left" spacing={type === "classic" ? 4 : 2} p="2">
|
|
|
|
<a href={getStoryUrl(story)} target="_blank">
|
|
|
|
<Flex justifyContent="space-between" alignItems="center">
|
|
|
|
<Layout alignItems={isLargeScreen ? "top" : "left"} cursor="pointer" pl="2" pt="1">
|
|
|
|
<StoryAuthor story={story} showFooter={false} size="md" showOrg={props.showOrg} />
|
|
|
|
<VStack alignItems="left" spacing={type === "classic" ? 3 : 2} width={isLargeScreen && type === "classic" ? "calc(100% - 15rem)" : '100%'}>
|
|
|
|
{props.onRemove && <Box cursor="pointer" onClick={() => setIsOpen(true)}>{getSvgIcon("close", "1.1rem")}</Box>}
|
|
|
|
<Heading size="md" fontSize={type === "classic" ? '1.3rem' : '1.2rem'}>
|
|
|
|
</Flex>
|
|
|
|
<Highlighter
|
|
|
|
<a href={getStoryUrl(story)} target="_blank">
|
|
|
|
highlightClassName="highlight-search-match"
|
|
|
|
<Layout alignItems={isLargeScreen ? "top" : "left"} cursor="pointer" pl="2" pt="1">
|
|
|
|
textToHighlight={story.title}
|
|
|
|
<VStack alignItems="left" spacing={type === "classic" ? 3 : 2} width={isLargeScreen && type === "classic" ? "calc(100% - 15rem)" : '100%'}>
|
|
|
|
searchWords={[props.highlight]}
|
|
|
|
<Heading size="md" fontSize={type === "classic" ? '1.3rem' : '1.2rem'}>
|
|
|
|
/>
|
|
|
|
<Highlighter
|
|
|
|
{story.type === IDType.Series && <Tag size="sm" ml="2" mt="2px">SERIES</Tag>}
|
|
|
|
highlightClassName="highlight-search-match"
|
|
|
|
{story.pinned && <Tag size="sm" ml="2" mt="2px">置顶</Tag>}
|
|
|
|
textToHighlight={story.title}
|
|
|
|
</Heading>
|
|
|
|
searchWords={[props.highlight]}
|
|
|
|
{type !== "classic" && <HStack>{story.rawTags.map(t => <Text layerStyle="textSecondary" fontSize="md">#{t.name}</Text>)}</HStack>}
|
|
|
|
/>
|
|
|
|
<Text layerStyle={type === "classic" ? "textSecondary" : null}>
|
|
|
|
{story.type === IDType.Series && <Tag size="sm" ml="2" mt="2px">SERIES</Tag>}
|
|
|
|
<Highlighter
|
|
|
|
{story.pinned && <Tag size="sm" ml="2" mt="2px">置顶</Tag>}
|
|
|
|
highlightClassName="highlight-search-match"
|
|
|
|
</Heading>
|
|
|
|
textToHighlight={story.brief}
|
|
|
|
|
|
|
|
searchWords={[props.highlight]}
|
|
|
|
|
|
|
|
/></Text>
|
|
|
|
|
|
|
|
</VStack>
|
|
|
|
|
|
|
|
{story.cover && type === "classic" && <Image src={story.cover} width="15rem" height="120px" pt={isLargeScreen ? 0 : 2} borderRadius="4px" />}
|
|
|
|
|
|
|
|
</Layout>
|
|
|
|
|
|
|
|
</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<HStack pl="2" spacing="5">
|
|
|
|
{type !== "classic" && <HStack>{story.rawTags.map(t => <Text layerStyle="textSecondary" fontSize="md">#{t.name}</Text>)}</HStack>}
|
|
|
|
<Like storyID={story.id} liked={story.liked} count={story.likes} fontSize="18px" />
|
|
|
|
<Text layerStyle={type === "classic" ? "textSecondary" : null}>
|
|
|
|
<a href={`${getCommentsUrl(story)}#comments`} target="_blank">
|
|
|
|
<Highlighter
|
|
|
|
<HStack opacity="0.9" cursor="pointer">
|
|
|
|
highlightClassName="highlight-search-match"
|
|
|
|
{getSvgIcon("comments", "1.3rem")}
|
|
|
|
textToHighlight={story.brief}
|
|
|
|
<Text ml="2"><Count count={story.comments} /></Text>
|
|
|
|
searchWords={[props.highlight]}
|
|
|
|
</HStack>
|
|
|
|
/></Text>
|
|
|
|
|
|
|
|
</VStack>
|
|
|
|
|
|
|
|
{story.cover && type === "classic" && <Image src={story.cover} width="15rem" height="120px" pt={isLargeScreen ? 0 : 2} borderRadius="4px" />}
|
|
|
|
|
|
|
|
</Layout>
|
|
|
|
</a>
|
|
|
|
</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<HStack pl="2" spacing="5">
|
|
|
|
|
|
|
|
<Like storyID={story.id} liked={story.liked} count={story.likes} fontSize="18px" />
|
|
|
|
|
|
|
|
<a href={`${getCommentsUrl(story)}#comments`} target="_blank">
|
|
|
|
|
|
|
|
<HStack opacity="0.9" cursor="pointer">
|
|
|
|
|
|
|
|
{getSvgIcon("comments", "1.3rem")}
|
|
|
|
|
|
|
|
<Text ml="2"><Count count={story.comments} /></Text>
|
|
|
|
|
|
|
|
</HStack>
|
|
|
|
|
|
|
|
</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<Box style={{ marginLeft: '4px' }}><Bookmark height="1.05rem" storyID={story.id} bookmarked={story.bookmarked} /></Box>
|
|
|
|
|
|
|
|
</HStack>
|
|
|
|
|
|
|
|
</VStack>
|
|
|
|
|
|
|
|
<AlertDialog
|
|
|
|
|
|
|
|
isOpen={isOpen}
|
|
|
|
|
|
|
|
leastDestructiveRef={cancelRef}
|
|
|
|
|
|
|
|
onClose={onClose}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<AlertDialogOverlay>
|
|
|
|
|
|
|
|
<AlertDialogContent>
|
|
|
|
|
|
|
|
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
|
|
|
|
|
|
|
移除 - {story.title}
|
|
|
|
|
|
|
|
</AlertDialogHeader>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<AlertDialogBody>
|
|
|
|
|
|
|
|
Are you sure? You can't undo this action afterwards.
|
|
|
|
|
|
|
|
</AlertDialogBody>
|
|
|
|
|
|
|
|
|
|
|
|
<Box style={{ marginLeft: '4px' }}><Bookmark height="1.05rem" storyID={story.id} bookmarked={story.bookmarked} /></Box>
|
|
|
|
<AlertDialogFooter>
|
|
|
|
</HStack>
|
|
|
|
<Button ref={cancelRef} onClick={onClose}>
|
|
|
|
</VStack>
|
|
|
|
Cancel
|
|
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
<Button colorScheme="red" onClick={() => {setIsOpen(false);props.onRemove(story.id)}} ml={3}>
|
|
|
|
|
|
|
|
Delete
|
|
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
</AlertDialogFooter>
|
|
|
|
|
|
|
|
</AlertDialogContent>
|
|
|
|
|
|
|
|
</AlertDialogOverlay>
|
|
|
|
|
|
|
|
</AlertDialog>
|
|
|
|
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|