diff --git a/.env.dev b/.env.dev index 01360cdf83..0ce5f15490 100644 --- a/.env.dev +++ b/.env.dev @@ -34,7 +34,7 @@ NEXT_PUBLIC_FACEBOOK_CLIENT_ID=1072677713882819 NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY=0x4AAAAAAAKiedvR5qiLUhIs NEXT_PUBLIC_NOMAD_MATTERS_CAMPAIGN_LINK=/@rsdyobw/22048-launching-the-nomad-matters-initiative NEXT_PUBLIC_BILLBOARD_ADDRESS=0x6a72820E1CCCba1B1FE02E37881cEa3F9Aa6375C -NEXT_PUBLIC_BILLBOARD_TOKEN_ID=1 +NEXT_PUBLIC_BILLBOARD_TOKEN_ID=6 NEXT_PUBLIC_BILLBOARD_IMAGE_URL=matters-billboard-ad-dev.s3.ap-southeast-1.amazonaws.com/ DEBUG=false PLAYWRIGHT_RUNTIME_ENV=ci diff --git a/.env.local.example b/.env.local.example index 2f052fe711..66d0354fa4 100644 --- a/.env.local.example +++ b/.env.local.example @@ -30,7 +30,7 @@ NEXT_PUBLIC_LOGBOOKS_URL=https://logbooks-vercel.matters.town NEXT_PUBLIC_ALCHEMY_KEY=1dMo8xjAFo8M6Y4sQ45WTD3Zie2-MA4C NEXT_PUBLIC_NOMAD_MATTERS_CAMPAIGN_LINK=/@rsdyobw/22048-launching-the-nomad-matters-initiative NEXT_PUBLIC_BILLBOARD_ADDRESS=0x6a72820E1CCCba1B1FE02E37881cEa3F9Aa6375C -NEXT_PUBLIC_BILLBOARD_TOKEN_ID=1 +NEXT_PUBLIC_BILLBOARD_TOKEN_ID=6 NEXT_PUBLIC_BILLBOARD_IMAGE_URL=matters-billboard-ad-dev.s3.ap-southeast-1.amazonaws.com/ DEBUG=false PLAYWRIGHT_RUNTIME_ENV=local diff --git a/package.json b/package.json index 0198b6fa58..a0a09e2273 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matters-web", - "version": "4.30.1", + "version": "4.30.2", "description": "codebase of Matters' website", "author": "Matters ", "engines": { diff --git a/src/components/Book/Collection/index.tsx b/src/components/Book/Collection/index.tsx index c808763c0f..680f5589a9 100644 --- a/src/components/Book/Collection/index.tsx +++ b/src/components/Book/Collection/index.tsx @@ -3,7 +3,6 @@ import { useEffect } from 'react' import BOOK_COVER from '@/public/static/images/book-cover.png' import { TEST_ID } from '~/common/enums' -import { countStrWidth } from '~/common/utils' import { IconCamera24, ResponsiveImage, @@ -35,13 +34,8 @@ const BookCollection: React.FC = ({ getColor() }, [cover]) - const titleWidth = countStrWidth(title) - const hasNonCJK = title.length * 2 !== titleWidth - const jacketClasses = classNames({ [styles.jacket]: true, - [styles.titleLg]: hasNonCJK ? titleWidth <= 10 : titleWidth <= 12, - [styles.titleMd]: titleWidth > 12 && titleWidth <= 28 && !hasNonCJK, }) return ( diff --git a/src/components/Book/Collection/styles.module.css b/src/components/Book/Collection/styles.module.css index c3d5768c0f..a1672c56d4 100644 --- a/src/components/Book/Collection/styles.module.css +++ b/src/components/Book/Collection/styles.module.css @@ -7,6 +7,8 @@ width: 7.5rem; height: 10rem; overflow: hidden; + text-align: left; + white-space: initial; border-radius: 0.5rem; box-shadow: 0 2px 2px 0 rgb(255 255 255 / 50%) inset, @@ -47,7 +49,7 @@ } & .jacket { - @mixin flex-start-center; + @mixin flex-start-start; position: absolute; right: 0; @@ -60,31 +62,14 @@ & .title { @mixin line-clamp; + padding-top: var(--spacing-x-tight); padding-right: var(--spacing-x-tight); padding-left: var(--spacing-x-base); - font-size: var(--font-size-xs); + font-size: var(--font-size-sm); font-weight: var(--font-weight-normal); - line-height: 1.0625rem; + line-height: 1.25rem; color: var(--color-white); - -webkit-line-clamp: 3; - } - - &.titleMd { - & .title { - font-size: var(--font-size-sm-s); - line-height: 1.25rem; - } - } - - &.titleLg { - @mixin flex-start-start; - - padding-top: var(--spacing-x-tight); - - & .title { - font-size: var(--font-size-md); - line-height: 1.5rem; - } + -webkit-line-clamp: 2; } } diff --git a/src/components/FileUploader/CoverUploader/index.tsx b/src/components/FileUploader/CoverUploader/index.tsx index 28b9b6106f..274b61ee8c 100644 --- a/src/components/FileUploader/CoverUploader/index.tsx +++ b/src/components/FileUploader/CoverUploader/index.tsx @@ -217,14 +217,12 @@ export const CoverUploader = ({ {isCollection && (
- { - - } +
)} diff --git a/src/components/SearchSelect/SearchingArea/EditorSearchingArea.tsx b/src/components/SearchSelect/SearchingArea/EditorSearchingArea.tsx index 418d22fc76..49b4c007e7 100644 --- a/src/components/SearchSelect/SearchingArea/EditorSearchingArea.tsx +++ b/src/components/SearchSelect/SearchingArea/EditorSearchingArea.tsx @@ -214,6 +214,7 @@ const EditorSearchingArea: React.FC = ({ return } } + // searching useEffect(() => { if (debouncedSearchKey) { @@ -306,8 +307,8 @@ const EditorSearchingArea: React.FC = ({ node={node} onClick={addNodeToStaging} selected={ - stagingNodes.findIndex((SN) => { - return SN.node.id === node.id + stagingNodes.findIndex((sn) => { + return sn.node.id === node.id }) !== -1 } inSearchingArea @@ -339,6 +340,7 @@ const EditorSearchingArea: React.FC = ({ )} + {/* URL Search */} {isArticleUrlMode && !searching && !hasArticle && } {isArticleUrlMode && @@ -350,8 +352,8 @@ const EditorSearchingArea: React.FC = ({ onClick={addNodeToStaging} selected={ stagingNodes.findIndex( - (SN) => - SN.node.id === + (sn) => + sn.node.id === (articleUrlData.node?.__typename === 'Article' && articleUrlData.node.id) ) !== -1 diff --git a/src/components/SearchSelect/SearchingArea/gql.ts b/src/components/SearchSelect/SearchingArea/gql.ts index 9671627b8f..3a57fc2300 100644 --- a/src/components/SearchSelect/SearchingArea/gql.ts +++ b/src/components/SearchSelect/SearchingArea/gql.ts @@ -21,6 +21,7 @@ export const SELECT_SEARCH = gql` first: $first exclude: $exclude includeAuthorTags: $includeAuthorTags + quicksearch: true } ) { pageInfo { diff --git a/src/components/SearchSelect/SearchingArea/index.tsx b/src/components/SearchSelect/SearchingArea/index.tsx index e85e542a14..946d405075 100644 --- a/src/components/SearchSelect/SearchingArea/index.tsx +++ b/src/components/SearchSelect/SearchingArea/index.tsx @@ -5,9 +5,12 @@ import { useDebouncedCallback } from 'use-debounce' import { INPUT_DEBOUNCE } from '~/common/enums' import { analytics, + isUrl, isValidEmail, mergeConnections, - normalizeTag, // stripTagAllPunct, // stripPunctPrefixSuffix, + normalizeTag, + parseURL, + toGlobalId, // stripTagAllPunct, // stripPunctPrefixSuffix, } from '~/common/utils' import { EmptySearch, @@ -19,6 +22,7 @@ import { } from '~/components' import { toUserDigestMiniPlaceholder } from '~/components/UserDigest/Mini' import { + ArticleUrlQueryQuery, ListViewerArticlesQuery, SearchExclude, SearchFilter, @@ -28,7 +32,7 @@ import { import SearchSelectNode from '../SearchSelectNode' import styles from '../styles.module.css' import CreateTag from './CreateTag' -import { LIST_VIEWER_ARTICLES, SELECT_SEARCH } from './gql' +import { ARTICLE_URL_QUERY, LIST_VIEWER_ARTICLES, SELECT_SEARCH } from './gql' import InviteEmail from './InviteEmail' import SearchInput, { SearchInputProps, @@ -69,7 +73,7 @@ type SearchingAreaProps = { inviteEmail?: boolean } & Pick -type Mode = 'search' | 'list' +type Mode = 'search' | 'list' | 'article_url' const SearchingArea: React.FC = ({ searchType, @@ -94,6 +98,7 @@ const SearchingArea: React.FC = ({ const [mode, setMode] = useState(hasListMode ? 'list' : 'search') const isSearchMode = mode === 'search' const isListMode = mode === 'list' + const isArticleUrlMode = mode === 'article_url' const [searching, setSearching] = useState(false) const [searchingNodes, setSearchingNodes] = useState([]) @@ -117,6 +122,14 @@ const SearchingArea: React.FC = ({ loadList, { data: listData, loading: listLoading, fetchMore: fetchMoreList }, ] = useLazyQuery(LIST_VIEWER_ARTICLES) + const [ + lazyArticleUrlQuery, + { data: articleUrlData, loading: articleUrlLoding }, + ] = usePublicLazyQuery( + ARTICLE_URL_QUERY, + {}, + { publicQuery: !viewer.isAuthed } + ) // pagination const { edges: searchEdges, pageInfo: searchPageInfo } = data?.search || {} @@ -160,16 +173,34 @@ const SearchingArea: React.FC = ({ .filter((node) => node.articleState === 'active') || [] const listNodeIds = listNode.map((n) => n.id).join(',') const search = (key: string) => { - const type = searchType === 'Invitee' ? 'User' : searchType - lazySearch({ - variables: { - key, - type, - filter: searchFilter, - exclude: searchExclude, - first: 10, - }, - }) + // Used to match links of the format like👇 + // https://matters.town/@az/12-来自matters的第一封信-致好朋友-zdpuAnuMKxNv6SUj7kTRzgrWRdp9q4aMMKHJ6TGtn8tp4FwX2 + const regex = new RegExp( + `^https://${process.env.NEXT_PUBLIC_SITE_DOMAIN}/@\\w+/\\d+.*$` + ) + if (searchType === 'Article' && isUrl(key) && regex.test(key)) { + const urlObj = parseURL(key) + const paths = urlObj.pathname.split('-') + const subPaths = paths[0].split('/') + const articleId = subPaths?.[subPaths.length - 1] + setMode('article_url') + lazyArticleUrlQuery({ + variables: { + id: toGlobalId({ type: 'Article', id: articleId }), + }, + }) + } else { + const type = searchType === 'Invitee' ? 'User' : searchType + lazySearch({ + variables: { + key, + type, + filter: searchFilter, + exclude: searchExclude, + first: 10, + }, + }) + } } // handling changes from search input @@ -182,11 +213,14 @@ const SearchingArea: React.FC = ({ toSearchingArea() } - if (hasListMode) { - setMode(value ? 'search' : 'list') + setMode('search') + + if (hasListMode && !value) { + setMode('list') return } } + const onSearchInputFocus = () => { if (hasListMode) { if (!searchKey) { @@ -196,6 +230,7 @@ const SearchingArea: React.FC = ({ return } } + const onSearchInputBlur = () => { if (isSearchMode) { return @@ -239,8 +274,14 @@ const SearchingArea: React.FC = ({ setSearchingNodes(listNode) }, [listLoading, listNodeIds]) + // article url + useEffect(() => { + setSearching(articleUrlLoding) + }, [articleUrlLoding]) + const hasNodes = searchNodes.length > 0 const haslistNode = listNode.length > 0 + const hasArticle = !!articleUrlData?.node const canCreateTag = isTag && searchKey && @@ -335,6 +376,19 @@ const SearchingArea: React.FC = ({ )} + + {/* URL Search */} + {isArticleUrlMode && !searching && !hasArticle && } + + {isArticleUrlMode && + !searching && + hasArticle && + articleUrlData?.node?.__typename === 'Article' && ( + + )} )}