Compare commits

..

88 Commits
main ... main2

Author SHA1 Message Date
github-actions[bot]
bdb0de56cb
2022-10-12 22:39:48 +00:00
github-actions[bot]
db0357be7b
2022-10-12 22:33:49 +00:00
github-actions[bot]
bbcfef4ad0
2022-10-12 22:30:53 +00:00
github-actions[bot]
c50c9bb498
2022-10-12 21:57:56 +00:00
github-actions[bot]
e9201300cc
2022-10-12 21:21:59 +00:00
github-actions[bot]
c9656efbc5
2022-10-12 21:07:30 +00:00
github-actions[bot]
f8c956f1c5
2022-10-12 21:05:42 +00:00
github-actions[bot]
256d6d1c62
2022-10-12 19:27:44 +00:00
github-actions[bot]
3ecf511c50
2022-10-12 19:25:23 +00:00
github-actions[bot]
1644031d79
2022-10-12 18:56:25 +00:00
github-actions[bot]
0c913c0675
2022-10-12 18:44:08 +00:00
github-actions[bot]
7fd2585a33
2022-10-12 18:06:26 +00:00
github-actions[bot]
a76ff58201
2022-10-12 17:12:56 +00:00
github-actions[bot]
48dd4ba2d8
2022-10-12 17:10:21 +00:00
github-actions[bot]
33f2c387eb
2022-10-12 17:00:07 +00:00
github-actions[bot]
fb38ba0f49
2022-10-12 16:53:29 +00:00
github-actions[bot]
7cef25805c
2022-10-12 16:45:08 +00:00
github-actions[bot]
90d0ff2565
2022-10-12 16:33:46 +00:00
github-actions[bot]
d9fd90b964
2022-10-12 16:31:28 +00:00
github-actions[bot]
07eb94aba1
2022-10-12 16:23:26 +00:00
github-actions[bot]
cccc5921ad
2022-10-12 15:43:25 +00:00
github-actions[bot]
651e61275f
2022-10-12 15:03:25 +00:00
github-actions[bot]
2541f1a861
2022-10-12 14:39:30 +00:00
github-actions[bot]
c336ec7728
2022-10-12 14:38:31 +00:00
github-actions[bot]
494e8880f4
2022-10-12 14:30:03 +00:00
github-actions[bot]
6689cf8db0
2022-10-12 14:06:19 +00:00
github-actions[bot]
8a31670063
2022-10-12 14:05:06 +00:00
github-actions[bot]
d9a6b6c431
2022-10-12 12:24:45 +00:00
github-actions[bot]
56f4626d1b
2022-10-12 06:59:32 +00:00
github-actions[bot]
cefa34aa12
2022-10-12 06:58:42 +00:00
github-actions[bot]
0e62e4b24a
2022-10-12 06:32:00 +00:00
github-actions[bot]
1546080811
2022-10-12 06:14:11 +00:00
github-actions[bot]
db9e82091c
2022-10-12 06:01:19 +00:00
github-actions[bot]
7f242a1970
2022-10-12 05:25:27 +00:00
github-actions[bot]
26ae76e640
2022-10-12 05:10:12 +00:00
github-actions[bot]
dae1bd70b3
2022-10-11 23:23:26 +00:00
github-actions[bot]
be52431296
2022-10-11 23:00:15 +00:00
github-actions[bot]
86811e31be
2022-10-11 22:35:57 +00:00
github-actions[bot]
f885f89cf4
2022-10-11 21:37:05 +00:00
github-actions[bot]
06a47aec59
2022-10-11 21:33:19 +00:00
github-actions[bot]
cd3a9d9614
2022-10-11 21:29:40 +00:00
github-actions[bot]
afb37bd5a6
2022-10-11 21:23:06 +00:00
github-actions[bot]
d067a534bf
2022-10-11 21:06:19 +00:00
github-actions[bot]
a13fdca5bb
2022-10-11 19:52:56 +00:00
github-actions[bot]
1d62195628
2022-10-11 19:28:41 +00:00
github-actions[bot]
55da2db1fe
2022-10-11 19:27:26 +00:00
github-actions[bot]
f29f9ecabf
2022-10-11 18:59:08 +00:00
github-actions[bot]
708224716c
2022-10-11 18:42:00 +00:00
github-actions[bot]
d0cd8ff222
2022-10-11 18:38:14 +00:00
github-actions[bot]
be407ada75
2022-10-11 18:30:58 +00:00
github-actions[bot]
05c59d3056
2022-10-11 16:27:05 +00:00
github-actions[bot]
0e9c6a80c1
2022-10-11 16:23:17 +00:00
github-actions[bot]
7c4b8a0f57
2022-10-11 14:36:28 +00:00
github-actions[bot]
5012a5d516
2022-10-11 12:40:21 +00:00
github-actions[bot]
07c4c0b064
2022-10-11 05:33:22 +00:00
github-actions[bot]
dc7e47cb2b
2022-10-11 02:56:41 +00:00
github-actions[bot]
0e15c905d3
2022-10-11 01:47:32 +00:00
github-actions[bot]
fadb6c016e
2022-10-10 21:38:54 +00:00
github-actions[bot]
c8c703285f
2022-10-10 21:01:45 +00:00
github-actions[bot]
ace743d47c
2022-10-10 20:52:19 +00:00
github-actions[bot]
fb31b621b8
2022-10-10 20:52:00 +00:00
github-actions[bot]
01bb0d111e
2022-10-10 20:41:50 +00:00
github-actions[bot]
e74b5094cc
2022-10-10 20:34:35 +00:00
github-actions[bot]
971f9e29f0
2022-10-10 20:32:51 +00:00
github-actions[bot]
4e22073469
2022-10-10 19:56:02 +00:00
github-actions[bot]
24326640b8
2022-10-10 19:23:41 +00:00
github-actions[bot]
5fa2afdc27
2022-10-10 18:34:34 +00:00
github-actions[bot]
544d55bb93
2022-10-10 18:05:44 +00:00
github-actions[bot]
af0e3082aa
2022-10-10 18:02:29 +00:00
github-actions[bot]
9cf91b6f0b
2022-10-10 16:48:32 +00:00
github-actions[bot]
ee906a5732
2022-10-10 15:54:11 +00:00
github-actions[bot]
c7284882df
2022-10-10 15:49:09 +00:00
github-actions[bot]
d8f894b80c
2022-10-10 15:25:08 +00:00
github-actions[bot]
d3c7f77076
2022-10-10 13:42:07 +00:00
github-actions[bot]
9324b30cf5
2022-10-10 02:37:51 +00:00
github-actions[bot]
08abc13ede
2022-10-10 00:12:08 +00:00
github-actions[bot]
6885001e7a
2022-10-09 23:10:24 +00:00
github-actions[bot]
513d0dddb8
2022-10-09 23:09:47 +00:00
github-actions[bot]
ea579183c6
2022-10-09 22:14:50 +00:00
github-actions[bot]
8984aae42f
2022-10-09 22:03:20 +00:00
github-actions[bot]
241d78b83c
2022-10-09 05:53:08 +00:00
github-actions[bot]
4f2ce031b3
2022-10-08 17:52:25 +00:00
github-actions[bot]
14003c1ad8
2022-10-08 17:17:10 +00:00
github-actions[bot]
bd2ff27848
2022-10-07 22:46:11 +00:00
github-actions[bot]
f37a1aa026
2022-10-07 22:30:48 +00:00
github-actions[bot]
c0192129da
2022-10-07 22:10:38 +00:00
github-actions[bot]
b0af92a3fc
2022-10-07 21:26:58 +00:00
github-actions[bot]
4d7a75a6bb
2022-10-07 20:14:28 +00:00
100 changed files with 1115 additions and 1924 deletions

View File

@ -26,7 +26,7 @@ module.exports = {
caughtErrorsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_',
}, },
], ],
'unused-imports/no-unused-imports': 'warn', 'unused-imports/no-unused-imports': 'error',
}, },
}, },
], ],

View File

@ -595,8 +595,7 @@ In addition to housing impact litigation, we provide free legal aid, education a
photo: photo:
'https://firebasestorage.googleapis.com/v0/b/mantic-markets.appspot.com/o/user-images%2Fdefault%2Fci2h3hStFM.47?alt=media&token=0d2cdc3d-e4d8-4f5e-8f23-4a586b6ff637', 'https://firebasestorage.googleapis.com/v0/b/mantic-markets.appspot.com/o/user-images%2Fdefault%2Fci2h3hStFM.47?alt=media&token=0d2cdc3d-e4d8-4f5e-8f23-4a586b6ff637',
preview: 'Donate supplies to soldiers in Ukraine', preview: 'Donate supplies to soldiers in Ukraine',
description: description: 'Donate supplies to soldiers in Ukraine, including tourniquets and plate carriers.',
'Donate supplies to soldiers in Ukraine, including tourniquets and plate carriers.',
}, },
].map((charity) => { ].map((charity) => {
const slug = charity.name.toLowerCase().replace(/\s/g, '-') const slug = charity.name.toLowerCase().replace(/\s/g, '-')

View File

@ -10,7 +10,6 @@ export type AnyOutcomeType =
| PseudoNumeric | PseudoNumeric
| FreeResponse | FreeResponse
| Numeric | Numeric
export type AnyContractType = export type AnyContractType =
| (CPMM & Binary) | (CPMM & Binary)
| (CPMM & PseudoNumeric) | (CPMM & PseudoNumeric)

View File

@ -16,6 +16,7 @@ export const DEV_CONFIG: EnvConfig = {
cloudRunId: 'w3txbmd3ba', cloudRunId: 'w3txbmd3ba',
cloudRunRegion: 'uc', cloudRunRegion: 'uc',
amplitudeApiKey: 'fd8cbfd964b9a205b8678a39faae71b3', amplitudeApiKey: 'fd8cbfd964b9a205b8678a39faae71b3',
twitchBotEndpoint: 'https://dev-twitch-bot.manifold.markets', // this is Phil's deployment
twitchBotEndpoint: 'https://king-prawn-app-5btyw.ondigitalocean.app',
sprigEnvironmentId: 'Tu7kRZPm7daP', sprigEnvironmentId: 'Tu7kRZPm7daP',
} }

View File

@ -70,7 +70,7 @@ export const PROD_CONFIG: EnvConfig = {
appId: '1:128925704902:web:f61f86944d8ffa2a642dc7', appId: '1:128925704902:web:f61f86944d8ffa2a642dc7',
measurementId: 'G-SSFK1Q138D', measurementId: 'G-SSFK1Q138D',
}, },
twitchBotEndpoint: 'https://twitch-bot.manifold.markets', twitchBotEndpoint: 'https://twitch-bot-nggbo3neva-uc.a.run.app',
cloudRunId: 'nggbo3neva', cloudRunId: 'nggbo3neva',
cloudRunRegion: 'uc', cloudRunRegion: 'uc',
adminEmails: [ adminEmails: [

View File

@ -1,9 +1,8 @@
export type Like = { export type Like = {
id: string // will be id of the object liked, i.e. contract.id id: string // will be id of the object liked, i.e. contract.id
userId: string userId: string
type: 'contract' | 'post' type: 'contract'
createdTime: number createdTime: number
tipTxnId?: string // only holds most recent tip txn id tipTxnId?: string // only holds most recent tip txn id
} }
export const LIKE_TIP_AMOUNT = 10 export const LIKE_TIP_AMOUNT = 10
export const TIP_UNDO_DURATION = 2000

View File

@ -8,13 +8,11 @@
}, },
"sideEffects": false, "sideEffects": false,
"dependencies": { "dependencies": {
"@tiptap/core": "2.0.0-beta.199", "@tiptap/core": "2.0.0-beta.182",
"@tiptap/extension-image": "2.0.0-beta.199", "@tiptap/extension-image": "2.0.0-beta.30",
"@tiptap/extension-link": "2.0.0-beta.199", "@tiptap/extension-link": "2.0.0-beta.43",
"@tiptap/extension-mention": "2.0.0-beta.199", "@tiptap/extension-mention": "2.0.0-beta.102",
"@tiptap/html": "2.0.0-beta.199", "@tiptap/starter-kit": "2.0.0-beta.191",
"@tiptap/starter-kit": "2.0.0-beta.199",
"@tiptap/suggestion": "2.0.0-beta.199",
"lodash": "4.17.21" "lodash": "4.17.21"
}, },
"devDependencies": { "devDependencies": {

View File

@ -13,9 +13,6 @@ export type Post = {
creatorName: string creatorName: string
creatorUsername: string creatorUsername: string
creatorAvatarUrl?: string creatorAvatarUrl?: string
likedByUserIds?: string[]
likedByUserCount?: number
} }
export type DateDoc = Post & { export type DateDoc = Post & {

View File

@ -1,5 +1,4 @@
import { generateText, JSONContent, Node } from '@tiptap/core' import { generateText, JSONContent } from '@tiptap/core'
import { generateJSON } from '@tiptap/html'
// Tiptap starter extensions // Tiptap starter extensions
import { Blockquote } from '@tiptap/extension-blockquote' import { Blockquote } from '@tiptap/extension-blockquote'
import { Bold } from '@tiptap/extension-bold' import { Bold } from '@tiptap/extension-bold'
@ -52,26 +51,6 @@ export function parseMentions(data: JSONContent): string[] {
return uniq(mentions) return uniq(mentions)
} }
// TODO: this is a hack to get around the fact that tiptap doesn't have a
// way to add a node view without bundling in tsx
function skippableComponent(name: string): Node<any, any> {
return Node.create({
name,
group: 'block',
content: 'inline*',
parseHTML() {
return [
{
tag: 'grid-cards-component',
},
]
},
})
}
const stringParseExts = [ const stringParseExts = [
// StarterKit extensions // StarterKit extensions
Blockquote, Blockquote,
@ -99,8 +78,6 @@ const stringParseExts = [
renderText: ({ node }) => renderText: ({ node }) =>
'[embed]' + node.attrs.src ? `(${node.attrs.src})` : '', '[embed]' + node.attrs.src ? `(${node.attrs.src})` : '',
}), }),
skippableComponent('gridCardsComponent'),
skippableComponent('staticReactEmbedComponent'),
TiptapTweet.extend({ renderText: () => '[tweet]' }), TiptapTweet.extend({ renderText: () => '[tweet]' }),
TiptapSpoiler.extend({ renderHTML: () => ['span', '[spoiler]', 0] }), TiptapSpoiler.extend({ renderHTML: () => ['span', '[spoiler]', 0] }),
] ]
@ -109,7 +86,3 @@ export function richTextToString(text?: JSONContent) {
if (!text) return '' if (!text) return ''
return generateText(text, stringParseExts) return generateText(text, stringParseExts)
} }
export function htmlToRichText(html: string) {
return generateJSON(html, stringParseExts)
}

View File

@ -680,17 +680,6 @@ $ curl https://manifold.markets/api/v0/market/{marketId}/sell -X POST \
--data-raw '{"outcome": "YES", "shares": 10}' --data-raw '{"outcome": "YES", "shares": 10}'
``` ```
### `POST /v0/comment`
Creates a comment in the specified market. Only supports top-level comments for now.
Parameters:
- `contractId`: Required. The ID of the market to comment on.
- `content`: The comment to post, formatted as [TipTap json](https://tiptap.dev/guide/output#option-1-json), OR
- `html`: The comment to post, formatted as an HTML string, OR
- `markdown`: The comment to post, formatted as a markdown string.
### `GET /v0/bets` ### `GET /v0/bets`
Gets a list of bets, ordered by creation date descending. Gets a list of bets, ordered by creation date descending.

View File

@ -110,7 +110,7 @@ service cloud.firestore {
match /contracts/{contractId} { match /contracts/{contractId} {
allow read; allow read;
allow update: if request.resource.data.diff(resource.data).affectedKeys() allow update: if request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['tags', 'lowercaseTags', 'groupSlugs', 'groupLinks', 'flaggedByUsernames']); .hasOnly(['tags', 'lowercaseTags', 'groupSlugs', 'groupLinks']);
allow update: if request.resource.data.diff(resource.data).affectedKeys() allow update: if request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['description', 'closeTime', 'question', 'visibility', 'unlistedById']) .hasOnly(['description', 'closeTime', 'question', 'visibility', 'unlistedById'])
&& resource.data.creatorId == request.auth.uid; && resource.data.creatorId == request.auth.uid;

View File

@ -26,7 +26,7 @@ module.exports = {
caughtErrorsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_',
}, },
], ],
'unused-imports/no-unused-imports': 'warn', 'unused-imports/no-unused-imports': 'error',
}, },
}, },
], ],

View File

