diff --git a/components/CollectionEditor/CollectForm.tsx b/components/CollectionEditor/CollectForm.tsx index 97c0df5ffa..2f8db4a6c9 100644 --- a/components/CollectionEditor/CollectForm.tsx +++ b/components/CollectionEditor/CollectForm.tsx @@ -18,7 +18,7 @@ import { translate } from '~/common/utils' import styles from './styles.css' interface Props { - onAdd: (articleId: string) => void + onAdd: (article: SearchArticles_search_edges_node_Article) => void } const debouncedSetSearch = _debounce((value, setSearch) => { @@ -70,7 +70,7 @@ const CollectForm: FC = ({ onAdd }) => { onClick={( article: SearchArticles_search_edges_node_Article ) => { - onAdd(article.id) + onAdd(article) setSearch('') hideDropdown() }} diff --git a/components/CollectionEditor/index.tsx b/components/CollectionEditor/index.tsx index 5f78aed918..7eeeb24229 100644 --- a/components/CollectionEditor/index.tsx +++ b/components/CollectionEditor/index.tsx @@ -20,12 +20,11 @@ import styles from './styles.css' interface State { articles: DropdownDigestArticle[] - prevArticleIds: string[] } interface Props { articles: DropdownDigestArticle[] - onEdit: (articleIds: string[]) => void + onEdit: (articles: DropdownDigestArticle[]) => void } const reorder = (list: any[], startIndex: number, endIndex: number) => { @@ -41,30 +40,28 @@ class CollectionEditor extends React.PureComponent { super(props) this.state = { - articles: this.props.articles, - prevArticleIds: this.props.articles.map(({ id }) => id) + articles: this.props.articles } } componentDidUpdate() { - const { prevArticleIds } = this.state + const { articles } = this.state + const prevArticleIds = articles.map(({ id }) => id) const articleIds = this.props.articles.map(({ id }) => id) if (_isEqual(prevArticleIds, articleIds)) { return } - this.setState({ articles: this.props.articles, prevArticleIds: articleIds }) + this.setState({ articles: this.props.articles }) } - onAdd = (articleId: string) => { - const prevArticleIds = this.state.articles.map(({ id }) => id) - this.props.onEdit([...prevArticleIds, articleId]) + onAdd = (article: any) => { + this.props.onEdit([...this.state.articles, article]) } - onDelete = (articleId: string) => { - const prevArticleIds = this.state.articles.map(({ id }) => id) - this.props.onEdit(prevArticleIds.filter(id => id !== articleId)) + onDelete = (article: any) => { + this.props.onEdit(this.state.articles.filter(({ id }) => id !== article.id)) } onDragEnd = (result: DropResult) => { @@ -85,7 +82,7 @@ class CollectionEditor extends React.PureComponent { result.destination.index ) this.setState({ articles: newItems }) - this.props.onEdit(newItems.map(({ id }) => id)) + this.props.onEdit(newItems) } render() { @@ -132,7 +129,7 @@ class CollectionEditor extends React.PureComponent { type="button" className="delete-handler" aria-label="刪除" - onClick={() => this.onDelete(article.id)} + onClick={() => this.onDelete(article)} > { const avatarWrapClasses = classNames({ 'avatar-wrap': true }) - console.log(notice) return (
@@ -34,7 +33,7 @@ const ArticleNewCollectedNotice = ({ notice }: { notice: NoticeType }) => { {' '} {' '} {' '} - + diff --git a/views/ArticleDetail/Collection/CollectionList.tsx b/views/ArticleDetail/Collection/CollectionList.tsx new file mode 100644 index 0000000000..c0c2b1320d --- /dev/null +++ b/views/ArticleDetail/Collection/CollectionList.tsx @@ -0,0 +1,151 @@ +import gql from 'graphql-tag' +import _get from 'lodash/get' +import _uniq from 'lodash/uniq' +import { QueryResult } from 'react-apollo' + +import { ArticleDigest, Icon, Spinner, TextIcon, Translate } from '~/components' +import { Query } from '~/components/GQL' + +import { ANALYTICS_EVENTS, FEED_TYPE } from '~/common/enums' +import { analytics, mergeConnections } from '~/common/utils' +import ICON_ADD from '~/static/icons/add.svg?sprite' +import ICON_MORE_CONTENT from '~/static/icons/more-content.svg?sprite' + +import { ArticleDetail_article } from '../__generated__/ArticleDetail' +import { SidebarCollection } from './__generated__/SidebarCollection' +import styles from './styles.css' + +export const SIDEBAR_COLLECTION = gql` + query SidebarCollection($mediaHash: String, $cursor: String, $first: Int) { + article(input: { mediaHash: $mediaHash }) { + id + collection(input: { after: $cursor, first: $first }) + @connection(key: "articleCollection") { + pageInfo { + startCursor + endCursor + hasNextPage + } + totalCount + edges { + cursor + node { + ...PlainDigestArticle + } + } + } + } + } + ${ArticleDigest.Plain.fragments.article} +` + +const CollectionList = ({ + article, + setEditing, + canEdit +}: { + article: ArticleDetail_article + setEditing: (editing: boolean) => void + canEdit?: boolean +}) => ( + + {({ + data, + loading, + error, + fetchMore + }: QueryResult & { data: SidebarCollection }) => { + if (loading) { + return + } + + const path = 'article.collection' + const { edges, pageInfo, totalCount } = _get(data, path, {}) + const loadRest = () => + fetchMore({ + variables: { + mediaHash: article.mediaHash, + cursor: pageInfo.endCursor, + first: null + }, + updateQuery: (previousResult, { fetchMoreResult }) => + mergeConnections({ + oldData: previousResult, + newData: fetchMoreResult, + path + }) + }) + + if (totalCount <= 0 && canEdit) { + return ( + + ) + } + + return ( + <> +
    + {edges.map( + ({ node, cursor }: { node: any; cursor: any }, i: number) => ( +
  1. + analytics.trackEvent(ANALYTICS_EVENTS.CLICK_COLLECTION, { + type: FEED_TYPE.COLLECTION, + location: i + }) + } + > + +
  2. + ) + )} +
+ + {pageInfo.hasNextPage && ( +
+ +
+ )} + + + + ) + }} +
+) + +export default CollectionList diff --git a/views/ArticleDetail/Collection/EditButton.tsx b/views/ArticleDetail/Collection/EditButton.tsx new file mode 100644 index 0000000000..313a8ba9f7 --- /dev/null +++ b/views/ArticleDetail/Collection/EditButton.tsx @@ -0,0 +1,151 @@ +import classNames from 'classnames' +import gql from 'graphql-tag' +import _get from 'lodash/get' +import _uniq from 'lodash/uniq' +import { useContext } from 'react' + +import { + ArticleDigest, + Button, + Icon, + LanguageContext, + TextIcon, + Translate +} from '~/components' +import { Mutation } from '~/components/GQL' + +import { translate } from '~/common/utils' +import ICON_EDIT from '~/static/icons/collection-edit.svg?sprite' +import ICON_SAVE from '~/static/icons/pen.svg?sprite' + +import { ArticleDetail_article } from '../__generated__/ArticleDetail' +import { SIDEBAR_COLLECTION } from './CollectionList' +import styles from './styles.css' + +const EDITOR_SET_COLLECTION = gql` + mutation EditorSetCollection($id: ID!, $collection: [ID!]!, $first: Int) { + setCollection(input: { id: $id, collection: $collection }) { + id + collection(input: { first: $first }) + @connection(key: "articleCollection") { + totalCount + edges { + cursor + node { + ...DropdownDigestArticle + } + } + } + } + } + ${ArticleDigest.Dropdown.fragments.article} +` + +const IconBox = ({ icon }: { icon: any }) => ( + +) + +const EditButton = ({ + article, + editing, + setEditing, + editingArticles, + inPopover +}: { + article: ArticleDetail_article + editing: boolean + setEditing: any + editingArticles: string[] + inPopover?: boolean +}) => { + const { lang } = useContext(LanguageContext) + const editButtonClass = classNames({ + 'edit-button': true, + inner: inPopover + }) + + if (editing) { + const refetchQueries = [ + { + query: SIDEBAR_COLLECTION, + variables: { mediaHash: article.mediaHash, first: 10 } + } + ] + + return ( + + {setCollection => ( + + + + + )} + + ) + } + + return ( + + + + + ) +} + +export default EditButton diff --git a/views/ArticleDetail/Collection/EditingList.tsx b/views/ArticleDetail/Collection/EditingList.tsx new file mode 100644 index 0000000000..2b17e8c624 --- /dev/null +++ b/views/ArticleDetail/Collection/EditingList.tsx @@ -0,0 +1,90 @@ +import gql from 'graphql-tag' +import _get from 'lodash/get' +import _uniqBy from 'lodash/uniqBy' +import dynamic from 'next/dynamic' +import { useEffect } from 'react' +import { QueryResult } from 'react-apollo' + +import { ArticleDigest, Spinner } from '~/components' +import { Query } from '~/components/GQL' + +import { ArticleDetail_article } from '../__generated__/ArticleDetail' +import { EditorCollection } from './__generated__/EditorCollection' +import styles from './styles.css' + +const EDITOR_COLLECTION = gql` + query EditorCollection($mediaHash: String, $first: Int) { + article(input: { mediaHash: $mediaHash }) { + id + collection(input: { first: $first }) + @connection(key: "articleCollection") { + pageInfo { + startCursor + endCursor + hasNextPage + } + totalCount + edges { + cursor + node { + ...DropdownDigestArticle + } + } + } + } + } + ${ArticleDigest.Dropdown.fragments.article} +` + +const CollectionEditor = dynamic( + () => import('~/components/CollectionEditor'), + { + ssr: false, + loading: () => + } +) + +const EditingList = ({ + article, + editingArticles, + setEditingArticles +}: { + article: ArticleDetail_article + editingArticles: any[] + setEditingArticles: (articles: any[]) => void +}) => ( + + {({ + data, + loading, + error, + fetchMore + }: QueryResult & { data: EditorCollection }) => { + if (loading) { + return + } + + const { edges } = _get(data, 'article.collection', {}) + + useEffect(() => { + setEditingArticles(edges.map(({ node }: { node: any }) => node)) + }, []) + + return ( +
+ setEditingArticles(_uniqBy(articles, 'id'))} + /> + + +
+ ) + }} +
+) + +export default EditingList diff --git a/views/ArticleDetail/Collection/index.tsx b/views/ArticleDetail/Collection/index.tsx index 8b47946344..ad39b76071 100644 --- a/views/ArticleDetail/Collection/index.tsx +++ b/views/ArticleDetail/Collection/index.tsx @@ -1,408 +1,47 @@ -import classNames from 'classnames' -import gql from 'graphql-tag' import _get from 'lodash/get' import _uniq from 'lodash/uniq' -import dynamic from 'next/dynamic' -import { withRouter, WithRouterProps } from 'next/router' -import { useContext, useState } from 'react' -import { QueryResult } from 'react-apollo' +import { useState } from 'react' -import { - ArticleDigest, - Button, - Icon, - Spinner, - TextIcon, - Translate -} from '~/components' -import { Mutation, Query } from '~/components/GQL' -import { LanguageContext } from '~/components/Language' - -import { ANALYTICS_EVENTS, FEED_TYPE } from '~/common/enums' -import { - analytics, - getQuery, - mergeConnections, - translate -} from '~/common/utils' -import ICON_ADD from '~/static/icons/add.svg?sprite' -import ICON_EDIT from '~/static/icons/collection-edit.svg?sprite' -import ICON_MORE_CONTENT from '~/static/icons/more-content.svg?sprite' -import ICON_SAVE from '~/static/icons/pen.svg?sprite' - -import { EditorCollection } from './__generated__/EditorCollection' -import { SidebarCollection } from './__generated__/SidebarCollection' +import { ArticleDetail_article } from '../__generated__/ArticleDetail' +import CollectionList from './CollectionList' +import EditButton from './EditButton' +import EditingList from './EditingList' import styles from './styles.css' -const SIDEBAR_COLLECTION = gql` - query SidebarCollection( - $mediaHash: String - $uuid: UUID - $cursor: String - $first: Int - ) { - article(input: { mediaHash: $mediaHash, uuid: $uuid }) { - id - collection(input: { after: $cursor, first: $first }) - @connection(key: "articleCollection") { - pageInfo { - startCursor - endCursor - hasNextPage - } - totalCount - edges { - cursor - node { - ...PlainDigestArticle - } - } - } - } - } - ${ArticleDigest.Plain.fragments.article} -` - -const EDITOR_COLLECTION = gql` - query EditorCollection($mediaHash: String, $uuid: UUID, $first: Int) { - article(input: { mediaHash: $mediaHash, uuid: $uuid }) { - id - collection(input: { first: $first }) - @connection(key: "articleCollection") { - pageInfo { - startCursor - endCursor - hasNextPage - } - totalCount - edges { - cursor - node { - ...DropdownDigestArticle - } - } - } - } - } - ${ArticleDigest.Dropdown.fragments.article} -` - -const EDITOR_SET_COLLECTION = gql` - mutation EditorSetCollection($id: ID!, $collection: [ID!]!, $first: Int) { - setCollection(input: { id: $id, collection: $collection }) { - id - collection(input: { first: $first }) - @connection(key: "articleCollection") { - totalCount - edges { - cursor - node { - ...DropdownDigestArticle - } - } - } - } - } - ${ArticleDigest.Dropdown.fragments.article} -` - -const CollectionEditor = dynamic( - () => import('~/components/CollectionEditor'), - { - ssr: false, - loading: () => - } -) - -const IconBox = ({ icon }: { icon: any }) => ( - -) - -const CollectionEditButton = ({ - editing, - setEditing, - inPopover -}: { - editing: boolean - setEditing: any +const Collection: React.FC<{ + article: ArticleDetail_article inPopover?: boolean -}) => { - const editButtonClass = classNames({ - 'edit-button': true, - inner: inPopover - }) - - if (editing) { - return ( - - - - - ) - } - - return ( - - - - - ) -} - -const CollectionList = ({ - mediaHash, - uuid, - setEditing, - canEdit -}: { - mediaHash: string | undefined - uuid: string | undefined - setEditing: any canEdit?: boolean -}) => ( - - {({ - data, - loading, - error, - fetchMore - }: QueryResult & { data: SidebarCollection }) => { - if (loading) { - return - } - - const path = 'article.collection' - const { edges, pageInfo, totalCount } = _get(data, path, {}) - const loadRest = () => - fetchMore({ - variables: { - mediaHash, - uuid, - cursor: pageInfo.endCursor, - first: null - }, - updateQuery: (previousResult, { fetchMoreResult }) => - mergeConnections({ - oldData: previousResult, - newData: fetchMoreResult, - path - }) - }) - - if (totalCount <= 0 && canEdit) { - return ( - - ) - } - - return ( - <> -
    - {edges.map( - ({ node, cursor }: { node: any; cursor: any }, i: number) => ( -
  1. - analytics.trackEvent(ANALYTICS_EVENTS.CLICK_COLLECTION, { - type: FEED_TYPE.COLLECTION, - location: i - }) - } - > - -
  2. - ) - )} -
- - {pageInfo.hasNextPage && ( -
- -
- )} - - - - ) - }} -
-) - -const CollectionEditingList = ({ - mediaHash, - uuid, - lang -}: { - mediaHash: string | undefined - uuid: string | undefined - lang: Language -}) => { - const refetchQueries = [ - { - query: SIDEBAR_COLLECTION, - variables: { mediaHash, uuid, first: 10 } - } - ] - - const onEdit = (id: string, setCollection: any) => async ( - articleIds: string[] - ) => { - try { - await setCollection({ - variables: { - id, - collection: _uniq(articleIds), - first: null - } - }) - window.dispatchEvent( - new CustomEvent('addToast', { - detail: { - color: 'green', - content: translate({ - zh_hant: '關聯已更新', - zh_hans: '关联已更新', - lang - }), - closeButton: true, - duration: 2000 - } - }) - ) - } catch (error) { - window.dispatchEvent( - new CustomEvent('addToast', { - detail: { - color: 'red', - content: translate({ - zh_hant: '關聯失敗', - zh_hans: '关联失敗', - lang - }), - clostButton: true, - duration: 2000 - } - }) - ) - } - } - - return ( - - {({ - data, - loading, - error, - fetchMore - }: QueryResult & { data: EditorCollection }) => { - if (loading) { - return - } - const { id } = _get(data, 'article', {}) - const { edges } = _get(data, 'article.collection', {}) - - return ( -
- - {setCollection => ( - node)} - onEdit={onEdit(id, setCollection)} - /> - )} - - - -
- ) - }} -
- ) -} - -const Collection: React.FC< - WithRouterProps & { - inPopover?: boolean - canEdit?: boolean - } -> = ({ router, inPopover, canEdit }) => { - const { lang } = useContext(LanguageContext) +}> = ({ article, inPopover, canEdit }) => { const [editing, setEditing] = useState(false) - const mediaHash = getQuery({ router, key: 'mediaHash' }) - const uuid = getQuery({ router, key: 'post' }) - - if (!mediaHash && !uuid) { - return null - } + const [editingArticles, setEditingArticles] = useState([]) return ( <> {canEdit && ( - )} {!editing && ( )} {editing && ( - + )} @@ -410,4 +49,4 @@ const Collection: React.FC< ) } -export default withRouter(Collection) +export default Collection diff --git a/views/ArticleDetail/CollectionMeta/index.tsx b/views/ArticleDetail/CollectionMeta/index.tsx index 4dd9ab469c..53053cca3f 100644 --- a/views/ArticleDetail/CollectionMeta/index.tsx +++ b/views/ArticleDetail/CollectionMeta/index.tsx @@ -6,6 +6,7 @@ import { Popover } from '~/components/Popper' import ICON_COLLECTION from '~/static/icons/collection.svg?sprite' +import { ArticleDetail_article } from '../__generated__/ArticleDetail' import Collection from '../Collection' import styles from './styles.css' @@ -18,9 +19,11 @@ const IconCollection = () => ( ) const CollectionMeta = ({ + article, count, canEditCollection }: { + article: ArticleDetail_article count: number canEditCollection?: boolean }) => { @@ -37,7 +40,7 @@ const CollectionMeta = ({ /> - +
diff --git a/views/ArticleDetail/Sidebar/index.tsx b/views/ArticleDetail/Sidebar/index.tsx index 9e64acc8e7..5c194dde9a 100644 --- a/views/ArticleDetail/Sidebar/index.tsx +++ b/views/ArticleDetail/Sidebar/index.tsx @@ -4,13 +4,16 @@ import { Footer, Responsive } from '~/components' import { dom } from '~/common/utils' +import { ArticleDetail_article } from '../__generated__/ArticleDetail' import Collection from '../Collection' import styles from './styles.css' export default ({ + article, hasCollection, canEditCollection }: { + article: ArticleDetail_article hasCollection: boolean canEditCollection?: boolean }) => { @@ -38,9 +41,9 @@ export default ({ {(match: boolean) => ( <> - {match && (hasCollection || canEditCollection) && ( + {match && article && (hasCollection || canEditCollection) && (
- +
)} diff --git a/views/ArticleDetail/index.tsx b/views/ArticleDetail/index.tsx index f073c81ef5..463159e68a 100644 --- a/views/ArticleDetail/index.tsx +++ b/views/ArticleDetail/index.tsx @@ -182,6 +182,7 @@ const ArticleDetail: React.FC = ({ router }) => { {data.article.live && } {(collectionCount > 0 || canEditCollection) && ( @@ -213,6 +214,7 @@ const ArticleDetail: React.FC = ({ router }) => { id="drawer-calc-hook" > 0} canEditCollection={canEditCollection} /> diff --git a/views/Me/DraftDetail/Sidebar/CollectArticles/index.tsx b/views/Me/DraftDetail/Sidebar/CollectArticles/index.tsx index e3a53a582d..731c4a4465 100644 --- a/views/Me/DraftDetail/Sidebar/CollectArticles/index.tsx +++ b/views/Me/DraftDetail/Sidebar/CollectArticles/index.tsx @@ -80,7 +80,7 @@ const CollectArticles = ({ draft }: { draft: CollectArticlesDraft }) => { }) const handleCollectionChange = (setCollection: any) => async ( - articleIds: string[] + articles: any[] ) => { updateHeaderState({ type: 'draft', @@ -91,7 +91,7 @@ const CollectArticles = ({ draft }: { draft: CollectArticlesDraft }) => { await setCollection({ variables: { id: draft.id, - collection: _uniq(articleIds) + collection: _uniq(articles.map(({ id }) => id)) } }) updateHeaderState({