pull/52/head
sunface 4 years ago
parent 1810263a25
commit bc63942425

@ -1,4 +1,4 @@
import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, HStack, Wrap, useMediaQuery, Avatar, Textarea, Table, Thead, Tr, Th, Tbody, Td, IconButton, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, NumberInput, NumberInputField, NumberInputStepper, NumberIncrementStepper, NumberDecrementStepper } from "@chakra-ui/react" import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, HStack, Wrap, useMediaQuery, Avatar, Textarea, Table, Thead, Tr, Th, Tbody, Td, IconButton, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, NumberInput, NumberInputField, NumberInputStepper, NumberIncrementStepper, NumberDecrementStepper ,AlertDialog, AlertDialogOverlay, AlertDialogContent, AlertDialogHeader, AlertDialogBody, AlertDialogFooter} from "@chakra-ui/react"
import Card from "components/card" import Card from "components/card"
import PageContainer from "layouts/page-container" import PageContainer from "layouts/page-container"
import Sidebar from "layouts/sidebar/sidebar" import Sidebar from "layouts/sidebar/sidebar"
@ -103,15 +103,7 @@ const UserNavbarPage = () => {
</Thead> </Thead>
<Tbody> <Tbody>
{ {
navbars.map((nv,i) => <Tr key={i}> navbars.map((nv,i) => <NV nv={nv} onEdit={onEditNavbar} onDelete={onDeleteNavbar} key={nv.id}/>)
<Td>{nv.label}</Td>
<Td>{nv.value}</Td>
<Td>{nv.weight}</Td>
<Td>
<IconButton aria-label="edit navbar" variant="ghost" icon={getSvgIcon('edit', ".95rem")} onClick={() => onEditNavbar(nv)}/>
<IconButton aria-label="delete navbar" variant="ghost" icon={getSvgIcon('close', "1rem")} onClick={() => onDeleteNavbar(nv.id)} />
</Td>
</Tr>)
} }
</Tbody> </Tbody>
@ -133,7 +125,7 @@ const UserNavbarPage = () => {
<HStack spacing="4"> <HStack spacing="4">
<Heading size="xs">Value</Heading> <Heading size="xs">Value</Heading>
<Input value={currentNavbar.value} _focus={null} variant="flushed" onChange={e => { currentNavbar.value = e.currentTarget.value; onNavbarChange() }} placeholder="enter a url, e.g /search"/> <Input value={currentNavbar.value} _focus={null} variant="flushed" onChange={e => { currentNavbar.value = e.currentTarget.value; onNavbarChange() }} placeholder="enter a url, e.g /search/posts"/>
</HStack> </HStack>
<HStack spacing="4"> <HStack spacing="4">
@ -156,3 +148,49 @@ const UserNavbarPage = () => {
} }
export default UserNavbarPage export default UserNavbarPage
const NV = ({ nv, onEdit, onDelete }) => {
const [isOpen, setIsOpen] = React.useState(false)
const onClose = () => setIsOpen(false)
const cancelRef = React.useRef()
return (
<>
<Tr>
<Td>{nv.label}</Td>
<Td>{nv.value}</Td>
<Td>{nv.weight}</Td>
<Td>
<IconButton aria-label="edit navbar" variant="ghost" icon={getSvgIcon('edit', ".95rem")} onClick={() => onEdit(nv)}/>
<IconButton aria-label="delete navbar" variant="ghost" icon={getSvgIcon('close', "1rem")} onClick={() => setIsOpen(true)} />
</Td>
</Tr>
<AlertDialog
isOpen={isOpen}
leastDestructiveRef={cancelRef}
onClose={onClose}
>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
- {nv.label}
</AlertDialogHeader>
<AlertDialogBody>
Are you sure? You can't undo this action afterwards.
</AlertDialogBody>
<AlertDialogFooter>
<Button ref={cancelRef} onClick={onClose}>
Cancel
</Button>
<Button colorScheme="red" onClick={() => onDelete(nv.id)} ml={3}>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
)
}

@ -1,4 +1,4 @@
import { Box, Button, Flex, useColorMode, useColorModeValue, useDisclosure, useRadioGroup, useToast, chakra, Input, HStack, IconButton, Heading, Divider } from '@chakra-ui/react'; import { Box, Button, Flex, useColorMode, useColorModeValue, useDisclosure, useRadioGroup, useToast, chakra, Input, HStack, IconButton, Heading, Divider, AlertDialog, AlertDialogOverlay, AlertDialogContent, AlertDialogHeader, AlertDialogBody, AlertDialogFooter, Text } from '@chakra-ui/react';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { MarkdownEditor } from 'components/markdown-editor/editor'; import { MarkdownEditor } from 'components/markdown-editor/editor';
import PageContainer from 'layouts/page-container'; import PageContainer from 'layouts/page-container';
@ -16,13 +16,14 @@ import RadioCard from 'components/radio-card';
import { useViewportScroll } from 'framer-motion'; import { useViewportScroll } from 'framer-motion';
import Card from 'components/card'; import Card from 'components/card';
import { Tag } from 'src/types/tag'; import { Tag } from 'src/types/tag';
import { ReserveUrls } from 'src/data/reserve-urls';
function PostEditPage() { function PostEditPage() {
const router = useRouter() const router = useRouter()
const { id } = router.query const { id } = router.query
const [editMode, setEditMode] = useState(EditMode.Edit) const [editMode, setEditMode] = useState(EditMode.Edit)
const [tag, setTag]:[Tag,any] = useState({ const [tag, setTag]: [Tag, any] = useState({
md: `标签介绍支持markdown`, md: `标签介绍支持markdown`,
title: '' title: ''
}) })
@ -84,26 +85,26 @@ function PostEditPage() {
</Box> </Box>
} }
</Card> </Card>
<Card width="35%"> <Card width="35%">
<Heading size="xs"> <Heading size="xs">
Title Title
</Heading> </Heading>
<Input value={tag.title} onChange={(e) => { tag.title = e.target.value; onChange()}} mt="4" variant="flushed" size="sm" placeholder="Tag title..." focusBorderColor="teal.400" /> <Input value={tag.title} onChange={(e) => { tag.title = e.target.value; onChange() }} mt="4" variant="flushed" size="sm" placeholder="Tag title..." focusBorderColor="teal.400" />
<Heading size="xs" mt="8"> <Heading size="xs" mt="8">
Name Name
</Heading> </Heading>
<Input value={tag.name} onChange={(e) => { tag.name = e.target.value; onChange()}} mt="4" variant="flushed" size="sm" placeholder="Tag name..." focusBorderColor="teal.400" /> <Input value={tag.name} onChange={(e) => { tag.name = e.target.value; onChange() }} mt="4" variant="flushed" size="sm" placeholder="Tag name..." focusBorderColor="teal.400" />
<Heading size="xs" mt="8"> <Heading size="xs" mt="8">
</Heading> </Heading>
<Input value={tag.cover} onChange={(e) => { tag.cover = e.target.value; onChange()}} mt="4" variant="flushed" size="sm" placeholder="图片链接你可以用github当图片存储服务" focusBorderColor="teal.400" /> <Input value={tag.cover} onChange={(e) => { tag.cover = e.target.value; onChange() }} mt="4" variant="flushed" size="sm" placeholder="图片链接你可以用github当图片存储服务" focusBorderColor="teal.400" />
<Heading size="xs" mt="8"> <Heading size="xs" mt="8">
</Heading> </Heading>
<Input value={tag.icon} onChange={(e) => { tag.icon = e.target.value; onChange()}} mt="4" variant="flushed" size="sm" placeholder="图片链接" focusBorderColor="teal.400" /> <Input value={tag.icon} onChange={(e) => { tag.icon = e.target.value; onChange() }} mt="4" variant="flushed" size="sm" placeholder="图片链接" focusBorderColor="teal.400" />
</Card> </Card>
</HStack> </HStack>
</PageContainer> </PageContainer>
@ -113,10 +114,12 @@ function PostEditPage() {
export default PostEditPage export default PostEditPage
function HeaderContent(props: any) { function HeaderContent(props: any) {
const [delOpen, setDelOpen] = React.useState(false)
const cancelRef = React.useRef()
const { toggleColorMode: toggleMode } = useColorMode() const { toggleColorMode: toggleMode } = useColorMode()
const text = useColorModeValue("dark", "light") const text = useColorModeValue("dark", "light")
const SwitchIcon = useColorModeValue(FaMoon, FaSun) const SwitchIcon = useColorModeValue(FaMoon, FaSun)
const router = useRouter()
const editOptions = [EditMode.Edit, EditMode.Preview] const editOptions = [EditMode.Edit, EditMode.Preview]
const { getRootProps, getRadioProps } = useRadioGroup({ const { getRootProps, getRadioProps } = useRadioGroup({
name: "framework", name: "framework",
@ -128,8 +131,10 @@ function HeaderContent(props: any) {
const group = getRootProps() const group = getRootProps()
const onDelete = async () => { const onDelete = async () => {
await requestApi.delete(`/tag/${props.tagID}`) await requestApi.delete(`/tag/${props.tagID}`)
setDelOpen(false)
router.push(`${ReserveUrls.Admin}/tags`)
} }
return ( return (
<> <>
<Flex w="100%" h="100%" align="center" justify="space-between" px={{ base: "4", md: "6" }}> <Flex w="100%" h="100%" align="center" justify="space-between" px={{ base: "4", md: "6" }}>
@ -171,9 +176,35 @@ function HeaderContent(props: any) {
icon={<SwitchIcon />} icon={<SwitchIcon />}
/> />
<Button layerStyle="colorButton" ml="2" onClick={props.publish}></Button> <Button layerStyle="colorButton" ml="2" onClick={props.publish}></Button>
<Button colorScheme="red" ml="2" onClick={onDelete}></Button> <Button colorScheme="red" ml="2" onClick={() => setDelOpen(true)}></Button>
</Box> </Box>
</Flex> </Flex>
<AlertDialog
isOpen={delOpen}
leastDestructiveRef={cancelRef}
onClose={() => setDelOpen(false)}
>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
Delete Tag
</AlertDialogHeader>
<AlertDialogBody>
<Text color="red">Tag</Text>
</AlertDialogBody>
<AlertDialogFooter>
<Button ref={cancelRef} onClick={() => setDelOpen(false)}>
Cancel
</Button>
<Button colorScheme="red" onClick={onDelete} ml={3}>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</> </>
) )
} }

@ -1,4 +1,4 @@
import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, HStack, Wrap, useMediaQuery, Avatar, Textarea, Table, Thead, Tr, Th, Tbody, Td, IconButton, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, NumberInput, NumberInputField, NumberInputStepper, NumberIncrementStepper, NumberDecrementStepper } from "@chakra-ui/react" import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, HStack, Wrap, useMediaQuery, Avatar, Textarea, Table, Thead, Tr, Th, Tbody, Td, IconButton, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, NumberInput, NumberInputField, NumberInputStepper, NumberIncrementStepper, NumberDecrementStepper,AlertDialog, AlertDialogOverlay, AlertDialogContent, AlertDialogHeader, AlertDialogBody, AlertDialogFooter } from "@chakra-ui/react"
import Card from "components/card" import Card from "components/card"
import PageContainer from "layouts/page-container" import PageContainer from "layouts/page-container"
import Sidebar from "layouts/sidebar/sidebar" import Sidebar from "layouts/sidebar/sidebar"
@ -39,7 +39,7 @@ export const NavbarEditor = ({ orgID = "" }) => {
}, []) }, [])
const getNavbars = async () => { const getNavbars = async () => {
const res = await requestApi.get(`/user/navbars/${orgID ? orgID : 0}`) const res = await requestApi.get(`/user/navbars/${orgID ? orgID : 0}`)
setNavbars(res.data) setNavbars(res.data)
} }
@ -76,7 +76,7 @@ export const NavbarEditor = ({ orgID = "" }) => {
} }
if (orgID) { if (orgID) {
await requestApi.post(`/org/navbar/${orgID}`,currentNavbar) await requestApi.post(`/org/navbar/${orgID}`, currentNavbar)
} else { } else {
await requestApi.post(`/user/navbar`, currentNavbar) await requestApi.post(`/user/navbar`, currentNavbar)
} }
@ -122,7 +122,7 @@ export const NavbarEditor = ({ orgID = "" }) => {
} }
const onDeleteNavbar = async id => { const onDeleteNavbar = async id => {
await requestApi.delete(`/${orgID?'org':'user'}/navbar/${id}`) await requestApi.delete(`/${orgID ? 'org' : 'user'}/navbar/${id}`)
getNavbars() getNavbars()
} }
@ -145,16 +145,7 @@ export const NavbarEditor = ({ orgID = "" }) => {
</Thead> </Thead>
<Tbody> <Tbody>
{ {
navbars.map((nv, i) => <Tr key={i}> navbars.map((nv, i) => <NV key={nv.id} nv={nv} onEdit={onEditNavbar} onDelete={onDeleteNavbar} getSeriesTitle={getSeriesTitle}/>)
<Td>{nv.label}</Td>
<Td>{nv.type === NavbarType.Link ? "link" : "series"}</Td>
<Td>{nv.type === NavbarType.Link ? nv.value : getSeriesTitle(nv.value)}</Td>
<Td>{nv.weight}</Td>
<Td>
<IconButton aria-label="edit navbar" variant="ghost" icon={getSvgIcon('edit', ".95rem")} onClick={() => onEditNavbar(nv)} />
<IconButton aria-label="delete navbar" variant="ghost" icon={getSvgIcon('close', "1rem")} onClick={() => onDeleteNavbar(nv.id)} />
</Td>
</Tr>)
} }
</Tbody> </Tbody>
@ -204,4 +195,51 @@ export const NavbarEditor = ({ orgID = "" }) => {
</Modal> </Modal>
</> </>
) )
}
const NV = ({ nv, onEdit, onDelete, getSeriesTitle }) => {
const [isOpen, setIsOpen] = React.useState(false)
const onClose = () => setIsOpen(false)
const cancelRef = React.useRef()
return (
<>
<Tr>
<Td>{nv.label}</Td>
<Td>{nv.type === NavbarType.Link ? "link" : "series"}</Td>
<Td>{nv.type === NavbarType.Link ? nv.value : getSeriesTitle(nv.value)}</Td>
<Td>{nv.weight}</Td>
<Td>
<IconButton aria-label="edit navbar" variant="ghost" icon={getSvgIcon('edit', ".95rem")} onClick={() => onEdit(nv)} />
<IconButton aria-label="delete navbar" variant="ghost" icon={getSvgIcon('close', "1rem")} onClick={() => setIsOpen(true)} />
</Td>
</Tr>
<AlertDialog
isOpen={isOpen}
leastDestructiveRef={cancelRef}
onClose={onClose}
>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
- {nv.label}
</AlertDialogHeader>
<AlertDialogBody>
Are you sure? You can't undo this action afterwards.
</AlertDialogBody>
<AlertDialogFooter>
<Button ref={cancelRef} onClick={onClose}>
Cancel
</Button>
<Button colorScheme="red" onClick={() => onDelete(nv.id)} ml={3}>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
)
} }

