From 87170894e2245fc733a2dbcda3721df242036cbe Mon Sep 17 00:00:00 2001 From: Austin Chen Date: Fri, 22 Jul 2022 09:12:01 -0700 Subject: [PATCH 1/8] Suppress eslint warning for script --- functions/src/scripts/convert-categories.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/functions/src/scripts/convert-categories.ts b/functions/src/scripts/convert-categories.ts index 7b291202..d559bf92 100644 --- a/functions/src/scripts/convert-categories.ts +++ b/functions/src/scripts/convert-categories.ts @@ -17,6 +17,7 @@ initAdmin() const adminFirestore = admin.firestore() +// eslint-disable-next-line @typescript-eslint/no-unused-vars const addGroupIdToContracts = async () => { const groups = await getValues(adminFirestore.collection('groups')) const contracts = await getValues( From 7cace82b83a6ebb2bae29f4aab643502fef497ca Mon Sep 17 00:00:00 2001 From: Austin Chen Date: Fri, 22 Jul 2022 09:12:23 -0700 Subject: [PATCH 2/8] Render iframes inside the rich text editor (#682) * Try embedding iframes in tiptap * When iframe code is pasted, inject it into the editor * Code cleanups and comments * Remove clsx dependency Cuz it doesn't exist in `common` anyways * Rename to tiptap-iframe --- common/util/parse.ts | 2 + common/util/tiptap-iframe.ts | 92 ++++++++++++++++++++++++++++++++++++ web/components/editor.tsx | 18 +++++-- 3 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 common/util/tiptap-iframe.ts diff --git a/common/util/parse.ts b/common/util/parse.ts index 30dcb952..cdaa6a6c 100644 --- a/common/util/parse.ts +++ b/common/util/parse.ts @@ -20,6 +20,7 @@ import { Text } from '@tiptap/extension-text' // other tiptap extensions import { Image } from '@tiptap/extension-image' import { Link } from '@tiptap/extension-link' +import Iframe from './tiptap-iframe' export function parseTags(text: string) { const regex = /(?:^|\s)(?:[#][a-z0-9_]+)/gi @@ -80,6 +81,7 @@ export const exhibitExts = [ Image, Link, + Iframe, ] // export const exhibitExts = [StarterKit as unknown as Extension, Image] diff --git a/common/util/tiptap-iframe.ts b/common/util/tiptap-iframe.ts new file mode 100644 index 00000000..5af63d2f --- /dev/null +++ b/common/util/tiptap-iframe.ts @@ -0,0 +1,92 @@ +// Adopted from https://github.com/ueberdosis/tiptap/blob/main/demos/src/Experiments/Embeds/Vue/iframe.ts + +import { Node } from '@tiptap/core' + +export interface IframeOptions { + allowFullscreen: boolean + HTMLAttributes: { + [key: string]: any + } +} + +declare module '@tiptap/core' { + interface Commands { + iframe: { + setIframe: (options: { src: string }) => ReturnType + } + } +} + +// These classes style the outer wrapper and the inner iframe; +// Adopted from css in https://github.com/ueberdosis/tiptap/blob/main/demos/src/Experiments/Embeds/Vue/index.vue +const wrapperClasses = 'relative h-auto w-full overflow-hidden' +const iframeClasses = 'absolute top-0 left-0 h-full w-full' + +export default Node.create({ + name: 'iframe', + + group: 'block', + + atom: true, + + addOptions() { + return { + allowFullscreen: true, + HTMLAttributes: { + class: 'iframe-wrapper' + ' ' + wrapperClasses, + // Tailwind JIT doesn't seem to pick up `pb-[20rem]`, so we hack this in: + style: 'padding-bottom: 20rem;', + }, + } + }, + + addAttributes() { + return { + src: { + default: null, + }, + frameborder: { + default: 0, + }, + allowfullscreen: { + default: this.options.allowFullscreen, + parseHTML: () => this.options.allowFullscreen, + }, + } + }, + + parseHTML() { + return [{ tag: 'iframe' }] + }, + + renderHTML({ HTMLAttributes }) { + return [ + 'div', + this.options.HTMLAttributes, + [ + 'iframe', + { + ...HTMLAttributes, + class: HTMLAttributes.class + ' ' + iframeClasses, + }, + ], + ] + }, + + addCommands() { + return { + setIframe: + (options: { src: string }) => + ({ tr, dispatch }) => { + const { selection } = tr + const node = this.type.create(options) + + if (dispatch) { + tr.replaceRangeWith(selection.from, selection.to, node) + } + + return true + }, + } + }, +}) diff --git a/web/components/editor.tsx b/web/components/editor.tsx index 7063fa42..d64dcc78 100644 --- a/web/components/editor.tsx +++ b/web/components/editor.tsx @@ -19,6 +19,7 @@ import { useMutation } from 'react-query' import { exhibitExts } from 'common/util/parse' import { FileUploadButton } from './file-upload-button' import { linkClass } from './site-link' +import Iframe from 'common/util/tiptap-iframe' const proseClass = clsx( 'prose prose-p:my-0 prose-li:my-0 prose-blockquote:not-italic max-w-none prose-quoteless leading-relaxed', @@ -56,6 +57,7 @@ export function useTextEditor(props: { class: clsx('no-underline !text-indigo-700', linkClass), }, }), + Iframe, ], content: defaultValue, }) @@ -69,12 +71,20 @@ export function useTextEditor(props: { (file) => file.type.startsWith('image') ) - if (!imageFiles.length) { - return // if no files pasted, use default paste handler + if (imageFiles.length) { + event.preventDefault() + upload.mutate(imageFiles) } - event.preventDefault() - upload.mutate(imageFiles) + // If the pasted content is iframe code, directly inject it + const text = event.clipboardData?.getData('text/plain').trim() ?? '' + const isValidIframe = /^$/.test(text) + if (isValidIframe) { + editor.chain().insertContent(text).run() + return true // Prevent the code from getting pasted as text + } + + return // Otherwise, use default paste handler }, }, }) From 624df763931543fa0d628f2815cb47f2b04bcad9 Mon Sep 17 00:00:00 2001 From: mantikoros Date: Fri, 22 Jul 2022 11:24:15 -0500 Subject: [PATCH 3/8] search: sort by liquidity; remove oldest --- web/components/contract-search.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/components/contract-search.tsx b/web/components/contract-search.tsx index 8eb7df6e..fca1b272 100644 --- a/web/components/contract-search.tsx +++ b/web/components/contract-search.tsx @@ -39,11 +39,12 @@ const indexPrefix = ENV === 'DEV' ? 'dev-' : '' const sortIndexes = [ { label: 'Newest', value: indexPrefix + 'contracts-newest' }, - { label: 'Oldest', value: indexPrefix + 'contracts-oldest' }, + // { label: 'Oldest', value: indexPrefix + 'contracts-oldest' }, { label: 'Most popular', value: indexPrefix + 'contracts-score' }, { label: 'Most traded', value: indexPrefix + 'contracts-most-traded' }, { label: '24h volume', value: indexPrefix + 'contracts-24-hour-vol' }, { label: 'Last updated', value: indexPrefix + 'contracts-last-updated' }, + { label: 'Subsidy', value: indexPrefix + 'contracts-liquidity' }, { label: 'Close date', value: indexPrefix + 'contracts-close-date' }, { label: 'Resolve date', value: indexPrefix + 'contracts-resolve-date' }, ] From de53a13c8479dba76934e3410ba611d038a8e2bf Mon Sep 17 00:00:00 2001 From: mantikoros Date: Fri, 22 Jul 2022 11:25:48 -0500 Subject: [PATCH 4/8] fix referrals seo --- web/pages/referrals.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web/pages/referrals.tsx b/web/pages/referrals.tsx index c879afaa..f50c2e2b 100644 --- a/web/pages/referrals.tsx +++ b/web/pages/referrals.tsx @@ -21,7 +21,12 @@ export default function ReferralsPage() { return ( - + From 2c80133856de6c8362d02d80ae03935800da785f Mon Sep 17 00:00:00 2001 From: mantikoros Date: Fri, 22 Jul 2022 11:56:03 -0500 Subject: [PATCH 5/8] add SEO tags to everything --- web/pages/charity/[charitySlug].tsx | 11 +++++++++-- web/pages/charity/index.tsx | 6 ++++++ web/pages/create.tsx | 6 ++++++ web/pages/groups.tsx | 6 ++++++ web/pages/leaderboards.tsx | 6 ++++++ web/pages/markets.tsx | 2 +- 6 files changed, 34 insertions(+), 3 deletions(-) diff --git a/web/pages/charity/[charitySlug].tsx b/web/pages/charity/[charitySlug].tsx index 2cefa13b..da3141d2 100644 --- a/web/pages/charity/[charitySlug].tsx +++ b/web/pages/charity/[charitySlug].tsx @@ -1,6 +1,9 @@ import { sortBy, sumBy, uniqBy } from 'lodash' import clsx from 'clsx' import React, { useEffect, useRef, useState } from 'react' +import Image from 'next/image' +import Confetti from 'react-confetti' + import { Col } from 'web/components/layout/col' import { Row } from 'web/components/layout/row' import { Page } from 'web/components/page' @@ -16,11 +19,10 @@ import { useRouter } from 'next/router' import Custom404 from '../404' import { useCharityTxns } from 'web/hooks/use-charity-txns' import { useWindowSize } from 'web/hooks/use-window-size' -import Confetti from 'react-confetti' import { Donation } from 'web/components/charity/feed-items' -import Image from 'next/image' import { manaToUSD } from '../../../common/util/format' import { track } from 'web/lib/service/analytics' +import { SEO } from 'web/components/SEO' export default function CharityPageWrapper() { const router = useRouter() @@ -63,6 +65,11 @@ function CharityPage(props: { charity: Charity }) { /> } > + {showConfetti && ( + diff --git a/web/pages/create.tsx b/web/pages/create.tsx index 1271730f..00e49f80 100644 --- a/web/pages/create.tsx +++ b/web/pages/create.tsx @@ -30,6 +30,7 @@ import { TextEditor, useTextEditor } from 'web/components/editor' import { Checkbox } from 'web/components/checkbox' import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth' import { Title } from 'web/components/title' +import { SEO } from 'web/components/SEO' export const getServerSideProps = redirectIfLoggedOut('/') @@ -63,6 +64,11 @@ export default function Create() { return ( <Page> + <SEO + title="Create a market" + description="Create a play-money prediction market on any question." + url="/create" + /> <div className="mx-auto w-full max-w-2xl"> <div className="rounded-lg px-6 py-4 sm:py-0"> <Title className="!mt-0" text="Create a market" /> diff --git a/web/pages/groups.tsx b/web/pages/groups.tsx index c87f801b..d1eed970 100644 --- a/web/pages/groups.tsx +++ b/web/pages/groups.tsx @@ -18,6 +18,7 @@ import { Avatar } from 'web/components/avatar' import { JoinOrLeaveGroupButton } from 'web/components/groups/groups-button' import { UserLink } from 'web/components/user-page' import { searchInAny } from 'common/util/parse' +import { SEO } from 'web/components/SEO' export async function getStaticProps() { const groups = await listAllGroups().catch((_) => []) @@ -100,6 +101,11 @@ export default function Groups(props: { return ( <Page> + <SEO + title="Groups" + description="Manifold Groups are communities centered around a collection of prediction markets. Discuss and compete on questions with your friends." + url="/groups" + /> <Col className="items-center"> <Col className="w-full max-w-2xl px-4 sm:px-2"> <Row className="items-center justify-between"> diff --git a/web/pages/leaderboards.tsx b/web/pages/leaderboards.tsx index 7ee13172..6ce5ca01 100644 --- a/web/pages/leaderboards.tsx +++ b/web/pages/leaderboards.tsx @@ -13,6 +13,7 @@ import { useEffect, useState } from 'react' import { Title } from 'web/components/title' import { Tabs } from 'web/components/layout/tabs' import { useTracking } from 'web/hooks/use-tracking' +import { SEO } from 'web/components/SEO' export async function getStaticProps() { const props = await fetchProps() @@ -123,6 +124,11 @@ export default function Leaderboards(_props: { return ( <Page> + <SEO + title="Leaderboards" + description="Manifold's leaderboards show the top traders and market creators." + url="/leaderboards" + /> <Title text={'Leaderboards'} className={'hidden md:block'} /> <Tabs currentPageForAnalytics={'leaderboards'} diff --git a/web/pages/markets.tsx b/web/pages/markets.tsx index a3e851fc..2d3346c1 100644 --- a/web/pages/markets.tsx +++ b/web/pages/markets.tsx @@ -8,7 +8,7 @@ export default function Markets() { <Page> <SEO title="Explore" - description="Discover what's new, trending, or soon-to-close. Or search among our hundreds of markets." + description="Discover what's new, trending, or soon-to-close. Or search thousands of prediction markets." url="/markets" /> <ContractSearch /> From e13f4d3d4de3260d6d27793a18286154048e40e3 Mon Sep 17 00:00:00 2001 From: mantikoros <sgrugett@gmail.com> Date: Fri, 22 Jul 2022 11:59:25 -0500 Subject: [PATCH 6/8] charity description --- web/pages/charity/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/pages/charity/index.tsx b/web/pages/charity/index.tsx index d416726b..80003c81 100644 --- a/web/pages/charity/index.tsx +++ b/web/pages/charity/index.tsx @@ -134,6 +134,9 @@ export default function Charity(props: { </SiteLink> ! </span> */} + <span className="text-gray-600"> + Convert your M$ earnings into real charitable donations. + </span> <DonatedStats stats={[ { From c3a0326b1ec9d1b960271e63e025872b3c3c96b4 Mon Sep 17 00:00:00 2001 From: mantikoros <sgrugett@gmail.com> Date: Fri, 22 Jul 2022 12:01:52 -0500 Subject: [PATCH 7/8] homepage seo --- web/pages/index.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/pages/index.tsx b/web/pages/index.tsx index c7e81d97..473189aa 100644 --- a/web/pages/index.tsx +++ b/web/pages/index.tsx @@ -5,6 +5,7 @@ import { Col } from 'web/components/layout/col' import { ManifoldLogo } from 'web/components/nav/manifold-logo' import { redirectIfLoggedIn } from 'web/lib/firebase/server-auth' import { useSaveReferral } from 'web/hooks/use-save-referral' +import { SEO } from 'web/components/SEO' export const getServerSideProps = redirectIfLoggedIn('/home', async (_) => { // These hardcoded markets will be shown in the frontpage for signed-out users: @@ -30,6 +31,11 @@ export default function Home(props: { hotContracts: Contract[] }) { return ( <Page> + <SEO + title="Manifold Markets" + description="Create a play-money prediction market on any topic you care about + and bet with your friends on what will happen!" + /> <div className="px-4 pt-2 md:mt-0 lg:hidden"> <ManifoldLogo /> </div> From 163c990e9d57b92ffc349afbdb43ce49e3a93c95 Mon Sep 17 00:00:00 2001 From: mantikoros <sgrugett@gmail.com> Date: Fri, 22 Jul 2022 12:03:33 -0500 Subject: [PATCH 8/8] "bettors" => "traders" --- web/components/contract/contract-leaderboard.tsx | 2 +- web/pages/group/[...slugs]/index.tsx | 4 ++-- web/pages/leaderboards.tsx | 2 +- web/pages/notifications.tsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/components/contract/contract-leaderboard.tsx b/web/components/contract/contract-leaderboard.tsx index 0623b6d7..deb9b857 100644 --- a/web/components/contract/contract-leaderboard.tsx +++ b/web/components/contract/contract-leaderboard.tsx @@ -49,7 +49,7 @@ export function ContractLeaderboard(props: { return users && users.length > 0 ? ( <Leaderboard - title="🏅 Top bettors" + title="🏅 Top traders" users={users || []} columns={[ { diff --git a/web/pages/group/[...slugs]/index.tsx b/web/pages/group/[...slugs]/index.tsx index 06f043e7..c27d998e 100644 --- a/web/pages/group/[...slugs]/index.tsx +++ b/web/pages/group/[...slugs]/index.tsx @@ -492,7 +492,7 @@ function GroupLeaderboards(props: { <SortedLeaderboard users={members} scoreFunction={(user) => traderScores[user.id] ?? 0} - title="🏅 Top bettors" + title="🏅 Top traders" header="Profit" maxToShow={maxToShow} /> @@ -508,7 +508,7 @@ function GroupLeaderboards(props: { <> <Leaderboard className="max-w-xl" - title="🏅 Top bettors" + title="🏅 Top traders" users={topTraders} columns={[ { diff --git a/web/pages/leaderboards.tsx b/web/pages/leaderboards.tsx index 6ce5ca01..45c484c4 100644 --- a/web/pages/leaderboards.tsx +++ b/web/pages/leaderboards.tsx @@ -79,7 +79,7 @@ export default function Leaderboards(_props: { <> <Col className="mx-4 items-center gap-10 lg:flex-row"> <Leaderboard - title="🏅 Top bettors" + title="🏅 Top traders" users={topTraders} columns={[ { diff --git a/web/pages/notifications.tsx b/web/pages/notifications.tsx index 3db345ef..72754d32 100644 --- a/web/pages/notifications.tsx +++ b/web/pages/notifications.tsx @@ -391,7 +391,7 @@ function IncomeNotificationItem(props: { reasonText = !simple ? `Bonus for ${ parseInt(sourceText) / UNIQUE_BETTOR_BONUS_AMOUNT - } unique bettors` + } unique traders` : 'bonus on' } else if (sourceType === 'tip') { reasonText = !simple ? `tipped you` : `in tips on`