@ -15,9 +15,9 @@
"dev": "nodemon src/serve.ts", "dev": "nodemon src/serve.ts",
"localDbScript": "firebase emulators:start --only functions,firestore,pubsub --import=./firestore_export", "localDbScript": "firebase emulators:start --only functions,firestore,pubsub --import=./firestore_export",
"serve": "firebase use dev && yarn build && firebase emulators:start --only functions,firestore,pubsub --import=./firestore_export", "serve": "firebase use dev && yarn build && firebase emulators:start --only functions,firestore,pubsub --import=./firestore_export",
"db:update-local-from-remote": "yarn db:backup-remote && gsutil -m rsync -r gs://$npm_package_config_firestore/firestore_export ./firestore_export", "db:update-local-from-remote": "yarn db:backup-remote && gsutil rsync -r gs://$npm_package_config_firestore/firestore_export ./firestore_export",
"db:backup-local": "firebase emulators:export --force ./firestore_export", "db:backup-local": "firebase emulators:export --force ./firestore_export",
"db:rename-remote-backup-folder": "gsutil -m mv gs://$npm_package_config_firestore/firestore_export gs://$npm_package_config_firestore/firestore_export_$(date +%d-%m-%Y-%H-%M)", "db:rename-remote-backup-folder": "gsutil mv gs://$npm_package_config_firestore/firestore_export gs://$npm_package_config_firestore/firestore_export_$(date +%d-%m-%Y-%H-%M)",
"db:backup-remote": "yarn db:rename-remote-backup-folder && gcloud firestore export gs://$npm_package_config_firestore/firestore_export/", "db:backup-remote": "yarn db:rename-remote-backup-folder && gcloud firestore export gs://$npm_package_config_firestore/firestore_export/",
"verify": "(cd .. && yarn verify)", "verify": "(cd .. && yarn verify)",
"verify:dir": "npx eslint . --max-warnings 0; tsc -b -v --pretty" "verify:dir": "npx eslint . --max-warnings 0; tsc -b -v --pretty"
@ -26,13 +26,11 @@
"dependencies": { "dependencies": {
"@amplitude/node": "1.10.0", "@amplitude/node": "1.10.0",
"@google-cloud/functions-framework": "3.1.2", "@google-cloud/functions-framework": "3.1.2",
"@tiptap/core": "2.0.0-beta.199", "@tiptap/core": "2.0.0-beta.182",
"@tiptap/extension-image": "2.0.0-beta.199", "@tiptap/extension-image": "2.0.0-beta.30",
"@tiptap/extension-link": "2.0.0-beta.199", "@tiptap/extension-link": "2.0.0-beta.43",
"@tiptap/extension-mention": "2.0.0-beta.199", "@tiptap/extension-mention": "2.0.0-beta.102",
"@tiptap/html": "2.0.0-beta.199", "@tiptap/starter-kit": "2.0.0-beta.191",
"@tiptap/starter-kit": "2.0.0-beta.199",
"@tiptap/suggestion": "2.0.0-beta.199",
"cors": "2.8.5", "cors": "2.8.5",
"dayjs": "1.11.4", "dayjs": "1.11.4",
"express": "4.18.1", "express": "4.18.1",
@ -40,7 +38,6 @@
"firebase-functions": "3.21.2", "firebase-functions": "3.21.2",
"lodash": "4.17.21", "lodash": "4.17.21",
"mailgun-js": "0.22.0", "mailgun-js": "0.22.0",
"marked": "4.1.1",
"module-alias": "2.2.2", "module-alias": "2.2.2",
"node-fetch": "2", "node-fetch": "2",
"stripe": "8.194.0", "stripe": "8.194.0",
@ -48,7 +45,6 @@
}, },
"devDependencies": { "devDependencies": {
"@types/mailgun-js": "0.22.12", "@types/mailgun-js": "0.22.12",
"@types/marked": "4.0.7",
"@types/module-alias": "2.0.1", "@types/module-alias": "2.0.1",
"@types/node-fetch": "2.6.2", "@types/node-fetch": "2.6.2",
"firebase-functions-test": "0.3.3", "firebase-functions-test": "0.3.3",

View File

@ -1,105 +0,0 @@
import * as admin from 'firebase-admin'
import { getContract, getUser, log } from './utils'
import { APIError, newEndpoint, validate } from './api'
import { JSONContent } from '@tiptap/core'
import { z } from 'zod'
import { removeUndefinedProps } from '../../common/util/object'
import { htmlToRichText } from '../../common/util/parse'
import { marked } from 'marked'
const contentSchema: z.ZodType<JSONContent> = z.lazy(() =>
z.intersection(
z.record(z.any()),
z.object({
type: z.string().optional(),
attrs: z.record(z.any()).optional(),
content: z.array(contentSchema).optional(),
marks: z
.array(
z.intersection(
z.record(z.any()),
z.object({
type: z.string(),
attrs: z.record(z.any()).optional(),
})
)
)
.optional(),
text: z.string().optional(),
})
)
)
const postSchema = z.object({
contractId: z.string(),
content: contentSchema.optional(),
html: z.string().optional(),
markdown: z.string().optional(),
})
const MAX_COMMENT_JSON_LENGTH = 20000
// For now, only supports creating a new top-level comment on a contract.
// Replies, posts, chats are not supported yet.
export const createcomment = newEndpoint({}, async (req, auth) => {
const firestore = admin.firestore()
const { contractId, content, html, markdown } = validate(postSchema, req.body)
const creator = await getUser(auth.uid)
const contract = await getContract(contractId)
if (!creator) {
throw new APIError(400, 'No user exists with the authenticated user ID.')
}
if (!contract) {
throw new APIError(400, 'No contract exists with the given ID.')
}
let contentJson = null
if (content) {
contentJson = content
} else if (html) {
console.log('html', html)
contentJson = htmlToRichText(html)
} else if (markdown) {
const markedParse = marked.parse(markdown)
log('parsed', markedParse)
contentJson = htmlToRichText(markedParse)
log('json', contentJson)
}
if (!contentJson) {
throw new APIError(400, 'No comment content provided.')
}
if (JSON.stringify(contentJson).length > MAX_COMMENT_JSON_LENGTH) {
throw new APIError(
400,
`Comment is too long; should be less than ${MAX_COMMENT_JSON_LENGTH} as a JSON string.`
)
}
const ref = firestore.collection(`contracts/${contractId}/comments`).doc()
const comment = removeUndefinedProps({
id: ref.id,
content: contentJson,
createdTime: Date.now(),
userId: creator.id,
userName: creator.name,
userUsername: creator.username,
userAvatarUrl: creator.avatarUrl,
// OnContract fields
commentType: 'contract',
contractId: contractId,
contractSlug: contract.slug,
contractQuestion: contract.question,
})
await ref.set(comment)
return { status: 'success', comment }
})

View File

@ -197,7 +197,6 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async (
return await notificationRef.set(removeUndefinedProps(notification)) return await notificationRef.set(removeUndefinedProps(notification))
} }
const needNotFollowContractReasons = ['tagged_user']
const stillFollowingContract = (userId: string) => { const stillFollowingContract = (userId: string) => {
return contractFollowersIds.includes(userId) return contractFollowersIds.includes(userId)
} }
@ -206,12 +205,7 @@ export const createCommentOrAnswerOrUpdatedContractNotification = async (
userId: string, userId: string,
reason: notification_reason_types reason: notification_reason_types
) => { ) => {
if ( if (!stillFollowingContract(userId) || sourceUser.id == userId) return
(!stillFollowingContract(userId) &&
!needNotFollowContractReasons.includes(reason)) ||
sourceUser.id == userId
)
return
const privateUser = await getPrivateUser(userId) const privateUser = await getPrivateUser(userId)
if (!privateUser) return if (!privateUser) return
const { sendToBrowser, sendToEmail } = getNotificationDestinationsForUser( const { sendToBrowser, sendToEmail } = getNotificationDestinationsForUser(

View File

@ -103,7 +103,6 @@ export const createpost = newEndpoint({}, async (req, auth) => {
creatorName: creator.name, creatorName: creator.name,
creatorUsername: creator.username, creatorUsername: creator.username,
creatorAvatarUrl: creator.avatarUrl, creatorAvatarUrl: creator.avatarUrl,
itemType: 'post',
}) })
await postRef.create(post) await postRef.create(post)

View File

@ -65,7 +65,6 @@ import { sellbet } from './sell-bet'
import { sellshares } from './sell-shares' import { sellshares } from './sell-shares'
import { claimmanalink } from './claim-manalink' import { claimmanalink } from './claim-manalink'
import { createmarket } from './create-market' import { createmarket } from './create-market'
import { createcomment } from './create-comment'
import { addcommentbounty, awardcommentbounty } from './update-comment-bounty' import { addcommentbounty, awardcommentbounty } from './update-comment-bounty'
import { creategroup } from './create-group' import { creategroup } from './create-group'
import { resolvemarket } from './resolve-market' import { resolvemarket } from './resolve-market'
@ -95,7 +94,6 @@ const claimManalinkFunction = toCloudFunction(claimmanalink)
const createMarketFunction = toCloudFunction(createmarket) const createMarketFunction = toCloudFunction(createmarket)
const addSubsidyFunction = toCloudFunction(addsubsidy) const addSubsidyFunction = toCloudFunction(addsubsidy)
const addCommentBounty = toCloudFunction(addcommentbounty) const addCommentBounty = toCloudFunction(addcommentbounty)
const createCommentFunction = toCloudFunction(createcomment)
const awardCommentBounty = toCloudFunction(awardcommentbounty) const awardCommentBounty = toCloudFunction(awardcommentbounty)
const createGroupFunction = toCloudFunction(creategroup) const createGroupFunction = toCloudFunction(creategroup)
const resolveMarketFunction = toCloudFunction(resolvemarket) const resolveMarketFunction = toCloudFunction(resolvemarket)
@ -132,7 +130,6 @@ export {
acceptChallenge as acceptchallenge, acceptChallenge as acceptchallenge,
createPostFunction as createpost, createPostFunction as createpost,
saveTwitchCredentials as savetwitchcredentials, saveTwitchCredentials as savetwitchcredentials,
createCommentFunction as createcomment,
addCommentBounty as addcommentbounty, addCommentBounty as addcommentbounty,
awardCommentBounty as awardcommentbounty, awardCommentBounty as awardcommentbounty,
updateMetricsFunction as updatemetrics, updateMetricsFunction as updatemetrics,

View File

@ -171,12 +171,12 @@ export const resolveMarket = async (
const openBets = bets.filter((b) => !b.isSold && !b.sale) const openBets = bets.filter((b) => !b.isSold && !b.sale)
const loanPayouts = getLoanPayouts(openBets) const loanPayouts = getLoanPayouts(openBets)
const payoutsWithoutLoans = [ const payouts = [
{ userId: creatorId, payout: creatorPayout, deposit: creatorPayout }, { userId: creatorId, payout: creatorPayout, deposit: creatorPayout },
...liquidityPayouts.map((p) => ({ ...p, deposit: p.payout })), ...liquidityPayouts.map((p) => ({ ...p, deposit: p.payout })),
...traderPayouts, ...traderPayouts,
...loanPayouts,
] ]
const payouts = [...payoutsWithoutLoans, ...loanPayouts]
if (!isProd()) if (!isProd())
console.log( console.log(
@ -208,7 +208,7 @@ export const resolveMarket = async (
await undoUniqueBettorRewardsIfCancelResolution(contract, outcome) await undoUniqueBettorRewardsIfCancelResolution(contract, outcome)
await revalidateStaticProps(getContractPath(contract)) await revalidateStaticProps(getContractPath(contract))
const userPayoutsWithoutLoans = groupPayoutsByUser(payoutsWithoutLoans) const userPayoutsWithoutLoans = groupPayoutsByUser(payouts)
const userInvestments = mapValues( const userInvestments = mapValues(
groupBy(bets, (bet) => bet.userId), groupBy(bets, (bet) => bet.userId),

View File

@ -19,7 +19,6 @@ import { sellbet } from './sell-bet'
import { sellshares } from './sell-shares' import { sellshares } from './sell-shares'
import { claimmanalink } from './claim-manalink' import { claimmanalink } from './claim-manalink'
import { createmarket } from './create-market' import { createmarket } from './create-market'
import { createcomment } from './create-comment'
import { creategroup } from './create-group' import { creategroup } from './create-group'
import { resolvemarket } from './resolve-market' import { resolvemarket } from './resolve-market'
import { unsubscribe } from './unsubscribe' import { unsubscribe } from './unsubscribe'
@ -54,7 +53,6 @@ addJsonEndpointRoute('/transact', transact)
addJsonEndpointRoute('/changeuserinfo', changeuserinfo) addJsonEndpointRoute('/changeuserinfo', changeuserinfo)
addJsonEndpointRoute('/createuser', createuser) addJsonEndpointRoute('/createuser', createuser)
addJsonEndpointRoute('/createanswer', createanswer) addJsonEndpointRoute('/createanswer', createanswer)
addJsonEndpointRoute('/createcomment', createcomment)
addJsonEndpointRoute('/placebet', placebet) addJsonEndpointRoute('/placebet', placebet)
addJsonEndpointRoute('/cancelbet', cancelbet) addJsonEndpointRoute('/cancelbet', cancelbet)
addJsonEndpointRoute('/sellbet', sellbet) addJsonEndpointRoute('/sellbet', sellbet)

View File

@ -22,9 +22,8 @@ module.exports = {
'@next/next/no-typos': 'off', '@next/next/no-typos': 'off',
'linebreak-style': ['error', 'unix'], 'linebreak-style': ['error', 'unix'],
'lodash/import-scope': [2, 'member'], 'lodash/import-scope': [2, 'member'],
'unused-imports/no-unused-imports': 'warn', 'unused-imports/no-unused-imports': 'error',
}, },
ignorePatterns: ['/public/mtg/*'],
env: { env: {
browser: true, browser: true,
node: true, node: true,

View File

@ -35,7 +35,7 @@ export function AddFundsModal(props: {
<div className="text-xl">{manaToUSD(amountSelected)}</div> <div className="text-xl">{manaToUSD(amountSelected)}</div>
</div> </div>
<div className="flex"> <div className="modal-action">
<Button color="gray-white" onClick={() => setOpen(false)}> <Button color="gray-white" onClick={() => setOpen(false)}>
Back Back
</Button> </Button>

View File

@ -7,8 +7,6 @@ import { ENV_CONFIG } from 'common/envs/constants'
import { Row } from './layout/row' import { Row } from './layout/row'
import { AddFundsModal } from './add-funds-modal' import { AddFundsModal } from './add-funds-modal'
import { Input } from './input' import { Input } from './input'
import Slider from 'rc-slider'
import 'rc-slider/assets/index.css'
export function AmountInput(props: { export function AmountInput(props: {
amount: number | undefined amount: number | undefined
@ -42,13 +40,17 @@ export function AmountInput(props: {
return ( return (
<> <>
<Col className={clsx('relative', className)}> <Col className={className}>
<label className="font-sm md:font-lg relative"> <label className="font-sm md:font-lg relative">
<span className="text-greyscale-4 absolute top-1/2 my-auto ml-2 -translate-y-1/2"> <span className="text-greyscale-4 absolute top-1/2 my-auto ml-2 -translate-y-1/2">
{label} {label}
</span> </span>
<Input <Input
className={clsx('w-24 pl-9 !text-base md:w-auto', inputClassName)} className={clsx(
'w-24 pl-9 !text-base md:w-auto',
error && 'input-error',
inputClassName
)}
ref={inputRef} ref={inputRef}
type="text" type="text"
pattern="[0-9]*" pattern="[0-9]*"
@ -56,14 +58,13 @@ export function AmountInput(props: {
placeholder="0" placeholder="0"
maxLength={6} maxLength={6}
value={amount ?? ''} value={amount ?? ''}
error={!!error}
disabled={disabled} disabled={disabled}
onChange={(e) => onAmountChange(e.target.value)} onChange={(e) => onAmountChange(e.target.value)}
/> />
</label> </label>
{error && ( {error && (
<div className="absolute -bottom-5 whitespace-nowrap text-xs font-medium tracking-wide text-red-500"> <div className="absolute mt-11 whitespace-nowrap text-xs font-medium tracking-wide text-red-500">
{error === 'Insufficient balance' ? ( {error === 'Insufficient balance' ? (
<> <>
Not enough funds. Not enough funds.
@ -147,7 +148,7 @@ export function BuyAmountInput(props: {
return ( return (
<> <>
<Row className="items-center gap-4"> <Row className="gap-4">
<AmountInput <AmountInput
amount={amount} amount={amount}
onChange={onAmountChange} onChange={onAmountChange}
@ -159,23 +160,14 @@ export function BuyAmountInput(props: {
inputRef={inputRef} inputRef={inputRef}
/> />
{showSlider && ( {showSlider && (
<Slider <input
min={0} type="range"
max={205} min="0"
max="205"
value={getRaw(amount ?? 0)} value={getRaw(amount ?? 0)}
onChange={(value) => onAmountChange(parseRaw(value as number))} onChange={(e) => onAmountChange(parseRaw(parseInt(e.target.value)))}
className="mx-4 !h-4 xl:hidden [&>.rc-slider-rail]:bg-gray-200 [&>.rc-slider-track]:bg-indigo-400 [&>.rc-slider-handle]:bg-indigo-400" className="range range-lg only-thumb my-auto align-middle xl:hidden"
railStyle={{ height: 16, top: 0, left: 0 }} step="5"
trackStyle={{ height: 16, top: 0 }}
handleStyle={{
height: 32,
width: 32,
opacity: 1,
border: 'none',
boxShadow: 'none',
top: -2,
}}
step={5}
/> />
)} )}
</Row> </Row>

View File

@ -100,8 +100,8 @@ export function AnswerItem(props: {
</div> </div>
))} ))}
{showChoice ? ( {showChoice ? (
<div className="flex flex-col py-1"> <div className="form-control py-1">
<label className="cursor-pointer gap-3 px-1 py-2"> <label className="label cursor-pointer gap-3">
<span className="">Choose this answer</span> <span className="">Choose this answer</span>
{showChoice === 'radio' && ( {showChoice === 'radio' && (
<input <input

View File

@ -27,13 +27,6 @@ import { CHOICE_ANSWER_COLORS } from '../charts/contract/choice'
import { useChartAnswers } from '../charts/contract/choice' import { useChartAnswers } from '../charts/contract/choice'
import { ChatIcon } from '@heroicons/react/outline' import { ChatIcon } from '@heroicons/react/outline'
export function getAnswerColor(answer: Answer, answersArray: string[]) {
const colorIndex = answersArray.indexOf(answer.text)
return colorIndex != undefined && colorIndex < CHOICE_ANSWER_COLORS.length
? CHOICE_ANSWER_COLORS[colorIndex]
: '#B1B1C7'
}
export function AnswersPanel(props: { export function AnswersPanel(props: {
contract: FreeResponseContract | MultipleChoiceContract contract: FreeResponseContract | MultipleChoiceContract
onAnswerCommentClick: (answer: Answer) => void onAnswerCommentClick: (answer: Answer) => void
@ -114,8 +107,8 @@ export function AnswersPanel(props: {
? 'checkbox' ? 'checkbox'
: undefined : undefined
const answersArray = useChartAnswers(contract).map( const colorSortedAnswer = useChartAnswers(contract).map(
(answer, _index) => answer.text (value, _index) => value.text
) )
return ( return (
@ -146,8 +139,8 @@ export function AnswersPanel(props: {
key={item.id} key={item.id}
answer={item} answer={item}
contract={contract} contract={contract}
colorIndex={colorSortedAnswer.indexOf(item.text)}
onAnswerCommentClick={onAnswerCommentClick} onAnswerCommentClick={onAnswerCommentClick}
color={getAnswerColor(item, answersArray)}
/> />
))} ))}
{hasZeroBetAnswers && !showAllAnswers && ( {hasZeroBetAnswers && !showAllAnswers && (
@ -192,14 +185,18 @@ export function AnswersPanel(props: {
function OpenAnswer(props: { function OpenAnswer(props: {
contract: FreeResponseContract | MultipleChoiceContract contract: FreeResponseContract | MultipleChoiceContract
answer: Answer answer: Answer
color: string colorIndex: number | undefined
onAnswerCommentClick: (answer: Answer) => void onAnswerCommentClick: (answer: Answer) => void
}) { }) {
const { answer, contract, onAnswerCommentClick, color } = props const { answer, contract, colorIndex, onAnswerCommentClick } = props
const { username, avatarUrl, text } = answer const { username, avatarUrl, text } = answer
const prob = getDpmOutcomeProbability(contract.totalShares, answer.id) const prob = getDpmOutcomeProbability(contract.totalShares, answer.id)
const probPercent = formatPercent(prob) const probPercent = formatPercent(prob)
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const color =
colorIndex != undefined && colorIndex < CHOICE_ANSWER_COLORS.length
? CHOICE_ANSWER_COLORS[colorIndex] + '55' // semi-transparent
: '#B1B1C755'
const colorWidth = 100 * Math.max(prob, 0.01) const colorWidth = 100 * Math.max(prob, 0.01)
return ( return (
@ -220,7 +217,7 @@ function OpenAnswer(props: {
tradingAllowed(contract) ? 'text-greyscale-7' : 'text-greyscale-5' tradingAllowed(contract) ? 'text-greyscale-7' : 'text-greyscale-5'
)} )}
style={{ style={{
background: `linear-gradient(to right, ${color}90 ${colorWidth}%, #FBFBFF ${colorWidth}%)`, background: `linear-gradient(to right, ${color} ${colorWidth}%, #FBFBFF ${colorWidth}%)`,
}} }}
> >
<Row className="z-20 -mb-1 justify-between gap-2 py-2 px-3"> <Row className="z-20 -mb-1 justify-between gap-2 py-2 px-3">
@ -230,7 +227,10 @@ function OpenAnswer(props: {
username={username} username={username}
avatarUrl={avatarUrl} avatarUrl={avatarUrl}
/> />
<Linkify className="text-md whitespace-pre-line" text={text} /> <Linkify
className="text-md cursor-pointer whitespace-pre-line"
text={text}
/>
</Row> </Row>
<Row className="gap-2"> <Row className="gap-2">
<div className="my-auto text-xl">{probPercent}</div> <div className="my-auto text-xl">{probPercent}</div>

View File

@ -5,6 +5,7 @@ import { awardCommentBounty } from 'web/lib/firebase/api'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
import { Row } from './layout/row' import { Row } from './layout/row'
import { Contract } from 'common/contract' import { Contract } from 'common/contract'
import { TextButton } from 'web/components/text-button'
import { COMMENT_BOUNTY_AMOUNT } from 'common/economy' import { COMMENT_BOUNTY_AMOUNT } from 'common/economy'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
@ -36,17 +37,10 @@ export function AwardBountyButton(prop: {
const canUp = me && me.id !== comment.userId && contract.creatorId === me.id const canUp = me && me.id !== comment.userId && contract.creatorId === me.id
if (!canUp) return <div /> if (!canUp) return <div />
return ( return (
<Row <Row className={clsx('-ml-2 items-center gap-0.5', !canUp ? '-ml-6' : '')}>
className={clsx('my-auto items-center gap-0.5', !canUp ? '-ml-6' : '')} <TextButton className={'font-bold'} onClick={submit}>
>
<button
className={
'rounded-full border border-indigo-400 bg-indigo-50 py-0.5 px-2 text-xs text-indigo-400 transition-colors hover:bg-indigo-400 hover:text-white'
}
onClick={submit}
>
Award {formatMoney(COMMENT_BOUNTY_AMOUNT)} Award {formatMoney(COMMENT_BOUNTY_AMOUNT)}
</button> </TextButton>
</Row> </Row>
) )
} }

View File

@ -92,7 +92,10 @@ export function BetInline(props: {
/> />
<BuyAmountInput <BuyAmountInput
className="-mb-4" className="-mb-4"
inputClassName="w-20 !text-base" inputClassName={clsx(
'input-sm w-20 !text-base',
error && 'input-error'
)}
amount={amount} amount={amount}
onChange={setAmount} onChange={setAmount}
error="" // handle error ourselves error="" // handle error ourselves

View File

@ -631,9 +631,9 @@ function LimitOrderPanel(props: {
return ( return (
<Col className={hidden ? 'hidden' : ''}> <Col className={hidden ? 'hidden' : ''}>
<Row className="mt-1 mb-4 items-center gap-4"> <Row className="mt-1 items-center gap-4">
<Col className="gap-2"> <Col className="gap-2">
<div className="text-sm text-gray-500"> <div className="relative ml-1 text-sm text-gray-500">
Buy {isPseudoNumeric ? <HigherLabel /> : <YesLabel />} up to Buy {isPseudoNumeric ? <HigherLabel /> : <YesLabel />} up to
</div> </div>
<ProbabilityOrNumericInput <ProbabilityOrNumericInput
@ -641,11 +641,10 @@ function LimitOrderPanel(props: {
prob={lowLimitProb} prob={lowLimitProb}
setProb={setLowLimitProb} setProb={setLowLimitProb}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
placeholder="10"
/> />
</Col> </Col>
<Col className="gap-2"> <Col className="gap-2">
<div className="text-sm text-gray-500"> <div className="ml-1 text-sm text-gray-500">
Buy {isPseudoNumeric ? <LowerLabel /> : <NoLabel />} down to Buy {isPseudoNumeric ? <LowerLabel /> : <NoLabel />} down to
</div> </div>
<ProbabilityOrNumericInput <ProbabilityOrNumericInput
@ -653,7 +652,6 @@ function LimitOrderPanel(props: {
prob={highLimitProb} prob={highLimitProb}
setProb={setHighLimitProb} setProb={setHighLimitProb}
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
placeholder="90"
/> />
</Col> </Col>
</Row> </Row>
@ -982,11 +980,11 @@ export function SellPanel(props: {
<Col className="mt-3 w-full gap-3 text-sm"> <Col className="mt-3 w-full gap-3 text-sm">
<Row className="items-center justify-between gap-2 text-gray-500"> <Row className="items-center justify-between gap-2 text-gray-500">
Sale amount Sale amount
<span className="text-gray-700">{formatMoney(saleValue)}</span> <span className="text-neutral">{formatMoney(saleValue)}</span>
</Row> </Row>
<Row className="items-center justify-between gap-2 text-gray-500"> <Row className="items-center justify-between gap-2 text-gray-500">
Profit Profit
<span className="text-gray-700">{formatMoney(profit)}</span> <span className="text-neutral">{formatMoney(profit)}</span>
</Row> </Row>
<Row className="items-center justify-between"> <Row className="items-center justify-between">
<div className="text-gray-500"> <div className="text-gray-500">
@ -1002,11 +1000,11 @@ export function SellPanel(props: {
<> <>
<Row className="mt-6 items-center justify-between gap-2 text-gray-500"> <Row className="mt-6 items-center justify-between gap-2 text-gray-500">
Loan payment Loan payment
<span className="text-gray-700">{formatMoney(-loanPaid)}</span> <span className="text-neutral">{formatMoney(-loanPaid)}</span>
</Row> </Row>
<Row className="items-center justify-between gap-2 text-gray-500"> <Row className="items-center justify-between gap-2 text-gray-500">
Net proceeds Net proceeds
<span className="text-gray-700">{formatMoney(netProceeds)}</span> <span className="text-neutral">{formatMoney(netProceeds)}</span>
</Row> </Row>
</> </>
)} )}

View File

@ -25,8 +25,10 @@ export function BetsSummary(props: {
const isBinary = outcomeType === 'BINARY' const isBinary = outcomeType === 'BINARY'
const bets = props.userBets.filter((b) => !b.isAnte) const bets = props.userBets.filter((b) => !b.isAnte)
const { profitPercent, payout, profit, invested, hasShares } = const { profitPercent, payout, profit, invested } = getContractBetMetrics(
getContractBetMetrics(contract, bets) contract,
bets
)
const excludeSales = bets.filter((b) => !b.isSold && !b.sale) const excludeSales = bets.filter((b) => !b.isSold && !b.sale)
const yesWinnings = sumBy(excludeSales, (bet) => const yesWinnings = sumBy(excludeSales, (bet) =>
@ -37,7 +39,6 @@ export function BetsSummary(props: {
) )
const position = yesWinnings - noWinnings const position = yesWinnings - noWinnings
const outcome = hasShares ? (position > 0 ? 'YES' : 'NO') : undefined
const prob = isBinary ? getProbability(contract) : 0 const prob = isBinary ? getProbability(contract) : 0
const expectation = prob * yesWinnings + (1 - prob) * noWinnings const expectation = prob * yesWinnings + (1 - prob) * noWinnings
@ -59,9 +60,7 @@ export function BetsSummary(props: {
<Col> <Col>
<div className="whitespace-nowrap text-sm text-gray-500"> <div className="whitespace-nowrap text-sm text-gray-500">
Position{' '} Position{' '}
<InfoTooltip <InfoTooltip text="Number of shares you own on net. 1 YES share = M$1 if the market resolves YES." />
text={`Number of shares you own on net. 1 ${outcome} share = M$1 if the market resolves ${outcome}.`}
/>
</div> </div>
<div className="whitespace-nowrap"> <div className="whitespace-nowrap">
{position > 1e-7 ? ( {position > 1e-7 ? (

View File

@ -52,8 +52,6 @@ import {
} from 'web/hooks/use-persistent-state' } from 'web/hooks/use-persistent-state'
import { safeLocalStorage } from 'web/lib/util/local' import { safeLocalStorage } from 'web/lib/util/local'
import { ExclamationIcon } from '@heroicons/react/outline' import { ExclamationIcon } from '@heroicons/react/outline'
import { Select } from './select'
import { Table } from './table'
type BetSort = 'newest' | 'profit' | 'closeTime' | 'value' type BetSort = 'newest' | 'profit' | 'closeTime' | 'value'
type BetFilter = 'open' | 'limit_bet' | 'sold' | 'closed' | 'resolved' | 'all' type BetFilter = 'open' | 'limit_bet' | 'sold' | 'closed' | 'resolved' | 'all'
@ -202,7 +200,8 @@ export function BetsList(props: { user: User }) {
</Row> </Row>
<Row className="gap-2"> <Row className="gap-2">
<Select <select
className="border-greyscale-4 self-start overflow-hidden rounded border px-2 py-2 text-sm"
value={filter} value={filter}
onChange={(e) => setFilter(e.target.value as BetFilter)} onChange={(e) => setFilter(e.target.value as BetFilter)}
> >
@ -212,9 +211,10 @@ export function BetsList(props: { user: User }) {
<option value="closed">Closed</option> <option value="closed">Closed</option>
<option value="resolved">Resolved</option> <option value="resolved">Resolved</option>
<option value="all">All</option> <option value="all">All</option>
</Select> </select>
<Select <select
className="border-greyscale-4 self-start overflow-hidden rounded px-2 py-2 text-sm"
value={sort} value={sort}
onChange={(e) => setSort(e.target.value as BetSort)} onChange={(e) => setSort(e.target.value as BetSort)}
> >
@ -222,7 +222,7 @@ export function BetsList(props: { user: User }) {
<option value="value">Value</option> <option value="value">Value</option>
<option value="profit">Profit</option> <option value="profit">Profit</option>
<option value="closeTime">Close date</option> <option value="closeTime">Close date</option>
</Select> </select>
</Row> </Row>
</Col> </Col>
@ -451,7 +451,7 @@ export function ContractBetsTable(props: {
</> </>
)} )}
<Table> <table className="table-zebra table-compact table w-full text-gray-500">
<thead> <thead>
<tr className="p-2"> <tr className="p-2">
<th></th> <th></th>
@ -480,7 +480,7 @@ export function ContractBetsTable(props: {
/> />
))} ))}
</tbody> </tbody>
</Table> </table>
</div> </div>
) )
} }
@ -551,7 +551,7 @@ function BetRow(props: {
return ( return (
<tr> <tr>
<td className="text-gray-700"> <td className="text-neutral">
{isYourBet && {isYourBet &&
!isCPMM && !isCPMM &&
!isResolved && !isResolved &&

View File

@ -82,39 +82,3 @@ export function Button(props: {
</button> </button>
) )
} }
export function IconButton(props: {
className?: string
onClick?: MouseEventHandler<any> | undefined
children?: ReactNode
size?: SizeType
type?: 'button' | 'reset' | 'submit'
disabled?: boolean
loading?: boolean
}) {
const {
children,
className,
onClick,
size = 'md',
type = 'button',
disabled = false,
loading,
} = props
return (
<button
type={type}
className={clsx(
'inline-flex items-center justify-center transition-colors disabled:cursor-not-allowed',
sizeClasses[size],
'disabled:text-greyscale-2 text-greyscale-5 hover:text-greyscale-6',
className
)}
disabled={disabled || loading}
onClick={onClick}
>
{children}
</button>
)
}

View File

@ -171,7 +171,7 @@ function CreateChallengeForm(props: {
<div>You'll bet:</div> <div>You'll bet:</div>
<Row <Row
className={ className={
'w-full max-w-xs items-center justify-between gap-4 pr-3' 'form-control w-full max-w-xs items-center justify-between gap-4 pr-3'
} }
> >
<AmountInput <AmountInput

View File

@ -8,12 +8,10 @@ import { useEffect, useState } from 'react'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { MAX_COMMENT_LENGTH } from 'web/lib/firebase/comments' import { MAX_COMMENT_LENGTH } from 'web/lib/firebase/comments'
import Curve from 'web/public/custom-components/curve' import Curve from 'web/public/custom-components/curve'
import { getAnswerColor } from './answers/answers-panel'
import { Avatar } from './avatar' import { Avatar } from './avatar'
import { TextEditor, useTextEditor } from './editor' import { TextEditor, useTextEditor } from './editor'
import { CommentsAnswer } from './feed/feed-answer-comment-group' import { CommentsAnswer } from './feed/feed-answer-comment-group'
import { ContractCommentInput } from './feed/feed-comments' import { ContractCommentInput } from './feed/feed-comments'
import { Col } from './layout/col'
import { Row } from './layout/row' import { Row } from './layout/row'
import { LoadingIndicator } from './loading-indicator' import { LoadingIndicator } from './loading-indicator'
@ -83,30 +81,20 @@ export function AnswerCommentInput(props: {
contract: Contract<AnyContractType> contract: Contract<AnyContractType>
answerResponse: Answer answerResponse: Answer
onCancelAnswerResponse?: () => void onCancelAnswerResponse?: () => void
answersArray: string[]
}) { }) {
const { contract, answerResponse, onCancelAnswerResponse, answersArray } = const { contract, answerResponse, onCancelAnswerResponse } = props
props
const replyTo = { const replyTo = {
id: answerResponse.id, id: answerResponse.id,
username: answerResponse.username, username: answerResponse.username,
} }
const color = getAnswerColor(answerResponse, answersArray)
return ( return (
<> <>
<Col> <CommentsAnswer answer={answerResponse} contract={contract} />
<Row className="relative"> <Row>
<div className="absolute -bottom-1 left-1.5"> <div className="ml-1">
<Curve size={32} strokeWidth={1} color="#D8D8EB" /> <Curve size={28} strokeWidth={1} color="#D8D8EB" />
</div> </div>
<div className="ml-[38px]">
<CommentsAnswer
answer={answerResponse}
contract={contract}
color={color}
/>
</div>
</Row>
<div className="relative w-full pt-1"> <div className="relative w-full pt-1">
<ContractCommentInput <ContractCommentInput
contract={contract} contract={contract}
@ -119,7 +107,7 @@ export function AnswerCommentInput(props: {
<XCircleIcon className="text-greyscale-5 hover:text-greyscale-6 absolute -top-1 -right-2 h-5 w-5" /> <XCircleIcon className="text-greyscale-5 hover:text-greyscale-6 absolute -top-1 -right-2 h-5 w-5" />
</button> </button>
</div> </div>
</Col> </Row>
</> </>
) )
} }

View File

@ -42,7 +42,6 @@ import { Button } from './button'
import { Modal } from './layout/modal' import { Modal } from './layout/modal'
import { Title } from './title' import { Title } from './title'
import { Input } from './input' import { Input } from './input'
import { Select } from './select'
export const SORTS = [ export const SORTS = [
{ label: 'Newest', value: 'newest' }, { label: 'Newest', value: 'newest' },
@ -438,7 +437,7 @@ function ContractSearchControls(props: {
} }
return ( return (
<Col className={clsx('bg-greyscale-1 top-0 z-20 gap-3 pb-3', className)}> <Col className={clsx('bg-base-200 top-0 z-20 gap-3 pb-3', className)}>
<Row className="gap-1 sm:gap-2"> <Row className="gap-1 sm:gap-2">
<Input <Input
type="text" type="text"
@ -544,7 +543,8 @@ export function SearchFilters(props: {
return ( return (
<div className={className}> <div className={className}>
<Select <select
className="select select-bordered"
value={filter} value={filter}
onChange={(e) => selectFilter(e.target.value as filter)} onChange={(e) => selectFilter(e.target.value as filter)}
> >
@ -552,9 +552,10 @@ export function SearchFilters(props: {
<option value="closed">Closed</option> <option value="closed">Closed</option>
<option value="resolved">Resolved</option> <option value="resolved">Resolved</option>
<option value="all">All</option> <option value="all">All</option>
</Select> </select>
{!hideOrderSelector && ( {!hideOrderSelector && (
<Select <select
className="select select-bordered"
value={sort} value={sort}
onChange={(e) => selectSort(e.target.value as Sort)} onChange={(e) => selectSort(e.target.value as Sort)}
> >
@ -563,7 +564,7 @@ export function SearchFilters(props: {
{option.label} {option.label}
</option> </option>
))} ))}
</Select> </select>
)} )}
</div> </div>
) )

View File

@ -1,11 +1,7 @@
import clsx from 'clsx' import clsx from 'clsx'
import Link from 'next/link' import Link from 'next/link'
import { Row } from '../layout/row' import { Row } from '../layout/row'
import { import { formatLargeNumber, formatPercent } from 'common/util/format'
formatLargeNumber,
formatPercent,
formatWithCommas,
} from 'common/util/format'
import { contractPath, getBinaryProbPercent } from 'web/lib/firebase/contracts' import { contractPath, getBinaryProbPercent } from 'web/lib/firebase/contracts'
import { Col } from '../layout/col' import { Col } from '../layout/col'
import { import {
@ -39,10 +35,10 @@ import { trackCallback } from 'web/lib/service/analytics'
import { getMappedValue } from 'common/pseudo-numeric' import { getMappedValue } from 'common/pseudo-numeric'
import { Tooltip } from '../tooltip' import { Tooltip } from '../tooltip'
import { SiteLink } from '../site-link' import { SiteLink } from '../site-link'
import { ProbOrNumericChange } from './prob-change-table' import { ProbChange } from './prob-change-table'
import { Card } from '../card' import { Card } from '../card'
import { ProfitBadgeMana } from '../profit-badge'
import { floatingEqual } from 'common/util/math' import { floatingEqual } from 'common/util/math'
import { ENV_CONFIG } from 'common/envs/constants'
export function ContractCard(props: { export function ContractCard(props: {
contract: Contract contract: Contract
@ -400,25 +396,14 @@ export function ContractCardProbChange(props: {
className?: string className?: string
}) { }) {
const { noLinkAvatar, showPosition, className } = props const { noLinkAvatar, showPosition, className } = props
const yesOutcomeLabel =
props.contract.outcomeType === 'PSEUDO_NUMERIC' ? 'HIGHER' : 'YES'
const noOutcomeLabel =
props.contract.outcomeType === 'PSEUDO_NUMERIC' ? 'LOWER' : 'NO'
const contract = useContractWithPreload(props.contract) as CPMMBinaryContract const contract = useContractWithPreload(props.contract) as CPMMBinaryContract
const user = useUser() const user = useUser()
const metrics = useUserContractMetrics(user?.id, contract.id) const metrics = useUserContractMetrics(user?.id, contract.id)
const dayMetrics = metrics && metrics.from && metrics.from.day const dayMetrics = metrics && metrics.from && metrics.from.day
const binaryOutcome = const outcome =
metrics && floatingEqual(metrics.totalShares.NO ?? 0, 0) ? 'YES' : 'NO' metrics && floatingEqual(metrics.totalShares.NO ?? 0, 0) ? 'YES' : 'NO'
const displayedProfit = dayMetrics
? ENV_CONFIG.moneyMoniker +
(dayMetrics.profit > 0 ? '+' : '') +
dayMetrics.profit.toFixed(0)
: undefined
return ( return (
<Card className={clsx(className, 'mb-4')}> <Card className={clsx(className, 'mb-4')}>
<AvatarDetails <AvatarDetails
@ -433,7 +418,7 @@ export function ContractCardProbChange(props: {
> >
<span className="line-clamp-3">{contract.question}</span> <span className="line-clamp-3">{contract.question}</span>
</SiteLink> </SiteLink>
<ProbOrNumericChange className="py-2 pr-4" contract={contract} /> <ProbChange className="py-2 pr-4" contract={contract} />
</Row> </Row>
{showPosition && metrics && metrics.hasShares && ( {showPosition && metrics && metrics.hasShares && (
<Row <Row
@ -441,11 +426,19 @@ export function ContractCardProbChange(props: {
'items-center justify-between gap-4 pl-6 pr-4 pb-2 text-sm' 'items-center justify-between gap-4 pl-6 pr-4 pb-2 text-sm'
)} )}
> >
<Row className="gap-1 text-gray-400"> <Row className="gap-1 text-gray-700">
You: {formatWithCommas(metrics.totalShares[binaryOutcome])}{' '} <div className="text-gray-500">Position</div>
{binaryOutcome === 'YES' ? yesOutcomeLabel : noOutcomeLabel} shares {Math.floor(metrics.totalShares[outcome])} {outcome}
<span className="ml-1.5">{displayedProfit} today</span>
</Row> </Row>
{dayMetrics && (
<>
<Row className="items-center">
<div className="mr-1 text-gray-500">Daily profit</div>
<ProfitBadgeMana amount={dayMetrics.profit} gray />
</Row>
</>
)}
</Row> </Row>
)} )}
</Card> </Card>

View File

@ -45,11 +45,13 @@ function RichEditContract(props: { contract: Contract; isAdmin?: boolean }) {
const { contract, isAdmin } = props const { contract, isAdmin } = props
const [editing, setEditing] = useState(false) const [editing, setEditing] = useState(false)
const [editingQ, setEditingQ] = useState(false) const [editingQ, setEditingQ] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
const { editor, upload } = useTextEditor({ const { editor, upload } = useTextEditor({
// key: `description ${contract.id}`, // key: `description ${contract.id}`,
max: MAX_DESCRIPTION_LENGTH, max: MAX_DESCRIPTION_LENGTH,
defaultValue: contract.description, defaultValue: contract.description,
disabled: isSubmitting,
}) })
async function saveDescription() { async function saveDescription() {
@ -64,8 +66,10 @@ function RichEditContract(props: { contract: Contract; isAdmin?: boolean }) {
<Row className="gap-2"> <Row className="gap-2">
<Button <Button
onClick={async () => { onClick={async () => {
setIsSubmitting(true)
await saveDescription() await saveDescription()
setEditing(false) setEditing(false)
setIsSubmitting(false)
}} }}
> >
Save Save

View File

@ -3,7 +3,6 @@ import {
ExclamationIcon, ExclamationIcon,
PencilIcon, PencilIcon,
PlusCircleIcon, PlusCircleIcon,
UserGroupIcon,
} from '@heroicons/react/solid' } from '@heroicons/react/solid'
import clsx from 'clsx' import clsx from 'clsx'
import { Editor } from '@tiptap/react' import { Editor } from '@tiptap/react'
@ -51,13 +50,8 @@ export function MiscDetails(props: {
hideGroupLink?: boolean hideGroupLink?: boolean
}) { }) {
const { contract, showTime, hideGroupLink } = props const { contract, showTime, hideGroupLink } = props
const { const { volume, closeTime, isResolved, createdTime, resolutionTime } =
closeTime, contract
isResolved,
createdTime,
resolutionTime,
uniqueBettorCount,
} = contract
const isClient = useIsClient() const isClient = useIsClient()
const isNew = createdTime > Date.now() - DAY_MS && !isResolved const isNew = createdTime > Date.now() - DAY_MS && !isResolved
@ -81,11 +75,8 @@ export function MiscDetails(props: {
<FeaturedContractBadge /> <FeaturedContractBadge />
) : (contract.openCommentBounties ?? 0) > 0 ? ( ) : (contract.openCommentBounties ?? 0) > 0 ? (
<BountiedContractBadge /> <BountiedContractBadge />
) : !isNew || (uniqueBettorCount ?? 0) > 1 ? ( ) : volume > 0 || !isNew ? (
<Row className={'shrink-0'}> <Row className={'shrink-0'}>{formatMoney(volume)} bet</Row>
<UserGroupIcon className="mr-1 h-4 w-4" />
{uniqueBettorCount} trader{uniqueBettorCount !== 1 ? 's' : ''}
</Row>
) : ( ) : (
<NewContractBadge /> <NewContractBadge />
)} )}

View File

@ -19,10 +19,11 @@ import ShortToggle from '../widgets/short-toggle'
import { DuplicateContractButton } from '../duplicate-contract-button' import { DuplicateContractButton } from '../duplicate-contract-button'
import { Row } from '../layout/row' import { Row } from '../layout/row'
import { BETTORS, User } from 'common/user' import { BETTORS, User } from 'common/user'
import { IconButton } from '../button' import { Button } from '../button'
import { AddLiquidityButton } from './add-liquidity-button' import { AddLiquidityButton } from './add-liquidity-button'
import { Tooltip } from '../tooltip'
import { Table } from '../table' export const contractDetailsButtonClassName =
'group flex items-center rounded-md px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-100 text-gray-400 hover:text-gray-500'
export function ContractInfoDialog(props: { export function ContractInfoDialog(props: {
contract: Contract contract: Contract
@ -83,23 +84,23 @@ export function ContractInfoDialog(props: {
return ( return (
<> <>
<Tooltip text="Market details" placement="bottom" noTap noFade> <Button
<IconButton size="sm"
size="2xs" color="gray-white"
className={clsx(className)} className={clsx(contractDetailsButtonClassName, className)}
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
> >
<DotsHorizontalIcon <DotsHorizontalIcon
className={clsx('h-5 w-5 flex-shrink-0')} className={clsx('h-5 w-5 flex-shrink-0')}
aria-hidden="true" aria-hidden="true"
/> />
</IconButton> </Button>
<Modal open={open} setOpen={setOpen}> <Modal open={open} setOpen={setOpen}>
<Col className="gap-4 rounded bg-white p-6"> <Col className="gap-4 rounded bg-white p-6">
<Title className="!mt-0 !mb-0" text="This Market" /> <Title className="!mt-0 !mb-0" text="This Market" />
<Table> <table className="table-compact table-zebra table w-full text-gray-500">
<tbody> <tbody>
<tr> <tr>
<td>Type</td> <td>Type</td>
@ -185,8 +186,7 @@ export function ContractInfoDialog(props: {
<td> <td>
{mechanism === 'cpmm-1' && outcomeType === 'BINARY' {mechanism === 'cpmm-1' && outcomeType === 'BINARY'
? `${Math.round(pool.YES)} YES, ${Math.round(pool.NO)} NO` ? `${Math.round(pool.YES)} YES, ${Math.round(pool.NO)} NO`
: mechanism === 'cpmm-1' && : mechanism === 'cpmm-1' && outcomeType === 'PSEUDO_NUMERIC'
outcomeType === 'PSEUDO_NUMERIC'
? `${Math.round(pool.YES)} HIGHER, ${Math.round( ? `${Math.round(pool.YES)} HIGHER, ${Math.round(
pool.NO pool.NO
)} LOWER` )} LOWER`
@ -239,7 +239,7 @@ export function ContractInfoDialog(props: {
</tr> </tr>
)} )}
</tbody> </tbody>
</Table> </table>
<Row className="flex-wrap"> <Row className="flex-wrap">
{mechanism === 'cpmm-1' && ( {mechanism === 'cpmm-1' && (
@ -249,7 +249,6 @@ export function ContractInfoDialog(props: {
</Row> </Row>
</Col> </Col>
</Modal> </Modal>
</Tooltip>
</> </>
) )
} }

View File

@ -32,7 +32,7 @@ export function ContractReportResolution(props: { contract: Contract }) {
} }
const flagClass = clsx( const flagClass = clsx(
'mx-2 flex flex-col items-center gap-1 w-6 h-6 rounded-md !bg-gray-100 px-1 py-2 hover:bg-gray-300', 'mx-2 flex flex-col items-center gap-1 w-6 h-6 rounded-md !bg-gray-100 px-2 py-1 hover:bg-gray-300',
userReported ? '!text-red-500' : '!text-gray-500' userReported ? '!text-red-500' : '!text-gray-500'
) )

View File

@ -2,11 +2,11 @@ import { memo, useState } from 'react'
import { Pagination } from 'web/components/pagination' import { Pagination } from 'web/components/pagination'
import { FeedBet } from '../feed/feed-bets' import { FeedBet } from '../feed/feed-bets'
import { FeedLiquidity } from '../feed/feed-liquidity' import { FeedLiquidity } from '../feed/feed-liquidity'
import { FreeResponseComments } from '../feed/feed-answer-comment-group' import { CommentsAnswer } from '../feed/feed-answer-comment-group'
import { FeedCommentThread, ContractCommentInput } from '../feed/feed-comments' import { FeedCommentThread, ContractCommentInput } from '../feed/feed-comments'
import { groupBy, sortBy, sum } from 'lodash' import { groupBy, sortBy, sum } from 'lodash'
import { Bet } from 'common/bet' import { Bet } from 'common/bet'
import { AnyContractType, Contract } from 'common/contract' import { Contract } from 'common/contract'
import { PAST_BETS } from 'common/user' import { PAST_BETS } from 'common/user'
import { ContractBetsTable } from '../bets-list' import { ContractBetsTable } from '../bets-list'
import { Spacer } from '../layout/spacer' import { Spacer } from '../layout/spacer'
@ -35,7 +35,9 @@ import {
} from 'web/hooks/use-persistent-state' } from 'web/hooks/use-persistent-state'
import { safeLocalStorage } from 'web/lib/util/local' import { safeLocalStorage } from 'web/lib/util/local'
import TriangleDownFillIcon from 'web/lib/icons/triangle-down-fill-icon' import TriangleDownFillIcon from 'web/lib/icons/triangle-down-fill-icon'
import Curve from 'web/public/custom-components/curve'
import { Answer } from 'common/answer' import { Answer } from 'common/answer'
import { AnswerCommentInput } from '../comment-input'
export function ContractTabs(props: { export function ContractTabs(props: {
contract: Contract contract: Contract
@ -137,27 +139,95 @@ const CommentsTabContent = memo(function CommentsTabContent(props: {
) )
const topLevelComments = commentsByParent['_'] ?? [] const topLevelComments = commentsByParent['_'] ?? []
const sortRow = comments.length > 0 && (
<Row className="mb-4 items-center justify-end gap-4">
<BountiedContractSmallBadge contract={contract} showAmount />
<Row className="items-center gap-1">
<div className="text-greyscale-4 text-sm">Sort by:</div>
<button
className="text-greyscale-6 w-20 text-sm"
onClick={() => setSort(sort === 'Newest' ? 'Best' : 'Newest')}
>
<Tooltip
text={sort === 'Best' ? 'Highest tips + bounties first.' : ''}
>
<Row className="items-center gap-1">
{sort}
<TriangleDownFillIcon className=" h-2 w-2" />
</Row>
</Tooltip>
</button>
</Row>
</Row>
)
if (contract.outcomeType === 'FREE_RESPONSE') {
return ( return (
<> <>
<ContractCommentInput className="mb-5" contract={contract} /> <ContractCommentInput className="mb-5" contract={contract} />
<SortRow {sortRow}
comments={comments} {answerResponse && (
contract={contract} <AnswerCommentInput
sort={sort}
onSortClick={() => setSort(sort === 'Newest' ? 'Best' : 'Newest')}
/>
{contract.outcomeType === 'FREE_RESPONSE' && (
<FreeResponseComments
contract={contract} contract={contract}
answerResponse={answerResponse} answerResponse={answerResponse}
onCancelAnswerResponse={onCancelAnswerResponse} onCancelAnswerResponse={onCancelAnswerResponse}
topLevelComments={topLevelComments}
commentsByParent={commentsByParent}
tips={tips}
/> />
)} )}
{contract.outcomeType !== 'FREE_RESPONSE' && {topLevelComments.map((parent) => {
topLevelComments.map((parent) => ( if (parent.answerOutcome === undefined) {
return (
<FeedCommentThread
key={parent.id}
contract={contract}
parentComment={parent}
threadComments={sortBy(
commentsByParent[parent.id] ?? [],
(c) => c.createdTime
)}
tips={tips}
/>
)
}
const answer = contract.answers.find(
(answer) => answer.id === parent.answerOutcome
)
if (answer === undefined) {
console.error('Could not find answer that matches ID')
return <></>
}
return (
<>
<Row className="gap-2">
<CommentsAnswer answer={answer} contract={contract} />
</Row>
<Row>
<div className="ml-1">
<Curve size={28} strokeWidth={1} color="#D8D8EB" />
</div>
<div className="w-full pt-1">
<FeedCommentThread
key={parent.id}
contract={contract}
parentComment={parent}
threadComments={sortBy(
commentsByParent[parent.id] ?? [],
(c) => c.createdTime
)}
tips={tips}
/>
</div>
</Row>
</>
)
})}
</>
)
} else {
return (
<>
<ContractCommentInput className="mb-5" contract={contract} />
{sortRow}
{topLevelComments.map((parent) => (
<FeedCommentThread <FeedCommentThread
key={parent.id} key={parent.id}
contract={contract} contract={contract}
@ -171,6 +241,7 @@ const CommentsTabContent = memo(function CommentsTabContent(props: {
))} ))}
</> </>
) )
}
}) })
const BetsTabContent = memo(function BetsTabContent(props: { const BetsTabContent = memo(function BetsTabContent(props: {
@ -239,33 +310,3 @@ const BetsTabContent = memo(function BetsTabContent(props: {
</> </>
) )
}) })
export function SortRow(props: {
comments: ContractComment[]
contract: Contract<AnyContractType>
sort: 'Best' | 'Newest'
onSortClick: () => void
}) {
const { comments, contract, sort, onSortClick } = props
if (comments.length <= 0) {
return <></>
}
return (
<Row className="mb-4 items-center justify-end gap-4">
<BountiedContractSmallBadge contract={contract} showAmount />
<Row className="items-center gap-1">
<div className="text-greyscale-4 text-sm">Sort by:</div>
<button className="text-greyscale-6 w-20 text-sm" onClick={onSortClick}>
<Tooltip
text={sort === 'Best' ? 'Highest tips + bounties first.' : ''}
>
<Row className="items-center gap-1">
{sort}
<TriangleDownFillIcon className=" h-2 w-2" />
</Row>
</Tooltip>
</button>
</Row>
</Row>
)
}

View File

@ -2,11 +2,11 @@ import { ShareIcon } from '@heroicons/react/outline'
import { Row } from '../layout/row' import { Row } from '../layout/row'
import { Contract } from 'web/lib/firebase/contracts' import { Contract } from 'web/lib/firebase/contracts'
import React, { useState } from 'react' import React, { useState } from 'react'
import { IconButton } from 'web/components/button' import { Button } from 'web/components/button'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { ShareModal } from './share-modal' import { ShareModal } from './share-modal'
import { FollowMarketButton } from 'web/components/follow-market-button' import { FollowMarketButton } from 'web/components/follow-market-button'
import { LikeItemButton } from 'web/components/contract/like-item-button' import { LikeMarketButton } from 'web/components/contract/like-market-button'
import { ContractInfoDialog } from 'web/components/contract/contract-info-dialog' import { ContractInfoDialog } from 'web/components/contract/contract-info-dialog'
import { Tooltip } from '../tooltip' import { Tooltip } from '../tooltip'
@ -16,14 +16,15 @@ export function ExtraContractActionsRow(props: { contract: Contract }) {
const [isShareOpen, setShareOpen] = useState(false) const [isShareOpen, setShareOpen] = useState(false)
return ( return (
<Row className="gap-1"> <Row>
<FollowMarketButton contract={contract} user={user} /> <FollowMarketButton contract={contract} user={user} />
<LikeItemButton item={contract} user={user} itemType={'contract'} /> <LikeMarketButton contract={contract} user={user} />
<Tooltip text="Share" placement="bottom" noTap noFade> <Tooltip text="Share" placement="bottom" noTap noFade>
<IconButton <Button
size="2xs" size="sm"
color="gray-white"
className={'flex'} className={'flex'}
onClick={() => setShareOpen(true)} onClick={() => setShareOpen(true)}
> >
@ -34,7 +35,7 @@ export function ExtraContractActionsRow(props: { contract: Contract }) {
contract={contract} contract={contract}
user={user} user={user}
/> />
</IconButton> </Button>
</Tooltip> </Tooltip>
<ContractInfoDialog contract={contract} user={user} /> <ContractInfoDialog contract={contract} user={user} />

View File

@ -1,71 +0,0 @@
import React, { useMemo, useState } from 'react'
import { User } from 'common/user'
import { useUserLikes } from 'web/hooks/use-likes'
import toast from 'react-hot-toast'
import { likeItem } from 'web/lib/firebase/likes'
import { LIKE_TIP_AMOUNT, TIP_UNDO_DURATION } from 'common/like'
import { firebaseLogin } from 'web/lib/firebase/users'
import { useItemTipTxns } from 'web/hooks/use-tip-txns'
import { sum } from 'lodash'
import { TipButton } from './tip-button'
import { Contract } from 'common/contract'
import { Post } from 'common/post'
import { TipToast } from '../tipper'
export function LikeItemButton(props: {
item: Contract | Post
user: User | null | undefined
itemType: string
}) {
const { item, user, itemType } = props
const tips = useItemTipTxns(item.id)
const totalTipped = useMemo(() => {
return sum(tips.map((tip) => tip.amount))
}, [tips])
const likes = useUserLikes(user?.id)
const [isLiking, setIsLiking] = useState(false)
const userLikedItemIds = likes
?.filter((l) => l.type === 'contract' || l.type === 'post')
.map((l) => l.id)
const onLike = async () => {
if (!user) return firebaseLogin()
setIsLiking(true)
const timeoutId = setTimeout(() => {
likeItem(user, item, itemType).catch(() => setIsLiking(false))
}, 3000)
toast.custom(
() => (
<TipToast
userName={item.creatorUsername}
onUndoClick={() => {
clearTimeout(timeoutId)
}}
/>
),
{ duration: TIP_UNDO_DURATION }
)
}
return (
<TipButton
onClick={onLike}
tipAmount={LIKE_TIP_AMOUNT}
totalTipped={totalTipped}
userTipped={
!!user &&
(isLiking ||
userLikedItemIds?.includes(item.id) ||
(!likes && !!item.likedByUserIds?.includes(user.id)))
}
disabled={item.creatorId === user?.id}
/>
)
}

View File

@ -0,0 +1,56 @@
import React, { useMemo, useState } from 'react'
import { Contract } from 'common/contract'
import { User } from 'common/user'
import { useUserLikes } from 'web/hooks/use-likes'
import toast from 'react-hot-toast'
import { formatMoney } from 'common/util/format'
import { likeContract } from 'web/lib/firebase/likes'
import { LIKE_TIP_AMOUNT } from 'common/like'
import { firebaseLogin } from 'web/lib/firebase/users'
import { useMarketTipTxns } from 'web/hooks/use-tip-txns'
import { sum } from 'lodash'
import { TipButton } from './tip-button'
export function LikeMarketButton(props: {
contract: Contract
user: User | null | undefined
}) {
const { contract, user } = props
const tips = useMarketTipTxns(contract.id)
const totalTipped = useMemo(() => {
return sum(tips.map((tip) => tip.amount))
}, [tips])
const likes = useUserLikes(user?.id)
const [isLiking, setIsLiking] = useState(false)
const userLikedContractIds = likes
?.filter((l) => l.type === 'contract')
.map((l) => l.id)
const onLike = async () => {
if (!user) return firebaseLogin()
setIsLiking(true)
likeContract(user, contract).catch(() => setIsLiking(false))
toast(`You tipped ${contract.creatorName} ${formatMoney(LIKE_TIP_AMOUNT)}!`)
}
return (
<TipButton
onClick={onLike}
tipAmount={LIKE_TIP_AMOUNT}
totalTipped={totalTipped}
userTipped={
!!user &&
(isLiking ||
userLikedContractIds?.includes(contract.id) ||
(!likes && !!contract.likedByUserIds?.includes(user.id)))
}
disabled={contract.creatorId === user?.id}
/>
)
}

View File

@ -7,14 +7,12 @@ import { formatPercent } from 'common/util/format'
import { Col } from '../layout/col' import { Col } from '../layout/col'
import { LoadingIndicator } from '../loading-indicator' import { LoadingIndicator } from '../loading-indicator'
import { ContractCardProbChange } from './contract-card' import { ContractCardProbChange } from './contract-card'
import { formatNumericProbability } from 'common/pseudo-numeric'
export function ProfitChangeTable(props: { export function ProfitChangeTable(props: {
contracts: CPMMBinaryContract[] contracts: CPMMBinaryContract[]
metrics: ContractMetrics[] metrics: ContractMetrics[]
maxRows?: number
}) { }) {
const { contracts, metrics, maxRows } = props const { contracts, metrics } = props
const contractProfit = metrics.map( const contractProfit = metrics.map(
(m) => [m.contractId, m.from?.day.profit ?? 0] as const (m) => [m.contractId, m.from?.day.profit ?? 0] as const
@ -28,7 +26,7 @@ export function ProfitChangeTable(props: {
positiveProfit.map(([contractId]) => positiveProfit.map(([contractId]) =>
contracts.find((c) => c.id === contractId) contracts.find((c) => c.id === contractId)
) )
).slice(0, maxRows) )
const negativeProfit = sortBy( const negativeProfit = sortBy(
contractProfit.filter(([, profit]) => profit < 0), contractProfit.filter(([, profit]) => profit < 0),
@ -38,7 +36,7 @@ export function ProfitChangeTable(props: {
negativeProfit.map(([contractId]) => negativeProfit.map(([contractId]) =>
contracts.find((c) => c.id === contractId) contracts.find((c) => c.id === contractId)
) )
).slice(0, maxRows) )
if (positive.length === 0 && negative.length === 0) if (positive.length === 0 && negative.length === 0)
return <div className="px-4 text-gray-500">None</div> return <div className="px-4 text-gray-500">None</div>
@ -120,7 +118,7 @@ export function ProbChangeTable(props: {
) )
} }
export function ProbOrNumericChange(props: { export function ProbChange(props: {
contract: CPMMContract contract: CPMMContract
className?: string className?: string
}) { }) {
@ -129,17 +127,13 @@ export function ProbOrNumericChange(props: {
prob, prob,
probChanges: { day: change }, probChanges: { day: change },
} = contract } = contract
const number =
contract.outcomeType === 'PSEUDO_NUMERIC'
? formatNumericProbability(prob, contract)
: null
const color = change >= 0 ? 'text-teal-500' : 'text-red-400' const color = change >= 0 ? 'text-teal-500' : 'text-red-400'
return ( return (
<Col className={clsx('flex flex-col items-end', className)}> <Col className={clsx('flex flex-col items-end', className)}>
<div className="mb-0.5 mr-0.5 text-2xl"> <div className="mb-0.5 mr-0.5 text-2xl">
{number ? number : formatPercent(Math.round(100 * prob) / 100)} {formatPercent(Math.round(100 * prob) / 100)}
</div> </div>
<div className={clsx('text-base', color)}> <div className={clsx('text-base', color)}>
{(change > 0 ? '+' : '') + (change * 100).toFixed(0) + '%'} {(change > 0 ? '+' : '') + (change * 100).toFixed(0) + '%'}

View File

@ -1,10 +1,10 @@
import clsx from 'clsx' import clsx from 'clsx'
import { HeartIcon } from '@heroicons/react/outline'
import { Button } from 'web/components/button'
import { formatMoney, shortFormatNumber } from 'common/util/format' import { formatMoney, shortFormatNumber } from 'common/util/format'
import { Col } from 'web/components/layout/col' import { Col } from 'web/components/layout/col'
import { Tooltip } from '../tooltip' import { Tooltip } from '../tooltip'
import TipJar from 'web/public/custom-components/tipJar'
import { useState } from 'react'
import Coin from 'web/public/custom-components/coin'
export function TipButton(props: { export function TipButton(props: {
tipAmount: number tipAmount: number
@ -14,84 +14,55 @@ export function TipButton(props: {
isCompact?: boolean isCompact?: boolean
disabled?: boolean disabled?: boolean
}) { }) {
const { tipAmount, totalTipped, userTipped, onClick, disabled } = props const { tipAmount, totalTipped, userTipped, isCompact, onClick, disabled } =
props
const tipDisplay = shortFormatNumber(Math.ceil(totalTipped / 10)) const tipDisplay = shortFormatNumber(Math.ceil(totalTipped / 10))
const [hover, setHover] = useState(false)
return ( return (
<Tooltip <Tooltip
text={ text={
disabled disabled
? `Total tips ${formatMoney(totalTipped)}` ? `Tips (${formatMoney(totalTipped)})`
: `Tip ${formatMoney(tipAmount)}` : `Tip ${formatMoney(tipAmount)}`
} }
placement="bottom" placement="bottom"
noTap noTap
noFade noFade
> >
<button <Button
size={'sm'}
className={clsx(
'max-w-xs self-center',
isCompact && 'px-0 py-0',
disabled && 'hover:bg-inherit'
)}
color={'gray-white'}
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}
className={clsx(
'px-2 py-1 text-xs', //2xs button
'text-greyscale-5 transition-transform disabled:cursor-not-allowed',
!disabled ? 'hover:text-greyscale-6' : ''
)}
onMouseOver={() => {
if (!disabled) {
setHover(true)
}
}}
onMouseLeave={() => setHover(false)}
> >
<Col className={clsx('relative')}> <Col className={'relative items-center sm:flex-row'}>
<HeartIcon
className={clsx(
'h-5 w-5',
totalTipped > 0 ? 'mr-2' : '',
userTipped ? 'fill-teal-500 text-teal-500' : ''
)}
/>
{totalTipped > 0 && (
<div <div
className={clsx( className={clsx(
'absolute transition-all', 'bg-greyscale-5 absolute ml-3.5 mt-2 h-4 w-4 rounded-full align-middle text-white sm:mt-3 sm:h-5 sm:w-5 sm:px-1',
hover ? 'left-[6px] -top-[9px]' : 'left-[8px] -top-[10px]' tipDisplay.length > 2
? 'text-[0.4rem] sm:text-[0.5rem]'
: 'sm:text-2xs text-[0.5rem]'
)} )}
> >
<Coin {tipDisplay}
size={10}
color={
hover && !userTipped
? '#66667C'
: userTipped
? '#4f46e5'
: '#9191a7'
}
strokeWidth={2}
/>
</div> </div>
<TipJar
size={18}
color={
hover && !disabled && !userTipped
? '#66667C'
: userTipped
? '#4f46e5'
: '#9191a7'
}
/>
<div
className={clsx(
userTipped && 'text-indigo-600',
' absolute top-[2px] text-[0.5rem]',
tipDisplay.length === 1
? 'left-[7px]'
: tipDisplay.length === 2
? 'left-[4.5px]'
: tipDisplay.length > 2
? 'left-[4px] top-[2.5px] text-[0.35rem]'
: ''
)} )}
>
{totalTipped > 0 ? tipDisplay : ''}
</div>
</Col> </Col>
</button> </Button>
</Tooltip> </Tooltip>
) )
} }

View File

@ -22,6 +22,7 @@ export function CreatePost(props: { group?: Group }) {
const { editor, upload } = useTextEditor({ const { editor, upload } = useTextEditor({
key: `post ${group?.id || ''}`, key: `post ${group?.id || ''}`,
disabled: isSubmitting,
}) })
const isValid = const isValid =
@ -55,8 +56,8 @@ export function CreatePost(props: { group?: Group }) {
<div className="rounded-lg px-6 py-4 sm:py-0"> <div className="rounded-lg px-6 py-4 sm:py-0">
<Title className="!mt-0" text="Create a post" /> <Title className="!mt-0" text="Create a post" />
<form> <form>
<div className="flex w-full flex-col"> <div className="form-control w-full">
<label className="px-1 py-2"> <label className="label">
<span className="mb-1"> <span className="mb-1">
Title<span className={'text-red-700'}> *</span> Title<span className={'text-red-700'}> *</span>
</span> </span>
@ -69,7 +70,7 @@ export function CreatePost(props: { group?: Group }) {
onChange={(e) => setTitle(e.target.value || '')} onChange={(e) => setTitle(e.target.value || '')}
/> />
<Spacer h={6} /> <Spacer h={6} />
<label className="px-1 py-2"> <label className="label">
<span className="mb-1"> <span className="mb-1">
Subtitle<span className={'text-red-700'}> *</span> Subtitle<span className={'text-red-700'}> *</span>
</span> </span>
@ -82,7 +83,7 @@ export function CreatePost(props: { group?: Group }) {
onChange={(e) => setSubtitle(e.target.value || '')} onChange={(e) => setSubtitle(e.target.value || '')}
/> />
<Spacer h={6} /> <Spacer h={6} />
<label className="px-1 py-2"> <label className="label">
<span className="mb-1"> <span className="mb-1">
Content<span className={'text-red-700'}> *</span> Content<span className={'text-red-700'}> *</span>
</span> </span>

View File

@ -22,7 +22,6 @@ import { linkClass } from './site-link'
import { DisplayMention } from './editor/mention' import { DisplayMention } from './editor/mention'
import { DisplayContractMention } from './editor/contract-mention' import { DisplayContractMention } from './editor/contract-mention'
import GridComponent from './editor/tiptap-grid-cards' import GridComponent from './editor/tiptap-grid-cards'
import StaticReactEmbedComponent from './editor/tiptap-static-react-embed'
import Iframe from 'common/util/tiptap-iframe' import Iframe from 'common/util/tiptap-iframe'
import TiptapTweet from './editor/tiptap-tweet' import TiptapTweet from './editor/tiptap-tweet'
@ -53,7 +52,7 @@ import { debounce } from 'lodash'
const DisplayImage = Image.configure({ const DisplayImage = Image.configure({
HTMLAttributes: { HTMLAttributes: {
class: 'max-h-60 hover:max-h-[120rem] transition-all', class: 'max-h-60',
}, },
}) })
@ -82,7 +81,6 @@ export const editorExtensions = (simple = false): Extensions => [
DisplayMention, DisplayMention,
DisplayContractMention, DisplayContractMention,
GridComponent, GridComponent,
StaticReactEmbedComponent,
Iframe, Iframe,
TiptapTweet, TiptapTweet,
TiptapSpoiler.configure({ TiptapSpoiler.configure({
@ -92,17 +90,18 @@ export const editorExtensions = (simple = false): Extensions => [
const proseClass = clsx( const proseClass = clsx(
'prose prose-p:my-0 prose-ul:my-0 prose-ol:my-0 prose-li:my-0 prose-blockquote:not-italic max-w-none prose-quoteless leading-relaxed', 'prose prose-p:my-0 prose-ul:my-0 prose-ol:my-0 prose-li:my-0 prose-blockquote:not-italic max-w-none prose-quoteless leading-relaxed',
'font-light prose-a:font-light prose-blockquote:font-light prose-sm' 'font-light prose-a:font-light prose-blockquote:font-light'
) )
export function useTextEditor(props: { export function useTextEditor(props: {
placeholder?: string placeholder?: string
max?: number max?: number
defaultValue?: Content defaultValue?: Content
disabled?: boolean
simple?: boolean simple?: boolean
key?: string // unique key for autosave. If set, plz call `clearContent(true)` on submit to clear autosave key?: string // unique key for autosave. If set, plz call `clearContent(true)` on submit to clear autosave
}) { }) {
const { placeholder, max, defaultValue, simple, key } = props const { placeholder, max, defaultValue, disabled, simple, key } = props
const [content, saveContent] = usePersistentState<JSONContent | undefined>( const [content, saveContent] = usePersistentState<JSONContent | undefined>(
undefined, undefined,
@ -170,6 +169,10 @@ export function useTextEditor(props: {
}, },
}) })
useEffect(() => {
editor?.setEditable(!disabled)
}, [editor, disabled])
return { editor, upload } return { editor, upload }
} }
@ -260,8 +263,7 @@ export function TextEditor(props: {
<> <>
{/* hide placeholder when focused */} {/* hide placeholder when focused */}
<div className="relative w-full [&:focus-within_p.is-empty]:before:content-none"> <div className="relative w-full [&:focus-within_p.is-empty]:before:content-none">
{/* matches input styling */} <div className="rounded-lg border border-gray-300 bg-white shadow-sm focus-within:border-indigo-500 focus-within:ring-1 focus-within:ring-indigo-500">
<div className="rounded-lg border border-gray-300 bg-white shadow-sm transition-colors focus-within:border-indigo-500 focus-within:ring-1 focus-within:ring-indigo-500">
<FloatingMenu editor={editor} /> <FloatingMenu editor={editor} />
<EditorContent editor={editor} /> <EditorContent editor={editor} />
{/* Toolbar, with buttons for images and embeds */} {/* Toolbar, with buttons for images and embeds */}
@ -365,7 +367,6 @@ export function RichContent(props: {
DisplayMention, DisplayMention,
DisplayContractMention, DisplayContractMention,
GridComponent, GridComponent,
StaticReactEmbedComponent,
Iframe, Iframe,
TiptapTweet, TiptapTweet,
TiptapSpoiler.configure({ TiptapSpoiler.configure({

View File

@ -14,7 +14,6 @@ const beginsWith = (text: string, query: string) =>
export const contractMentionSuggestion: Suggestion = { export const contractMentionSuggestion: Suggestion = {
char: '%', char: '%',
allowSpaces: true, allowSpaces: true,
allowedPrefixes: [' '],
pluginKey: new PluginKey('contract-mention'), pluginKey: new PluginKey('contract-mention'),
items: async ({ query }) => items: async ({ query }) =>
orderBy( orderBy(

View File

@ -120,10 +120,7 @@ function DreamTab(props: {
<div className="text-sm">This may take ~10 seconds...</div> <div className="text-sm">This may take ~10 seconds...</div>
)} )}
{/* TODO: Allow the user to choose their own modifiers */} {/* TODO: Allow the user to choose their own modifiers */}
<div className="pt-2 text-sm text-gray-400"> <div className="pt-2 text-sm text-gray-400">Modifiers: {MODIFIERS}</div>
Commission a custom image using AI.
</div>
<div className="pt-2 text-xs text-gray-400">Modifiers: {MODIFIERS}</div>
{/* Show the current imageUrl */} {/* Show the current imageUrl */}
{/* TODO: Keep the other generated images, so the user can play with different attempts. */} {/* TODO: Keep the other generated images, so the user can play with different attempts. */}

View File

@ -14,7 +14,6 @@ const beginsWith = (text: string, query: string) =>
// copied from https://tiptap.dev/api/nodes/mention#usage // copied from https://tiptap.dev/api/nodes/mention#usage
export const mentionSuggestion: Suggestion = { export const mentionSuggestion: Suggestion = {
allowedPrefixes: [' '],
items: async ({ query }) => items: async ({ query }) =>
orderBy( orderBy(
(await getCachedUsers()).filter((u) => (await getCachedUsers()).filter((u) =>

View File

@ -1,44 +0,0 @@
import { mergeAttributes, Node } from '@tiptap/core'
import React from 'react'
import { NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react'
import { StaticReactEmbed } from '../static-react-embed'
export default Node.create({
name: 'staticReactEmbedComponent',
group: 'block',
atom: true,
addAttributes() {
return {
embedName: '',
}
},
parseHTML() {
return [
{
tag: 'static-react-embed-component',
},
]
},
renderHTML({ HTMLAttributes }) {
return ['static-react-embed-component', mergeAttributes(HTMLAttributes)]
},
addNodeView() {
return ReactNodeViewRenderer(StaticReactEmbedComponent)
},
})
export function StaticReactEmbedComponent(props: any) {
const embedName = props.node.attrs.embedName
return (
<NodeViewWrapper className="static-react-embed-component">
<StaticReactEmbed embedName={embedName} />
</NodeViewWrapper>
)
}

View File

@ -7,7 +7,7 @@ export const ExpandingInput = (props: Parameters<typeof Textarea>[0]) => {
return ( return (
<Textarea <Textarea
className={clsx( className={clsx(
'resize-none rounded-md border border-gray-300 bg-white px-4 text-[16px] leading-loose shadow-sm transition-colors focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 disabled:cursor-not-allowed disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 md:text-[14px]', 'textarea textarea-bordered resize-none text-[16px] md:text-[14px]',
className className
)} )}
{...rest} {...rest}

View File

@ -1,60 +1,32 @@
import { Answer } from 'common/answer' import { Answer } from 'common/answer'
import { import { Contract } from 'common/contract'
Contract, import React, { useEffect, useRef } from 'react'
FreeResponseContract,
MultipleChoiceContract,
} from 'common/contract'
import React, { useEffect, useRef, useState } from 'react'
import { Col } from 'web/components/layout/col' import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { Avatar } from 'web/components/avatar'
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time' import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { UserLink } from 'web/components/user-link' import { UserLink } from 'web/components/user-link'
import { FeedCommentThread } from './feed-comments'
import { AnswerCommentInput } from '../comment-input'
import { ContractComment } from 'common/comment'
import { Dictionary, sortBy } from 'lodash'
import { getAnswerColor } from '../answers/answers-panel'
import Curve from 'web/public/custom-components/curve'
import { CommentTipMap } from 'web/hooks/use-tip-txns'
import { useChartAnswers } from '../charts/contract/choice'
export function CommentsAnswer(props: { export function CommentsAnswer(props: { answer: Answer; contract: Contract }) {
answer: Answer const { answer, contract } = props
contract: Contract const { username, avatarUrl, name, text } = answer
color: string
}) {
const { answer, contract, color } = props
const { username, name, text } = answer
const answerElementId = `answer-${answer.id}` const answerElementId = `answer-${answer.id}`
const router = useRouter()
const { isReady, asPath } = useRouter() const highlighted = router.asPath.endsWith(`#${answerElementId}`)
const [highlighted, setHighlighted] = useState(false)
const answerRef = useRef<HTMLDivElement>(null) const answerRef = useRef<HTMLDivElement>(null)
useEffect(() => { useEffect(() => {
if (isReady && asPath.endsWith(`#${answerElementId}`)) { if (highlighted && answerRef.current != null) {
setHighlighted(true)
}
}, [isReady, asPath, answerElementId])
useEffect(() => {
if (highlighted && answerRef.current) {
answerRef.current.scrollIntoView(true) answerRef.current.scrollIntoView(true)
} }
}, [highlighted]) }, [highlighted])
return ( return (
<Row> <Col className="bg-greyscale-2 w-fit gap-1 rounded-t-xl rounded-bl-xl py-2 px-4">
<div
className="w-2"
style={{
background: color ? color : '#B1B1C7',
}}
/>
<Col className="w-fit bg-gray-100 py-1 pl-2 pr-2">
<Row className="gap-2"> <Row className="gap-2">
<div className="text-greyscale-4 text-xs"> <Avatar username={username} avatarUrl={avatarUrl} size="xxs" />
<div className="text-greyscale-6 text-xs">
<UserLink username={username} name={name} /> answered <UserLink username={username} name={name} /> answered
<CopyLinkDateTimeComponent <CopyLinkDateTimeComponent
prefix={contract.creatorUsername} prefix={contract.creatorUsername}
@ -66,89 +38,5 @@ export function CommentsAnswer(props: {
</Row> </Row>
<div className="text-sm">{text}</div> <div className="text-sm">{text}</div>
</Col> </Col>
</Row>
)
}
export function FreeResponseComments(props: {
contract: FreeResponseContract | MultipleChoiceContract
answerResponse: Answer | undefined
onCancelAnswerResponse?: () => void
topLevelComments: ContractComment[]
commentsByParent: Dictionary<[ContractComment, ...ContractComment[]]>
tips: CommentTipMap
}) {
const {
contract,
answerResponse,
onCancelAnswerResponse,
topLevelComments,
commentsByParent,
tips,
} = props
const answersArray = useChartAnswers(contract).map((answer) => answer.text)
return (
<>
{answerResponse && (
<AnswerCommentInput
contract={contract}
answerResponse={answerResponse}
onCancelAnswerResponse={onCancelAnswerResponse}
answersArray={answersArray}
/>
)}
{topLevelComments.map((parent) => {
if (parent.answerOutcome === undefined) {
return (
<FeedCommentThread
key={parent.id}
contract={contract}
parentComment={parent}
threadComments={sortBy(
commentsByParent[parent.id] ?? [],
(c) => c.createdTime
)}
tips={tips}
/>
)
}
const answer = contract.answers.find(
(answer) => answer.id === parent.answerOutcome
)
if (answer === undefined) {
console.error('Could not find answer that matches ID')
return <></>
}
const color = getAnswerColor(answer, answersArray)
return (
<>
<Row className="relative">
<div className="absolute -bottom-1 left-1.5">
<Curve size={32} strokeWidth={1} color="#D8D8EB" />
</div>
<div className="ml-[38px]">
<CommentsAnswer
answer={answer}
contract={contract}
color={color}
/>
</div>
</Row>
<div className="w-full pt-1">
<FeedCommentThread
key={parent.id}
contract={contract}
parentComment={parent}
threadComments={sortBy(
commentsByParent[parent.id] ?? [],
(c) => c.createdTime
)}
tips={tips}
/>
</div>
</>
)
})}
</>
) )
} }

View File

@ -24,7 +24,7 @@ import { UserLink } from 'web/components/user-link'
import { CommentInput } from '../comment-input' import { CommentInput } from '../comment-input'
import { AwardBountyButton } from 'web/components/award-bounty-button' import { AwardBountyButton } from 'web/components/award-bounty-button'
import { ReplyIcon } from '@heroicons/react/solid' import { ReplyIcon } from '@heroicons/react/solid'
import { IconButton } from '../button' import { Button } from '../button'
import { ReplyToggle } from '../comments/reply-toggle' import { ReplyToggle } from '../comments/reply-toggle'
export type ReplyTo = { id: string; username: string } export type ReplyTo = { id: string; username: string }
@ -37,7 +37,7 @@ export function FeedCommentThread(props: {
}) { }) {
const { contract, threadComments, tips, parentComment } = props const { contract, threadComments, tips, parentComment } = props
const [replyTo, setReplyTo] = useState<ReplyTo>() const [replyTo, setReplyTo] = useState<ReplyTo>()
const [seeReplies, setSeeReplies] = useState(true) const [seeReplies, setSeeReplies] = useState(false)
const user = useUser() const user = useUser()
const onSubmitComment = useEvent(() => setReplyTo(undefined)) const onSubmitComment = useEvent(() => setReplyTo(undefined))
@ -154,43 +154,33 @@ export function ParentFeedComment(props: {
numComments={numComments} numComments={numComments}
onClick={onSeeReplyClick} onClick={onSeeReplyClick}
/> />
<CommentActions <Row className="grow justify-end gap-2">
onReplyClick={onReplyClick}
comment={comment}
showTip={showTip}
myTip={myTip}
totalTip={totalTip}
contract={contract}
/>
</Row>
</Col>
</Row>
)
}
export function CommentActions(props: {
onReplyClick?: (comment: ContractComment) => void
comment: ContractComment
showTip?: boolean
myTip?: number
totalTip?: number
contract: Contract
}) {
const { onReplyClick, comment, showTip, myTip, totalTip, contract } = props
return (
<Row className="grow justify-end">
{onReplyClick && ( {onReplyClick && (
<IconButton size={'xs'} onClick={() => onReplyClick(comment)}> <Button
size={'sm'}
className={clsx(
'hover:bg-greyscale-2 mt-0 mb-1 max-w-xs px-0 py-0'
)}
color={'gray-white'}
onClick={() => onReplyClick(comment)}
>
<ReplyIcon className="h-5 w-5" /> <ReplyIcon className="h-5 w-5" />
</IconButton> </Button>
)} )}
{showTip && ( {showTip && (
<Tipper comment={comment} myTip={myTip ?? 0} totalTip={totalTip ?? 0} /> <Tipper
comment={comment}
myTip={myTip ?? 0}
totalTip={totalTip ?? 0}
/>
)} )}
{(contract.openCommentBounties ?? 0) > 0 && ( {(contract.openCommentBounties ?? 0) > 0 && (
<AwardBountyButton comment={comment} contract={contract} /> <AwardBountyButton comment={comment} contract={contract} />
)} )}
</Row> </Row>
</Row>
</Col>
</Row>
) )
} }
@ -243,14 +233,30 @@ export const FeedComment = memo(function FeedComment(props: {
content={content || text} content={content || text}
smallImage smallImage
/> />
<CommentActions <Row className="grow justify-end gap-2">
onReplyClick={onReplyClick} {onReplyClick && (
<Button
size={'sm'}
className={clsx(
'hover:bg-greyscale-2 mt-0 mb-1 max-w-xs px-0 py-0'
)}
color={'gray-white'}
onClick={() => onReplyClick(comment)}
>
<ReplyIcon className="h-5 w-5" />
</Button>
)}
{showTip && (
<Tipper
comment={comment} comment={comment}
showTip={showTip} myTip={myTip ?? 0}
myTip={myTip} totalTip={totalTip ?? 0}
totalTip={totalTip}
contract={contract}
/> />
)}
{(contract.openCommentBounties ?? 0) > 0 && (
<AwardBountyButton comment={comment} contract={contract} />
)}
</Row>
</Col> </Col>
</Row> </Row>
) )

View File

@ -1,4 +1,4 @@
import { IconButton } from 'web/components/button' import { Button } from 'web/components/button'
import { import {
Contract, Contract,
followContract, followContract,
@ -33,8 +33,9 @@ export const FollowMarketButton = (props: {
noTap noTap
noFade noFade
> >
<IconButton <Button
size="2xs" size={'sm'}
color={'gray-white'}
onClick={async () => { onClick={async () => {
if (!user) return firebaseLogin() if (!user) return firebaseLogin()
if (followers?.includes(user.id)) { if (followers?.includes(user.id)) {
@ -64,12 +65,18 @@ export const FollowMarketButton = (props: {
> >
{watching ? ( {watching ? (
<Col className={'items-center gap-x-2 sm:flex-row'}> <Col className={'items-center gap-x-2 sm:flex-row'}>
<EyeOffIcon className={clsx('h-5 w-5')} aria-hidden="true" /> <EyeOffIcon
className={clsx('h-5 w-5 sm:h-6 sm:w-6')}
aria-hidden="true"
/>
{/* Unwatch */} {/* Unwatch */}
</Col> </Col>
) : ( ) : (
<Col className={'items-center gap-x-2 sm:flex-row'}> <Col className={'items-center gap-x-2 sm:flex-row'}>
<EyeIcon className={clsx('h-5 w-5')} aria-hidden="true" /> <EyeIcon
className={clsx('h-5 w-5 sm:h-6 sm:w-6')}
aria-hidden="true"
/>
{/* Watch */} {/* Watch */}
</Col> </Col>
)} )}
@ -80,7 +87,7 @@ export const FollowMarketButton = (props: {
followers?.includes(user?.id ?? 'nope') ? 'watched' : 'unwatched' followers?.includes(user?.id ?? 'nope') ? 'watched' : 'unwatched'
} a question!`} } a question!`}
/> />
</IconButton> </Button>
</Tooltip> </Tooltip>
) )
} }

View File

@ -103,7 +103,7 @@ export function CreateGroupButton(props: {
</Col> </Col>
{errorText && <div className={'text-error'}>{errorText}</div>} {errorText && <div className={'text-error'}>{errorText}</div>}
<div className="flex w-full flex-col"> <div className="form-control w-full">
<label className="mb-2 ml-1 mt-0">Group name</label> <label className="mb-2 ml-1 mt-0">Group name</label>
<Input <Input
placeholder={'Your group name'} placeholder={'Your group name'}

View File

@ -51,8 +51,8 @@ export function EditGroupButton(props: { group: Group; className?: string }) {
</Button> </Button>
<Modal open={open} setOpen={updateOpen}> <Modal open={open} setOpen={updateOpen}>
<div className="h-full rounded-md bg-white p-8"> <div className="h-full rounded-md bg-white p-8">
<div className="flex w-full flex-col"> <div className="form-control w-full">
<label className="px-1 py-2"> <label className="label">
<span className="mb-1">Group name</span> <span className="mb-1">Group name</span>
</label> </label>
@ -66,8 +66,8 @@ export function EditGroupButton(props: { group: Group; className?: string }) {
<Spacer h={4} /> <Spacer h={4} />
<div className="flex w-full flex-col"> <div className="form-control w-full">
<label className="px-1 py-2"> <label className="label">
<span className="mb-0">Add members</span> <span className="mb-0">Add members</span>
</label> </label>
<FilterSelectUsers <FilterSelectUsers
@ -77,7 +77,7 @@ export function EditGroupButton(props: { group: Group; className?: string }) {
/> />
</div> </div>
<div className="flex"> <div className="modal-action">
<Button <Button
color="red" color="red"
size="xs" size="xs"

View File

@ -33,9 +33,11 @@ export function GroupOverviewPost(props: {
function RichEditGroupAboutPost(props: { group: Group; post: Post | null }) { function RichEditGroupAboutPost(props: { group: Group; post: Post | null }) {
const { group, post } = props const { group, post } = props
const [editing, setEditing] = useState(false) const [editing, setEditing] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
const { editor, upload } = useTextEditor({ const { editor, upload } = useTextEditor({
defaultValue: post?.content, defaultValue: post?.content,
disabled: isSubmitting,
}) })
async function savePost() { async function savePost() {
@ -74,8 +76,10 @@ function RichEditGroupAboutPost(props: { group: Group; post: Post | null }) {
<Row className="gap-2"> <Row className="gap-2">
<Button <Button
onClick={async () => { onClick={async () => {
setIsSubmitting(true)
await savePost() await savePost()
setEditing(false) setEditing(false)
setIsSubmitting(false)
}} }}
> >
Save Save

View File

@ -377,7 +377,7 @@ export function GroupAbout(props: {
<div className={'inline-flex items-center'}> <div className={'inline-flex items-center'}>
<div className="mr-1 text-gray-500">Created by</div> <div className="mr-1 text-gray-500">Created by</div>
<UserLink <UserLink
className="text-gray-700" className="text-neutral"
name={creator.name} name={creator.name}
username={creator.username} username={creator.username}
/> />

View File

@ -72,7 +72,7 @@ export function GroupSelector(props: {
) )
} }
return ( return (
<div className="flex flex-col items-start"> <div className="form-control items-start">
<Combobox <Combobox
as="div" as="div"
value={selectedGroup} value={selectedGroup}
@ -83,7 +83,7 @@ export function GroupSelector(props: {
{() => ( {() => (
<> <>
{showLabel && ( {showLabel && (
<Combobox.Label className="justify-start gap-2 px-1 py-2 text-base"> <Combobox.Label className="label justify-start gap-2 text-base">
Add to Group Add to Group
<InfoTooltip text="Question will be displayed alongside the other questions in the group." /> <InfoTooltip text="Question will be displayed alongside the other questions in the group." />
</Combobox.Label> </Combobox.Label>

View File

@ -2,21 +2,21 @@ import clsx from 'clsx'
import React from 'react' import React from 'react'
/** Text input. Wraps html `<input>` */ /** Text input. Wraps html `<input>` */
export const Input = ( export const Input = (props: JSX.IntrinsicElements['input']) => {
props: { error?: boolean } & JSX.IntrinsicElements['input'] const { className, ...rest } = props
) => {
const { error, className, ...rest } = props
return ( return (
<input <input
className={clsx( className={clsx('input input-bordered text-base md:text-sm', className)}
'h-12 rounded-md border bg-white px-4 shadow-sm transition-colors invalid:border-red-600 invalid:text-red-900 invalid:placeholder-red-300 focus:outline-none disabled:cursor-not-allowed disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 md:text-sm',
error
? 'border-red-300 text-red-900 placeholder-red-300 focus:border-red-600 focus:ring-red-500' // matches invalid: styles
: 'placeholder-greyscale-4 border-gray-300 focus:border-indigo-500 focus:ring-indigo-500',
className
)}
{...rest} {...rest}
/> />
) )
} }
/*
TODO: replace daisyui style with our own. For reference:
james: text-lg placeholder:text-gray-400
inga: placeholder:text-greyscale-4 border-greyscale-2 rounded-md
austin: border-gray-300 text-gray-400 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm
*/

View File

@ -2,7 +2,6 @@ import clsx from 'clsx'
import { Avatar } from './avatar' import { Avatar } from './avatar'
import { Row } from './layout/row' import { Row } from './layout/row'
import { SiteLink } from './site-link' import { SiteLink } from './site-link'
import { Table } from './table'
import { Title } from './title' import { Title } from './title'
interface LeaderboardEntry { interface LeaderboardEntry {
@ -32,9 +31,9 @@ export function Leaderboard<T extends LeaderboardEntry>(props: {
<div className="ml-2 text-gray-500">None yet</div> <div className="ml-2 text-gray-500">None yet</div>
) : ( ) : (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<Table> <table className="table-zebra table-compact table w-full text-gray-500">
<thead> <thead>
<tr> <tr className="p-2">
<th>#</th> <th>#</th>
<th>Name</th> <th>Name</th>
{columns.map((column) => ( {columns.map((column) => (
@ -60,7 +59,7 @@ export function Leaderboard<T extends LeaderboardEntry>(props: {
</tr> </tr>
))} ))}
</tbody> </tbody>
</Table> </table>
</div> </div>
)} )}
</div> </div>

View File

@ -14,7 +14,6 @@ import { Row } from './layout/row'
import { LoadingIndicator } from './loading-indicator' import { LoadingIndicator } from './loading-indicator'
import { BinaryOutcomeLabel, PseudoNumericOutcomeLabel } from './outcome-label' import { BinaryOutcomeLabel, PseudoNumericOutcomeLabel } from './outcome-label'
import { Subtitle } from './subtitle' import { Subtitle } from './subtitle'
import { Table } from './table'
import { Title } from './title' import { Title } from './title'
export function LimitBets(props: { export function LimitBets(props: {
@ -75,7 +74,7 @@ export function LimitOrderTable(props: {
const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC' const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC'
return ( return (
<Table className="rounded"> <table className="table-compact table w-full rounded text-gray-500">
<thead> <thead>
<tr> <tr>
{!isYou && <th></th>} {!isYou && <th></th>}
@ -90,7 +89,7 @@ export function LimitOrderTable(props: {
<LimitBet key={bet.id} bet={bet} contract={contract} isYou={isYou} /> <LimitBet key={bet.id} bet={bet} contract={contract} isYou={isYou} />
))} ))}
</tbody> </tbody>
</Table> </table>
) )
} }

View File

@ -9,9 +9,9 @@ import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { Claim, Manalink } from 'common/manalink' import { Claim, Manalink } from 'common/manalink'
import { ShareIconButton } from './share-icon-button' import { ShareIconButton } from './share-icon-button'
import { contractDetailsButtonClassName } from './contract/contract-info-dialog'
import { useUserById } from 'web/hooks/use-user' import { useUserById } from 'web/hooks/use-user'
import getManalinkUrl from 'web/get-manalink-url' import getManalinkUrl from 'web/get-manalink-url'
import { IconButton } from './button'
export type ManalinkInfo = { export type ManalinkInfo = {
expiresTime: number | null expiresTime: number | null
@ -123,7 +123,7 @@ export function ManalinkCardFromView(props: {
src="/logo-white.svg" src="/logo-white.svg"
/> />
</Col> </Col>
<Row className="relative w-full rounded-b-lg bg-white px-4 py-2 align-middle text-lg"> <Row className="relative w-full gap-1 rounded-b-lg bg-white px-4 py-2 text-lg">
<div <div
className={clsx( className={clsx(
'my-auto mb-1 w-full', 'my-auto mb-1 w-full',
@ -133,23 +133,32 @@ export function ManalinkCardFromView(props: {
{formatMoney(amount)} {formatMoney(amount)}
</div> </div>
<IconButton size="2xs" onClick={() => (window.location.href = qrUrl)}> <button
onClick={() => (window.location.href = qrUrl)}
className={clsx(contractDetailsButtonClassName)}
>
<QrcodeIcon className="h-6 w-6" /> <QrcodeIcon className="h-6 w-6" />
</IconButton> </button>
<ShareIconButton <ShareIconButton
toastClassName={'-left-48 min-w-[250%]'} toastClassName={'-left-48 min-w-[250%]'}
buttonClassName={'transition-colors'}
onCopyButtonClassName={
'bg-gray-200 text-gray-600 transition-none hover:bg-gray-200 hover:text-gray-600'
}
copyPayload={getManalinkUrl(link.slug)} copyPayload={getManalinkUrl(link.slug)}
/> />
<IconButton <button
size="xs"
onClick={() => setShowDetails(!showDetails)} onClick={() => setShowDetails(!showDetails)}
className={clsx( className={clsx(
showDetails ? ' text-indigo-600 hover:text-indigo-700' : '' contractDetailsButtonClassName,
showDetails
? 'bg-gray-200 text-gray-600 hover:bg-gray-200 hover:text-gray-600'
: ''
)} )}
> >
<DotsHorizontalIcon className="h-5 w-5" /> <DotsHorizontalIcon className="h-[24px] w-5" />
</IconButton> </button>
</Row> </Row>
</Col> </Col>
<div className="mt-2 mb-4 text-xs text-gray-500 md:text-sm"> <div className="mt-2 mb-4 text-xs text-gray-500 md:text-sm">

View File

@ -14,7 +14,6 @@ import { DuplicateIcon } from '@heroicons/react/outline'
import { QRCode } from '../qr-code' import { QRCode } from '../qr-code'
import { Input } from '../input' import { Input } from '../input'
import { ExpandingInput } from '../expanding-input' import { ExpandingInput } from '../expanding-input'
import { Select } from '../select'
export function CreateLinksButton(props: { export function CreateLinksButton(props: {
user: User user: User
@ -116,8 +115,8 @@ function CreateManalinkForm(props: {
> >
<Title className="!my-0" text="Create a Manalink" /> <Title className="!my-0" text="Create a Manalink" />
<div className="flex flex-col flex-wrap gap-x-5 gap-y-2"> <div className="flex flex-col flex-wrap gap-x-5 gap-y-2">
<div className="flex flex-auto flex-col"> <div className="form-control flex-auto">
<label className="px-1 py-2">Amount</label> <label className="label">Amount</label>
<div className="relative"> <div className="relative">
<span className="absolute mx-3 mt-3.5 text-sm text-gray-400"> <span className="absolute mx-3 mt-3.5 text-sm text-gray-400">
M$ M$
@ -136,8 +135,8 @@ function CreateManalinkForm(props: {
</div> </div>
</div> </div>
<div className="flex flex-col gap-2 md:flex-row"> <div className="flex flex-col gap-2 md:flex-row">
<div className="flex w-full flex-col md:w-1/2"> <div className="form-control w-full md:w-1/2">
<label className="px-1 py-2">Uses</label> <label className="label">Uses</label>
<Input <Input
type="number" type="number"
min="1" min="1"
@ -149,9 +148,10 @@ function CreateManalinkForm(props: {
} }
/> />
</div> </div>
<div className="flex w-full flex-col md:w-1/2"> <div className="form-control w-full md:w-1/2">
<label className="px-1 py-2">Expires in</label> <label className="label">Expires in</label>
<Select <select
className="!select !select-bordered"
value={expiresIn} value={expiresIn}
defaultValue={defaultExpire} defaultValue={defaultExpire}
onChange={(e) => { onChange={(e) => {
@ -160,11 +160,11 @@ function CreateManalinkForm(props: {
}} }}
> >
{expireOptions} {expireOptions}
</Select> </select>
</div> </div>
</div> </div>
<div className="flex w-full flex-col"> <div className="form-control w-full">
<label className="px-1 py-2">Message</label> <label className="label">Message</label>
<ExpandingInput <ExpandingInput
placeholder={defaultMessage} placeholder={defaultMessage}
maxLength={200} maxLength={200}

View File

@ -1,212 +0,0 @@
import { useEffect } from 'react'
import { getContractFromSlug } from 'web/lib/firebase/contracts'
import {
StateElectionMap,
StateElectionMarket,
} from './usa-map/state-election-map'
import { useState } from 'react'
import { LoadingIndicator } from './loading-indicator'
import { CPMMBinaryContract } from 'common/contract'
export function MidtermsMaps(props: { mapType: string }) {
const { mapType } = props
const [contracts, setContracts] = useState<CPMMBinaryContract[] | null>(null)
useEffect(() => {
const getContracts = async () => {
if (props.mapType === 'senate') {
const senateContracts = await Promise.all(
senateMidterms.map((m) =>
getContractFromSlug(m.slug).then((c) => c ?? null)
)
)
setContracts(senateContracts as CPMMBinaryContract[])
} else if (props.mapType === 'governor') {
const governorContracts = await Promise.all(
governorMidterms.map((m) =>
getContractFromSlug(m.slug).then((c) => c ?? null)
)
)
setContracts(governorContracts as CPMMBinaryContract[])
}
}
getContracts()
}, [props.mapType, setContracts])
return contracts ? (
<StateElectionMap
markets={mapType == 'senate' ? senateMidterms : governorMidterms}
contracts={contracts}
/>
) : (
<LoadingIndicator />
)
}
const senateMidterms: StateElectionMarket[] = [
{
state: 'AZ',
creatorUsername: 'BTE',
slug: 'will-blake-masters-win-the-arizona',
isWinRepublican: true,
},
{
state: 'OH',
creatorUsername: 'BTE',
slug: 'will-jd-vance-win-the-ohio-senate-s',
isWinRepublican: true,
},
{
state: 'WI',
creatorUsername: 'BTE',
slug: 'will-ron-johnson-be-reelected-in-th',
isWinRepublican: true,
},
{
state: 'FL',
creatorUsername: 'BTE',
slug: 'will-marco-rubio-be-reelected-to-th',
isWinRepublican: true,
},
{
state: 'PA',
creatorUsername: 'MattP',
slug: 'will-dr-oz-be-elected-to-the-us-sen',
isWinRepublican: true,
},
{
state: 'GA',
creatorUsername: 'NcyRocks',
slug: 'will-a-democrat-win-the-2022-us-sen-3d2432ba6d79',
isWinRepublican: false,
},
{
state: 'NV',
creatorUsername: 'NcyRocks',
slug: 'will-a-democrat-win-the-2022-us-sen',
isWinRepublican: false,
},
{
state: 'NC',
creatorUsername: 'NcyRocks',
slug: 'will-a-democrat-win-the-2022-us-sen-6f1a901e1fcf',
isWinRepublican: false,
},
{
state: 'NH',
creatorUsername: 'NcyRocks',
slug: 'will-a-democrat-win-the-2022-us-sen-23194a72f1b7',
isWinRepublican: false,
},
{
state: 'UT',
creatorUsername: 'SG',
slug: 'will-mike-lee-win-the-2022-utah-sen',
isWinRepublican: true,
},
{
state: 'CO',
creatorUsername: 'SG',
slug: 'will-michael-bennet-win-the-2022-co',
isWinRepublican: false,
},
]
const governorMidterms: StateElectionMarket[] = [
{
state: 'TX',
creatorUsername: 'LarsDoucet',
slug: 'republicans-will-win-the-2022-texas',
isWinRepublican: true,
},
{
state: 'GA',
creatorUsername: 'MattP',
slug: 'will-stacey-abrams-win-the-2022-geo',
isWinRepublican: false,
},
{
state: 'FL',
creatorUsername: 'Tetraspace',
slug: 'if-charlie-crist-is-the-democratic',
isWinRepublican: false,
},
{
state: 'PA',
creatorUsername: 'JonathanMast',
slug: 'will-josh-shapiro-win-the-2022-penn',
isWinRepublican: false,
},
{
state: 'PA',
creatorUsername: 'JonathanMast',
slug: 'will-josh-shapiro-win-the-2022-penn',
isWinRepublican: false,
},
{
state: 'CO',
creatorUsername: 'ScottLawrence',
slug: 'will-jared-polis-be-reelected-as-co',
isWinRepublican: false,
},
{
state: 'OR',
creatorUsername: 'Tetraspace',
slug: 'if-tina-kotek-is-the-2022-democrati',
isWinRepublican: false,
},
{
state: 'MD',
creatorUsername: 'Tetraspace',
slug: 'if-wes-moore-is-the-2022-democratic',
isWinRepublican: false,
},
{
state: 'AK',
creatorUsername: 'SG',
slug: 'will-a-republican-win-the-2022-alas',
isWinRepublican: true,
},
{
state: 'AZ',
creatorUsername: 'SG',
slug: 'will-a-republican-win-the-2022-ariz',
isWinRepublican: true,
},
{
state: 'AZ',
creatorUsername: 'SG',
slug: 'will-a-republican-win-the-2022-ariz',
isWinRepublican: true,
},
{
state: 'WI',
creatorUsername: 'SG',
slug: 'will-a-democrat-win-the-2022-wiscon',
isWinRepublican: false,
},
{
state: 'NV',
creatorUsername: 'SG',
slug: 'will-a-democrat-win-the-2022-nevada',
isWinRepublican: false,
},
{
state: 'KS',
creatorUsername: 'SG',
slug: 'will-a-democrat-win-the-2022-kansas',
isWinRepublican: false,
},
{
state: 'NV',
creatorUsername: 'SG',
slug: 'will-a-democrat-win-the-2022-new-me',
isWinRepublican: false,
},
{
state: 'ME',
creatorUsername: 'SG',
slug: 'will-a-democrat-win-the-2022-maine',
isWinRepublican: false,
},
]

View File

@ -32,8 +32,13 @@ export function NumberInput(props: {
return ( return (
<Col className={className}> <Col className={className}>
<label className="input-group">
<Input <Input
className={clsx('max-w-[200px] !text-lg', inputClassName)} className={clsx(
'max-w-[200px] !text-lg',
error && 'input-error',
inputClassName
)}
ref={inputRef} ref={inputRef}
type="number" type="number"
pattern="[0-9]*" pattern="[0-9]*"
@ -41,10 +46,10 @@ export function NumberInput(props: {
placeholder={placeholder ?? '0'} placeholder={placeholder ?? '0'}
maxLength={9} maxLength={9}
value={numberString} value={numberString}
error={!!error}
disabled={disabled} disabled={disabled}
onChange={(e) => onChange(e.target.value.substring(0, 9))} onChange={(e) => onChange(e.target.value.substring(0, 9))}
/> />
</label>
<Spacer h={4} /> <Spacer h={4} />

View File

@ -4,15 +4,18 @@ import { getPseudoProbability } from 'common/pseudo-numeric'
import { BucketInput } from './bucket-input' import { BucketInput } from './bucket-input'
import { Input } from './input' import { Input } from './input'
import { Col } from './layout/col' import { Col } from './layout/col'
import { Spacer } from './layout/spacer'
function ProbabilityInput(props: { export function ProbabilityInput(props: {
prob: number | undefined prob: number | undefined
onChange: (newProb: number | undefined) => void onChange: (newProb: number | undefined) => void
disabled?: boolean disabled?: boolean
placeholder?: string placeholder?: string
className?: string
inputClassName?: string inputClassName?: string
}) { }) {
const { prob, onChange, disabled, placeholder, inputClassName } = props const { prob, onChange, disabled, placeholder, className, inputClassName } =
props
const onProbChange = (str: string) => { const onProbChange = (str: string) => {
let prob = parseInt(str.replace(/\D/g, '')) let prob = parseInt(str.replace(/\D/g, ''))
@ -26,10 +29,10 @@ function ProbabilityInput(props: {
} }
return ( return (
<Col> <Col className={className}>
<label className="relative w-fit"> <label className="input-group">
<Input <Input
className={clsx('pr-2 !text-lg', inputClassName)} className={clsx('max-w-[200px] !text-lg', inputClassName)}
type="number" type="number"
max={99} max={99}
min={1} min={1}
@ -41,10 +44,9 @@ function ProbabilityInput(props: {
disabled={disabled} disabled={disabled}
onChange={(e) => onProbChange(e.target.value)} onChange={(e) => onProbChange(e.target.value)}
/> />
<span className="text-greyscale-4 absolute top-1/2 right-10 my-auto -translate-y-1/2"> <span className="bg-gray-200 text-sm">%</span>
%
</span>
</label> </label>
<Spacer h={4} />
</Col> </Col>
) )
} }
@ -80,7 +82,7 @@ export function ProbabilityOrNumericInput(props: {
/> />
) : ( ) : (
<ProbabilityInput <ProbabilityInput
inputClassName="w-24" inputClassName="w-full max-w-none"
prob={prob} prob={prob}
onChange={setProb} onChange={setProb}
disabled={isSubmitting} disabled={isSubmitting}

View File

@ -1,4 +1,5 @@
import { Input } from './input' import { Input } from './input'
import { Row } from './layout/row'
export function ProbabilitySelector(props: { export function ProbabilitySelector(props: {
probabilityInt: number probabilityInt: number
@ -8,7 +9,8 @@ export function ProbabilitySelector(props: {
const { probabilityInt, setProbabilityInt, isSubmitting } = props const { probabilityInt, setProbabilityInt, isSubmitting } = props
return ( return (
<label className="flex items-center text-lg"> <Row className="items-center gap-2">
<label className="input-group input-group-lg text-lg">
<Input <Input
type="number" type="number"
value={probabilityInt} value={probabilityInt}
@ -22,5 +24,6 @@ export function ProbabilitySelector(props: {
/> />
<span>%</span> <span>%</span>
</label> </label>
</Row>
) )
} }

View File

@ -7,7 +7,7 @@ import { useUserLikedContracts } from 'web/hooks/use-likes'
import { SiteLink } from 'web/components/site-link' import { SiteLink } from 'web/components/site-link'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { XIcon } from '@heroicons/react/outline' import { XIcon } from '@heroicons/react/outline'
import { unLikeItem } from 'web/lib/firebase/likes' import { unLikeContract } from 'web/lib/firebase/likes'
import { contractPath } from 'web/lib/firebase/contracts' import { contractPath } from 'web/lib/firebase/contracts'
export function UserLikesButton(props: { user: User; className?: string }) { export function UserLikesButton(props: { user: User; className?: string }) {
@ -36,7 +36,7 @@ export function UserLikesButton(props: { user: User; className?: string }) {
</SiteLink> </SiteLink>
<XIcon <XIcon
className="ml-2 h-5 w-5 shrink-0 cursor-pointer" className="ml-2 h-5 w-5 shrink-0 cursor-pointer"
onClick={() => unLikeItem(user.id, likedContract.id)} onClick={() => unLikeContract(user.id, likedContract.id)}
/> />
</Row> </Row>
))} ))}

View File

@ -1,17 +0,0 @@
import clsx from 'clsx'
export const Select = (props: JSX.IntrinsicElements['select']) => {
const { className, children, ...rest } = props
return (
<select
className={clsx(
'h-12 cursor-pointer self-start overflow-hidden rounded-md border border-gray-300 pl-4 pr-10 text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-indigo-500',
className
)}
{...rest}
>
ß{children}
</select>
)
}

View File

@ -5,22 +5,34 @@ import clsx from 'clsx'
import { copyToClipboard } from 'web/lib/util/copy' import { copyToClipboard } from 'web/lib/util/copy'
import { ToastClipboard } from 'web/components/toast-clipboard' import { ToastClipboard } from 'web/components/toast-clipboard'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
import { IconButton } from './button' import { contractDetailsButtonClassName } from 'web/components/contract/contract-info-dialog'
export function ShareIconButton(props: { export function ShareIconButton(props: {
buttonClassName?: string
onCopyButtonClassName?: string
toastClassName?: string toastClassName?: string
children?: React.ReactNode children?: React.ReactNode
iconClassName?: string iconClassName?: string
copyPayload: string copyPayload: string
}) { }) {
const { toastClassName, children, iconClassName, copyPayload } = props const {
buttonClassName,
onCopyButtonClassName,
toastClassName,
children,
iconClassName,
copyPayload,
} = props
const [showToast, setShowToast] = useState(false) const [showToast, setShowToast] = useState(false)
return ( return (
<div className="relative z-10 flex-shrink-0"> <div className="relative z-10 flex-shrink-0">
<IconButton <button
size="2xs" className={clsx(
className={clsx('mt-1', showToast ? 'text-indigo-600' : '')} contractDetailsButtonClassName,
buttonClassName,
showToast ? onCopyButtonClassName : ''
)}
onClick={() => { onClick={() => {
copyToClipboard(copyPayload) copyToClipboard(copyPayload)
track('copy share link') track('copy share link')
@ -29,11 +41,11 @@ export function ShareIconButton(props: {
}} }}
> >
<LinkIcon <LinkIcon
className={clsx(iconClassName ? iconClassName : 'h-5 w-5')} className={clsx(iconClassName ? iconClassName : 'h-[24px] w-5')}
aria-hidden="true" aria-hidden="true"
/> />
{children} {children}
</IconButton> </button>
{showToast && <ToastClipboard className={toastClassName} />} {showToast && <ToastClipboard className={toastClassName} />}
</div> </div>

View File

@ -1,20 +0,0 @@
import { useState, useEffect } from 'react'
import { MidtermsMaps } from './midterms-maps'
export function StaticReactEmbed(props: { embedName: string }) {
const { embedName } = props
const [embed, setEmbed] = useState<JSX.Element | null>(null)
useEffect(() => {
const governorMidtermsMap = <MidtermsMaps mapType="governor" />
const senateMidtermsMap = <MidtermsMaps mapType="senate" />
if (embedName === 'governor-midterms-map') {
setEmbed(governorMidtermsMap)
} else if (embedName === 'senate-midterms-map') {
setEmbed(senateMidtermsMap)
}
}, [embedName, setEmbed])
return <div>{embed}</div>
}

View File

@ -1,21 +0,0 @@
import clsx from 'clsx'
/** `<table>` with styles. Expects table html (`<thead>`, `<td>` etc) */
export const Table = (props: {
zebra?: boolean
className?: string
children: React.ReactNode
}) => {
const { className, children } = props
return (
<table
className={clsx(
'w-full whitespace-nowrap text-left text-sm text-gray-500 [&_td]:p-2 [&_th]:p-2 [&>thead]:font-bold [&>tbody_tr:nth-child(odd)]:bg-white',
className
)}
>
{children}
</table>
)
}

View File

@ -1,5 +1,6 @@
import { useState } from 'react' import { useEffect, useRef, useState } from 'react'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { debounce } from 'lodash'
import { Comment } from 'common/comment' import { Comment } from 'common/comment'
import { User } from 'common/user' import { User } from 'common/user'
@ -8,10 +9,8 @@ import { transact } from 'web/lib/firebase/api'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
import { TipButton } from './contract/tip-button' import { TipButton } from './contract/tip-button'
import { Row } from './layout/row' import { Row } from './layout/row'
import { LIKE_TIP_AMOUNT, TIP_UNDO_DURATION } from 'common/like' import { LIKE_TIP_AMOUNT } from 'common/like'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
import { Button } from './button'
import clsx from 'clsx'
export function Tipper(prop: { export function Tipper(prop: {
comment: Comment comment: Comment
@ -20,13 +19,24 @@ export function Tipper(prop: {
}) { }) {
const { comment, myTip, totalTip } = prop const { comment, myTip, totalTip } = prop
// This is a temporary tipping amount before it actually gets confirmed. This is so tha we dont accidentally tip more than you have
const [tempTip, setTempTip] = useState(0)
const me = useUser() const me = useUser()
const [saveTip] = useState( const [localTip, setLocalTip] = useState(myTip)
() => async (user: User, comment: Comment, change: number) => {
// listen for user being set
const initialized = useRef(false)
useEffect(() => {
if (myTip && !initialized.current) {
setLocalTip(myTip)
initialized.current = true
}
}, [myTip])
const total = totalTip - myTip + localTip
// declare debounced function only on first render
const [saveTip] = useState(() =>
debounce(async (user: User, comment: Comment, change: number) => {
if (change === 0) { if (change === 0) {
return return
} }
@ -57,88 +67,30 @@ export function Tipper(prop: {
fromId: user.id, fromId: user.id,
toId: comment.userId, toId: comment.userId,
}) })
} }, 1500)
) )
// instant save on unrender
useEffect(() => () => void saveTip.flush(), [saveTip])
const addTip = (delta: number) => { const addTip = (delta: number) => {
setTempTip((tempTip) => tempTip + delta) setLocalTip(localTip + delta)
const timeoutId = setTimeout(() => { me && saveTip(me, comment, localTip - myTip + delta)
me && toast(`You tipped ${comment.userName} ${formatMoney(LIKE_TIP_AMOUNT)}!`)
saveTip(me, comment, delta)
.then(() => setTempTip((tempTip) => tempTip - delta))
.catch((e) => console.error(e))
}, TIP_UNDO_DURATION + 1000)
toast.custom(
() => (
<TipToast
userName={comment.userName}
onUndoClick={() => {
clearTimeout(timeoutId)
setTempTip((tempTip) => tempTip - delta)
}}
/>
),
{ duration: TIP_UNDO_DURATION }
)
} }
const canUp = const canUp =
me && comment.userId !== me.id && me.balance - tempTip >= LIKE_TIP_AMOUNT me && comment.userId !== me.id && me.balance >= localTip + LIKE_TIP_AMOUNT
return ( return (
<Row className="items-center gap-0.5"> <Row className="items-center gap-0.5">
<TipButton <TipButton
tipAmount={LIKE_TIP_AMOUNT} tipAmount={LIKE_TIP_AMOUNT}
totalTipped={totalTip} totalTipped={total}
onClick={() => addTip(+LIKE_TIP_AMOUNT)} onClick={() => addTip(+LIKE_TIP_AMOUNT)}
userTipped={tempTip > 0 || myTip > 0} userTipped={localTip > 0}
disabled={!canUp} disabled={!canUp}
isCompact isCompact
/> />
</Row> </Row>
) )
} }
export function TipToast(props: { userName: string; onUndoClick: () => void }) {
const { userName, onUndoClick } = props
const [cancelled, setCancelled] = useState(false)
// There is a strange bug with toast where sometimes if you interact with one popup, the others will not dissappear at the right time, overriding it for now with this
const [timedOut, setTimedOut] = useState(false)
setTimeout(() => {
setTimedOut(true)
}, TIP_UNDO_DURATION)
if (timedOut) {
return <></>
}
return (
<div className="relative overflow-hidden rounded-lg bg-white drop-shadow-md">
<div
className={clsx(
'animate-progress-loading absolute bottom-0 z-10 h-1 w-full bg-indigo-600',
cancelled ? 'hidden' : ''
)}
/>
<Row className="text-greyscale-6 items-center gap-4 px-4 py-2 text-sm">
<div className={clsx(cancelled ? 'hidden' : 'inline')}>
Tipping {userName} {formatMoney(LIKE_TIP_AMOUNT)}...
</div>
<div className={clsx('py-1', cancelled ? 'inline' : 'hidden')}>
Cancelled tipping
</div>
<Button
className={clsx(cancelled ? 'hidden' : 'inline')}
size="xs"
color="gray-outline"
onClick={() => {
onUndoClick()
setCancelled(true)
}}
disabled={cancelled}
>
Cancel
</Button>
</Row>
</div>
)
}

View File

@ -8,7 +8,7 @@ export function ToastClipboard(props: { className?: string }) {
return ( return (
<Row <Row
className={clsx( className={clsx(
'border-greyscale-4 absolute items-center' + 'border-base-300 absolute items-center' +
'gap-2 divide-x divide-gray-200 rounded-md border-2 bg-white ' + 'gap-2 divide-x divide-gray-200 rounded-md border-2 bg-white ' +
'h-15 z-10 w-[15rem] p-2 pr-3 text-gray-500', 'h-15 z-10 w-[15rem] p-2 pr-3 text-gray-500',
className className

View File

@ -48,7 +48,6 @@ const BOT_USERNAMES = [
'MarketManagerBot', 'MarketManagerBot',
'Botlab', 'Botlab',
'JuniorBot', 'JuniorBot',
'ManifoldDream',
] ]
function BotBadge() { function BotBadge() {

View File

@ -291,7 +291,7 @@ export function ProfilePrivateStats(props: {
<Row className={'justify-between gap-4 sm:justify-end'}> <Row className={'justify-between gap-4 sm:justify-end'}>
<Col className={'text-greyscale-4 text-md sm:text-lg'}> <Col className={'text-greyscale-4 text-md sm:text-lg'}>
<span <span
className={clsx(profit >= 0 ? 'text-teal-600' : 'text-red-400')} className={clsx(profit >= 0 ? 'text-green-600' : 'text-red-400')}
> >
{formatMoney(profit)} {formatMoney(profit)}
</span> </span>

View File

@ -1,17 +1,12 @@
import { GlobalConfig } from 'common/globalConfig' import { GlobalConfig } from 'common/globalConfig'
import { useEffect } from 'react' import { useEffect, useState } from 'react'
import { listenForGlobalConfig } from 'web/lib/firebase/globalConfig' import { listenForGlobalConfig } from 'web/lib/firebase/globalConfig'
import { inMemoryStore, usePersistentState } from './use-persistent-state'
export const useGlobalConfig = () => { export const useGlobalConfig = () => {
const [globalConfig, setGlobalConfig] = const [globalConfig, setGlobalConfig] = useState<GlobalConfig | null>(null)
usePersistentState<GlobalConfig | null>(null, {
store: inMemoryStore(),
key: 'globalConfig',
})
useEffect(() => { useEffect(() => {
return listenForGlobalConfig(setGlobalConfig) return listenForGlobalConfig(setGlobalConfig)
}, [setGlobalConfig]) }, [])
return globalConfig return globalConfig
} }

View File

@ -89,17 +89,6 @@ export const historyStore = <T>(prefix = '__manifold'): PersistentStore<T> => ({
}, },
}) })
const store: Record<string, any> = {}
export const inMemoryStore = <T>(): PersistentStore<T> => ({
get: (k: string) => {
return store[k]
},
set: (k: string, v: T | undefined) => {
store[k] = v
},
})
export const usePersistentState = <T>( export const usePersistentState = <T>(
initial: T, initial: T,
persist?: PersistenceOptions<T> persist?: PersistenceOptions<T>
@ -114,7 +103,6 @@ export const usePersistentState = <T>(
if (key != null && store != null) { if (key != null && store != null) {
store.set(key, state) store.set(key, state)
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [key, state]) }, [key, state])
if (store?.readsUrl) { if (store?.readsUrl) {
@ -128,7 +116,6 @@ export const usePersistentState = <T>(
const savedValue = key != null ? store.get(key) : undefined const savedValue = key != null ? store.get(key) : undefined
setState(savedValue ?? initial) setState(savedValue ?? initial)
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady]) }, [router.isReady])
} }

View File

@ -1,4 +1,3 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { isEmpty } from 'lodash' import { isEmpty } from 'lodash'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
@ -13,7 +12,7 @@ type PropzProps = {
// This allows us to client-side render the page for authenticated users. // This allows us to client-side render the page for authenticated users.
// TODO: Could cache the result using stale-while-revalidate: https://swr.vercel.app/ // TODO: Could cache the result using stale-while-revalidate: https://swr.vercel.app/
export function usePropz( export function usePropz(
initialProps: Record<string, unknown>, initialProps: Object,
getStaticPropz: (props: PropzProps) => Promise<any> getStaticPropz: (props: PropzProps) => Promise<any>
) { ) {
// If props were successfully server-side generated, just use those // If props were successfully server-side generated, just use those
@ -30,7 +29,6 @@ export function usePropz(
if (router.isReady) { if (router.isReady) {
getStaticPropz({ params }).then((result) => setPropz(result.props)) getStaticPropz({ params }).then((result) => setPropz(result.props))
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [params]) }, [params])
return propz return propz
} }

View File

@ -33,14 +33,14 @@ export function useTipTxns(on: {
}, [txns]) }, [txns])
} }
export function useItemTipTxns(itemId: string): TipTxn[] { export function useMarketTipTxns(contractId: string): TipTxn[] {
const [txns, setTxns] = useState<TipTxn[]>([]) const [txns, setTxns] = useState<TipTxn[]>([])
useEffect(() => { useEffect(() => {
return listenForTipTxns(itemId, (txns) => { return listenForTipTxns(contractId, (txns) => {
setTxns(txns.filter((txn) => !txn.data.commentId)) setTxns(txns.filter((txn) => !txn.data.commentId))
}) })
}, [itemId]) }, [contractId])
return txns return txns
} }

View File

@ -47,7 +47,10 @@ export const usePrefetchUsers = (userIds: string[]) => {
) )
} }
export const useUserContractMetricsByProfit = (userId: string, count = 50) => { export const useUserContractMetricsByProfit = (
userId: string,
count: number
) => {
const positiveResult = useFirestoreQueryData<ContractMetrics>( const positiveResult = useFirestoreQueryData<ContractMetrics>(
['contract-metrics-descending', userId, count], ['contract-metrics-descending', userId, count],
getUserContractMetricsQuery(userId, count, 'desc') getUserContractMetricsQuery(userId, count, 'desc')
@ -68,13 +71,10 @@ export const useUserContractMetricsByProfit = (userId: string, count = 50) => {
if (!positiveResult.data || !negativeResult.data || !contracts) if (!positiveResult.data || !negativeResult.data || !contracts)
return undefined return undefined
const filteredContracts = filterDefined(contracts).filter( const filteredContracts = filterDefined(contracts) as CPMMBinaryContract[]
(c) => !c.isResolved const filteredMetrics = metrics.filter(
) as CPMMBinaryContract[] (m) => m.from && Math.abs(m.from.day.profit) >= 0.5
const filteredMetrics = metrics )
.filter((m) => m.from && Math.abs(m.from.day.profit) >= 0.5)
.filter((m) => filteredContracts.find((c) => c.id === m.contractId))
return { contracts: filteredContracts, metrics: filteredMetrics } return { contracts: filteredContracts, metrics: filteredMetrics }
} }

View File

@ -6,23 +6,18 @@ import { removeUndefinedProps } from 'common/util/object'
import { Like, LIKE_TIP_AMOUNT } from 'common/like' import { Like, LIKE_TIP_AMOUNT } from 'common/like'
import { track } from '@amplitude/analytics-browser' import { track } from '@amplitude/analytics-browser'
import { User } from 'common/user' import { User } from 'common/user'
import { Post } from 'common/post'
import { Contract } from 'common/contract' import { Contract } from 'common/contract'
function getLikesCollection(userId: string) { function getLikesCollection(userId: string) {
return collection(db, 'users', userId, 'likes') return collection(db, 'users', userId, 'likes')
} }
export const unLikeItem = async (userId: string, itemId: string) => { export const unLikeContract = async (userId: string, contractId: string) => {
const ref = await doc(getLikesCollection(userId), itemId) const ref = await doc(getLikesCollection(userId), contractId)
return await deleteDoc(ref) return await deleteDoc(ref)
} }
export const likeItem = async ( export const likeContract = async (user: User, contract: Contract) => {
user: User,
item: Contract | Post,
itemType: string
) => {
if (user.balance < LIKE_TIP_AMOUNT) { if (user.balance < LIKE_TIP_AMOUNT) {
toast('You do not have enough M$ to like this market!') toast('You do not have enough M$ to like this market!')
return return
@ -33,27 +28,27 @@ export const likeItem = async (
amount: LIKE_TIP_AMOUNT, amount: LIKE_TIP_AMOUNT,
fromId: user.id, fromId: user.id,
fromType: 'USER', fromType: 'USER',
toId: item.creatorId, toId: contract.creatorId,
toType: 'USER', toType: 'USER',
token: 'M$', token: 'M$',
category: 'TIP', category: 'TIP',
data: { contractId: item.id }, data: { contractId: contract.id },
description: `${user.name} liked ${itemType}${item.id} for M$ ${LIKE_TIP_AMOUNT} to ${item.creatorId} `, description: `${user.name} liked contract ${contract.id} for M$ ${LIKE_TIP_AMOUNT} to ${contract.creatorId} `,
}) })
console.log('result', result) console.log('result', result)
} }
// create new like in db under users collection // create new like in db under users collection
const ref = doc(getLikesCollection(user.id), item.id) const ref = doc(getLikesCollection(user.id), contract.id)
// contract slug and question are set via trigger // contract slug and question are set via trigger
const like = removeUndefinedProps({ const like = removeUndefinedProps({
id: ref.id, id: ref.id,
userId: user.id, userId: user.id,
createdTime: Date.now(), createdTime: Date.now(),
type: itemType, type: 'contract',
tipTxnId: result.txn.id, tipTxnId: result.txn.id,
} as Like) } as Like)
track('like', { track('like', {
itemId: item.id, contractId: contract.id,
}) })
await setDoc(ref, like) await setDoc(ref, like)
} }

View File

@ -26,16 +26,14 @@
"@hello-pangea/dnd": "16.0.0", "@hello-pangea/dnd": "16.0.0",
"@heroicons/react": "1.0.6", "@heroicons/react": "1.0.6",
"@react-query-firebase/firestore": "0.4.2", "@react-query-firebase/firestore": "0.4.2",
"@tiptap/core": "2.0.0-beta.199", "@tiptap/core": "2.0.0-beta.182",
"@tiptap/extension-character-count": "2.0.0-beta.199", "@tiptap/extension-character-count": "2.0.0-beta.31",
"@tiptap/extension-image": "2.0.0-beta.199", "@tiptap/extension-image": "2.0.0-beta.30",
"@tiptap/extension-link": "2.0.0-beta.199", "@tiptap/extension-link": "2.0.0-beta.43",
"@tiptap/extension-mention": "2.0.0-beta.199", "@tiptap/extension-mention": "2.0.0-beta.102",
"@tiptap/extension-placeholder": "2.0.0-beta.199", "@tiptap/extension-placeholder": "2.0.0-beta.53",
"@tiptap/html": "2.0.0-beta.199", "@tiptap/react": "2.0.0-beta.114",
"@tiptap/react": "2.0.0-beta.199", "@tiptap/starter-kit": "2.0.0-beta.191",
"@tiptap/starter-kit": "2.0.0-beta.199",
"@tiptap/suggestion": "2.0.0-beta.199",
"algoliasearch": "4.13.0", "algoliasearch": "4.13.0",
"browser-image-compression": "2.0.0", "browser-image-compression": "2.0.0",
"clsx": "1.1.1", "clsx": "1.1.1",
@ -46,6 +44,7 @@
"d3-scale": "4.0.2", "d3-scale": "4.0.2",
"d3-selection": "3.0.0", "d3-selection": "3.0.0",
"d3-shape": "3.1.0", "d3-shape": "3.1.0",
"daisyui": "1.16.4",
"dayjs": "1.10.7", "dayjs": "1.10.7",
"firebase": "9.9.3", "firebase": "9.9.3",
"gridjs": "5.0.2", "gridjs": "5.0.2",
@ -55,7 +54,6 @@
"next": "12.3.1", "next": "12.3.1",
"node-fetch": "3.2.4", "node-fetch": "3.2.4",
"prosemirror-state": "1.4.1", "prosemirror-state": "1.4.1",
"rc-slider": "10.0.1",
"react": "18.2.0", "react": "18.2.0",
"react-confetti": "6.0.1", "react-confetti": "6.0.1",
"react-dom": "18.2.0", "react-dom": "18.2.0",
@ -65,7 +63,7 @@
"react-masonry-css": "1.0.16", "react-masonry-css": "1.0.16",
"react-query": "3.39.0", "react-query": "3.39.0",
"react-twitter-embed": "4.0.4", "react-twitter-embed": "4.0.4",
"stability-client": "1.6.1", "stability-client": "1.5.0",
"string-similarity": "^4.0.4", "string-similarity": "^4.0.4",
"tippy.js": "6.3.7" "tippy.js": "6.3.7"
}, },

View File

@ -23,7 +23,7 @@ export default function Document() {
crossOrigin="anonymous" crossOrigin="anonymous"
/> />
</Head> </Head>
<body className="font-readex-pro bg-greyscale-1 min-h-screen"> <body className="font-readex-pro bg-base-200 min-h-screen">
<Main /> <Main />
<NextScript /> <NextScript />
</body> </body>

View File

@ -1,23 +0,0 @@
import { NextApiRequest, NextApiResponse } from 'next'
import {
CORS_ORIGIN_MANIFOLD,
CORS_ORIGIN_LOCALHOST,
} from 'common/envs/constants'
import { applyCorsHeaders } from 'web/lib/api/cors'
import { fetchBackend, forwardResponse } from 'web/lib/api/proxy'
export const config = { api: { bodyParser: true } }
export default async function route(req: NextApiRequest, res: NextApiResponse) {
await applyCorsHeaders(req, res, {
origin: [CORS_ORIGIN_MANIFOLD, CORS_ORIGIN_LOCALHOST],
methods: 'POST',
})
try {
const backendRes = await fetchBackend(req, 'createcomment')
await forwardResponse(res, backendRes)
} catch (err) {
console.error('Error talking to cloud function: ', err)
res.status(500).json({ message: 'Error communicating with backend.' })
}
}

View File

@ -10,7 +10,6 @@ import {
} from 'web/hooks/use-persistent-state' } from 'web/hooks/use-persistent-state'
import { PAST_BETS } from 'common/user' import { PAST_BETS } from 'common/user'
import { Input } from 'web/components/input' import { Input } from 'web/components/input'
import { Select } from 'web/components/select'
const MAX_CONTRACTS_RENDERED = 100 const MAX_CONTRACTS_RENDERED = 100
@ -97,13 +96,17 @@ export default function ContractSearchFirestore(props: {
placeholder="Search markets" placeholder="Search markets"
className="w-full" className="w-full"
/> />
<Select value={sort} onChange={(e) => setSort(e.target.value)}> <select
className="select select-bordered"
value={sort}
onChange={(e) => setSort(e.target.value)}
>
<option value="score">Trending</option> <option value="score">Trending</option>
<option value="newest">Newest</option> <option value="newest">Newest</option>
<option value="most-traded">Most ${PAST_BETS}</option> <option value="most-traded">Most ${PAST_BETS}</option>
<option value="24-hour-vol">24h volume</option> <option value="24-hour-vol">24h volume</option>
<option value="close-date">Closing soon</option> <option value="close-date">Closing soon</option>
</Select> </select>
</div> </div>
<ContractsGrid contracts={matches} showTime={showTime} /> <ContractsGrid contracts={matches} showTime={showTime} />
</div> </div>

View File

@ -96,9 +96,11 @@ export default function Create(props: { auth: { user: User } }) {
<Title className="!mt-0" text="Create a market" /> <Title className="!mt-0" text="Create a market" />
<form> <form>
<div className="flex w-full flex-col"> <div className="form-control w-full">
<label className="px-1 pt-2 pb-3"> <label className="label">
<span className="mb-1">
Question<span className={'text-red-700'}>*</span> Question<span className={'text-red-700'}>*</span>
</span>
</label> </label>
<ExpandingInput <ExpandingInput
@ -227,6 +229,7 @@ export function NewContract(props: {
key: 'create market', key: 'create market',
max: MAX_DESCRIPTION_LENGTH, max: MAX_DESCRIPTION_LENGTH,
placeholder: descriptionPlaceholder, placeholder: descriptionPlaceholder,
disabled: isSubmitting,
defaultValue: params?.description defaultValue: params?.description
? JSON.parse(params.description) ? JSON.parse(params.description)
: undefined, : undefined,
@ -278,7 +281,9 @@ export function NewContract(props: {
return ( return (
<div> <div>
<label className="px-1 pt-2 pb-3">Answer type</label> <label className="label">
<span className="mb-1">Answer type</span>
</label>
<Row> <Row>
<ChoicesToggleGroup <ChoicesToggleGroup
currentChoice={outcomeType} currentChoice={outcomeType}
@ -314,8 +319,8 @@ export function NewContract(props: {
{outcomeType === 'PSEUDO_NUMERIC' && ( {outcomeType === 'PSEUDO_NUMERIC' && (
<> <>
<div className="mb-2 flex flex-col items-start"> <div className="form-control mb-2 items-start">
<label className="gap-2 px-1 py-2"> <label className="label gap-2">
<span className="mb-1">Range</span> <span className="mb-1">Range</span>
<InfoTooltip text="The lower and higher bounds of the numeric range. Choose bounds the value could reasonably be expected to hit." /> <InfoTooltip text="The lower and higher bounds of the numeric range. Choose bounds the value could reasonably be expected to hit." />
</label> </label>
@ -359,8 +364,8 @@ export function NewContract(props: {
</div> </div>
)} )}
</div> </div>
<div className="mb-2 flex flex-col items-start"> <div className="form-control mb-2 items-start">
<label className="gap-2 px-1 py-2"> <label className="label gap-2">
<span className="mb-1">Initial value</span> <span className="mb-1">Initial value</span>
<InfoTooltip text="The starting value for this market. Should be in between min and max values." /> <InfoTooltip text="The starting value for this market. Should be in between min and max values." />
</label> </label>
@ -406,7 +411,7 @@ export function NewContract(props: {
)} )}
</Row> </Row>
<Row className="my-2 items-center gap-2 text-sm"> <Row className="form-control my-2 items-center gap-2 text-sm">
<span>Display this market on homepage</span> <span>Display this market on homepage</span>
<ShortToggle <ShortToggle
on={visibility === 'public'} on={visibility === 'public'}
@ -416,8 +421,8 @@ export function NewContract(props: {
<Spacer h={6} /> <Spacer h={6} />
<div className="mb-1 flex flex-col items-start"> <div className="form-control mb-1 items-start">
<label className="mb-1 gap-2 px-1 py-2"> <label className="label mb-1 gap-2">
<span>Question closes in</span> <span>Question closes in</span>
<InfoTooltip text="Predicting will be halted after this time (local timezone)." /> <InfoTooltip text="Predicting will be halted after this time (local timezone)." />
</label> </label>
@ -459,8 +464,8 @@ export function NewContract(props: {
<Spacer h={6} /> <Spacer h={6} />
<div className="mb-1 flex flex-col items-start gap-1"> <div className="form-control mb-1 items-start gap-1">
<label className="gap-2 px-1 py-2"> <label className="label gap-2">
<span className="mb-1">Description</span> <span className="mb-1">Description</span>
<InfoTooltip text="Optional. Describe how you will resolve this question." /> <InfoTooltip text="Optional. Describe how you will resolve this question." />
</label> </label>
@ -470,24 +475,24 @@ export function NewContract(props: {
<Spacer h={6} /> <Spacer h={6} />
<span className={'text-error'}>{errorText}</span> <span className={'text-error'}>{errorText}</span>
<Row className="items-end justify-between"> <Row className="items-end justify-between">
<div className="mb-1 flex flex-col items-start"> <div className="form-control mb-1 items-start">
<label className="mb-1 gap-2 px-1 py-2"> <label className="label mb-1 gap-2">
<span>Cost</span> <span>Cost</span>
<InfoTooltip <InfoTooltip
text={`Cost to create your question. This amount is used to subsidize predictions.`} text={`Cost to create your question. This amount is used to subsidize predictions.`}
/> />
</label> </label>
{!deservesFreeMarket ? ( {!deservesFreeMarket ? (
<div className="pl-1 text-sm text-gray-700"> <div className="label-text text-neutral pl-1">
{formatMoney(ante)} {formatMoney(ante)}
</div> </div>
) : ( ) : (
<Row className="text-sm"> <Row>
<div className="pl-1 text-gray-700 line-through"> <div className="label-text text-neutral pl-1 line-through">
{formatMoney(ante)} {formatMoney(ante)}
</div> </div>
<div className="text-primary pl-1">FREE </div> <div className="label-text text-primary pl-1">FREE </div>
<div className="pl-1 text-gray-500"> <div className="label-text pl-1 text-gray-500">
(You have{' '} (You have{' '}
{FREE_MARKETS_PER_USER_MAX - (creator?.freeMarketsCreated ?? 0)}{' '} {FREE_MARKETS_PER_USER_MAX - (creator?.freeMarketsCreated ?? 0)}{' '}
free markets left) free markets left)

View File

@ -28,7 +28,7 @@ export default function DailyMovers() {
function ProbChangesWrapper(props: { userId: string }) { function ProbChangesWrapper(props: { userId: string }) {
const { userId } = props const { userId } = props
const data = useUserContractMetricsByProfit(userId) const data = useUserContractMetricsByProfit(userId, 50)
if (!data) return <LoadingIndicator /> if (!data) return <LoadingIndicator />

View File

@ -35,7 +35,9 @@ export default function CreateDateDocPage() {
const [isSubmitting, setIsSubmitting] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false)
const { editor, upload } = useTextEditor({}) const { editor, upload } = useTextEditor({
disabled: isSubmitting,
})
const birthdayTime = birthday ? dayjs(birthday).valueOf() : undefined const birthdayTime = birthday ? dayjs(birthday).valueOf() : undefined
const isValid = const isValid =

View File

@ -228,7 +228,7 @@ function GroupMembersList(props: { group: Group }) {
const { totalMembers } = group const { totalMembers } = group
if (totalMembers === 1) return <div /> if (totalMembers === 1) return <div />
return ( return (
<div className="flex flex-wrap gap-1 text-gray-700"> <div className="text-neutral flex flex-wrap gap-1">
<span>{totalMembers} members</span> <span>{totalMembers} members</span>
</div> </div>
) )

View File

@ -1,4 +1,4 @@
import React, { ReactNode, useEffect } from 'react' import React, { ReactNode, useEffect, useState } from 'react'
import Router from 'next/router' import Router from 'next/router'
import { import {
AdjustmentsIcon, AdjustmentsIcon,
@ -63,10 +63,6 @@ import { useAllPosts } from 'web/hooks/use-post'
import { useGlobalConfig } from 'web/hooks/use-global-config' import { useGlobalConfig } from 'web/hooks/use-global-config'
import { useAdmin } from 'web/hooks/use-admin' import { useAdmin } from 'web/hooks/use-admin'
import { GlobalConfig } from 'common/globalConfig' import { GlobalConfig } from 'common/globalConfig'
import {
inMemoryStore,
usePersistentState,
} from 'web/hooks/use-persistent-state'
export default function Home() { export default function Home() {
const user = useUser() const user = useUser()
@ -95,7 +91,8 @@ export default function Home() {
}, [user, sections]) }, [user, sections])
const contractMetricsByProfit = useUserContractMetricsByProfit( const contractMetricsByProfit = useUserContractMetricsByProfit(
user?.id ?? '_' user?.id ?? '_',
3
) )
const trendingContracts = useTrendingContracts(6) const trendingContracts = useTrendingContracts(6)
@ -108,10 +105,7 @@ export default function Home() {
groups?.map((g) => g.slug) groups?.map((g) => g.slug)
) )
const [pinned, setPinned] = usePersistentState<JSX.Element[] | null>(null, { const [pinned, setPinned] = useState<JSX.Element[] | null>(null)
store: inMemoryStore(),
key: 'home-pinned',
})
useEffect(() => { useEffect(() => {
const pinnedItems = globalConfig?.pinnedItems const pinnedItems = globalConfig?.pinnedItems
@ -145,7 +139,7 @@ export default function Home() {
} }
} }
getPinned() getPinned()
}, [globalConfig, setPinned]) }, [globalConfig])
const isLoading = const isLoading =
!user || !user ||
@ -493,7 +487,7 @@ function DailyMoversSection(props: {
return ( return (
<Col className="gap-2"> <Col className="gap-2">
<SectionHeader label="Daily movers" href="/daily-movers" /> <SectionHeader label="Daily movers" href="/daily-movers" />
<ProfitChangeTable contracts={contracts} metrics={metrics} maxRows={3} /> <ProfitChangeTable contracts={contracts} metrics={metrics} />
</Col> </Col>
) )
} }
@ -529,7 +523,8 @@ export function DailyProfit(props: { user: User | null | undefined }) {
const { user } = props const { user } = props
const contractMetricsByProfit = useUserContractMetricsByProfit( const contractMetricsByProfit = useUserContractMetricsByProfit(
user?.id ?? '_' user?.id ?? '_',
100
) )
const profit = sum( const profit = sum(
contractMetricsByProfit?.metrics.map((m) => contractMetricsByProfit?.metrics.map((m) =>

View File

@ -26,7 +26,6 @@ import { useUser } from 'web/hooks/use-user'
import { usePost } from 'web/hooks/use-post' import { usePost } from 'web/hooks/use-post'
import { SEO } from 'web/components/SEO' import { SEO } from 'web/components/SEO'
import { Subtitle } from 'web/components/subtitle' import { Subtitle } from 'web/components/subtitle'
import { LikeItemButton } from 'web/components/contract/like-item-button'
export async function getStaticProps(props: { params: { slugs: string[] } }) { export async function getStaticProps(props: { params: { slugs: string[] } }) {
const { slugs } = props.params const { slugs } = props.params
@ -82,20 +81,17 @@ export default function PostPage(props: {
<br /> <br />
<Subtitle className="!mt-2 px-2 pb-4" text={post.subtitle} /> <Subtitle className="!mt-2 px-2 pb-4" text={post.subtitle} />
</div> </div>
<Row className="items-center"> <Row>
<Col className="flex-1 px-2"> <Col className="flex-1 px-2">
<div className={'inline-flex'}> <div className={'inline-flex'}>
<div className="mr-1 text-gray-500">Created by</div> <div className="mr-1 text-gray-500">Created by</div>
<UserLink <UserLink
className="text-gray-700" className="text-neutral"
name={creator.name} name={creator.name}
username={creator.username} username={creator.username}
/> />
</div> </div>
</Col> </Col>
<Row className="items-center">
<LikeItemButton item={post} user={user} itemType={'post'} />
<Col className="px-2"> <Col className="px-2">
<Button <Button
size="lg" size="lg"
@ -118,11 +114,10 @@ export default function PostPage(props: {
</Button> </Button>
</Col> </Col>
</Row> </Row>
</Row>
<Spacer h={2} /> <Spacer h={2} />
<div className="rounded-lg bg-white px-6 py-4 sm:py-0"> <div className="rounded-lg bg-white px-6 py-4 sm:py-0">
<div className="flex w-full flex-col py-2"> <div className="form-control w-full py-2">
{user && user.id === post.creatorId ? ( {user && user.id === post.creatorId ? (
<RichEditPost post={post} /> <RichEditPost post={post} />
) : ( ) : (
@ -176,9 +171,11 @@ export function PostCommentsActivity(props: {
export function RichEditPost(props: { post: Post }) { export function RichEditPost(props: { post: Post }) {
const { post } = props const { post } = props
const [editing, setEditing] = useState(false) const [editing, setEditing] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
const { editor, upload } = useTextEditor({ const { editor, upload } = useTextEditor({
defaultValue: post.content, defaultValue: post.content,
disabled: isSubmitting,
}) })
async function savePost() { async function savePost() {
@ -196,8 +193,10 @@ export function RichEditPost(props: { post: Post }) {
<Row className="gap-2"> <Row className="gap-2">
<Button <Button
onClick={async () => { onClick={async () => {
setIsSubmitting(true)
await savePost() await savePost()
setEditing(false) setEditing(false)
setIsSubmitting(false)
}} }}
> >
Save Save

View File

@ -43,7 +43,7 @@ function EditUserField(props: {
return ( return (
<div> <div>
<label className="px-1 py-2">{label}</label> <label className="label">{label}</label>
{field === 'bio' ? ( {field === 'bio' ? (
<ExpandingInput <ExpandingInput
@ -156,7 +156,7 @@ export default function ProfilePage(props: {
</Row> </Row>
<div> <div>
<label className="px-1 py-2">Display name</label> <label className="label">Display name</label>
<Input <Input
type="text" type="text"
placeholder="Display name" placeholder="Display name"
@ -167,7 +167,7 @@ export default function ProfilePage(props: {
</div> </div>
<div> <div>
<label className="px-1 py-2">Username</label> <label className="label">Username</label>
<Input <Input
type="text" type="text"
placeholder="Username" placeholder="Username"
@ -193,15 +193,15 @@ export default function ProfilePage(props: {
))} ))}
<div> <div>
<label className="px-1 py-2">Email</label> <label className="label">Email</label>
<div className="ml-1 text-gray-500"> <div className="ml-1 text-gray-500">
{privateUser.email ?? '\u00a0'} {privateUser.email ?? '\u00a0'}
</div> </div>
</div> </div>
<div> <div>
<label className="px-1 py-2">API key</label> <label className="label">API key</label>
<div className="flex w-full items-stretch"> <div className="input-group w-full">
<Input <Input
type="text" type="text"
placeholder="Click refresh to generate key" placeholder="Click refresh to generate key"

View File

@ -25,7 +25,6 @@ import { Carousel } from 'web/components/carousel'
import { usePagination } from 'web/hooks/use-pagination' import { usePagination } from 'web/hooks/use-pagination'
import { LoadingIndicator } from 'web/components/loading-indicator' import { LoadingIndicator } from 'web/components/loading-indicator'
import { Title } from 'web/components/title' import { Title } from 'web/components/title'
import { useTracking } from 'web/hooks/use-tracking'
dayjs.extend(utc) dayjs.extend(utc)
dayjs.extend(timezone) dayjs.extend(timezone)
@ -171,9 +170,6 @@ export default function TournamentPage(props: { sections: SectionInfo[] }) {
const description = `Win real prizes (including cash!) by participating in forecasting const description = `Win real prizes (including cash!) by participating in forecasting
tournaments on current events, sports, science, and more.` tournaments on current events, sports, science, and more.`
useTracking('view tournaments')
return ( return (
<Page> <Page>
<SEO title="Tournaments" description={description} /> <SEO title="Tournaments" description={description} />

View File

@ -1,29 +0,0 @@
export default function Coin({
size = 18,
color = '#66667C',
strokeWidth = 1.5,
fill = 'none',
}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 18 18"
width={size}
height={size}
fill={fill}
stroke={color}
strokeWidth={strokeWidth}
opacity={50}
transform="rotate(-30)"
>
<path
className="cls-2"
d="M15,9c0,.35-.07,.68-.2,1-.66,1.73-3.01,3-5.8,3s-5.14-1.27-5.8-3c-.13-.32-.2-.65-.2-1,0-2.21,2.69-4,6-4s6,1.79,6,4Z"
/>
<path
className="cls-1"
d="M15,9v2c0,2.21-2.69,4-6,4s-6-1.79-6-4v-2c0,.35,.07,.68,.2,1,.66,1.73,3.01,3,5.8,3s5.14-1.27,5.8-3c.13-.32,.2-.65,.2-1Z"
/>
</svg>
)
}

View File

@ -12,7 +12,6 @@ export default function Curve({
fill="none" fill="none"
stroke={color} stroke={color}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
transform="rotate(90)"
> >
<path d="M5.02,0V5.24c0,4.3,3.49,7.79,7.79,7.79h5.2" /> <path d="M5.02,0V5.24c0,4.3,3.49,7.79,7.79,7.79h5.2" />
</svg> </svg>

View File

@ -1,23 +0,0 @@
export default function TipJar({
size = 18,
color = '#66667C',
strokeWidth = 1.5,
fill = 'none',
}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 18 18"
width={size}
height={size}
fill={fill}
stroke={color}
strokeWidth={strokeWidth}
opacity={50}
>
<path d="M15.5,8.1v5.8c0,1.43-1.16,2.6-2.6,2.6H5.1c-1.44,0-2.6-1.16-2.6-2.6v-5.8c0-1.04,.89-3.25,1.5-4.1h0v-2c0-.55,.45-1,1-1H13c.55,0,1,.45,1,1v2h0c.61,.85,1.5,3.06,1.5,4.1Z" />
<line x1="4" y1="4" x2="9" y2="4" />
<line x1="11.26" y1="4" x2="14" y2="4" />
</svg>
)
}

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const defaultTheme = require('tailwindcss/defaultTheme') const defaultTheme = require('tailwindcss/defaultTheme')
const plugin = require('tailwindcss/plugin') const plugin = require('tailwindcss/plugin')
@ -16,20 +15,7 @@ module.exports = {
} }
), ),
extend: { extend: {
keyframes: {
progress: {
'0%': { width: '0%' },
'100%': { width: '100%' },
},
},
animation: {
'progress-loading': 'progress 2s linear',
},
colors: { colors: {
primary: '#11b981',
'primary-focus': '#069668',
warning: '#F59E0B', // amber-500 TODO: change color
error: '#ff5724', // TODO: change color
'red-25': '#FDF7F6', 'red-25': '#FDF7F6',
'greyscale-1': '#FBFBFF', 'greyscale-1': '#FBFBFF',
'greyscale-1.5': '#F4F4FB', 'greyscale-1.5': '#F4F4FB',
@ -56,6 +42,7 @@ module.exports = {
require('@tailwindcss/forms'), require('@tailwindcss/forms'),
require('@tailwindcss/typography'), require('@tailwindcss/typography'),
require('@tailwindcss/line-clamp'), require('@tailwindcss/line-clamp'),
require('daisyui'),
plugin(function ({ addUtilities }) { plugin(function ({ addUtilities }) {
addUtilities({ addUtilities({
'.scrollbar-hide': { '.scrollbar-hide': {
@ -74,7 +61,57 @@ module.exports = {
'overflow-wrap': 'anywhere', 'overflow-wrap': 'anywhere',
'word-break': 'break-word', // for Safari 'word-break': 'break-word', // for Safari
}, },
'.only-thumb': {
'pointer-events': 'none',
'&::-webkit-slider-thumb': {
'pointer-events': 'auto !important',
},
'&::-moz-range-thumb': {
'pointer-events': 'auto !important',
},
'&::-ms-thumb': {
'pointer-events': 'auto !important',
},
},
}) })
}), }),
], ],
daisyui: {
themes: [
{
mantic: {
primary: '#11b981',
'primary-focus': '#069668',
// Foreground content color to use on primary color
'primary-content': '#ffffff',
secondary: '#a991f7',
'secondary-focus': '#8462f4',
// Foreground content color to use on secondary color
'secondary-content': '#ffffff',
accent: '#f6d860',
'accent-focus': '#f3cc30',
// Foreground content color to use on accent color
'accent-content': '#ffffff',
neutral: '#3d4451',
'neutral-focus': '#2a2e37',
// Foreground content color to use on neutral color
'neutral-content': '#ffffff',
'base-100': '#ffffff' /* Base page color, for blank backgrounds */,
'base-200': '#f9fafb' /* Base color, a little darker */,
'base-300': '#d1d5db' /* Base color, even more dark */,
// Foreground content color to use on base color
'base-content': '#1f2937',
info: '#2094f3',
success: '#009485',
warning: '#ff9900',
error: '#ff5724',
},
},
],
},
} }

468
yarn.lock
View File

@ -1303,13 +1303,6 @@
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/runtime@^7.10.1", "@babel/runtime@^7.18.3":
version "7.19.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78"
integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.17.2", "@babel/runtime@^7.8.4": "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.17.2", "@babel/runtime@^7.8.4":
version "7.18.3" version "7.18.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4"
@ -2862,227 +2855,219 @@
lodash.isplainobject "^4.0.6" lodash.isplainobject "^4.0.6"
lodash.merge "^4.6.2" lodash.merge "^4.6.2"
"@tiptap/core@2.0.0-beta.199", "@tiptap/core@^2.0.0-beta.199": "@tiptap/core@2.0.0-beta.182", "@tiptap/core@^2.0.0-beta.182":
version "2.0.0-beta.199" version "2.0.0-beta.182"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.199.tgz#77c3d8df10a4594cf5860b8a73d5007b0a020d01" resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.182.tgz#d2001e9b765adda95e15d171479860a3349e2d04"
integrity sha512-34GaXcBEmNFjW1R7nf1LSmOHo3Q81YjKqvLAXjDLLG7MTx+YTrQ4yWwUvMsZtmi4o/FchUzrs1NVCfr571Zxzg== integrity sha512-MZGkMGnVnWhBzjvpBNwQ9zBz38ndi3Irbf90uCTSArR0kaCVkW4vmyuPuOXd+0SO8Yv/l5oyDdOCpaG3rnQYfw==
dependencies: dependencies:
prosemirror-commands "^1.3.1" prosemirror-commands "1.3.0"
prosemirror-keymap "^1.2.0" prosemirror-keymap "1.2.0"
prosemirror-model "^1.18.1" prosemirror-model "1.18.1"
prosemirror-schema-list "^1.2.2" prosemirror-schema-list "1.2.0"
prosemirror-state "^1.4.1" prosemirror-state "1.4.1"
prosemirror-transform "^1.7.0" prosemirror-transform "1.6.0"
prosemirror-view "^1.28.2" prosemirror-view "1.26.2"
"@tiptap/extension-blockquote@^2.0.0-beta.199": "@tiptap/extension-blockquote@^2.0.0-beta.29":
version "2.0.0-beta.199" version "2.0.0-beta.29"
resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.199.tgz#f3957a638d515f6e38cd91eacb59cbedef06af6c" resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.29.tgz#6f1c4b17efa6457c7776f32d0807e96d848d4389"
integrity sha512-BbHKaIkVYgJCV5giJC3/bdXMZWxFylLKiAbOGSGwIsnnS5/oL+V4XN6hqcIDBxlcj3MQ/d9zG0+mvFyjRssAkg== integrity sha512-zMYT5TtpKWav9VhTn4JLyMvXmhEdbD6on0MdhcTjRm0I5ugyR4ZbJwh2aelM7G9DZVYzB8jZU18OSDJmo7Af7w==
"@tiptap/extension-bold@^2.0.0-beta.199": "@tiptap/extension-bold@^2.0.0-beta.28":
version "2.0.0-beta.199" version "2.0.0-beta.28"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.199.tgz#edb3dca9ca49ec128611629fbb5d17eaca62c506" resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.28.tgz#cf67c264a80434ffb2368f3dd37cf357ae0c2064"
integrity sha512-l513jgGLmt8C69Yuh5Et7a46Tn8QpW4q1HhZK6ih0ajNT+L5Xk0CSxEK/K5EmHSACPhwqjsJztLpGjAdoOn0mA== integrity sha512-DY8GOzw9xjmTFrnvTbgHUNxTnDfKrkDgrhe0SUvdkT2udntWp8umPdhPiD3vczLgHOJw6tX68qMRjbsR1ZPcHQ==
"@tiptap/extension-bubble-menu@^2.0.0-beta.199": "@tiptap/extension-bubble-menu@^2.0.0-beta.61":
version "2.0.0-beta.199" version "2.0.0-beta.61"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.199.tgz#23a01c4c57f5af2197299d836e18ffe498fd8d67" resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.61.tgz#cc61ce8b094fdbcec58f44f0fa39172a726c024c"
integrity sha512-T3K8xoDbX6J62lhIUpclQoW/1XFt7yfI5DCoxtVWUeKaF+pG6kdsB3CPG5C/+AQVlz2jSIJmQuPf8RQFpQs+yg== integrity sha512-T3Yx+y1sUnXAJjK1CUfsQewSxOpDca9KzKqN2H9c9RZ9UlorR9XmZg6YYW7m9a7adeihj+o3cCO9jRd8dV+nnA==
dependencies: dependencies:
prosemirror-state "^1.4.1" prosemirror-state "1.4.1"
prosemirror-view "^1.28.2" prosemirror-view "1.26.2"
tippy.js "^6.3.7" tippy.js "^6.3.7"
"@tiptap/extension-bullet-list@^2.0.0-beta.199": "@tiptap/extension-bullet-list@^2.0.0-beta.29":
version "2.0.0-beta.199" version "2.0.0-beta.29"
resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.199.tgz#72df0c94c6a9a5bce97ee1e0180657f1a63ffe18" resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.29.tgz#640883e4fffc1a86c7cbd78792688e7edee5ee41"
integrity sha512-gGRQRqdQqCZQstB3ztSy8yzIdm5/5IIYxhCuFNb3Z9c9p/CzyRmaNqa7XkRLrXSajp4lS0OH8RkFUJqL6U+/9w== integrity sha512-R8VB2l1ZB6VeGWx/t/04nBS5Wg3qjIDEZCpPihj2fccJOw99Lu0Ub2UJg/SfdGmeNNpBh4ZYYFv1g/XjyzlXKg==
"@tiptap/extension-character-count@2.0.0-beta.199": "@tiptap/extension-character-count@2.0.0-beta.31":
version "2.0.0-beta.199" version "2.0.0-beta.31"
resolved "https://registry.yarnpkg.com/@tiptap/extension-character-count/-/extension-character-count-2.0.0-beta.199.tgz#cd6b008a8e242240c30dda7ee5fe515fc39a8d78" resolved "https://registry.yarnpkg.com/@tiptap/extension-character-count/-/extension-character-count-2.0.0-beta.31.tgz#fac9ba809ddc38cf67c8a05a42d94e062a1967d2"
integrity sha512-7QEyLZeTVHRi7XaI97n+yF/R9cs7Xo1pf/cpV+wn/QSkfr5YtYvy33LCN74Vlkw9FJ45KrROEXfawRE/UgM/Cw== integrity sha512-NNA9MN1IjZe+yYQLuYVAg9RNG/3RonYrHiM5mL6vsegd+PF4uMqyZLgsM0/9dMhxh9K/pDPaCRxhuDoZC8V1wA==
dependencies: dependencies:
prosemirror-model "^1.18.1" prosemirror-model "1.18.1"
prosemirror-state "^1.4.1" prosemirror-state "1.4.1"
"@tiptap/extension-code-block@^2.0.0-beta.199": "@tiptap/extension-code-block@^2.0.0-beta.42":
version "2.0.0-beta.199" version "2.0.0-beta.42"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.199.tgz#145baa37276601bce75ddad55ab4926bc78b28fc" resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.42.tgz#2abfd92eb22399fa542aafb3b76dddfb41d87ab5"
integrity sha512-ZfftYE1kHA2pD46hXDkeYd1vuxp3bJLS854B2yHfw1cp3JVDjMXzm4Mzg7zLfr+YV1dT/N/fUfdCg38fqEUCyA== integrity sha512-4wzLup4mI8w9ypIceekUV/8g41cQIPn31qs1iC9u1/JuTkjMj/tA+TFUyp6IMugLxoI/P2DlTztU6/6m7n9DyQ==
dependencies: dependencies:
prosemirror-state "^1.4.1" prosemirror-state "1.4.1"
"@tiptap/extension-code@^2.0.0-beta.199": "@tiptap/extension-code@^2.0.0-beta.28":
version "2.0.0-beta.199" version "2.0.0-beta.28"
resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.0-beta.199.tgz#ab6e2355297b3faddf6ac6e9e02a52eca38c16f6" resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.0-beta.28.tgz#a22c0e873497ac0bbcd77e4a855322f8591f954e"
integrity sha512-P1U/xYD0MLT7JU2OHb3QoW7+JiPZXizFG/gTYmAHQV/gLH87cmflI7pPnloBdTkeIF0Q/cd6sSd75V9FxR4XJA== integrity sha512-QPJ2Gwb1+3NgcC1ZIhvVcb+FsnWWDu5VZXTKXM4mz892i9V2x48uHg5anPiUV6pcolXsW1F5VNbXIHGTUUO6CQ==
"@tiptap/extension-document@^2.0.0-beta.199": "@tiptap/extension-document@^2.0.0-beta.17":
version "2.0.0-beta.199" version "2.0.0-beta.17"
resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.0-beta.199.tgz#4a9432ab239cc951b4d9903a90d3a7d5597d3318" resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.0-beta.17.tgz#ded4182dd860762bcf41c588f712d83908c472a3"
integrity sha512-l/3k9N2O4wIMQoN/SM3aIBwOhZ2KRxQoqGJfsbAUUwBURBDiT4N2VZaNiJC/w3xCVQXIxHSIlqtm9ZBcZeiH/Q== integrity sha512-L6sg0FNchbtIpQkCSjMmItVGs3/vep8Fq56WRtDc1wBSGUSmtHaxQG7F2FZLnNIUMuvzVMRD81m2vYG73WkY6A==
"@tiptap/extension-dropcursor@^2.0.0-beta.199": "@tiptap/extension-dropcursor@^2.0.0-beta.29":
version "2.0.0-beta.199" version "2.0.0-beta.29"
resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.199.tgz#93d04db4ca56614b7b180632456cb9f2098c6156" resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.29.tgz#9ccc9d82cb9f8fa28a59ffc061c4c83ee059a12c"
integrity sha512-RhdYm0yBJxVLECaHWsZcBIwRJUoUqZ79jvs+kUVodxHW4+IxRAgEA+lImr0GD+kk8aX5Mrk8YhWuUUeu5nzpTg== integrity sha512-I+joyoFB8pfdXUPLMqdNO08nlB5m2lbu0VQ5dpqdi/HzgVThMZPZA1cW0X8vAUvrALs5/JFRiFoR9hrLN5R5ng==
dependencies: dependencies:
prosemirror-dropcursor "1.5.0" prosemirror-dropcursor "1.5.0"
"@tiptap/extension-floating-menu@^2.0.0-beta.199": "@tiptap/extension-floating-menu@^2.0.0-beta.56":
version "2.0.0-beta.199" version "2.0.0-beta.56"
resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.199.tgz#f0b0acb64e9b9bfe4935a0bd6b6d1863ea2d39cd" resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.56.tgz#c7428d9109d215bdbd9033f69782c4aadb2aabec"
integrity sha512-ELjqnNbxW66uqg54zlP2b4EVYUWvT2WvHmeOXALzoLlNzbqUopIl3XNRsvU2Dv1W88C1UjKgnRZIkHKFE1X3CA== integrity sha512-j/evHE/6UPGkIgXny9IGcAh0IrcnQmg0b2NBYebs2mqx9xYKYoe+0jVgNdLp/0M3MRgQCzyWTyatBDBFOUR2mw==
dependencies: dependencies:
prosemirror-state "^1.4.1" prosemirror-state "1.4.1"
prosemirror-view "^1.28.2" prosemirror-view "1.26.2"
tippy.js "^6.3.7" tippy.js "^6.3.7"
"@tiptap/extension-gapcursor@^2.0.0-beta.199": "@tiptap/extension-gapcursor@^2.0.0-beta.39":
version "2.0.0-beta.199" version "2.0.0-beta.39"
resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.199.tgz#ec801e30690d1b30183b9751d64615724311be97" resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.39.tgz#b8585d2936df7ca90446758c3af90b46d552a1fb"
integrity sha512-0TDpDfDyay+IbD+wJMsBJ2c0Cq0NtllUOxbi0NPjjWW94Jrvs1yqUSzX4Qp9m5MW8qP24IV6krgZBM1JyQc6ng== integrity sha512-oCyz5WEeQXrEIoa1WXaD52yf1EwMFCXaK1cVzFgUj8lkXJ+nJj+O/Zp0Mg+9/MVR0LYu/kifqVorKNXM4AFA/g==
dependencies: dependencies:
prosemirror-gapcursor "^1.3.1" prosemirror-gapcursor "1.3.0"
"@tiptap/extension-hard-break@^2.0.0-beta.199": "@tiptap/extension-hard-break@^2.0.0-beta.33":
version "2.0.0-beta.199" version "2.0.0-beta.33"
resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.199.tgz#6ac23806c4e7e13f15e56cc2e1db7c12248e8091" resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.33.tgz#e2f355a22aaaec6e831cf2880c52aa5b0b860573"
integrity sha512-DF2wDo/+gSYRhzGowCvZJk3/j/zYJ22BHxZpkAEmLJ69mWSIqZv3S2/brujnNmnji9c3/+JN7ppPSeVykz0b9Q== integrity sha512-41xf0vSV9hcyTFd01ItLq/CjhjgmOFLCrO3UWN/P2E/cIxuDTyXcvjTE/KXeqRCOV3OYd9fVr0wO91hc8Ij1Yg==
"@tiptap/extension-heading@^2.0.0-beta.199": "@tiptap/extension-heading@^2.0.0-beta.29":
version "2.0.0-beta.199" version "2.0.0-beta.29"
resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.199.tgz#b766de44fd09843364280a03d1e180db346f1ccd" resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.29.tgz#d017d216c0fd1962c266f6f61a335093f9749862"
integrity sha512-WGQ7ET2TBpldrD8JX37OXHXq05LU3OWItIVBs9nKGh4otZTUwPtwfOyMlFfA+IMfQif+ilwLGvUC6EHOw/LwxQ== integrity sha512-q92jYcsT5bPhvuQaB0h44Z9r+Ii22tDYo082KMVnR4+tknHT/3xx+p4JC8KHjh+/5W8Quyafqy6mS8L8VX0zsQ==
"@tiptap/extension-history@^2.0.0-beta.199": "@tiptap/extension-history@^2.0.0-beta.26":
version "2.0.0-beta.199" version "2.0.0-beta.26"
resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.0-beta.199.tgz#ff83184b6f7e0ed9a5c6362cfbc15319a451b960" resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.0-beta.26.tgz#ae4c0ee8d19b3530e72d99cb5d0f69aefcf96d04"
integrity sha512-oZMjKHFqqZuUuf0+IG5+OoKw9DIGilG+v8cm2JK9XnxF5CxF6HIXNDWl3552wRIA+Ro7fBRJEJ//hfJzp0Uhjw== integrity sha512-ly19uwvdmXG8Fw1KcavXIHi3Qx6JBASOR7394zghOEpW3atpY8nd/8I373rZ8eDUcGOClfaF7bCx2xvIotAAnw==
dependencies: dependencies:
prosemirror-history "^1.3.0" prosemirror-history "1.3.0"
"@tiptap/extension-horizontal-rule@^2.0.0-beta.199": "@tiptap/extension-horizontal-rule@^2.0.0-beta.36":
version "2.0.0-beta.199" version "2.0.0-beta.36"
resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.199.tgz#6037b11f5a300ce0921a483d7fbbeea6a5b58f58" resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.36.tgz#daf8e2d0f30b210a90fdb8f015646653661cfa04"
integrity sha512-ISQndGiC6Y3+Ds3OJHKa2iB7s4FkRQxn8US/Hhj4yK7DOifoykLOrgDghwLu0H0dSM8KNb9caYEtmj64vDogNg== integrity sha512-o+Zp7dcn3zAQhtlhZiFB69mTHuH3ZRbGEF7Cbf1D3uX1izotni5zIZbPaFFUT4r6OmVe/vDDt/nopfcGc10ktQ==
dependencies: dependencies:
prosemirror-state "^1.4.1" prosemirror-state "1.4.1"
"@tiptap/extension-image@2.0.0-beta.199": "@tiptap/extension-image@2.0.0-beta.30":
version "2.0.0-beta.199" version "2.0.0-beta.30"
resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.199.tgz#48704c12e0cfc3da06a17cd44d2d8a8b9fd33de7" resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.30.tgz#60c6cfd09bfd017a3d8b1feaf0931462ffd71a60"
integrity sha512-prmFRXdJucUeWVhSprrPfcn9pAtYkzm57N8WOUlVad5ocdeR9aMz9+0u0akb5v6XhaVAQgWqwqNb0Kx8lSuqhA== integrity sha512-VhEmgiKkZMiKR7hbpJgIlIUS/QNjSGI5ER7mKDAbuV1IB5yb6nGjZ6o3Exrr2/CaTaW5hQarBC1z2Xgdu05EGg==
"@tiptap/extension-italic@^2.0.0-beta.199": "@tiptap/extension-italic@^2.0.0-beta.28":
version "2.0.0-beta.199" version "2.0.0-beta.28"
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.199.tgz#db24dbdd0d47fdfaa22dde8ba35e2c08b7162e82" resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.28.tgz#bf88ecae64c8f2f69f1f508b802c1efd7454a84e"
integrity sha512-jaYJr5ZMxU2swK6h1XJr6Wb1LlWOWbvsX/wo59iZ9KVv1AHiKZlCMcWGThy4aoAs/CUT11pB8qbzyOO163LHZg== integrity sha512-/pKRiCfewh7nqiXRD3N4hQHfGrGNOiWPFYZfY35bSpvTms7PDb/MF7xT1CWW23hSpY31BBS+R/a66vlR/gqu7Q==
"@tiptap/extension-link@2.0.0-beta.199": "@tiptap/extension-link@2.0.0-beta.43":
version "2.0.0-beta.199" version "2.0.0-beta.43"
resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.199.tgz#57865aa0211d7d9972ed5fee9a92fedbf0083b37" resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.43.tgz#c123a2170dd50d075b9fe7fb91d86d23f778ffb0"
integrity sha512-zwXDg+zsHhn2a4rJkFd/pND4zUfJ2RCgyrkBqAL+nimSiknaSsIw4NpnZTZFWze9i3NDcc2BNngDNovoEIEukg== integrity sha512-AYueqfTW713KGVfWSWhVbj4ObeWudgawikm3m0uYcKSdsAz/CfEvOD2/NA0uyQzlxmYLA6Pf8HMxoKGN+O4Cmg==
dependencies: dependencies:
linkifyjs "^3.0.5" linkifyjs "^3.0.5"
prosemirror-model "^1.18.1" prosemirror-model "1.18.1"
prosemirror-state "^1.4.1" prosemirror-state "1.4.1"
"@tiptap/extension-list-item@^2.0.0-beta.199": "@tiptap/extension-list-item@^2.0.0-beta.23":
version "2.0.0-beta.199" version "2.0.0-beta.23"
resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.199.tgz#2e667f0ea5d9314307427625345e915edf91b989" resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.23.tgz#6d1ac7235462b0bcee196f42bb1871669480b843"
integrity sha512-rzcz5MJgoX1M9M9e1iruyRxcwYyYmdCXsl9gB8hhJYh4R+AW1peRmHJ3vVX5oPZXg/tXOMTv/or2x8v30c9tJw== integrity sha512-AkzvdELz3ZnrlZM0r9+ritBDOnAjXHR/8zCZhW0ZlWx4zyKPMsNG5ygivY+xr4QT65NEGRT8P8b2zOhXrMjjMQ==
"@tiptap/extension-mention@2.0.0-beta.199": "@tiptap/extension-mention@2.0.0-beta.102":
version "2.0.0-beta.199" version "2.0.0-beta.102"
resolved "https://registry.yarnpkg.com/@tiptap/extension-mention/-/extension-mention-2.0.0-beta.199.tgz#15f021411d48809ffc4c908204e6e262b0b9160b" resolved "https://registry.yarnpkg.com/@tiptap/extension-mention/-/extension-mention-2.0.0-beta.102.tgz#a80036b0a4481efc4f69b788af3f5c76428624cc"
integrity sha512-zNCZbU03GNPaO9Aga/3AKUQBv0EridVdIFxUY3GVb5uixoxMeXDf/OI0GqnQ2KyW7ufmD5VdlZS0mP/9QCo+DA== integrity sha512-QTBBpWnRnoV7/ZW31HwhPvZL3HiwnlehlHSLeMioVxAQPF5WrRtlOpxK/SRu7+KuwdCb7ZA1eWW/yjbXI3oktg==
dependencies: dependencies:
prosemirror-model "^1.18.1" "@tiptap/suggestion" "^2.0.0-beta.97"
prosemirror-state "^1.4.1" prosemirror-model "1.18.1"
prosemirror-state "1.4.1"
"@tiptap/extension-ordered-list@^2.0.0-beta.199": "@tiptap/extension-ordered-list@^2.0.0-beta.30":
version "2.0.0-beta.199" version "2.0.0-beta.30"
resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.199.tgz#cdc6eb58e94d095013b5182683ce1e31b8733083" resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.30.tgz#1f656b664302d90272c244b2e478d7056203f2a8"
integrity sha512-ciQhBRtNUudQyCgvQKRZ1WbV7Q9IZP82GHEsk+wScZgI0SsrGY8pnfJT7CyF8aPIjkQkccozKVTbyMrjBOqWSw== integrity sha512-GRxGQdq1u0Rp5N8TjthCqoZ//460m343A0HCN7UwfQOnX7Ipv0UJemwNkSHWrl7Pexym9vy3yPWgrn7oRRmgEw==
"@tiptap/extension-paragraph@^2.0.0-beta.199": "@tiptap/extension-paragraph@^2.0.0-beta.26":
version "2.0.0-beta.199" version "2.0.0-beta.26"
resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.199.tgz#34213e6594a1183a77bb33ced49502bafb0a3d1c" resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.26.tgz#5199c8cedb9c076347a2e15cc67442ef7c3c3fbb"
integrity sha512-+BoMCaxlsHqw065zTUNd+ywkvFJzNKbTY461/AlKX2dgHeaO8doXHDQK+9icOpibQvrKaMhOJmuBTgGlJlUUgw== integrity sha512-WcYsuUa7LLfk0vi7I1dVjdMRu53B52FMMqd+UL1qPdDKVkU3DBsZVwPj+yyfQyqN8Mc/xyg9VacGaiKFLmWNDg==
"@tiptap/extension-placeholder@2.0.0-beta.199": "@tiptap/extension-placeholder@2.0.0-beta.53":
version "2.0.0-beta.199" version "2.0.0-beta.53"
resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.0.0-beta.199.tgz#0208c42f8b92a88e66b726353d07b652f09fd823" resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.0.0-beta.53.tgz#df29d813044da9a0e30bf8409335e77f6857c2b2"
integrity sha512-Tdq0r9XQ6hcu4ASvw2Xko6h8uS/xONmMmOFiTkK/54REB3RRQpkdCtXrhFn/T4DunJVBf6FUOLTjYN3SONhuew== integrity sha512-NGU/a+GvcJVBjFqb2vI45+rNa3Cjsq/M+R/2xg9olb1w/HBr17NKf/5WSoqcc1S2cdnmMH6rB0/mVhG7Ciur+Q==
dependencies: dependencies:
prosemirror-model "^1.18.1" prosemirror-model "1.18.1"
prosemirror-state "^1.4.1" prosemirror-state "1.4.1"
prosemirror-view "^1.28.2" prosemirror-view "1.26.2"
"@tiptap/extension-strike@^2.0.0-beta.199": "@tiptap/extension-strike@^2.0.0-beta.29":
version "2.0.0-beta.199" version "2.0.0-beta.29"
resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.199.tgz#5fc6e067728009d92027e58a042f18449f2fa264" resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.29.tgz#7004d0c5d126b0517fa78efc5a333a4b8e3334bf"
integrity sha512-KyN5+d9o9FGvrSiSuh81oo4+XjMDsZVY4UHc9lBY0nAzaGAkJOwkCjk40RfyO5ZJ2GdEEQ6Nh/3YqVMcJTY+rA== integrity sha512-zqFuY7GfNmZ/KClt6kxQ+msGo3syqucP/Xnlihxi+/h/G+oTvEwyOIXCtDOltvxcsWH/TUsdr5vzLp0j+Mdc6Q==
"@tiptap/extension-text@^2.0.0-beta.199": "@tiptap/extension-text@^2.0.0-beta.17":
version "2.0.0-beta.199" version "2.0.0-beta.17"
resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.199.tgz#0204f3e50622e39b6fd08b5ef72fe7d0199117f5" resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.17.tgz#4fdd1bdf62c82c1af6feef91c689906a8f5b171e"
integrity sha512-ntOqEhkBjDHrdzxvpPe4U1JB5GgE9/yyWqWdgzSL9lpSndRTJN1xQLOmyuv0qsLqOgBHn1YITHvaxPb3t8FrFw== integrity sha512-OyKL+pqWJEtjyd9/mrsuY1kZh2b3LWpOQDWKtd4aWR4EA0efmQG+7FPwcIeAVEh7ZoqM+/ABCnPjN6IjzIrSfg==
"@tiptap/html@2.0.0-beta.199": "@tiptap/react@2.0.0-beta.114":
version "2.0.0-beta.199" version "2.0.0-beta.114"
resolved "https://registry.yarnpkg.com/@tiptap/html/-/html-2.0.0-beta.199.tgz#cb33319ad00b31ebf68dcc9cbb758c0aff78403c" resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.0.0-beta.114.tgz#fa2b3fcdf379bf7ee25388c0eddbda49249977d5"
integrity sha512-/Ow/LOCXZrNXR6cYJMN8O4ZMm7Jl/t6L0K6MCJ1p+pKu11xtf1mI73t8WYFA5J8t7cL7eWUgDPtqlOE+DJTvHg== integrity sha512-9JbRE+16WM6RxbBxzY74SrJtLodvjeRBnEbWxuhxVgGKxMunRj6r8oED87ODJgqLmkpofwE0KFHTPGdEXfdcKA==
dependencies: dependencies:
"@tiptap/core" "^2.0.0-beta.199" "@tiptap/extension-bubble-menu" "^2.0.0-beta.61"
prosemirror-model "^1.18.1" "@tiptap/extension-floating-menu" "^2.0.0-beta.56"
zeed-dom "^0.9.19" prosemirror-view "1.26.2"
"@tiptap/react@2.0.0-beta.199": "@tiptap/starter-kit@2.0.0-beta.191":
version "2.0.0-beta.199" version "2.0.0-beta.191"
resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.0.0-beta.199.tgz#98f9fb2134fdc385648ed06ea1ec78902c0e99cf" resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-2.0.0-beta.191.tgz#3f549367f6dbb8cf83f63aa0941722d91d0fd8e7"
integrity sha512-AjBtoavcJ7WOoEXdJlrVEdEv6xuI5UFnqB88w8NlORSkWbfQ3uuOm3A0LUZ92/SsBz6NISZbsFahMy0DYgGbIA== integrity sha512-YRrBCi9W4jiH/xLTJJOCdD7pL4Wb98Ip8qCJ94RElShDj0O1i5tT9wWlgVWoGIU+CRAds5XENRwZ97sJ+YfYyg==
dependencies: dependencies:
"@tiptap/extension-bubble-menu" "^2.0.0-beta.199" "@tiptap/core" "^2.0.0-beta.182"
"@tiptap/extension-floating-menu" "^2.0.0-beta.199" "@tiptap/extension-blockquote" "^2.0.0-beta.29"
prosemirror-view "^1.28.2" "@tiptap/extension-bold" "^2.0.0-beta.28"
"@tiptap/extension-bullet-list" "^2.0.0-beta.29"
"@tiptap/extension-code" "^2.0.0-beta.28"
"@tiptap/extension-code-block" "^2.0.0-beta.42"
"@tiptap/extension-document" "^2.0.0-beta.17"
"@tiptap/extension-dropcursor" "^2.0.0-beta.29"
"@tiptap/extension-gapcursor" "^2.0.0-beta.39"
"@tiptap/extension-hard-break" "^2.0.0-beta.33"
"@tiptap/extension-heading" "^2.0.0-beta.29"
"@tiptap/extension-history" "^2.0.0-beta.26"
"@tiptap/extension-horizontal-rule" "^2.0.0-beta.36"
"@tiptap/extension-italic" "^2.0.0-beta.28"
"@tiptap/extension-list-item" "^2.0.0-beta.23"
"@tiptap/extension-ordered-list" "^2.0.0-beta.30"
"@tiptap/extension-paragraph" "^2.0.0-beta.26"
"@tiptap/extension-strike" "^2.0.0-beta.29"
"@tiptap/extension-text" "^2.0.0-beta.17"
"@tiptap/starter-kit@2.0.0-beta.199": "@tiptap/suggestion@^2.0.0-beta.97":
version "2.0.0-beta.199" version "2.0.0-beta.97"
resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-2.0.0-beta.199.tgz#b0d48784abe711afc2973592467f213f86794c82" resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.0.0-beta.97.tgz#2e3dc20deebc2c37c5d39c848e61e9c837e7188a"
integrity sha512-ToPhccdSAixNhAujBa5VEOg+G6Nkc+JJGaUVEEGH2EV2ICzbhFhcdbAHqI6lNtCFleaX9NULJBGHXEwLR5T83Q== integrity sha512-3NWG+HE7v2w97Ek6z1tUosoZKpCDH+oAtIG9XoNkK1PmlaVV/H4d6HT9uPX+Y6SeN7fSAqlcXFUGLXcDi9d+Zw==
dependencies: dependencies:
"@tiptap/core" "^2.0.0-beta.199" prosemirror-model "1.18.1"
"@tiptap/extension-blockquote" "^2.0.0-beta.199" prosemirror-state "1.4.1"
"@tiptap/extension-bold" "^2.0.0-beta.199" prosemirror-view "1.26.2"
"@tiptap/extension-bullet-list" "^2.0.0-beta.199"
"@tiptap/extension-code" "^2.0.0-beta.199"
"@tiptap/extension-code-block" "^2.0.0-beta.199"
"@tiptap/extension-document" "^2.0.0-beta.199"
"@tiptap/extension-dropcursor" "^2.0.0-beta.199"
"@tiptap/extension-gapcursor" "^2.0.0-beta.199"
"@tiptap/extension-hard-break" "^2.0.0-beta.199"
"@tiptap/extension-heading" "^2.0.0-beta.199"
"@tiptap/extension-history" "^2.0.0-beta.199"
"@tiptap/extension-horizontal-rule" "^2.0.0-beta.199"
"@tiptap/extension-italic" "^2.0.0-beta.199"
"@tiptap/extension-list-item" "^2.0.0-beta.199"
"@tiptap/extension-ordered-list" "^2.0.0-beta.199"
"@tiptap/extension-paragraph" "^2.0.0-beta.199"
"@tiptap/extension-strike" "^2.0.0-beta.199"
"@tiptap/extension-text" "^2.0.0-beta.199"
"@tiptap/suggestion@2.0.0-beta.199":
version "2.0.0-beta.199"
resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.0.0-beta.199.tgz#8bf8029a25826bc41266bb9fffe8b33195bf9a40"
integrity sha512-FwkaMW0fX1Xlsv4n5GPlVkHwwMSMvIXgZ6LZnwy4Mxo+Njz7YgHw8X8YQo927lMbYsVHYhNcsk726HaxudDMEw==
dependencies:
prosemirror-model "^1.18.1"
prosemirror-state "^1.4.1"
prosemirror-view "^1.28.2"
"@tootallnate/once@2": "@tootallnate/once@2":
version "2.0.0" version "2.0.0"
@ -3498,11 +3483,6 @@
"@types/node" "*" "@types/node" "*"
form-data "^2.5.0" form-data "^2.5.0"
"@types/marked@4.0.7":
version "4.0.7"
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.0.7.tgz#400a76809fd08c2bbd9e25f3be06ea38c8e0a1d3"
integrity sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==
"@types/mdast@^3.0.0": "@types/mdast@^3.0.0":
version "3.0.10" version "3.0.10"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
@ -4981,9 +4961,9 @@ commander@^8.0.0, commander@^8.3.0:
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
commander@^9.4.0: commander@^9.4.0:
version "9.4.0" version "9.4.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.0.tgz#bc4a40918fefe52e22450c111ecd6b7acce6f11c" resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd"
integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw== integrity sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==
commondir@^1.0.1: commondir@^1.0.1:
version "1.0.1" version "1.0.1"
@ -5482,6 +5462,11 @@ d3-transition@3:
d3-interpolate "1 - 3" d3-interpolate "1 - 3"
d3-timer "1 - 3" d3-timer "1 - 3"
daisyui@1.16.4:
version "1.16.4"
resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-1.16.4.tgz#52773401c0962e37ef40507d29f0e513c7f2856f"
integrity sha512-bpPUlIR6PJdnaM+Vj+Rd0ljMwbdwjvFAW9E/bhxCRDEU48OnmpKQxnkwNGAtXbwWWATS6I1dEIlLdM8zCbV3uQ==
damerau-levenshtein@^1.0.7: damerau-levenshtein@^1.0.7:
version "1.0.8" version "1.0.8"
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
@ -5859,9 +5844,9 @@ dot-prop@^5.2.0:
is-obj "^2.0.0" is-obj "^2.0.0"
dotenv@^16.0.2: dotenv@^16.0.2:
version "16.0.2" version "16.0.3"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.2.tgz#0b0f8652c016a3858ef795024508cddc4bffc5bf" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07"
integrity sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA== integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==
duplexer3@^0.1.4: duplexer3@^0.1.4:
version "0.1.4" version "0.1.4"
@ -7094,9 +7079,9 @@ google-p12-pem@^3.1.3:
node-forge "^1.3.1" node-forge "^1.3.1"
google-protobuf@^3.21.0: google-protobuf@^3.21.0:
version "3.21.0" version "3.21.2"
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.0.tgz#8dfa3fca16218618d373d414d3c1139e28034d6e" resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.2.tgz#4580a2bea8bbb291ee579d1fefb14d6fa3070ea4"
integrity sha512-byR7MBTK4tZ5PZEb+u5ZTzpt4SfrTxv5682MjPlHN16XeqgZE2/8HOIWeiXe8JKnT9OVbtBGhbq8mtvkK8cd5g== integrity sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==
got@^9.6.0: got@^9.6.0:
version "9.6.0" version "9.6.0"
@ -8596,11 +8581,6 @@ markdown-escapes@^1.0.0:
resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535"
integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==
marked@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.1.1.tgz#2f709a4462abf65a283f2453dc1c42ab177d302e"
integrity sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==
match-sorter@^6.0.2: match-sorter@^6.0.2:
version "6.3.1" version "6.3.1"
resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda" resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda"
@ -9913,10 +9893,10 @@ property-information@^5.0.0, property-information@^5.3.0:
dependencies: dependencies:
xtend "^4.0.0" xtend "^4.0.0"
prosemirror-commands@^1.3.1: prosemirror-commands@1.3.0:
version "1.3.1" version "1.3.0"
resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.3.1.tgz#926c88801eebaa50363d4658850b41406d375a31" resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.3.0.tgz#361b2e2b2a347ce7453386459f97c3f549a1113b"
integrity sha512-XTporPgoECkOQACVw0JTe3RZGi+fls3/byqt+tXwGTkD7qLuB4KdVrJamDMJf4kfKga3uB8hZ+kUUyZ5oWpnfg== integrity sha512-BwBbZ5OAScPcm0x7H8SPbqjuEJnCU2RJT9LDyOiiIl/3NbL1nJZI4SFNHwU2e/tRr2Xe7JsptpzseqvZvToLBQ==
dependencies: dependencies:
prosemirror-model "^1.0.0" prosemirror-model "^1.0.0"
prosemirror-state "^1.0.0" prosemirror-state "^1.0.0"
@ -9931,17 +9911,17 @@ prosemirror-dropcursor@1.5.0:
prosemirror-transform "^1.1.0" prosemirror-transform "^1.1.0"
prosemirror-view "^1.1.0" prosemirror-view "^1.1.0"
prosemirror-gapcursor@^1.3.1: prosemirror-gapcursor@1.3.0:
version "1.3.1" version "1.3.0"
resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.1.tgz#8cfd874592e4504d63720e14ed680c7866e64554" resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.0.tgz#e07c22ad959b86ec0c4cfc590cc5f484dd984d56"
integrity sha512-GKTeE7ZoMsx5uVfc51/ouwMFPq0o8YrZ7Hx4jTF4EeGbXxBveUV8CGv46mSHuBBeXGmvu50guoV2kSnOeZZnUA== integrity sha512-9Tdx83xB2W4Oqchm12FtCkSizbqvi64cjs1I9TRPblqdA5TUWoVZ4ZI+t71Jh6HSEh4cDMPzx3UwfryJtKlb/w==
dependencies: dependencies:
prosemirror-keymap "^1.0.0" prosemirror-keymap "^1.0.0"
prosemirror-model "^1.0.0" prosemirror-model "^1.0.0"
prosemirror-state "^1.0.0" prosemirror-state "^1.0.0"
prosemirror-view "^1.0.0" prosemirror-view "^1.0.0"
prosemirror-history@^1.3.0: prosemirror-history@1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.3.0.tgz#bf5a1ff7759aca759ddf0c722c2fa5b14fb0ddc1" resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.3.0.tgz#bf5a1ff7759aca759ddf0c722c2fa5b14fb0ddc1"
integrity sha512-qo/9Wn4B/Bq89/YD+eNWFbAytu6dmIM85EhID+fz9Jcl9+DfGEo8TTSrRhP15+fFEoaPqpHSxlvSzSEbmlxlUA== integrity sha512-qo/9Wn4B/Bq89/YD+eNWFbAytu6dmIM85EhID+fz9Jcl9+DfGEo8TTSrRhP15+fFEoaPqpHSxlvSzSEbmlxlUA==
@ -9950,7 +9930,7 @@ prosemirror-history@^1.3.0:
prosemirror-transform "^1.0.0" prosemirror-transform "^1.0.0"
rope-sequence "^1.3.0" rope-sequence "^1.3.0"
prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.2.0: prosemirror-keymap@1.2.0, prosemirror-keymap@^1.0.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.0.tgz#d5cc9da9b712020690a994b50b92a0e448a60bf5" resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.0.tgz#d5cc9da9b712020690a994b50b92a0e448a60bf5"
integrity sha512-TdSfu+YyLDd54ufN/ZeD1VtBRYpgZnTPnnbY+4R08DDgs84KrIPEPbJL8t1Lm2dkljFx6xeBE26YWH3aIzkPKg== integrity sha512-TdSfu+YyLDd54ufN/ZeD1VtBRYpgZnTPnnbY+4R08DDgs84KrIPEPbJL8t1Lm2dkljFx6xeBE26YWH3aIzkPKg==
@ -9958,23 +9938,23 @@ prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.2.0:
prosemirror-state "^1.0.0" prosemirror-state "^1.0.0"
w3c-keyname "^2.2.0" w3c-keyname "^2.2.0"
prosemirror-model@^1.0.0, prosemirror-model@^1.16.0, prosemirror-model@^1.18.1: prosemirror-model@1.18.1, prosemirror-model@^1.0.0, prosemirror-model@^1.16.0:
version "1.18.1" version "1.18.1"
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.18.1.tgz#1d5d6b6de7b983ee67a479dc607165fdef3935bd" resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.18.1.tgz#1d5d6b6de7b983ee67a479dc607165fdef3935bd"
integrity sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw== integrity sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw==
dependencies: dependencies:
orderedmap "^2.0.0" orderedmap "^2.0.0"
prosemirror-schema-list@^1.2.2: prosemirror-schema-list@1.2.0:
version "1.2.2" version "1.2.0"
resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.2.2.tgz#bafda37b72367d39accdcaf6ddf8fb654a16e8e5" resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.2.0.tgz#1932268593a7396c0ac168cbe31f28187406ce24"
integrity sha512-rd0pqSDp86p0MUMKG903g3I9VmElFkQpkZ2iOd3EOVg1vo5Cst51rAsoE+5IPy0LPXq64eGcCYlW1+JPNxOj2w== integrity sha512-8PT/9xOx1HHdC7fDNNfhQ50Z8Mzu7nKyA1KCDltSpcZVZIbB0k7KtsHrnXyuIhbLlScoymBiLZ00c5MH6wdFsA==
dependencies: dependencies:
prosemirror-model "^1.0.0" prosemirror-model "^1.0.0"
prosemirror-state "^1.0.0" prosemirror-state "^1.0.0"
prosemirror-transform "^1.0.0" prosemirror-transform "^1.0.0"
prosemirror-state@1.4.1, prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.1: prosemirror-state@1.4.1, prosemirror-state@^1.0.0, prosemirror-state@^1.2.2:
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.1.tgz#f6e26c7b6a7e11206176689eb6ebbf91870953e1" resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.1.tgz#f6e26c7b6a7e11206176689eb6ebbf91870953e1"
integrity sha512-U/LBDW2gNmVa07sz/D229XigSdDQ5CLFwVB1Vb32MJbAHHhWe/6pOc721faI17tqw4pZ49i1xfY/jEZ9tbIhPg== integrity sha512-U/LBDW2gNmVa07sz/D229XigSdDQ5CLFwVB1Vb32MJbAHHhWe/6pOc721faI17tqw4pZ49i1xfY/jEZ9tbIhPg==
@ -9982,21 +9962,14 @@ prosemirror-state@1.4.1, prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, pro
prosemirror-model "^1.0.0" prosemirror-model "^1.0.0"
prosemirror-transform "^1.0.0" prosemirror-transform "^1.0.0"
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0: prosemirror-transform@1.6.0, prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.6.0.tgz#8162dbfaf124f9253a7ab28605a9460411a96a53" resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.6.0.tgz#8162dbfaf124f9253a7ab28605a9460411a96a53"
integrity sha512-MAp7AjsjEGEqQY0sSMufNIUuEyB1ZR9Fqlm8dTwwWwpEJRv/plsKjWXBbx52q3Ml8MtaMcd7ic14zAHVB3WaMw== integrity sha512-MAp7AjsjEGEqQY0sSMufNIUuEyB1ZR9Fqlm8dTwwWwpEJRv/plsKjWXBbx52q3Ml8MtaMcd7ic14zAHVB3WaMw==
dependencies: dependencies:
prosemirror-model "^1.0.0" prosemirror-model "^1.0.0"
prosemirror-transform@^1.7.0: prosemirror-view@1.26.2, prosemirror-view@^1.0.0, prosemirror-view@^1.1.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.7.0.tgz#a8a0768f3ee6418d26ebef435beda9d43c65e472"
integrity sha512-O4T697Cqilw06Zvc3Wm+e237R6eZtJL/xGMliCi+Uo8VL6qHk6afz1qq0zNjT3eZMuYwnP8ZS0+YxX/tfcE9TQ==
dependencies:
prosemirror-model "^1.0.0"
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0:
version "1.26.2" version "1.26.2"
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.26.2.tgz#e673894ecf26aea330b727622d561c51b41d31eb" resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.26.2.tgz#e673894ecf26aea330b727622d561c51b41d31eb"
integrity sha512-CGKw+GadkfSBEwRAJTHCEKJ4DlV6/3IhAdjpwGyZHUHtbP7jX4Ol4zmi7xa2c6GOabDlIJLYXJydoNYLX7lNeQ== integrity sha512-CGKw+GadkfSBEwRAJTHCEKJ4DlV6/3IhAdjpwGyZHUHtbP7jX4Ol4zmi7xa2c6GOabDlIJLYXJydoNYLX7lNeQ==
@ -10005,15 +9978,6 @@ prosemirror-view@^1.0.0, prosemirror-view@^1.1.0:
prosemirror-state "^1.0.0" prosemirror-state "^1.0.0"
prosemirror-transform "^1.1.0" prosemirror-transform "^1.1.0"
prosemirror-view@^1.28.2:
version "1.28.3"
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.28.3.tgz#51131ede4d3852380be9a5e0e4ba7662725dac1f"
integrity sha512-YnJxLRzIaCNEt3VKiy+PBxtpwsCbjrfiBKIgHJeqbKhdeP8bU2qL4ngdGmxp9K4+06cZG5bE9vipuhP+KUl+BQ==
dependencies:
prosemirror-model "^1.16.0"
prosemirror-state "^1.0.0"
prosemirror-transform "^1.1.0"
proto3-json-serializer@^0.1.8: proto3-json-serializer@^0.1.8:
version "0.1.9" version "0.1.9"
resolved "https://registry.yarnpkg.com/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz#705ddb41b009dd3e6fcd8123edd72926abf65a34" resolved "https://registry.yarnpkg.com/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz#705ddb41b009dd3e6fcd8123edd72926abf65a34"
@ -10218,25 +10182,6 @@ raw-body@2.5.1, raw-body@^2.2.0:
iconv-lite "0.4.24" iconv-lite "0.4.24"
unpipe "1.0.0" unpipe "1.0.0"
rc-slider@10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-10.0.1.tgz#7058c68ff1e1aa4e7c3536e5e10128bdbccb87f9"
integrity sha512-igTKF3zBet7oS/3yNiIlmU8KnZ45npmrmHlUUio8PNbIhzMcsh+oE/r2UD42Y6YD2D/s+kzCQkzQrPD6RY435Q==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "^2.2.5"
rc-util "^5.18.1"
shallowequal "^1.1.0"
rc-util@^5.18.1:
version "5.24.4"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.24.4.tgz#a4126f01358c86f17c1bf380a1d83d6c9155ae65"
integrity sha512-2a4RQnycV9eV7lVZPEJ7QwJRPlZNc06J7CwcwZo4vIHr3PfUqtYgl1EkUV9ETAc6VRRi8XZOMFhYG63whlIC9Q==
dependencies:
"@babel/runtime" "^7.18.3"
react-is "^16.12.0"
shallowequal "^1.1.0"
rc@^1.2.8: rc@^1.2.8:
version "1.2.8" version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@ -10367,7 +10312,7 @@ react-instantsearch-hooks@6.24.1:
dequal "^2.0.0" dequal "^2.0.0"
instantsearch.js "^4.40.1" instantsearch.js "^4.40.1"
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -11371,10 +11316,10 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
stability-client@1.6.1: stability-client@1.5.0:
version "1.6.1" version "1.5.0"
resolved "https://registry.yarnpkg.com/stability-client/-/stability-client-1.6.1.tgz#960250c8083119227d25972db662c873333f9a26" resolved "https://registry.yarnpkg.com/stability-client/-/stability-client-1.5.0.tgz#f221420a297c808f209c469a0df8fa2401f8f6ae"
integrity sha512-N5igY8YH1vT6T0p++GWbr1KHqeAyYO+/WGgrgsTeKk7bCTXLeQng8B9OWYOOAczb95iyvFXDdUCRj5Cry00iaA== integrity sha512-hXuDK6QW/msf50pu8M4L1hTCYG4w5ZrhLgxirigUrSZEARupIPT88O7qz4YSmUbbp9nKlzS8UJ6NjYUH7qy45w==
dependencies: dependencies:
"@improbable-eng/grpc-web" "^0.15.0" "@improbable-eng/grpc-web" "^0.15.0"
"@improbable-eng/grpc-web-node-http-transport" "^0.15.0" "@improbable-eng/grpc-web-node-http-transport" "^0.15.0"
@ -12682,13 +12627,6 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zeed-dom@^0.9.19:
version "0.9.26"
resolved "https://registry.yarnpkg.com/zeed-dom/-/zeed-dom-0.9.26.tgz#f0127d1024b34a1233a321bd6d0275b3ba998b30"
integrity sha512-HWjX8rA3Y/RI32zby3KIN1D+mgskce+She4K7kRyyx62OiVxJ5FnYm8vWq0YVAja3Tf2S1M0XAc6O2lRFcMgcQ==
dependencies:
css-what "^6.1.0"
zod@3.17.2: zod@3.17.2:
version "3.17.2" version "3.17.2"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.17.2.tgz#d20b32146a3b5068f8f71768b4f9a4bfe52cddb0" resolved "https://registry.yarnpkg.com/zod/-/zod-3.17.2.tgz#d20b32146a3b5068f8f71768b4f9a4bfe52cddb0"