diff --git a/web/components/contract-feed.tsx b/web/components/contract-feed.tsx
index 32db9013..926f8d88 100644
--- a/web/components/contract-feed.tsx
+++ b/web/components/contract-feed.tsx
@@ -6,7 +6,6 @@ import {
CheckIcon,
DotsVerticalIcon,
LockClosedIcon,
- StarIcon,
UserIcon,
UsersIcon,
XIcon,
@@ -43,6 +42,7 @@ import { fromNow } from '../lib/util/time'
import BetRow from './bet-row'
import { parseTags } from '../../common/util/parse'
import { Avatar } from './avatar'
+import { User } from '../../common/user'
function FeedComment(props: {
activityItem: any
@@ -470,7 +470,12 @@ function groupBets(
if (group.length == 1) {
items.push(toActivityItem(group[0]))
} else if (group.length > 1) {
- items.push({ type: 'betgroup', bets: [...group], id: group[0].id })
+ items.push({
+ type: 'betgroup',
+ bets: [...group],
+ id: group[0].id,
+ createdTime: group[group.length - 1].createdTime,
+ })
}
group = []
}
@@ -504,6 +509,52 @@ function groupBets(
return items as ActivityItem[]
}
+export function getContractFeedItems(
+ contract: Contract,
+ bets: Bet[],
+ comments: Comment[],
+ user: User | null | undefined,
+ options: { feedType: 'activity' | 'market'; expanded: boolean }
+) {
+ const { feedType, expanded } = options
+ const groupWindow = feedType == 'activity' ? 10 * DAY_IN_MS : DAY_IN_MS
+
+ const allItems: ActivityItem[] = [
+ { type: 'start', id: '0', createdTime: contract.createdTime },
+ ...groupBets(
+ bets.filter((bet) => !bet.isAnte),
+ comments,
+ groupWindow,
+ user?.id
+ ),
+ ]
+ if (contract.closeTime && contract.closeTime <= Date.now()) {
+ allItems.push({
+ type: 'close',
+ id: `${contract.closeTime}`,
+ createdTime: contract.closeTime,
+ })
+ }
+ if (contract.resolution) {
+ allItems.push({
+ type: 'resolve',
+ id: `${contract.resolutionTime}`,
+ createdTime: contract.resolutionTime,
+ })
+ }
+
+ // If there are more than 5 items, only show the first, an expand item, and last 3
+ let items = allItems
+ if (!expanded && allItems.length > 5 && feedType == 'activity') {
+ items = [
+ allItems[0],
+ { type: 'expand', id: 'expand' },
+ ...allItems.slice(-3),
+ ]
+ }
+ return items
+}
+
function BetGroupSpan(props: { bets: Bet[]; outcome: 'YES' | 'NO' }) {
const { bets, outcome } = props
@@ -583,9 +634,7 @@ function FeedExpand(props: { setExpanded: (expanded: boolean) => void }) {
)
}
-// Missing feed items:
-// - Bet sold?
-type ActivityItem = {
+export type ActivityItem = {
id: string
type:
| 'bet'
@@ -595,48 +644,34 @@ type ActivityItem = {
| 'close'
| 'resolve'
| 'expand'
+
+ createdTime?: number
}
export function ContractFeed(props: {
contract: Contract
- bets: Bet[]
- comments: Comment[]
+ activityItems: ActivityItem[]
// Feed types: 'activity' = Activity feed, 'market' = Comments feed on a market
feedType: 'activity' | 'market'
betRowClassName?: string
}) {
- const { contract, feedType, betRowClassName } = props
+ const { contract, activityItems, feedType, betRowClassName } = props
const { id } = contract
const [expanded, setExpanded] = useState(false)
const user = useUser()
- let bets = useBets(id) ?? props.bets
+ let bets = useBets(id)
bets = withoutAnteBets(contract, bets)
- const comments = useComments(id) ?? props.comments
+ const comments = useComments(id)
- const groupWindow = feedType == 'activity' ? 10 * DAY_IN_MS : DAY_IN_MS
-
- const allItems = [
- { type: 'start', id: 0 },
- ...groupBets(bets, comments, groupWindow, user?.id),
- ]
- if (contract.closeTime && contract.closeTime <= Date.now()) {
- allItems.push({ type: 'close', id: `${contract.closeTime}` })
- }
- if (contract.resolution) {
- allItems.push({ type: 'resolve', id: `${contract.resolutionTime}` })
- }
-
- // If there are more than 5 items, only show the first, an expand item, and last 3
- let items = allItems
- if (!expanded && allItems.length > 5 && feedType == 'activity') {
- items = [
- allItems[0],
- { type: 'expand', id: 'expand' },
- ...allItems.slice(-3),
- ]
- }
+ const items =
+ bets && comments
+ ? getContractFeedItems(contract, bets, comments, user, {
+ feedType,
+ expanded,
+ })
+ : activityItems
return (
diff --git a/web/components/contract-overview.tsx b/web/components/contract-overview.tsx
index 3528a727..1de1d157 100644
--- a/web/components/contract-overview.tsx
+++ b/web/components/contract-overview.tsx
@@ -14,7 +14,7 @@ import { Row } from './layout/row'
import { Linkify } from './linkify'
import clsx from 'clsx'
import { ContractDetails, ResolutionOrChance } from './contract-card'
-import { ContractFeed } from './contract-feed'
+import { ContractFeed, getContractFeedItems } from './contract-feed'
import { TweetButton } from './tweet-button'
import { Bet } from '../../common/bet'
import { Comment } from '../../common/comment'
@@ -46,6 +46,11 @@ export const ContractOverview = (props: {
const url = `https://manifold.markets${contractPath(contract)}`
const tweetText = `${tweetQuestion}\n\n${tweetDescription}\n\n${url}`
+ const activityItems = getContractFeedItems(contract, bets, comments, user, {
+ feedType: 'market',
+ expanded: true,
+ })
+
return (
@@ -131,8 +136,7 @@ export const ContractOverview = (props: {
diff --git a/web/pages/activity.tsx b/web/pages/activity.tsx
index be5a9809..15df7311 100644
--- a/web/pages/activity.tsx
+++ b/web/pages/activity.tsx
@@ -1,5 +1,9 @@
import _ from 'lodash'
-import { ContractFeed } from '../components/contract-feed'
+import {
+ ActivityItem,
+ ContractFeed,
+ getContractFeedItems,
+} from '../components/contract-feed'
import { Page } from '../components/page'
import { Contract } from '../lib/firebase/contracts'
import { Comment } from '../lib/firebase/comments'
@@ -85,27 +89,86 @@ export function findActiveContracts(
return contracts.slice(0, MAX_ACTIVE_CONTRACTS)
}
+export function getActivity(
+ contracts: Contract[],
+ contractBets: Bet[][],
+ contractComments: Comment[][]
+) {
+ const contractActivityItems = contracts.map((contract, i) => {
+ const bets = contractBets[i]
+ const comments = contractComments[i]
+ return getContractFeedItems(contract, bets, comments, undefined, {
+ expanded: false,
+ feedType: 'activity',
+ })
+ })
+
+ const hotMarketTimes: { [contractId: string]: number } = {}
+
+ // Add recent top-trading contracts, ordered by last bet.
+ const DAY_IN_MS = 24 * 60 * 60 * 1000
+ const contractTotalBets = _.map(contractBets, (bets) => {
+ const recentBets = bets.filter(
+ (bet) => bet.createdTime > Date.now() - DAY_IN_MS
+ )
+ return _.sumBy(recentBets, (bet) => bet.amount)
+ })
+ const topTradedContracts = _.sortBy(
+ contractTotalBets.map((total, index) => [total, index] as [number, number]),
+ ([total, _]) => -1 * total
+ ).slice(0, MAX_HOT_MARKETS)
+
+ for (const [_total, index] of topTradedContracts) {
+ const lastBet = _.last(contractBets[index])
+ if (lastBet) {
+ hotMarketTimes[contracts[index].id] = lastBet.createdTime
+ }
+ }
+
+ const orderedContracts = _.sortBy(
+ contracts.map((c, index) => [c, index] as const),
+ ([contract, index]) => {
+ const activeTypes = ['start', 'comment', 'close', 'resolve']
+ const { createdTime } = _.last(
+ contractActivityItems[index].filter((item) =>
+ activeTypes.includes(item.type)
+ )
+ ) as ActivityItem
+ const activeTime = createdTime ?? 0
+ const hotTime = hotMarketTimes[contract.id] ?? 0
+ return -1 * Math.max(activeTime, hotTime)
+ }
+ )
+
+ return {
+ contracts: orderedContracts.map(([_, index]) => contracts[index]),
+ contractActivityItems: orderedContracts.map(
+ ([_, index]) => contractActivityItems[index]
+ ),
+ }
+}
+
export function ActivityFeed(props: {
contracts: Contract[]
- contractBets: Bet[][]
- contractComments: Comment[][]
+ contractActivityItems: ActivityItem[][]
}) {
- const { contracts, contractBets, contractComments } = props
+ const { contracts, contractActivityItems } = props
return contracts.length > 0 ? (
- {contracts.map((contract, i) => (
-
-
-
- ))}
+ {contracts.map((contract, i) => {
+ return (
+
+
+
+ )
+ })}
@@ -117,7 +180,7 @@ export function ActivityFeed(props: {
export default function ActivityPage() {
return (
-
+
)
}
diff --git a/web/pages/fold/[...slugs]/index.tsx b/web/pages/fold/[...slugs]/index.tsx
index f43f847a..e0e9103b 100644
--- a/web/pages/fold/[...slugs]/index.tsx
+++ b/web/pages/fold/[...slugs]/index.tsx
@@ -39,6 +39,7 @@ import { FollowFoldButton } from '../../../components/follow-fold-button'
import FeedCreate from '../../../components/feed-create'
import { SEO } from '../../../components/SEO'
import { useTaggedContracts } from '../../../hooks/use-contracts'
+import { getContractFeedItems } from '../../../components/contract-feed'
export async function getStaticProps(props: { params: { slugs: string[] } }) {
const { slugs } = props.params
@@ -175,6 +176,16 @@ export default function FoldPage(props: {
(contract) => contractsMap[contract.id]
)
+ const contractActivityItems = activeContracts.map((contract, index) =>
+ getContractFeedItems(
+ contract,
+ activeContractBets[index],
+ activeContractComments[index],
+ user,
+ { feedType: 'activity', expanded: false }
+ )
+ )
+
if (fold === null || !foldSubpages.includes(page) || slugs[2]) {
return
}
@@ -260,8 +271,7 @@ export default function FoldPage(props: {
<>
{activeContracts.length === 0 && (
diff --git a/web/pages/home.tsx b/web/pages/home.tsx
index f80c32b1..eef04ff8 100644
--- a/web/pages/home.tsx
+++ b/web/pages/home.tsx
@@ -4,9 +4,9 @@ import _ from 'lodash'
import { Contract, listAllContracts } from '../lib/firebase/contracts'
import { Page } from '../components/page'
-import { ActivityFeed, findActiveContracts } from './activity'
-import { Comment, listAllComments } from '../lib/firebase/comments'
-import { Bet, listAllBets } from '../lib/firebase/bets'
+import { ActivityFeed, getActivity } from './activity'
+import { listAllComments } from '../lib/firebase/comments'
+import { listAllBets } from '../lib/firebase/bets'
import FeedCreate from '../components/feed-create'
import { Spacer } from '../components/layout/spacer'
import { Col } from '../components/layout/col'
@@ -22,6 +22,7 @@ import { SearchIcon } from '@heroicons/react/outline'
import { Row } from '../components/layout/row'
import { SparklesIcon } from '@heroicons/react/solid'
import { useFollowedFolds } from '../hooks/use-fold'
+import { ActivityItem } from '../components/contract-feed'
export async function getStaticProps() {
const [contracts, folds] = await Promise.all([
@@ -36,9 +37,7 @@ export async function getStaticProps() {
return {
props: {
- contracts,
- contractBets,
- contractComments,
+ ...getActivity(contracts, contractBets, contractComments),
folds,
},
@@ -48,18 +47,14 @@ export async function getStaticProps() {
const Home = (props: {
contracts: Contract[]
- contractBets: Bet[][]
- contractComments: Comment[][]
+ contractActivityItems: ActivityItem[][]
folds: Fold[]
}) => {
- const { contractBets, contractComments, folds } = props
+ const { contractActivityItems, folds } = props
const user = useUser()
const contracts = useUpdatedContracts(props.contracts)
- const contractIdToIndex = _.fromPairs(
- contracts.map((contract, index) => [contract.id, index])
- )
const followedFoldIds = useFollowedFolds(user)
const followedFolds = filterDefined(
@@ -76,36 +71,20 @@ const Home = (props: {
const feedContracts =
followedFoldIds && yourBetContracts
- ? contracts.filter(
- (contract) =>
- contract.lowercaseTags.some((tag) => tagSet.has(tag)) ||
- yourBetContracts.has(contract.id)
- )
+ ? contracts
+ .filter(
+ (contract) =>
+ contract.lowercaseTags.some((tag) => tagSet.has(tag)) ||
+ yourBetContracts.has(contract.id)
+ )
+ .slice(0, 75)
: undefined
- const oneDayMS = 24 * 60 * 60 * 1000
- const recentBets = (feedContracts ?? [])
- .map((c) => contractBets[contractIdToIndex[c.id]])
- .flat()
- .filter((bet) => bet.createdTime > Date.now() - oneDayMS)
- const feedComments = (feedContracts ?? [])
- .map((c) => contractComments[contractIdToIndex[c.id]])
- .flat()
+ const feedContractSet = new Set(feedContracts?.map((contract) => contract.id))
- const activeContracts =
- feedContracts &&
- findActiveContracts(feedContracts, feedComments, recentBets, 365)
-
- const activeBets = activeContracts
- ? activeContracts.map(
- (contract) => contractBets[contractIdToIndex[contract.id]]
- )
- : []
- const activeComments = activeContracts
- ? activeContracts.map(
- (contract) => contractComments[contractIdToIndex[contract.id]]
- )
- : []
+ const feedActivityItems = contractActivityItems.filter((_, index) =>
+ feedContractSet.has(contracts[index].id)
+ )
if (user === null) {
Router.replace('/')
@@ -143,11 +122,10 @@ const Home = (props: {
Recent activity
- {activeContracts ? (
+ {feedContracts ? (
) : (