diff --git a/web/components/contracts-list.tsx b/web/components/contracts-list.tsx index b40c926a..07c052b0 100644 --- a/web/components/contracts-list.tsx +++ b/web/components/contracts-list.tsx @@ -16,6 +16,7 @@ import { UserLink } from './user-page' import { Linkify } from './linkify' import { Col } from './layout/col' import { SiteLink } from './site-link' +import { parseTags } from '../lib/util/parse' export function ContractDetails(props: { contract: Contract }) { const { contract } = props @@ -162,9 +163,64 @@ export function CreatorContractsGrid(props: { contracts: Contract[] }) { ) } +export function TagContractsGrid(props: { contracts: Contract[] }) { + const { contracts } = props + + const contractTags = _.flatMap(contracts, (contract) => + parseTags(contract.question + ' ' + contract.description).map((tag) => ({ + tag, + contract, + })) + ) + const groupedByTag = _.groupBy(contractTags, ({ tag }) => tag) + const byTag = _.mapValues(groupedByTag, (contractTags) => + contractTags.map(({ contract }) => contract) + ) + const tags = _.sortBy(Object.keys(byTag), (tag) => + _.sumBy(byTag[tag], (contract) => -1 * compute(contract).truePool) + ) + + return ( + + {tags.map((tag) => { + return ( + + + #{tag} + + + + + {byTag[tag].length > MAX_GROUPED_CONTRACTS_DISPLAYED ? ( + + e.stopPropagation()} + > + See all + + + ) : ( +
+ )} + + ) + })} + + ) +} + const MAX_CONTRACTS_DISPLAYED = 99 -type Sort = 'creator' | 'createdTime' | 'pool' | 'resolved' | 'all' +type Sort = 'creator' | 'tag' | 'createdTime' | 'pool' | 'resolved' | 'all' export function SearchableGrid(props: { contracts: Contract[] defaultSort?: Sort @@ -189,7 +245,7 @@ export function SearchableGrid(props: { if (sort === 'createdTime' || sort === 'resolved' || sort === 'all') { matches.sort((a, b) => b.createdTime - a.createdTime) - } else if (sort === 'pool' || sort === 'creator') { + } else if (sort === 'pool' || sort === 'creator' || sort === 'tag') { matches.sort((a, b) => compute(b).truePool - compute(a).truePool) } @@ -224,13 +280,16 @@ export function SearchableGrid(props: { ) : ( )} +
- {!byOneCreator && (sort === 'creator' || sort === 'resolved') ? ( + {sort === 'tag' ? ( + + ) : !byOneCreator && (sort === 'creator' || sort === 'resolved') ? ( ) : ( diff --git a/web/lib/util/parse.ts b/web/lib/util/parse.ts new file mode 100644 index 00000000..2857b7a1 --- /dev/null +++ b/web/lib/util/parse.ts @@ -0,0 +1,8 @@ +export function parseTags(text: string) { + const regex = /(?:^|\s)(?:[#][a-z0-9_]+)/gi + const matches = text.match(regex) || [] + return matches.map((match) => { + const tag = match.trim().substring(1) + return tag + }) +}