@ -94,7 +94,7 @@ func DeleteTag(c *gin.Context) {
} }
user := user.CurrentUser(c) user := user.CurrentUser(c)
if !user.Role.IsAdmin() { if !user.Role.IsSuperAdmin() {
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission)) c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
} }

@ -14,6 +14,10 @@ func (r RoleType) IsValid() bool {
return r == ROLE_NORMAL || r == ROLE_EDITOR || r == ROLE_ADMIN || r == ROLE_SUPER_ADMIN return r == ROLE_NORMAL || r == ROLE_EDITOR || r == ROLE_ADMIN || r == ROLE_SUPER_ADMIN
} }
func (r RoleType) IsSuperAdmin() bool {
return r == ROLE_SUPER_ADMIN
}
func (r RoleType) IsAdmin() bool { func (r RoleType) IsAdmin() bool {
return r == ROLE_ADMIN || r == ROLE_SUPER_ADMIN return r == ROLE_ADMIN || r == ROLE_SUPER_ADMIN
} }

@ -1,5 +1,5 @@
import React from "react" import React from "react"
import { chakra, Heading, VStack, Text, HStack, Button, Flex, PropsOf, Tag, useMediaQuery, IconButton, Tooltip } from "@chakra-ui/react" import { chakra, Heading, VStack, Text, HStack, Button, Flex, PropsOf, Tag, useMediaQuery, IconButton, Tooltip, AlertDialog, AlertDialogOverlay, AlertDialogContent, AlertDialogHeader, AlertDialogBody, AlertDialogFooter } from "@chakra-ui/react"
import { Story } from "src/types/story" import { Story } from "src/types/story"
import moment from 'moment' import moment from 'moment'
import { IDType } from "src/types/id" import { IDType } from "src/types/id"
@ -17,11 +17,15 @@ type Props = PropsOf<typeof chakra.div> & {
// 文章卡片,展示需要被管理的文章 // 文章卡片,展示需要被管理的文章
export const ManageStoryCard = (props: Props) => { export const ManageStoryCard = (props: Props) => {
const { story, onEdit, onDelete, showSource = true,onPin, ...rest } = props const { story, onEdit, onDelete, showSource = true, onPin, ...rest } = props
const [isSmallScreen] = useMediaQuery("(max-width: 768px)") const [isSmallScreen] = useMediaQuery("(max-width: 768px)")
const Lay = isSmallScreen ? VStack : Flex const Lay = isSmallScreen ? VStack : Flex
const gap = moment(story.created).fromNow() const gap = moment(story.created).fromNow()
const [isOpen, setIsOpen] = React.useState(false)
const onClose = () => setIsOpen(false)
const cancelRef = React.useRef()
const showActions = onEdit || onDelete || onPin const showActions = onEdit || onDelete || onPin
return ( return (
//@ts-ignore //@ts-ignore
@ -34,18 +38,18 @@ export const ManageStoryCard = (props: Props) => {
<Text fontSize=".9rem">{gap}</Text> <Text fontSize=".9rem">{gap}</Text>
</VStack> </VStack>
{showActions && <HStack pt={{ base: 3, md: 0 }} layerStyle="textSecondary"> {showActions && <HStack pt={{ base: 3, md: 0 }} layerStyle="textSecondary">
{onPin && <Tooltip label={story.pinned? "取消置顶" : "置顶"}> {onPin && <Tooltip label={story.pinned ? "取消置顶" : "置顶"}>
<IconButton <IconButton
aria-label="a icon button" aria-label="a icon button"
variant="ghost" variant="ghost"
_focus={null} _focus={null}
icon={<FaPaperclip />} icon={<FaPaperclip />}
onClick={() => onPin(story.id)} onClick={() => onPin(story.id)}
color={story.pinned? "teal" : null} color={story.pinned ? "teal" : null}
/> />
</Tooltip>} </Tooltip>}
{onEdit&&<Tooltip label="编辑"> {onEdit && <Tooltip label="编辑">
<IconButton <IconButton
aria-label="a icon button" aria-label="a icon button"
variant="ghost" variant="ghost"
@ -55,16 +59,42 @@ export const ManageStoryCard = (props: Props) => {
/> />
</Tooltip>} </Tooltip>}
{onDelete&&<Tooltip label="删除"> {onDelete && <Tooltip label="删除">
<IconButton <IconButton
aria-label="a icon button" aria-label="a icon button"
variant="ghost" variant="ghost"
_focus={null} _focus={null}
icon={<FaRegTrashAlt />} icon={<FaRegTrashAlt />}
onClick={() => props.onDelete(story.id)} onClick={() => setIsOpen(true)}
/> />
</Tooltip>} </Tooltip>}
</HStack>} </HStack>}
<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>
<AlertDialogFooter>
<Button ref={cancelRef} onClick={onClose}>
Cancel
</Button>
<Button colorScheme="red" onClick={() => props.onDelete(story.id)} ml={3}>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</Lay> </Lay>
) )
} }

Loading…
Cancel
Save