feat: tags list.
This commit is contained in:
parent
9490d874a9
commit
c5ca8819e7
@ -230,6 +230,33 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateTagInput",
|
||||
"description": null,
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "DateTime",
|
||||
@ -370,6 +397,105 @@
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "createTag",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "createTagInput",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateTagInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Tag",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "updateTag",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "updateTagInput",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateTagInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Tag",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "removeTag",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Tag",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
@ -464,6 +590,106 @@
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Tag",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "tag",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Tag",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Tag",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
@ -554,6 +780,45 @@
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateTagInput",
|
||||
"description": null,
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
|
@ -32,6 +32,10 @@ export type CreateArticleInput = {
|
||||
tags: Array<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type CreateTagInput = {
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type Hello = {
|
||||
__typename?: 'Hello';
|
||||
@ -43,6 +47,9 @@ export type Mutation = {
|
||||
createArticle: Article;
|
||||
updateArticle: Article;
|
||||
removeArticle: Scalars['Int'];
|
||||
createTag: Tag;
|
||||
updateTag: Tag;
|
||||
removeTag: Tag;
|
||||
};
|
||||
|
||||
|
||||
@ -60,11 +67,28 @@ export type MutationRemoveArticleArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateTagArgs = {
|
||||
createTagInput: CreateTagInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateTagArgs = {
|
||||
updateTagInput: UpdateTagInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationRemoveTagArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
hello: Hello;
|
||||
articles: Array<Article>;
|
||||
article: Article;
|
||||
tags: Array<Tag>;
|
||||
tag: Tag;
|
||||
};
|
||||
|
||||
|
||||
@ -72,6 +96,17 @@ export type QueryArticleArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryTagArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Tag = {
|
||||
__typename?: 'Tag';
|
||||
id: Scalars['ID'];
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
export type UpdateArticleInput = {
|
||||
title?: Maybe<Scalars['String']>;
|
||||
content?: Maybe<Scalars['String']>;
|
||||
@ -79,3 +114,8 @@ export type UpdateArticleInput = {
|
||||
tags?: Maybe<Array<Scalars['String']>>;
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type UpdateTagInput = {
|
||||
name?: Maybe<Scalars['String']>;
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
@ -62,8 +62,13 @@ export default prepareRoutes([
|
||||
{
|
||||
name: "tags",
|
||||
path: "tags",
|
||||
respond() {
|
||||
return { body: () => <div>Tags</div> };
|
||||
resolve() {
|
||||
return import(/* webpackChunkName: "tags" */ "./tags").then(
|
||||
(m) => m.TagsIndex
|
||||
);
|
||||
},
|
||||
respond({ resolved }) {
|
||||
return { body: resolved };
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
201
src/tags/article-editor.tsx
Normal file
201
src/tags/article-editor.tsx
Normal file
@ -0,0 +1,201 @@
|
||||
import {
|
||||
Button,
|
||||
createStyles,
|
||||
Grid,
|
||||
makeStyles,
|
||||
Paper,
|
||||
} from "@material-ui/core";
|
||||
import { Field, Form, Formik, FormikHelpers } from "formik";
|
||||
import { FC } from "react";
|
||||
import { Editor } from "../commons/editor/vditor";
|
||||
import * as Yup from "yup";
|
||||
import { Article, CreateArticleInput, UpdateArticleInput } from "../generated/graphql";
|
||||
import { DateTimePicker } from "formik-material-ui-pickers";
|
||||
import { gql, useMutation } from "@apollo/client";
|
||||
import { useRouter } from "@curi/react-universal";
|
||||
|
||||
const CREATE_ARTICLE = gql`
|
||||
mutation createArticle($createArticleInput: CreateArticleInput!) {
|
||||
createArticle(createArticleInput: $createArticleInput) {
|
||||
id
|
||||
title
|
||||
content
|
||||
publishedAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const UPDATE_ARTICLE = gql`
|
||||
mutation updateArticle($updateArticleInput: UpdateArticleInput!) {
|
||||
updateArticle(updateArticleInput: $updateArticleInput) {
|
||||
id
|
||||
title
|
||||
content
|
||||
publishedAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
form: {
|
||||
padding: "15px 30px",
|
||||
},
|
||||
editor: {
|
||||
height: "700px",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
type Values = CreateArticleInput | UpdateArticleInput;
|
||||
interface Props {
|
||||
article?: Values;
|
||||
}
|
||||
|
||||
export const ArticleEditor: FC<Props> = ({ article }) => {
|
||||
const classes = useStyles();
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
content: Yup.string()
|
||||
.max(65535, "文章内容不得超过 65535 个字符")
|
||||
.required("文章内容不得为空"),
|
||||
publishedAt: Yup.date(),
|
||||
});
|
||||
const router = useRouter();
|
||||
|
||||
const [createArticle] = useMutation<{createArticle: Article} ,{
|
||||
createArticleInput: CreateArticleInput;
|
||||
}>(CREATE_ARTICLE, {
|
||||
update(cache, { data }) {
|
||||
cache.modify({
|
||||
fields: {
|
||||
articles(existingArticles = []) {
|
||||
const newArticleRef = cache.writeFragment({
|
||||
data: data!.createArticle,
|
||||
fragment: gql`
|
||||
fragment NewArticle on Article {
|
||||
id
|
||||
title
|
||||
content
|
||||
publishedAt
|
||||
}
|
||||
`,
|
||||
});
|
||||
return [newArticleRef, ...existingArticles];
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
const [updateArticle] = useMutation<{
|
||||
updateArticleInput: UpdateArticleInput;
|
||||
}>(UPDATE_ARTICLE);
|
||||
|
||||
const SubmitForm = (
|
||||
values: Values,
|
||||
{ setSubmitting }: FormikHelpers<Values>
|
||||
) => {
|
||||
if ("id" in article!) {
|
||||
const title = /# (.+)$/m.exec(values.content ?? "")?.[1];
|
||||
if (!title) {
|
||||
console.log("no title");
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
updateArticle({
|
||||
variables: {
|
||||
updateArticleInput: {
|
||||
...values,
|
||||
title,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
router.navigate({
|
||||
url: router.url({ name: "articles" }),
|
||||
method: "replace",
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
} else {
|
||||
const title = /# (.+)$/m.exec(values.content ?? "")?.[1];
|
||||
if (!title) {
|
||||
console.log("no title");
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
createArticle({
|
||||
variables: {
|
||||
createArticleInput: {
|
||||
...values as CreateArticleInput,
|
||||
title,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
router.navigate({
|
||||
url: router.url({ name: "articles" }),
|
||||
method: "replace",
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
}
|
||||
setSubmitting(false);
|
||||
};
|
||||
return (
|
||||
<Paper>
|
||||
<Formik
|
||||
initialValues={article!}
|
||||
validationSchema={validationSchema}
|
||||
autoComplete="off"
|
||||
onSubmit={SubmitForm}
|
||||
>
|
||||
{({ submitForm, isSubmitting }) => (
|
||||
<Form className={classes.form}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Editor
|
||||
className={classes.editor}
|
||||
name="content"
|
||||
label="Content"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Field
|
||||
ampm={false}
|
||||
component={DateTimePicker}
|
||||
label="Published At"
|
||||
name="publishedAt"
|
||||
format="yyyy-MM-dd HH:mm:ss"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={isSubmitting}
|
||||
onClick={submitForm}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
ArticleEditor.defaultProps = {
|
||||
article: {
|
||||
title: "",
|
||||
content: "",
|
||||
publishedAt: new Date(),
|
||||
tags: [],
|
||||
} as CreateArticleInput,
|
||||
};
|
3
src/tags/index.ts
Normal file
3
src/tags/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./article-editor";
|
||||
export * from "./tags-index";
|
||||
export * from "./tags.constants";
|
80
src/tags/tags-index.tsx
Normal file
80
src/tags/tags-index.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import {
|
||||
IconButton,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@material-ui/core";
|
||||
import { FC } from "react";
|
||||
import { Tag } from "../generated/graphql";
|
||||
import EditIcon from "@material-ui/icons/Edit";
|
||||
import { useRouter } from "@curi/react-dom";
|
||||
import { Delete } from "@material-ui/icons";
|
||||
import { REMOVE_TAG, TAGS } from "./tags.constants";
|
||||
|
||||
export const TagsIndex: FC = () => {
|
||||
const { data } = useQuery<{
|
||||
tags: Tag[];
|
||||
}>(TAGS, {});
|
||||
|
||||
const [removeArticle] = useMutation<any, { id: string }>(REMOVE_TAG);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<section>
|
||||
<TableContainer component={Paper}>
|
||||
<Table aria-label="articles table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Tag Name</TableCell>
|
||||
<TableCell>Views</TableCell>
|
||||
<TableCell>Articles</TableCell>
|
||||
<TableCell>Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data?.tags.map((tag) => (
|
||||
<TableRow key={tag.id}>
|
||||
<TableCell component="th" scope="row">
|
||||
{tag.name}
|
||||
</TableCell>
|
||||
<TableCell align="right"> -- </TableCell>
|
||||
<TableCell align="right"> -- </TableCell>
|
||||
<TableCell>
|
||||
<IconButton
|
||||
aria-label="edit"
|
||||
onClick={() =>
|
||||
router.navigate({
|
||||
url: router.url({
|
||||
name: "modify-tag",
|
||||
params: tag,
|
||||
}),
|
||||
})
|
||||
}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() =>
|
||||
removeArticle({
|
||||
variables: tag,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</section>
|
||||
);
|
||||
};
|
16
src/tags/tags.constants.tsx
Normal file
16
src/tags/tags.constants.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
export const TAGS = gql`
|
||||
query Tags {
|
||||
tags {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const REMOVE_TAG = gql`
|
||||
mutation RemoveTag($id: ID!) {
|
||||
removeTag(id: $id)
|
||||
}
|
||||
`;
|
Loading…
Reference in New Issue
Block a user