From 6fabc1243b42059a866392ad7411052a32fa2d5a Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Wed, 1 Dec 2021 19:15:22 +0800 Subject: [PATCH] bak. --- .vscode/settings.json | 3 +- src/articles/article-editor-history.tsx | 5 + src/articles/article-editor.tsx | 165 +++++++---- src/articles/articles.constants.tsx | 5 + src/articles/hooks/useDrafts.tsx | 147 ++++++++++ src/generated/graphql.schema.json | 370 +++++++----------------- src/generated/graphql.tsx | 44 +-- src/routes.tsx | 35 ++- 8 files changed, 415 insertions(+), 359 deletions(-) create mode 100644 src/articles/article-editor-history.tsx create mode 100644 src/articles/hooks/useDrafts.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 6711284..2cec82b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,10 @@ { "cSpell.words": [ - "Formik", "clsx", "fontsource", + "Formik", "notistack", + "Swipeable", "vditor" ] } \ No newline at end of file diff --git a/src/articles/article-editor-history.tsx b/src/articles/article-editor-history.tsx new file mode 100644 index 0000000..4f8f632 --- /dev/null +++ b/src/articles/article-editor-history.tsx @@ -0,0 +1,5 @@ +import { FC } from "react"; + +export const ArticleEditorHistory: FC = () => { + return
ArticleEditorHistory
; +}; diff --git a/src/articles/article-editor.tsx b/src/articles/article-editor.tsx index 5fb4eb1..d324760 100644 --- a/src/articles/article-editor.tsx +++ b/src/articles/article-editor.tsx @@ -1,21 +1,39 @@ -import { Button, Grid, Paper } from "@mui/material"; -import createStyles from '@mui/styles/createStyles'; -import makeStyles from '@mui/styles/makeStyles'; +import { + Badge, + Box, + Button, + Grid, + IconButton, + Paper, + SwipeableDrawer, + Typography, + useTheme, +} from "@mui/material"; +import createStyles from "@mui/styles/createStyles"; +import makeStyles from "@mui/styles/makeStyles"; import { Field, Form, Formik, FormikHelpers, FormikProps } from "formik"; -import { FC, useCallback, useMemo, useRef, useState } from "react"; +import { FC, useCallback, useRef, useState } from "react"; import { Editor } from "../commons/editor/vditor"; import * as Yup from "yup"; import { Article, CreateArticleInput, - CreateDraftArticleInput, - DraftArticle, + ArticleHistory, UpdateArticleInput, } from "../generated/graphql"; import { DateTimePicker } from "formik-material-ui-pickers"; import { gql, useMutation } from "@apollo/client"; import * as R from "ramda"; import { useNavigate } from "react-router-dom"; +import { useDrafts } from "./hooks/useDrafts"; +import Timeline from "@mui/lab/Timeline"; +import TimelineItem from "@mui/lab/TimelineItem"; +import TimelineSeparator from "@mui/lab/TimelineSeparator"; +import TimelineConnector from "@mui/lab/TimelineConnector"; +import TimelineContent from "@mui/lab/TimelineContent"; +import TimelineDot from "@mui/lab/TimelineDot"; +import { HistoryEduOutlined } from "@mui/icons-material"; +import { format } from "date-fns"; const CREATE_ARTICLE = gql` mutation createArticle($createArticleInput: CreateArticleInput!) { @@ -52,15 +70,17 @@ const useStyles = makeStyles((theme) => const fields = ["title", "content", "publishedAt", "tags"] as const; type Values = Pick & - Partial>; + Partial>; interface Props { article?: Values; + draftKey: string; } -export const ArticleEditor: FC = ({ article }) => { +export const ArticleEditor: FC = ({ article, draftKey }) => { const classes = useStyles(); const navigate = useNavigate(); const formik = useRef>(null); + const { draft, putDraft, drafts, getDraft } = useDrafts(draftKey); const validationSchema = Yup.object({ content: Yup.string() @@ -100,30 +120,6 @@ export const ArticleEditor: FC = ({ article }) => { updateArticleInput: UpdateArticleInput; }>(UPDATE_ARTICLE); - const [putDraft] = useMutation< - { - draft: DraftArticle; - }, - { - draft: CreateDraftArticleInput; - } - >(gql` - mutation ($draft: CreateDraftArticleInput!) { - draft: putDraftForArticle(draft: $draft) { - id - createdAt - } - } - `); - - const tempKey = useMemo(() => { - if (article?.key) { - return article.key; - } else { - return new Date().valueOf().toString(); - } - }, [article]); - const submitForm = async ( values: Values, { setSubmitting }: FormikHelpers @@ -174,33 +170,15 @@ export const ArticleEditor: FC = ({ article }) => { setSubmitting(false); }; - const [drafts, setDrafts] = useState([]); const handleSave = useCallback( async (automatically = true) => { - const { data } = await putDraft({ - variables: { - draft: { - key: tempKey, - payload: formik.current?.values, - automatically, - }, - }, - }); - if (!data?.draft) { - return; - } - setDrafts((drafts) => { - const index = drafts.findIndex((it) => it.id === data.draft.id); - if (index === -1) { - return [data.draft, ...drafts]; - } else { - const arr = [...drafts]; - arr[index] = data.draft; - return arr; - } + await putDraft({ + key: draftKey, + payload: formik.current?.values, + automatically, }); }, - [tempKey, putDraft, formik, drafts] + [draftKey, putDraft, formik] ); return ( @@ -251,6 +229,11 @@ export const ArticleEditor: FC = ({ article }) => { > Publish + + getDraft(draft.id)} + /> @@ -260,6 +243,78 @@ export const ArticleEditor: FC = ({ article }) => { ); }; +interface HistoryDrawerProps { + drafts?: DraftArticle[]; + setCurrentDraft: (draft: DraftArticle) => void; +} + +const HistoryDrawer: FC = ({ drafts, setCurrentDraft }) => { + const theme = useTheme(); + const [open, setOpen] = useState(false); + return ( + <> + setOpen(!open)}> + + + + + setOpen(false)} + onOpen={() => setOpen(true)} + swipeAreaWidth={50} + disableSwipeToOpen={false} + ModalProps={{ + keepMounted: true, + BackdropProps: { + sx: { + backgroundColor: "transparent", + }, + }, + }} + > + + + {drafts?.map((draft, index) => ( + + + + + + + + {index + 1} - {format(draft.updatedAt, "yyyy-MM-dd")} + + {format(draft.updatedAt, "HH:mm:ss")} + + + ))} + + + + + ); +}; + ArticleEditor.defaultProps = { article: { title: "", diff --git a/src/articles/articles.constants.tsx b/src/articles/articles.constants.tsx index 80b838a..9185a44 100644 --- a/src/articles/articles.constants.tsx +++ b/src/articles/articles.constants.tsx @@ -7,6 +7,11 @@ export const ARTICLE = gql` title content publishedAt + histories { + updatedAt + payload + automatically + } } } `; diff --git a/src/articles/hooks/useDrafts.tsx b/src/articles/hooks/useDrafts.tsx new file mode 100644 index 0000000..0f6a897 --- /dev/null +++ b/src/articles/hooks/useDrafts.tsx @@ -0,0 +1,147 @@ +import { gql, useLazyQuery, useMutation, useQuery } from "@apollo/client"; +import { useCallback, useState } from "react"; +import { CreateDraftArticleInput, DraftArticle } from "../../generated/graphql"; + +export interface useDraftsProps { + key: string; +} + +export interface useDraftsReturn { + drafts?: DraftArticle[]; + draft?: DraftArticle; + listLoading: boolean; + itemLoading: boolean; + getDraft: (id: string) => Promise; + putDraft: (input: CreateDraftArticleInput) => Promise; +} + +export const useDrafts = (key: string): useDraftsReturn => { + const { data: { drafts, lastDraft: last } = {}, loading: listLoading } = + useQuery< + { drafts: DraftArticle[]; lastDraft: DraftArticle }, + { key: string } + >( + gql` + query DraftArticles($key: String!) { + drafts: listDraftForArticle(key: $key) { + id + key + automatically + updatedAt + } + lastDraft: lastDraftForArticle(key: $key) { + id + key + payload + automatically + updatedAt + } + } + `, + { + variables: { key }, + } + ); + const [ + get, + { data: { draft } = { draft: undefined }, loading: itemLoading }, + ] = useLazyQuery< + { + draft: DraftArticle; + }, + { + id: string; + } + >( + gql` + query DraftArticles($id: String!) { + draft: lastDraftForArticle(id: $id) { + id + key + payload + automatically + updatedAt + } + } + ` + ); + + const [put] = useMutation< + { + draft: DraftArticle; + }, + { + draft: CreateDraftArticleInput; + } + >( + gql` + mutation PutDraftForArticle($draft: CreateDraftArticleInput!) { + draft: putDraftForArticle(draft: $draft) { + id + key + payload + automatically + updatedAt + } + } + `, + { + update(cache, { data }) { + if (data?.draft) { + const newDraftRef = cache.writeFragment({ + data: data.draft, + fragment: gql` + fragment NewDraftArticle on DraftArticle { + id + key + payload + automatically + updatedAt + } + `, + }); + cache.modify({ + fields: { + drafts(existingDrafts: DraftArticle[] = [], { readField }) { + return [ + newDraftRef, + ...existingDrafts.filter( + (draft: any) => readField("id", draft) !== data.draft.id + ), + ]; + }, + lastDraft() { + return newDraftRef; + }, + }, + }); + } + }, + } + ); + + const getDraft = useCallback( + async (id: string) => { + return get({ variables: { id } }).then(({ data }) => data!.draft); + }, + [get] + ); + + const putDraft = useCallback( + async (input: CreateDraftArticleInput) => { + return put({ variables: { draft: input } }).then( + ({ data }) => data!.draft + ); + }, + [put] + ); + + return { + drafts, + draft: draft ?? last, + listLoading, + itemLoading, + getDraft, + putDraft, + }; +}; diff --git a/src/generated/graphql.schema.json b/src/generated/graphql.schema.json index 34ee416..b4b4a9f 100644 --- a/src/generated/graphql.schema.json +++ b/src/generated/graphql.schema.json @@ -97,18 +97,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "key", - "description": null, - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "html", "description": null, @@ -136,6 +124,30 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "histories", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ArticleHistory", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -163,6 +175,91 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "ArticleHistory", + "description": null, + "fields": [ + { + "name": "payload", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Object", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "updatedAt", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "DateTime", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "automatic", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "published", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "CreateArticleInput", @@ -242,71 +339,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "INPUT_OBJECT", - "name": "CreateDraftArticleInput", - "description": null, - "fields": null, - "inputFields": [ - { - "name": "payload", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Object", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "key", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "automatically", - "description": null, - "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - }, - "defaultValue": "true", - "isDeprecated": false, - "deprecationReason": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "Boolean", - "description": "The `Boolean` scalar type represents `true` or `false`.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, { "kind": "INPUT_OBJECT", "name": "CreateTagInput", @@ -344,81 +376,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "DraftArticle", - "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": "payload", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Object", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "key", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "automatically", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, { "kind": "OBJECT", "name": "Hello", @@ -550,113 +507,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "putDraftForArticle", - "description": null, - "args": [ - { - "name": "draft", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "CreateDraftArticleInput", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "DraftArticle", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "listDraftForArticle", - "description": null, - "args": [ - { - "name": "key", - "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": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "DraftArticle", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "lastDraftForArticle", - "description": null, - "args": [ - { - "name": "key", - "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": "DraftArticle", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "createTag", "description": null, @@ -775,7 +625,7 @@ { "kind": "SCALAR", "name": "Object", - "description": "The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).", + "description": "The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).", "fields": null, "inputFields": null, "interfaces": null, diff --git a/src/generated/graphql.tsx b/src/generated/graphql.tsx index 931c182..7ecf9c5 100644 --- a/src/generated/graphql.tsx +++ b/src/generated/graphql.tsx @@ -12,7 +12,7 @@ export type Scalars = { Float: number; /** A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format. */ DateTime: any; - /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ + /** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */ Object: any; }; @@ -23,9 +23,17 @@ export type Article = { content: Scalars['String']; publishedAt?: Maybe; tags: Array; - key?: Maybe; html: Scalars['String']; description?: Maybe; + histories: Array; +}; + +export type ArticleHistory = { + __typename?: 'ArticleHistory'; + payload: Scalars['Object']; + updatedAt: Scalars['DateTime']; + automatic: Scalars['Boolean']; + published: Scalars['Boolean']; }; export type CreateArticleInput = { @@ -35,25 +43,11 @@ export type CreateArticleInput = { tags: Array; }; -export type CreateDraftArticleInput = { - payload: Scalars['Object']; - key: Scalars['String']; - automatically?: Maybe; -}; - export type CreateTagInput = { name: Scalars['String']; }; -export type DraftArticle = { - __typename?: 'DraftArticle'; - id: Scalars['ID']; - payload: Scalars['Object']; - key: Scalars['String']; - automatically: Scalars['Boolean']; -}; - export type Hello = { __typename?: 'Hello'; message: Scalars['String']; @@ -64,9 +58,6 @@ export type Mutation = { createArticle: Article; updateArticle: Article; removeArticle: Scalars['Int']; - putDraftForArticle: DraftArticle; - listDraftForArticle: Array; - lastDraftForArticle: DraftArticle; createTag: Tag; updateTag: Tag; removeTag: Tag; @@ -88,21 +79,6 @@ export type MutationRemoveArticleArgs = { }; -export type MutationPutDraftForArticleArgs = { - draft: CreateDraftArticleInput; -}; - - -export type MutationListDraftForArticleArgs = { - key: Scalars['String']; -}; - - -export type MutationLastDraftForArticleArgs = { - key: Scalars['String']; -}; - - export type MutationCreateTagArgs = { createTagInput: CreateTagInput; }; diff --git a/src/routes.tsx b/src/routes.tsx index 12dbb35..02d98b7 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -1,18 +1,31 @@ import { useApolloClient } from "@apollo/client"; import * as R from "ramda"; import { FC, lazy, useMemo } from "react"; -import { useParams, useRoutes } from "react-router-dom"; +import { + Navigate, + useParams, + useRoutes, + useSearchParams, +} from "react-router-dom"; import { ARTICLE } from "./articles"; import { Article } from "./generated/graphql"; import { DefaultLayout } from "./layouts"; -const CreateArticle = lazy(() => - import( - /* webpackChunkName: "article-editor" */ "./articles/article-editor" - ).then((m) => ({ - default: m.ArticleEditor, - })) -); +const CreateArticle = () => { + const ArticleEditor = lazy(() => + import( + /* webpackChunkName: "article-editor" */ "./articles/article-editor" + ).then((m) => ({ + default: m.ArticleEditor, + })) + ); + const [search] = useSearchParams(); + if (!search.has("key")) { + search.set("key", new Date().toISOString()); + return ; + } + return ; +}; const ModifyArticle: FC = () => { const { id } = useParams(); @@ -33,7 +46,11 @@ const ModifyArticle: FC = () => { return R.omit(["__typename"], data.article); }), ]); - return { default: () => }; + return { + default: () => ( + + ), + }; }), [id, client] );