mirror of https://github.com/sunface/rust-course
parent
3f7f70db3d
commit
672680e117
@ -0,0 +1,61 @@
|
||||
import { chakra, PropsOf, useColorModeValue } from "@chakra-ui/react"
|
||||
import NextLink from "next/link"
|
||||
import { useRouter } from "next/router"
|
||||
import React from "react"
|
||||
|
||||
const StyledLink = React.forwardRef(function StyledLink(
|
||||
props: PropsOf<typeof chakra.a> & { isActive?: boolean },
|
||||
ref: React.Ref<any>,
|
||||
) {
|
||||
const { isActive, icon,children, ...rest } = props
|
||||
return (
|
||||
<chakra.a
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
width="100%"
|
||||
px="3"
|
||||
py="2"
|
||||
rounded="md"
|
||||
ref={ref}
|
||||
fontSize="14px"
|
||||
fontWeight="500"
|
||||
color={useColorModeValue("gray.700", "whiteAlpha.900")}
|
||||
transition="all 0.2s"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
_activeLink={{
|
||||
bg: useColorModeValue("teal.50", "rgba(48, 140, 122, 0.3)"),
|
||||
color: useColorModeValue("teal.700", "teal.200"),
|
||||
fontWeight: "600",
|
||||
}}
|
||||
{...rest}
|
||||
><chakra.span fontSize="1.1rem">{icon}</chakra.span> <chakra.span ml="5">{children}</chakra.span></chakra.a>
|
||||
)
|
||||
})
|
||||
|
||||
type SidebarLinkProps = PropsOf<typeof chakra.div> & {
|
||||
href?: string
|
||||
icon?: React.ReactElement
|
||||
}
|
||||
|
||||
const SidebarLink = (props: SidebarLinkProps) => {
|
||||
const { href, icon, children, ...rest } = props
|
||||
|
||||
const { pathname } = useRouter()
|
||||
const isActive = pathname === href
|
||||
|
||||
return (
|
||||
<chakra.div
|
||||
userSelect="none"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
lineHeight="1.5rem"
|
||||
{...rest}
|
||||
>
|
||||
<NextLink href={href} passHref>
|
||||
<StyledLink isActive={isActive} icon={icon}>{children}</StyledLink>
|
||||
</NextLink>
|
||||
</chakra.div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SidebarLink
|
@ -0,0 +1,56 @@
|
||||
import { Box, Stack } from "@chakra-ui/react"
|
||||
import Card from "components/card"
|
||||
import { useRouter } from "next/router"
|
||||
import * as React from "react"
|
||||
import { Route } from "src/types/route"
|
||||
import SidebarLink from "./sidebar-link"
|
||||
|
||||
|
||||
export function SidebarContent(props) {
|
||||
const { routes, pathname, contentRef } = props
|
||||
return (
|
||||
<>
|
||||
<Stack as="ul">
|
||||
{routes.map((route:Route) => {
|
||||
if (route.disabled) {return null}
|
||||
return <SidebarLink as="li" key={route.path} href={route.path} icon={route.icon}>
|
||||
<span>{route.title}</span>
|
||||
</SidebarLink>
|
||||
})}
|
||||
</Stack>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const Sidebar = ({ routes, ...props }) => {
|
||||
const { pathname } = useRouter()
|
||||
const ref = React.useRef<HTMLDivElement>(null)
|
||||
|
||||
return (
|
||||
<Card p="0" {...props}>
|
||||
<Box
|
||||
ref={ref}
|
||||
as="nav"
|
||||
aria-label="Main Navigation"
|
||||
pos="sticky"
|
||||
sx={{
|
||||
overscrollBehavior: "contain",
|
||||
}}
|
||||
top="6.5rem"
|
||||
pr="3"
|
||||
pb="6"
|
||||
pl="3"
|
||||
pt="6"
|
||||
overflowY="auto"
|
||||
className="sidebar-content"
|
||||
flexShrink={0}
|
||||
display={{ base: "none", md: "block" }}
|
||||
>
|
||||
<SidebarContent routes={routes} pathname={pathname} contentRef={ref} />
|
||||
</Box>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default Sidebar
|
||||
|
@ -0,0 +1,190 @@
|
||||
import { createStandaloneToast, Text, Box, Heading, Image, HStack, Center, Button, Flex, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalFooter, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, VStack, Textarea, Divider } from "@chakra-ui/react"
|
||||
import Card from "components/card"
|
||||
import Nav from "layouts/nav/nav"
|
||||
import PageContainer from "layouts/page-container"
|
||||
import Sidebar from "layouts/sidebar/sidebar"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import editorLinks from "src/data/editor-links"
|
||||
import { requestApi } from "utils/axios/request"
|
||||
import { useDisclosure } from "@chakra-ui/react"
|
||||
import { Field, Form, Formik } from "formik"
|
||||
import { config } from "utils/config"
|
||||
import TextArticleCard from "components/articles/text-article-card"
|
||||
import { Article } from "src/types/posts"
|
||||
var validator = require('validator');
|
||||
const toast = createStandaloneToast()
|
||||
|
||||
const newPost:Article = { title: '', url: '', cover: ''}
|
||||
const ArticlesPage = () => {
|
||||
const [posts, setPosts] = useState([])
|
||||
const [currentPost, setCurrentPost] = useState(newPost)
|
||||
useEffect(() => {
|
||||
getPosts()
|
||||
}, [])
|
||||
|
||||
const getPosts = () => {
|
||||
requestApi.get(`/editor/articles`).then((res) => setPosts(res.data)).catch(_ => setPosts([]))
|
||||
}
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
|
||||
function validateTitle(value) {
|
||||
console.log(value)
|
||||
let error
|
||||
if (!value?.trim()) {
|
||||
error = "标题不能为空"
|
||||
}
|
||||
return error
|
||||
}
|
||||
|
||||
function validateUrl(value) {
|
||||
let error
|
||||
if (!validator.isURL(value)) {
|
||||
error = "URL格式不合法"
|
||||
}
|
||||
return error
|
||||
}
|
||||
|
||||
function validateBrief(value) {
|
||||
let error
|
||||
if (value && value.length > config.posts.briefMaxLen) {
|
||||
error = `文本长度不能超过${config.posts.briefMaxLen}`
|
||||
}
|
||||
return error
|
||||
}
|
||||
|
||||
const submitArticle = async (values, _) => {
|
||||
await requestApi.post(`/editor/article`, values)
|
||||
onClose()
|
||||
toast({
|
||||
description: "提交成功",
|
||||
status: "success",
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
})
|
||||
setCurrentPost(newPost)
|
||||
}
|
||||
|
||||
const editArticle = (ar: Article) => {
|
||||
setCurrentPost(ar)
|
||||
onOpen()
|
||||
}
|
||||
|
||||
const onDeleteArticle = () => {
|
||||
getPosts()
|
||||
toast({
|
||||
description: "删除成功",
|
||||
status: "success",
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Nav />
|
||||
<PageContainer>
|
||||
<Box display="flex">
|
||||
<Sidebar routes={editorLinks} width="250px" height="fit-content" />
|
||||
<Card ml="4" p="6" width="100%">
|
||||
<Flex alignItems="center" justify="space-between">
|
||||
<Heading size="md">文章列表({posts.length})</Heading>
|
||||
<Button colorScheme="teal" size="sm" onClick={onOpen} _focus={null}>添加文章</Button>
|
||||
</Flex>
|
||||
{
|
||||
posts.length === 0 ?
|
||||
<>
|
||||
<Center mt="4">
|
||||
<Image height="25rem" src="/empty-posts.png" />
|
||||
</Center>
|
||||
<Center mt="8">
|
||||
<Heading size="sm">你还没创建任何文章</Heading>
|
||||
</Center>
|
||||
</>
|
||||
:
|
||||
<VStack mt="4">
|
||||
{posts.map(post =>
|
||||
<Box width="100%" key={post.id}>
|
||||
<TextArticleCard article={post} showActions={true} mt="4" onEdit={() => editArticle(post)} onDelete={() => onDeleteArticle()} />
|
||||
<Divider mt="5" />
|
||||
</Box>
|
||||
)}
|
||||
</VStack>
|
||||
}
|
||||
|
||||
<Center><Text layerStyle="textSecondary" fontSize="sm" mt="4">没有更多文章了</Text></Center>
|
||||
</Card>
|
||||
</Box>
|
||||
</PageContainer>
|
||||
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>编辑文章</ModalHeader>
|
||||
<ModalBody mb="2">
|
||||
<Formik
|
||||
initialValues={currentPost}
|
||||
onSubmit={submitArticle}
|
||||
>
|
||||
{(props) => (
|
||||
<Form>
|
||||
<VStack>
|
||||
<Field name="title" validate={validateTitle}>
|
||||
{({ field, form }) => (
|
||||
<FormControl isInvalid={form.errors.title && form.touched.title} >
|
||||
<FormLabel>标题</FormLabel>
|
||||
<Input {...field} placeholder="name" />
|
||||
<FormErrorMessage>{form.errors.title}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="url" validate={validateUrl}>
|
||||
{({ field, form }) => (
|
||||
<FormControl isInvalid={form.errors.url && form.touched.url}>
|
||||
<FormLabel>URL</FormLabel>
|
||||
<Input {...field} placeholder="https://..." />
|
||||
<FormErrorMessage>{form.errors.url}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="cover" validate={validateUrl}>
|
||||
{({ field, form }) => (
|
||||
<FormControl isInvalid={form.errors.cover && form.touched.cover}>
|
||||
<FormLabel>封面图片</FormLabel>
|
||||
<Input {...field} placeholder="https://..." />
|
||||
<FormErrorMessage>{form.errors.cover}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="brief" validate={validateBrief}>
|
||||
{({ field, form }) => (
|
||||
<FormControl isInvalid={form.errors.brief && form.touched.brief}>
|
||||
<FormLabel>文章简介</FormLabel>
|
||||
<Textarea {...field} placeholder="往往是开头的一段文字"></Textarea>
|
||||
<FormErrorMessage>{form.errors.brief}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
</VStack>
|
||||
<Box mt={6}>
|
||||
<Button
|
||||
colorScheme="teal"
|
||||
variant="outline"
|
||||
type="submit"
|
||||
_focus={null}
|
||||
>
|
||||
提交文章
|
||||
</Button>
|
||||
<Button variant="ghost" ml="4" _focus={null} onClick={onClose}>取消</Button>
|
||||
</Box>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default ArticlesPage
|
||||
|
After Width: | Height: | Size: 28 KiB |
@ -0,0 +1,5 @@
|
||||
package api
|
||||
|
||||
import "github.com/imdotdev/im.dev/server/pkg/log"
|
||||
|
||||
var logger = log.RootLogger.New("logger", "api")
|
@ -0,0 +1,44 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/imdotdev/im.dev/server/internal/posts"
|
||||
"github.com/imdotdev/im.dev/server/internal/session"
|
||||
"github.com/imdotdev/im.dev/server/pkg/common"
|
||||
)
|
||||
|
||||
func GetEditorArticles(c *gin.Context) {
|
||||
user := session.CurrentUser(c)
|
||||
ars, err := posts.UserArticles(int64(user.ID))
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
logger.Warn("get user articles error", "error", err)
|
||||
c.JSON(http.StatusInternalServerError, common.RespInternalError())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, common.RespSuccess(ars))
|
||||
}
|
||||
|
||||
func PostEditorArticle(c *gin.Context) {
|
||||
err := posts.PostArticle(c)
|
||||
if err != nil {
|
||||
logger.Warn("post article error", "error", err)
|
||||
c.JSON(400, common.RespError(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, common.RespSuccess(nil))
|
||||
}
|
||||
|
||||
func DeleteEditorArticle(c *gin.Context) {
|
||||
err := posts.DeleteArticle(c)
|
||||
if err != nil {
|
||||
logger.Warn("delete article error", "error", err)
|
||||
c.JSON(400, common.RespError(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, common.RespSuccess(nil))
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package posts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/imdotdev/im.dev/server/internal/session"
|
||||
"github.com/imdotdev/im.dev/server/pkg/config"
|
||||
"github.com/imdotdev/im.dev/server/pkg/db"
|
||||
"github.com/imdotdev/im.dev/server/pkg/errcode"
|
||||
"github.com/imdotdev/im.dev/server/pkg/models"
|
||||
)
|
||||
|
||||
func UserArticles(uid int64) (models.Articles, error) {
|
||||
ars := make(models.Articles, 0)
|
||||
rows, err := db.Conn.Query("select id,title,url,cover,brief,created,updated from articles where creator=?", uid)
|
||||
if err != nil {
|
||||
return ars, err
|
||||
}
|
||||
|
||||
creator := &models.UserSimple{ID: uid}
|
||||
creator.Query()
|
||||
for rows.Next() {
|
||||
ar := &models.Article{}
|
||||
err := rows.Scan(&ar.ID, &ar.Title, &ar.URL, &ar.Cover, &ar.Brief, &ar.Created, &ar.Updated)
|
||||
if err != nil {
|
||||
logger.Warn("scan articles error", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
ar.Creator = creator
|
||||
ars = append(ars, ar)
|
||||
}
|
||||
|
||||
sort.Sort(ars)
|
||||
return ars, nil
|
||||
}
|
||||
|
||||
func PostArticle(c *gin.Context) error {
|
||||
user := session.CurrentUser(c)
|
||||
if !user.Role.IsEditor() {
|
||||
return errors.New(errcode.NoEditorPermission)
|
||||
}
|
||||
|
||||
ar := &models.Article{}
|
||||
err := c.Bind(&ar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.TrimSpace(ar.Title) == "" || utf8.RuneCountInString(ar.Brief) > config.Data.Posts.BriefMaxLen || !govalidator.IsURL(ar.URL) || !govalidator.IsURL(ar.Cover) {
|
||||
return errors.New(errcode.ParamInvalid)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if ar.ID == 0 {
|
||||
//create
|
||||
_, err = db.Conn.Exec("INSERT INTO articles (creator, title, url, cover, brief, created, updated) VALUES(?,?,?,?,?,?,?)",
|
||||
user.ID, ar.Title, ar.URL, ar.Cover, ar.Brief, now, now)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Conn.Exec("UPDATE articles SET title=?, url=?, cover=?, brief=?, updated=? WHERE id=?",
|
||||
ar.Title, ar.URL, ar.Cover, ar.Brief, now, ar.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteArticle(c *gin.Context) error {
|
||||
user := session.CurrentUser(c)
|
||||
if !user.Role.IsEditor() {
|
||||
return errors.New(errcode.NoEditorPermission)
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
_, err := db.Conn.Exec("DELETE FROM articles WHERE id=?", id)
|
||||
return err
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package posts
|
||||
|
||||
import "github.com/imdotdev/im.dev/server/pkg/log"
|
||||
|
||||
var logger = log.RootLogger.New("logger", "posts")
|
@ -0,0 +1 @@
|
||||
package posts
|
@ -0,0 +1,27 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/imdotdev/im.dev/server/pkg/common"
|
||||
"github.com/imdotdev/im.dev/server/pkg/config"
|
||||
)
|
||||
|
||||
type UIConfig struct {
|
||||
Posts *UIPosts `json:"posts"`
|
||||
}
|
||||
|
||||
type UIPosts struct {
|
||||
BriefMaxLen int `json:"briefMaxLen"`
|
||||
}
|
||||
|
||||
func GetUIConfig(c *gin.Context) {
|
||||
conf := &UIConfig{
|
||||
Posts: &UIPosts{
|
||||
BriefMaxLen: config.Data.Posts.BriefMaxLen,
|
||||
},
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, common.RespSuccess(conf))
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type Article struct {
|
||||
ID int64 `json:"id"`
|
||||
Creator *UserSimple `json:"creator"`
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
Cover string `json:"cover"`
|
||||
Brief string `json:"brief"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
}
|
||||
|
||||
type Articles []*Article
|
||||
|
||||
func (ar Articles) Len() int { return len(ar) }
|
||||
func (ar Articles) Swap(i, j int) { ar[i], ar[j] = ar[j], ar[i] }
|
||||
func (ar Articles) Less(i, j int) bool {
|
||||
return ar[i].Created.Unix() > ar[j].Created.Unix()
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import React from "react"
|
||||
import {chakra, Heading, VStack, Text, HStack,Button, Flex,PropsOf } from "@chakra-ui/react"
|
||||
import { Article } from "src/types/posts"
|
||||
import moment from 'moment'
|
||||
import { requestApi } from "utils/axios/request"
|
||||
|
||||
type Props = PropsOf<typeof chakra.div> & {
|
||||
article: Article
|
||||
showActions: boolean
|
||||
onEdit?: any
|
||||
onDelete?: any
|
||||
}
|
||||
|
||||
|
||||
export const TextArticleCard= (props:Props) =>{
|
||||
const {article,showActions,onEdit,onDelete, ...rest} = props
|
||||
|
||||
const gap = moment(article.created).fromNow()
|
||||
const onDeleteArticle = async () => {
|
||||
await requestApi.delete(`/editor/article/${article.id}`)
|
||||
onDelete()
|
||||
}
|
||||
return (
|
||||
<Flex justifyContent="space-between" {...rest}>
|
||||
<VStack>
|
||||
<Heading size="sm">{props.article.title}</Heading>
|
||||
<Text fontSize=".9rem">{gap}</Text>
|
||||
</VStack>
|
||||
{props.showActions && <HStack>
|
||||
<Button size="sm" colorScheme="teal" variant="outline" onClick={onEdit}>Edit</Button>
|
||||
<Button size="sm" onClick={onDeleteArticle}>Delete</Button>
|
||||
</HStack>}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default TextArticleCard
|
@ -0,0 +1,14 @@
|
||||
import React from "react"
|
||||
import { Box, BoxProps } from "@chakra-ui/react"
|
||||
|
||||
export const Card = (props: BoxProps) => (
|
||||
<Box
|
||||
borderRadius=".5rem"
|
||||
borderWidth="1px"
|
||||
p="1rem"
|
||||
boxShadow="0 1px 2px 0 rgb(0 0 0 / 5%)"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
export default Card
|
@ -0,0 +1,24 @@
|
||||
import React from 'react'
|
||||
import { FaFileAlt, FaScroll, FaBookOpen } from 'react-icons/fa'
|
||||
import { Route } from 'src/types/route'
|
||||
const editorLinks: Route[] = [{
|
||||
title: '文章',
|
||||
path: '/editor/article',
|
||||
icon: <FaFileAlt />,
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
title: '系列',
|
||||
path: '/editor/series',
|
||||
icon: <FaBookOpen />,
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
title: '课程',
|
||||
path: '/editor/course',
|
||||
icon: <FaScroll />,
|
||||
disabled: false
|
||||
},
|
||||
]
|
||||
|
||||
export default editorLinks
|
@ -0,0 +1 @@
|
||||
export const needLogin = "你需要登录才能访问该页面"
|
@ -0,0 +1,11 @@
|
||||
import {User} from './session'
|
||||
|
||||
export interface Article {
|
||||
id?: number
|
||||
creator?: User
|
||||
title: string
|
||||
url: string
|
||||
cover: string
|
||||
brief?: string
|
||||
created?: string
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
export interface Route {
|
||||
path: string
|
||||
icon: any
|
||||
title: string
|
||||
disabled: boolean
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { requestApi } from "./axios/request"
|
||||
|
||||
export let config = {
|
||||
posts: {
|
||||
briefMaxLen: 10
|
||||
}
|
||||
}
|
||||
|
||||
export function initUIConfig() {
|
||||
requestApi.get("/uiconfig").then((res) => {
|
||||
console.log("初始化UI config:", res.data)
|
||||
config = res.data
|
||||
})}
|
@ -0,0 +1,98 @@
|
||||
import EventEmitter3, { EventEmitter } from 'eventemitter3';
|
||||
|
||||
export type AppEvent<T> = {
|
||||
readonly name: string;
|
||||
payload?: T;
|
||||
}
|
||||
|
||||
export class Emitter {
|
||||
emitter: EventEmitter3;
|
||||
|
||||
constructor() {
|
||||
this.emitter = new EventEmitter();
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED.
|
||||
*/
|
||||
emit(name: string, data?: any): void;
|
||||
|
||||
/**
|
||||
* Emits an `event` with `payload`.
|
||||
*/
|
||||
emit<T extends undefined>(event: AppEvent<T>): void;
|
||||
//@ts-ignore
|
||||
emit<T extends Partial<T> extends T ? Partial<T> : never>(event: AppEvent<T>): void;
|
||||
emit<T>(event: AppEvent<T>, payload: T): void;
|
||||
emit<T>(event: AppEvent<T> | string, payload?: T | any): void {
|
||||
if (typeof event === 'string') {
|
||||
// console.log(`Using strings as events is deprecated and will be removed in a future version. (${event})`);
|
||||
this.emitter.emit(event, payload);
|
||||
} else {
|
||||
this.emitter.emit(event.name, payload);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED.
|
||||
*/
|
||||
on(name: string, handler: (payload?: any) => void, scope?: any): void;
|
||||
|
||||
/**
|
||||
* Handles `event` with `handler()` when emitted.
|
||||
*/
|
||||
on<T extends undefined>(event: AppEvent<T>, handler: () => void, scope?: any): void;
|
||||
//@ts-ignore
|
||||
on<T extends Partial<T> extends T ? Partial<T> : never>(event: AppEvent<T>, handler: () => void, scope?: any): void;
|
||||
on<T>(event: AppEvent<T>, handler: (payload: T) => void, scope?: any): void;
|
||||
on<T>(event: AppEvent<T> | string, handler: (payload?: T | any) => void, scope?: any) {
|
||||
if (typeof event === 'string') {
|
||||
// console.log(`Using strings as events is deprecated and will be removed in a future version. (${event})`);
|
||||
this.emitter.on(event, handler);
|
||||
|
||||
if (scope) {
|
||||
const unbind = scope.$on('$destroy', () => {
|
||||
this.emitter.off(event, handler);
|
||||
unbind();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.emitter.on(event.name, handler);
|
||||
|
||||
if (scope) {
|
||||
const unbind = scope.$on('$destroy', () => {
|
||||
this.emitter.off(event.name, handler);
|
||||
unbind();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED.
|
||||
*/
|
||||
off(name: string, handler: (payload?: any) => void): void;
|
||||
|
||||
off<T extends undefined>(event: AppEvent<T>, handler: () => void): void;
|
||||
//@ts-ignore
|
||||
off<T extends Partial<T> extends T ? Partial<T> : never>(event: AppEvent<T>, handler: () => void, scope?: any): void;
|
||||
off<T>(event: AppEvent<T>, handler: (payload: T) => void): void;
|
||||
off<T>(event: AppEvent<T> | string, handler: (payload?: T | any) => void) {
|
||||
if (typeof event === 'string') {
|
||||
// console.log(`Using strings as events is deprecated and will be removed in a future version. (${event})`);
|
||||
this.emitter.off(event, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
this.emitter.off(event.name, handler);
|
||||
}
|
||||
|
||||
removeAllListeners(evt?: string) {
|
||||
this.emitter.removeAllListeners(evt);
|
||||
}
|
||||
|
||||
getEventCount(): number {
|
||||
return (this.emitter as any)._eventsCount;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { Emitter } from './emitter';
|
||||
|
||||
export const events = new Emitter();
|
||||
|
||||
export default events;
|
@ -0,0 +1,9 @@
|
||||
import { removeToken } from "./axios/getToken"
|
||||
import { requestApi } from "./axios/request"
|
||||
import events from "./events"
|
||||
|
||||
export const logout = async () => {
|
||||
await requestApi.post("/logout")
|
||||
removeToken()
|
||||
events.emit('set-session', null)
|
||||
}
|
Loading…
Reference in new issue