diff --git a/frontend/package.json b/frontend/package.json index 699487c2..b4d2358e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,6 +35,7 @@ "react-dom": "^18.2.0", "react-helmet-async": "^2.0.5", "react-intersection-observer": "^9.8.1", + "react-markdown": "^9.0.1", "react-parallax-tilt": "^1.7.217", "react-responsive": "^9.0.2", "react-router-dom": "^6.22.2", diff --git a/frontend/src/components/ACLs/allowAll.ts b/frontend/src/components/ACLs/allowAll.ts index 0709114a..ba81268e 100644 --- a/frontend/src/components/ACLs/allowAll.ts +++ b/frontend/src/components/ACLs/allowAll.ts @@ -1,4 +1,4 @@ -import { defineACL } from './common' +import { CheckPermissionResults, defineACL } from './common' import { designDecisions, VITE_CONTRACT_ACL_ALLOWALL } from '../../constants/config' import { denyWithReason, useOneOfField } from '../InputFields' @@ -34,7 +34,7 @@ export const allowAll = defineACL({ }), isThisMine: options => 'allowAll' in options, - checkPermission: async (pollACL, daoAddress, proposalId, userAddress) => { + checkPermission: async (pollACL, daoAddress, proposalId, userAddress): Promise => { const proof = new Uint8Array() const result = 0n !== (await pollACL.canVoteOnPoll(daoAddress, proposalId, userAddress, proof)) const canVote = result ? true : denyWithReason('some unknown reason') diff --git a/frontend/src/components/ACLs/allowList.ts b/frontend/src/components/ACLs/allowList.ts index 30c4dd73..2292b859 100644 --- a/frontend/src/components/ACLs/allowList.ts +++ b/frontend/src/components/ACLs/allowList.ts @@ -1,4 +1,4 @@ -import { defineACL } from './common' +import { CheckPermissionResults, defineACL } from './common' import { designDecisions, VITE_CONTRACT_ACL_VOTERALLOWLIST } from '../../constants/config' import { abiEncode, isValidAddress } from '../../utils/poll.utils' import { denyWithReason, useOneOfField, useTextArrayField } from '../InputFields' @@ -80,7 +80,7 @@ export const allowList = defineACL({ isThisMine: options => 'allowList' in options, - checkPermission: async (pollACL, daoAddress, proposalId, userAddress) => { + checkPermission: async (pollACL, daoAddress, proposalId, userAddress): Promise => { const proof = new Uint8Array() const explanation = 'This poll is only for a predefined list of addresses.' const result = 0n !== (await pollACL.canVoteOnPoll(daoAddress, proposalId, userAddress, proof)) diff --git a/frontend/src/components/ACLs/common.ts b/frontend/src/components/ACLs/common.ts index ed95db1c..a5a24638 100644 --- a/frontend/src/components/ACLs/common.ts +++ b/frontend/src/components/ACLs/common.ts @@ -1,9 +1,17 @@ import { Choice, DecisionWithReason, FieldConfiguration } from '../InputFields' import { AclOptions, IPollACL } from '@oasisprotocol/blockvote-contracts' import { BytesLike } from 'ethers' -import { ReactNode } from 'react' +import { MarkdownCode } from '../../types' + export type StatusUpdater = (status: string | undefined) => void +export type CheckPermissionResults = { + canVote: DecisionWithReason + explanation?: MarkdownCode + proof: BytesLike + error?: string +} + /** * This data structure describes an ACL */ @@ -56,7 +64,7 @@ export type ACL = Choice Promise<{ canVote: DecisionWithReason; explanation?: ReactNode; proof: BytesLike; error?: string }> + ) => Promise } export function defineACL( diff --git a/frontend/src/components/ACLs/tokenHolder.tsx b/frontend/src/components/ACLs/tokenHolder.ts similarity index 81% rename from frontend/src/components/ACLs/tokenHolder.tsx rename to frontend/src/components/ACLs/tokenHolder.ts index cad5639d..47b9e857 100644 --- a/frontend/src/components/ACLs/tokenHolder.tsx +++ b/frontend/src/components/ACLs/tokenHolder.ts @@ -1,4 +1,4 @@ -import { defineACL } from './common' +import { CheckPermissionResults, defineACL } from './common' import { DecisionWithReason, denyWithReason, useLabel, useOneOfField, useTextField } from '../InputFields' import { abiEncode, getLocalContractDetails, isValidAddress } from '../../utils/poll.utils' import { @@ -9,6 +9,8 @@ import { } from '../../constants/config' import { StringUtils } from '../../utils/string.utils' import { FLAG_WEIGHT_LOG10, FLAG_WEIGHT_ONE } from '../../types' +import { renderMarkdown } from '../Markdown' +import { getLink } from '../../utils/markdown.utils' export const tokenHolder = defineACL({ value: 'acl_tokenHolder', @@ -53,14 +55,7 @@ export const tokenHolder = defineACL({ visible: hasValidSapphireTokenAddress, label: 'Selected token:', initialValue: '', - renderer: name => - tokenUrl ? ( - - {name} - - ) : ( - name - ), + renderer: name => renderMarkdown(getLink({ label: name, href: tokenUrl })), }) const tokenSymbol = useLabel({ @@ -123,27 +118,19 @@ export const tokenHolder = defineACL({ isThisMine: options => 'token' in options, - checkPermission: async (pollACL, daoAddress, proposalId, userAddress, options) => { + checkPermission: async ( + pollACL, + daoAddress, + proposalId, + userAddress, + options, + ): Promise => { const tokenAddress = options.token const tokenInfo = await getLocalContractDetails(tokenAddress) const url = configuredExplorerUrl ? StringUtils.getTokenUrl(configuredExplorerUrl, tokenAddress) : undefined - const explanation = url ? ( - - You need to hold some{' '} - - {tokenInfo?.name ?? 'specific'} - {' '} - (on the{' '} - - {configuredNetworkName} - - ) to vote. - - ) : ( - `You need to hold some ${tokenInfo?.name ?? 'specific'} token (on the ${configuredNetworkName}) to vote.` - ) + const explanation = `You need to hold some ${getLink({ label: tokenInfo?.name ?? StringUtils.truncateAddress(tokenAddress), href: url })} on ${getLink({ label: configuredNetworkName, href: configuredExplorerUrl })} to vote.` const proof = new Uint8Array() let canVote: DecisionWithReason try { @@ -153,16 +140,7 @@ export const tokenHolder = defineACL({ canVote = true } else { canVote = denyWithReason( - url ? ( - <> - you don't hold any{' '} - - {tokenInfo?.name ?? StringUtils.truncateAddress(tokenAddress)} - - - ) : ( - `you don't hold any ${tokenInfo?.name ?? tokenAddress}` - ), + `you don't hold any ${getLink({ label: tokenInfo?.name ?? StringUtils.truncateAddress(tokenAddress), href: url })}`, ) } } catch { diff --git a/frontend/src/components/ACLs/xchain.tsx b/frontend/src/components/ACLs/xchain.ts similarity index 89% rename from frontend/src/components/ACLs/xchain.tsx rename to frontend/src/components/ACLs/xchain.ts index f73ea7a2..ae30f08b 100644 --- a/frontend/src/components/ACLs/xchain.tsx +++ b/frontend/src/components/ACLs/xchain.ts @@ -1,4 +1,4 @@ -import { defineACL } from './common' +import { CheckPermissionResults, defineACL } from './common' import { addMockValidation, Choice, @@ -32,9 +32,11 @@ import type { TokenInfo, NFTInfo } from '@oasisprotocol/blockvote-contracts' import { designDecisions, VITE_CONTRACT_ACL_STORAGEPROOF } from '../../constants/config' import classes from './index.module.css' import { BytesLike, getBytes, getUint, hexlify } from 'ethers' -import { ReactNode, useMemo } from 'react' +import { useMemo } from 'react' import { StringUtils } from '../../utils/string.utils' import { FLAG_WEIGHT_LOG10, FLAG_WEIGHT_ONE } from '../../types' +import { renderMarkdown } from '../Markdown' +import { getLink } from '../../utils/markdown.utils' export const xchain = defineACL({ value: 'acl_xchain', @@ -132,14 +134,7 @@ export const xchain = defineACL({ label: `${isToken(contractType.value as any) ? 'Token' : 'NFT'}:`, initialValue: '', compact: true, - renderer: name => - tokenUrl ? ( - - {name} - - ) : ( - name - ), + renderer: name => renderMarkdown(getLink({ label: name, href: tokenUrl })), ...addMockValidation, }) @@ -322,14 +317,20 @@ export const xchain = defineACL({ isThisMine: options => 'xchain' in options, - checkPermission: async (pollACL, daoAddress, proposalId, userAddress, options) => { + checkPermission: async ( + pollACL, + daoAddress, + proposalId, + userAddress, + options, + ): Promise => { const { xchain } = options const chainId = xchain.c const blockHash = hexlify(xchain.b) const tokenAddress = hexlify(xchain.a) const slot = xchain.s - let explanation: ReactNode = '' + let explanation = '' let error = '' let proof: BytesLike = '' let tokenInfo: TokenInfo | NFTInfo | undefined @@ -353,21 +354,7 @@ export const xchain = defineACL({ try { tokenInfo = await getContractDetails(chainId, tokenAddress) if (!tokenInfo) throw new Error("Can't load token details") - explanation = tokenUrl ? ( - - This poll is only for those who have hold{' '} - - {tokenInfo?.name ?? StringUtils.truncateAddress(tokenInfo.addr)} - {' '} - on{' '} - - {chainDefinition.name} - {' '} - when the poll was created. - - ) : ( - `This poll is only for those who have hold ${tokenInfo?.name} on ${chainDefinition.name} when the poll was created.` - ) + explanation = `This poll is only for those who have hold ${getLink({ label: tokenInfo?.name ?? StringUtils.truncateAddress(tokenInfo.addr), href: tokenUrl })} on ${getLink({ label: chainDefinition.name, href: explorerUrl })} when the poll was created.` let isBalancePositive = false const holderBalance = getUint( await fetchStorageValue(provider, blockHash, tokenAddress, slot, userAddress), @@ -383,20 +370,7 @@ export const xchain = defineACL({ } if (!isBalancePositive) { canVote = denyWithReason( - tokenUrl ? ( - - you don't hold any{' '} - - {tokenInfo.name} - {' '} - on{' '} - - {chainDefinition.name} - - - ) : ( - `you don't hold any ${tokenInfo.name} tokens on ${chainDefinition.name}` - ), + `you don't hold any ${getLink({ label: tokenInfo.name ?? StringUtils.truncateAddress(tokenAddress), href: tokenUrl })} on ${getLink({ label: chainDefinition.name, href: explorerUrl })}`, ) } } catch (e) { diff --git a/frontend/src/components/InputFields/SelectInput.tsx b/frontend/src/components/InputFields/SelectInput.tsx index 066b3009..ed9baf95 100644 --- a/frontend/src/components/InputFields/SelectInput.tsx +++ b/frontend/src/components/InputFields/SelectInput.tsx @@ -28,7 +28,7 @@ export const SelectInput: FC> = props => { key={choice.value} value={choice.value} disabled={disabled} - // TODO: we can't display HTML reason here, so we just _hope_ that it will be a string. + // TODO: we can't display HTML reason here, so display the markdown test. // The proper solution is to use a custom select component. title={(getReasonForDenial(choice.enabled) as string) ?? choice.description} > diff --git a/frontend/src/components/InputFields/TextArrayInput.tsx b/frontend/src/components/InputFields/TextArrayInput.tsx index dd6f4902..4f10c7e7 100644 --- a/frontend/src/components/InputFields/TextArrayInput.tsx +++ b/frontend/src/components/InputFields/TextArrayInput.tsx @@ -1,4 +1,4 @@ -import React, { FC, ReactNode, useCallback } from 'react' +import React, { FC, useCallback } from 'react' import { TextArrayControls } from './useTextArrayField' import classes from './index.module.css' import { StringUtils } from '../../utils/string.utils' @@ -11,9 +11,11 @@ import { WithLabelAndDescription } from './WithLabelAndDescription' import { WithValidation } from './WithValidation' import { MotionDiv } from '../Animations' import { MaybeWithTooltip } from '../Tooltip/MaybeWithTooltip' +import { MarkdownCode } from '../../types' +import { MarkdownBlock } from '../Markdown' const TrashIcon: FC<{ - label: ReactNode + label: MarkdownCode | undefined remove: () => void enabled?: boolean }> = ({ label, remove, enabled }) => { @@ -35,10 +37,10 @@ const TrashIcon: FC<{ } const AddIcon: FC<{ - label: string + label: MarkdownCode add: () => void enabled?: boolean - overlay?: string | undefined + overlay?: MarkdownCode | undefined }> = ({ label, add, enabled, overlay }) => { const handleClick = useCallback(() => { if (enabled) add() @@ -59,7 +61,7 @@ const AddIcon: FC<{ - {label} + ) diff --git a/frontend/src/components/InputFields/useInputField.ts b/frontend/src/components/InputFields/useInputField.ts index eb19f2d3..0e87e2f1 100644 --- a/frontend/src/components/InputFields/useInputField.ts +++ b/frontend/src/components/InputFields/useInputField.ts @@ -1,4 +1,4 @@ -import { ReactNode, useEffect, useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { AllProblems, ValidatorControls, @@ -14,6 +14,7 @@ import { ValidatorFunction, wrapProblem, } from './util' +import { MarkdownCode } from '../../types' type ValidatorBundle = SingleOrArray> @@ -159,7 +160,7 @@ export type InputFieldControls = Pick< type: string visible: boolean enabled: boolean - whyDisabled?: ReactNode + whyDisabled?: MarkdownCode containerClassName?: string value: DataType setValue: (value: DataType) => void diff --git a/frontend/src/components/InputFields/useTextArrayField.ts b/frontend/src/components/InputFields/useTextArrayField.ts index c8c429bf..1a0b2938 100644 --- a/frontend/src/components/InputFields/useTextArrayField.ts +++ b/frontend/src/components/InputFields/useTextArrayField.ts @@ -15,6 +15,7 @@ import { flatten, } from './util' import { useState } from 'react' +import { MarkdownCode } from '../../types' /** * Parameters for defining an input field that accepts a list of strings @@ -138,11 +139,11 @@ export type TextArrayControls = InputFieldControls & { placeholders: string[] canAddItem: boolean - addItemLabel: string + addItemLabel: MarkdownCode addItem: () => void canRemoveItem: (index: number) => boolean - removeItemLabel: string + removeItemLabel: MarkdownCode removeItem: (index: number) => void pendingValidationIndex: number | undefined diff --git a/frontend/src/components/InputFields/util.ts b/frontend/src/components/InputFields/util.ts index d2350d3e..19e7900c 100644 --- a/frontend/src/components/InputFields/util.ts +++ b/frontend/src/components/InputFields/util.ts @@ -1,4 +1,4 @@ -import { ReactNode } from 'react' +import { MarkdownCode } from '../../types' export type ProblemLevel = 'warning' | 'error' @@ -138,17 +138,19 @@ export const findDuplicates = ( } type SimpleDecision = boolean -type FullDecision = { verdict: boolean; reason?: ReactNode } +type FullDecision = { verdict: boolean; reason?: MarkdownCode | undefined } export type Decision = SimpleDecision | FullDecision -type FullPositiveDecision = { verdict: boolean; reason?: ReactNode } -type FullNegativeDecision = { verdict: boolean; reason: ReactNode } +type FullPositiveDecision = { verdict: true; reason: MarkdownCode } +type FullNegativeDecision = { verdict: false; reason: MarkdownCode } export type DecisionWithReason = true | FullPositiveDecision | FullNegativeDecision -export const allow = (reason?: ReactNode): Decision => ({ verdict: true, reason }) - -export const deny = (reason?: ReactNode): Decision => ({ verdict: false, reason }) -export const denyWithReason = (reason: ReactNode): FullNegativeDecision => ({ verdict: false, reason }) +export const allow = (reason?: MarkdownCode | undefined): Decision => ({ verdict: true, reason: reason }) +export const deny = (reason?: MarkdownCode | undefined): Decision => ({ verdict: false, reason: reason }) +export const denyWithReason = (reason: MarkdownCode): FullNegativeDecision => ({ + verdict: false, + reason: reason, +}) export const expandDecision = (decision: Decision): FullDecision => typeof decision === 'boolean' ? { verdict: decision } : decision @@ -168,7 +170,7 @@ export const andDecisions = (a: Decision, b: Decision): Decision => { if (bVerdict) { return { verdict: true, - reason: `${getReason(a)}; ${getReason(b)}`, + reason: `${getReason(a) as string}; ${getReason(b) as string}`, } } else { return b @@ -181,17 +183,17 @@ export const andDecisions = (a: Decision, b: Decision): Decision => { export const getVerdict = (decision: Decision | undefined, defaultVerdict: boolean): boolean => decision === undefined ? defaultVerdict : typeof decision === 'boolean' ? decision : decision.verdict -export const getReason = (decision: Decision | undefined): ReactNode | undefined => +export const getReason = (decision: Decision | undefined): MarkdownCode | undefined => decision === undefined ? undefined : typeof decision === 'boolean' ? undefined : decision.reason -export const getReasonForDenial = (decision: Decision | undefined): ReactNode | undefined => +export const getReasonForDenial = (decision: Decision | undefined): MarkdownCode | undefined => decision === undefined ? undefined : typeof decision === 'boolean' || decision.verdict ? undefined : decision.reason -export const getReasonForAllowing = (decision: Decision | undefined): ReactNode | undefined => +export const getReasonForAllowing = (decision: Decision | undefined): MarkdownCode | undefined => decision === undefined ? undefined : typeof decision === 'boolean' || !decision.verdict diff --git a/frontend/src/components/Markdown/index.tsx b/frontend/src/components/Markdown/index.tsx new file mode 100644 index 00000000..22ca299e --- /dev/null +++ b/frontend/src/components/Markdown/index.tsx @@ -0,0 +1,32 @@ +import { FC } from 'react' +import { MarkdownCode } from '../../types' +import Markdown from 'react-markdown' +import type { Components } from 'react-markdown' +import { JSX } from 'react/jsx-runtime' +import IntrinsicElements = JSX.IntrinsicElements + +export const renderComponents: Components = { + // Always links so that they open on a new tab + a: ({ children, href }) => { + return ( + + {children} + + ) + }, +} + +type MarkdownBlockProps = { + code: MarkdownCode | undefined + mainTag?: keyof IntrinsicElements +} +export const MarkdownBlock: FC = ({ code, mainTag }) => { + if (!code) return undefined + return ( + + {code as string} + + ) +} + +export const renderMarkdown = (markdown: MarkdownCode | undefined) => diff --git a/frontend/src/components/PollCard/PollAccessIndicator.tsx b/frontend/src/components/PollCard/PollAccessIndicator.tsx index 6acc21c3..b7d78ec6 100644 --- a/frontend/src/components/PollCard/PollAccessIndicator.tsx +++ b/frontend/src/components/PollCard/PollAccessIndicator.tsx @@ -1,4 +1,5 @@ -import { FC, ReactNode } from 'react' +import { FC } from 'react' +import { MarkdownCode } from '../../types' import { OpenPollIcon } from '../icons/OpenPollIcon' import { RestrictedPollIcon } from '../icons/RestrictedPollIcon' import { SpinnerIcon } from '../icons/SpinnerIcon' @@ -14,7 +15,7 @@ export const PollAccessIndicator: FC<{ hideRestrictedNoAccess?: boolean isPending: boolean isBroken: boolean - explanation: ReactNode + explanation: MarkdownCode | undefined hasAccess: boolean isCompleted: boolean isMine: boolean | undefined diff --git a/frontend/src/components/Tooltip/MaybeWithTooltip.tsx b/frontend/src/components/Tooltip/MaybeWithTooltip.tsx index 60638285..5b7adbfa 100644 --- a/frontend/src/components/Tooltip/MaybeWithTooltip.tsx +++ b/frontend/src/components/Tooltip/MaybeWithTooltip.tsx @@ -1,13 +1,15 @@ import Tooltip from 'rc-tooltip' import { FC, forwardRef } from 'react' +import { MarkdownCode } from '../../types' +import { MarkdownBlock } from '../Markdown' -type TooltipProps = Parameters[0] +type TooltipProps = Omit[0], 'overlay'> & { overlay: MarkdownCode | undefined } export const MaybeWithTooltip: FC = forwardRef((props, ref) => { - const { children, ...rest } = props - return props.overlay ? ( - - {children} + const { overlay, ...rest } = props + return overlay ? ( + }> + {props.children} ) : ( props.children diff --git a/frontend/src/components/icons/RestrictedPollIcon.tsx b/frontend/src/components/icons/RestrictedPollIcon.tsx index f9172df1..276e4fad 100644 --- a/frontend/src/components/icons/RestrictedPollIcon.tsx +++ b/frontend/src/components/icons/RestrictedPollIcon.tsx @@ -1,13 +1,13 @@ -import { FC, MouseEventHandler, ReactNode, useCallback } from 'react' +import { FC, MouseEventHandler, useCallback } from 'react' import { ClosedLockIcon } from './ClosedLockIcon' -import { IconProps } from '../../types' +import { IconProps, MarkdownCode } from '../../types' import classes from './index.module.css' import { designDecisions } from '../../constants/config' import { MaybeWithTooltip } from '../Tooltip/MaybeWithTooltip' export const RestrictedPollIcon: FC< IconProps & { - explanation: ReactNode + explanation: MarkdownCode completed?: boolean hasAccess: boolean onClick: () => void @@ -27,7 +27,7 @@ export const RestrictedPollIcon: FC< return ( = ({ +export const SpinnerIcon: FC = ({ size = 'large', spinning, overlay, diff --git a/frontend/src/hooks/PermissionCache.ts b/frontend/src/hooks/PermissionCache.ts index 11bae824..76daa429 100644 --- a/frontend/src/hooks/PermissionCache.ts +++ b/frontend/src/hooks/PermissionCache.ts @@ -10,14 +10,6 @@ import { PollPermissions, } from '../utils/poll.utils' import { StoredLRUCache } from '../utils/StoredLRUCache' -import { DecisionWithReason } from '../components/InputFields' -import { deserializeReactElement } from '../utils/react.utils' - -const deserializeDecision = (input: any): DecisionWithReason => { - return typeof input === 'object' - ? { verdict: input.vertict, reason: deserializeReactElement(input.reason) } - : input -} export abstract class PermissionCache { static #cache = new StoredLRUCache({ @@ -31,7 +23,7 @@ export abstract class PermissionCache { ], // debug: ['load', 'save'], storageKey: 'blockvote.pollPermissions2', - dataVersion: 9, + dataVersion: 10, transformValues: { encode: data => JSON.stringify(data, bigNumberify.stringify), decode: (stringData): PollPermissions => { @@ -39,8 +31,6 @@ export abstract class PermissionCache { return { ...rawData, proof: new Uint8Array(Object.values(rawData.proof)), - canVote: deserializeDecision(rawData.canVote), - explanation: deserializeReactElement(rawData.explanation), } }, }, diff --git a/frontend/src/pages/PollPage/ActivePoll.tsx b/frontend/src/pages/PollPage/ActivePoll.tsx index 4ec39573..7f9d1bf0 100644 --- a/frontend/src/pages/PollPage/ActivePoll.tsx +++ b/frontend/src/pages/PollPage/ActivePoll.tsx @@ -22,6 +22,7 @@ import { SpinnerIcon } from '../../components/icons/SpinnerIcon' import { AnimatePresence } from 'framer-motion' import { MotionDiv } from '../../components/Animations' import { shouldPublishVoters, shouldPublishVotes } from '../../types' +import { MarkdownBlock } from '../../components/Markdown' export const ActivePoll: FC = ({ hasWallet, @@ -199,16 +200,16 @@ export const ActivePoll: FC = ({ animate={{ height: 'auto', opacity: 1 }} exit={{ height: 0, opacity: 0 }} > -

- You can't vote on this poll, -
since {getReason(canAclVote)}. -

+ ) : ( aclExplanation && ( <> -

{aclExplanation}

+ {getVerdict(canAclVote, false) &&

You have access.

} ) diff --git a/frontend/src/pages/PollPage/CompletedPoll.tsx b/frontend/src/pages/PollPage/CompletedPoll.tsx index 4781d1d0..a324866e 100644 --- a/frontend/src/pages/PollPage/CompletedPoll.tsx +++ b/frontend/src/pages/PollPage/CompletedPoll.tsx @@ -9,6 +9,7 @@ import { getVerdict } from '../../components/InputFields' import { MotionDiv } from '../../components/Animations' import { VoteBrowser } from '../../components/VoteBrowser/VoteBrowser' import { VoterBrowser } from '../../components/VoterBrowser/VoterBrowser' +import { MarkdownBlock } from '../../components/Markdown' export const CompletedPoll: FC< Pick< @@ -94,7 +95,7 @@ export const CompletedPoll: FC< {aclExplanation && ( <> -

{aclExplanation}

+ {getVerdict(aclCanVote, false) ?

You had access.

:

You didn't have access.

} )} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index ceecce94..3a820125 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -3,3 +3,18 @@ export type * from './poll' export * from './poll-flags' export type { IconSize } from './icon-size' export type { IconProps } from './icon-props' + +const md = Symbol('md') + +/** + * Markdown text + * + * This is basically just normal string with markdown code. + * We are defining a type in order to avoid accidentally + * passing Markdown to a component that accepts string and is + * not equipped to handle markdown. + * + * So use this type to mark strings that can hold markdown. + * Just use "as string" if you need the actual value. + */ +export type MarkdownCode = string | typeof md diff --git a/frontend/src/utils/markdown.utils.ts b/frontend/src/utils/markdown.utils.ts new file mode 100644 index 00000000..88612f63 --- /dev/null +++ b/frontend/src/utils/markdown.utils.ts @@ -0,0 +1,7 @@ +import { MarkdownCode } from '../types' + +export const getStrictLink = (props: { href: string; label: MarkdownCode }) => + `[${props.label as string}](${props.href})` + +export const getLink = (props: { href: string | undefined; label: MarkdownCode }) => + props.label ? getStrictLink(props as any) : props.label diff --git a/frontend/src/utils/poll.utils.ts b/frontend/src/utils/poll.utils.ts index f3e94483..dbb4b2c1 100644 --- a/frontend/src/utils/poll.utils.ts +++ b/frontend/src/utils/poll.utils.ts @@ -29,6 +29,7 @@ import { FLAG_HIDDEN, FLAG_PUBLISH_VOTERS, FLAG_PUBLISH_VOTES, + MarkdownCode, Poll, PollManager, StoredPoll, @@ -38,7 +39,6 @@ import { DecisionWithReason, denyWithReason } from '../components/InputFields' import { FetcherFetchOptions } from './StoredLRUCache' import { findACLForOptions } from '../components/ACLs' import { VITE_NETWORK_NUMBER } from '../constants/config' -import { ReactNode } from 'react' export { parseEther } from 'ethers' @@ -288,7 +288,7 @@ export const destroyPoll = async (eth: EthereumContext, pollManager: PollManager export type PollPermissions = { proof: BytesLike - explanation: ReactNode + explanation: MarkdownCode | undefined canVote: DecisionWithReason error: string } diff --git a/frontend/src/utils/react.utils.ts b/frontend/src/utils/react.utils.ts index d3857f16..df6a7ead 100644 --- a/frontend/src/utils/react.utils.ts +++ b/frontend/src/utils/react.utils.ts @@ -1,5 +1,4 @@ -import React, { ReactNode, useEffect, useRef } from 'react' -import { getAsArray } from '../components/InputFields' +import { useEffect, useRef } from 'react' const usePrevious = (value: any, initialValue: any) => { const ref = useRef(initialValue) @@ -36,13 +35,3 @@ export const useEffectDebugger = (effectHook: any, dependencies: any, dependency useEffect(effectHook, dependencies) } - -export const deserializeReactElement = (input: any, key?: string): ReactNode => { - if (typeof input === 'string') return input - const { children = [], ...props } = input.props - return React.createElement( - input.type || 'span', - { ...props, key }, - getAsArray(children).map((c, index) => deserializeReactElement(c, `child${index}`)), - ) -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 18118bc5..66081cd1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,6 +90,9 @@ importers: react-intersection-observer: specifier: ^9.8.1 version: 9.13.0(react-dom@18.3.1)(react@18.3.1) + react-markdown: + specifier: ^9.0.1 + version: 9.0.1(@types/react@18.3.4)(react@18.3.1) react-parallax-tilt: specifier: ^1.7.217 version: 1.7.237(react-dom@18.3.1)(react@18.3.1) @@ -3513,11 +3516,21 @@ packages: resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} dependencies: '@types/ms': 0.7.34 - dev: true + + /@types/estree-jsx@1.0.5: + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + dependencies: + '@types/estree': 1.0.5 + dev: false /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true + + /@types/hast@3.0.4: + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + dependencies: + '@types/unist': 3.0.3 + dev: false /@types/http-cache-semantics@4.0.4: resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} @@ -3535,13 +3548,18 @@ packages: resolution: {integrity: sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==} dev: true + /@types/mdast@4.0.4: + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + dependencies: + '@types/unist': 3.0.3 + dev: false + /@types/mocha@10.0.7: resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==} dev: true /@types/ms@0.7.34: resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - dev: true /@types/node@18.15.13: resolution: {integrity: sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==} @@ -3567,7 +3585,6 @@ packages: /@types/prop-types@15.7.12: resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - dev: true /@types/react-dom@18.3.0: resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} @@ -3580,7 +3597,6 @@ packages: dependencies: '@types/prop-types': 15.7.12 csstype: 3.1.3 - dev: true /@types/readable-stream@2.3.15: resolution: {integrity: sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==} @@ -3598,6 +3614,14 @@ packages: resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} dev: true + /@types/unist@2.0.11: + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + dev: false + + /@types/unist@3.0.3: + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + dev: false + /@types/uuid@10.0.0: resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} dev: true @@ -3811,7 +3835,6 @@ packages: /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - dev: true /@vitejs/plugin-react-swc@3.7.0(vite@5.4.2): resolution: {integrity: sha512-yrknSb3Dci6svCd/qhHqhFPDSw0QtjumcqdKMoNNzmOl5lMXTTiqzjWtG4Qask2HdvvzaNgSunbQGet8/GrKdA==} @@ -4182,6 +4205,10 @@ packages: - supports-color dev: true + /bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + dev: false + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true @@ -4361,6 +4388,10 @@ packages: resolution: {integrity: sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==} hasBin: true + /ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + dev: false + /chai@4.5.0: resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} engines: {node: '>=4'} @@ -4391,6 +4422,22 @@ packages: supports-color: 7.2.0 dev: true + /character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + dev: false + + /character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + dev: false + + /character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + dev: false + + /character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + dev: false + /check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} dependencies: @@ -4496,6 +4543,10 @@ packages: color-string: 0.3.0 dev: false + /comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + dev: false + /command-exists@1.2.9: resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==} dev: true @@ -4800,6 +4851,12 @@ packages: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} dev: false + /decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + dependencies: + character-entities: 2.0.2 + dev: false + /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -4880,6 +4937,17 @@ packages: engines: {node: '>= 0.8'} dev: true + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: false + + /devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dependencies: + dequal: 2.0.3 + dev: false + /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -5525,6 +5593,10 @@ packages: engines: {node: '>=4.0'} dev: true + /estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + dev: false + /estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true @@ -5662,6 +5734,10 @@ packages: safe-buffer: 5.2.1 dev: true + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: false + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -6182,6 +6258,34 @@ packages: function-bind: 1.1.2 dev: true + /hast-util-to-jsx-runtime@2.3.0: + resolution: {integrity: sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==} + dependencies: + '@types/estree': 1.0.5 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.1.3 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + style-to-object: 1.0.8 + unist-util-position: 5.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + dev: false + + /hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + dependencies: + '@types/hast': 3.0.4 + dev: false + /he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -6199,6 +6303,10 @@ packages: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true + /html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + dev: false + /http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} dev: true @@ -6285,6 +6393,10 @@ packages: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} dev: true + /inline-style-parser@0.2.4: + resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} + dev: false + /internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} @@ -6311,6 +6423,17 @@ packages: fp-ts: 1.19.3 dev: true + /is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + dev: false + + /is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + dev: false + /is-arguments@1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} @@ -6392,6 +6515,10 @@ packages: has-tostringtag: 1.0.2 dev: true + /is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + dev: false + /is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -6433,6 +6560,10 @@ packages: engines: {node: '>=6.5.0', npm: '>=3'} dev: true + /is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + dev: false + /is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -6465,6 +6596,11 @@ packages: engines: {node: '>=8'} dev: true + /is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + dev: false + /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -6759,6 +6895,10 @@ packages: is-unicode-supported: 0.1.0 dev: true + /longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + dev: false + /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -6818,6 +6958,110 @@ packages: safe-buffer: 5.2.1 dev: true + /mdast-util-from-markdown@2.0.1: + resolution: {integrity: sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==} + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-decode-string: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.1 + mdast-util-to-markdown: 2.1.0 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-mdx-jsx@3.1.3: + resolution: {integrity: sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==} + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.1 + mdast-util-to-markdown: 2.1.0 + parse-entities: 4.0.1 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.1 + mdast-util-to-markdown: 2.1.0 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + dev: false + + /mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.2.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.0 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + dev: false + + /mdast-util-to-markdown@2.1.0: + resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-decode-string: 2.0.0 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + dev: false + + /mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + dependencies: + '@types/mdast': 4.0.4 + dev: false + /memorystream@0.3.1: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} engines: {node: '>= 0.10.0'} @@ -6832,6 +7076,181 @@ packages: resolution: {integrity: sha512-mUYWsMKNrm4lfygPkL3OfGzOPTR2DBlTkBNHM//F6hGp8cLThY897crAlk3/Jo17LEOOjQUrNAx6DvgO77QJkA==} dev: false + /micromark-core-commonmark@2.0.1: + resolution: {integrity: sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==} + dependencies: + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-factory-destination: 2.0.0 + micromark-factory-label: 2.0.0 + micromark-factory-space: 2.0.0 + micromark-factory-title: 2.0.0 + micromark-factory-whitespace: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-classify-character: 2.0.0 + micromark-util-html-tag-name: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-subtokenize: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-destination@2.0.0: + resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} + dependencies: + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-label@2.0.0: + resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-space@2.0.0: + resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + dependencies: + micromark-util-character: 2.1.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-title@2.0.0: + resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-whitespace@2.0.0: + resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-character@2.1.0: + resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==} + dependencies: + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-chunked@2.0.0: + resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + dependencies: + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-classify-character@2.0.0: + resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + dependencies: + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-combine-extensions@2.0.0: + resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} + dependencies: + micromark-util-chunked: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-decode-numeric-character-reference@2.0.1: + resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==} + dependencies: + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-decode-string@2.0.0: + resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==} + dependencies: + decode-named-character-reference: 1.0.2 + micromark-util-character: 2.1.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-encode@2.0.0: + resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + dev: false + + /micromark-util-html-tag-name@2.0.0: + resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} + dev: false + + /micromark-util-normalize-identifier@2.0.0: + resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + dependencies: + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-resolve-all@2.0.0: + resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} + dependencies: + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-sanitize-uri@2.0.0: + resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + dependencies: + micromark-util-character: 2.1.0 + micromark-util-encode: 2.0.0 + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-subtokenize@2.0.1: + resolution: {integrity: sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==} + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-symbol@2.0.0: + resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + dev: false + + /micromark-util-types@2.0.0: + resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + dev: false + + /micromark@4.0.0: + resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + dependencies: + '@types/debug': 4.1.12 + debug: 4.3.6(supports-color@8.1.1) + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.1 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-combine-extensions: 2.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-encode: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-sanitize-uri: 2.0.0 + micromark-util-subtokenize: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /micromatch@4.0.7: resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} engines: {node: '>=8.6'} @@ -7181,6 +7600,19 @@ packages: callsites: 3.1.0 dev: true + /parse-entities@4.0.1: + resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==} + dependencies: + '@types/unist': 2.0.11 + character-entities: 2.0.2 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.0.2 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + dev: false + /parse-json@4.0.0: resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} @@ -7345,6 +7777,10 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 + /property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + dev: false + /proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} dev: true @@ -7486,6 +7922,28 @@ packages: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} dev: false + /react-markdown@9.0.1(@types/react@18.3.4)(react@18.3.1): + resolution: {integrity: sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + dependencies: + '@types/hast': 3.0.4 + '@types/react': 18.3.4 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.0 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.0 + react: 18.3.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.1 + unified: 11.0.5 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + dev: false + /react-parallax-tilt@1.7.237(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-Rg8YmehDjTTcAn637HEd3ttTeZfT8UzKNmMKQhbn00/5mojkU4H+gLbYkxWFIM0hLnvfOZewkqgCGEOMzn4SlQ==} peerDependencies: @@ -7708,6 +8166,27 @@ packages: jsesc: 0.5.0 dev: true + /remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.1 + micromark-util-types: 2.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + dev: false + + /remark-rehype@11.1.1: + resolution: {integrity: sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==} + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.0 + unified: 11.0.5 + vfile: 6.0.3 + dev: false + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -8089,6 +8568,10 @@ packages: engines: {node: '>= 8'} dev: true + /space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + dev: false + /spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: @@ -8230,6 +8713,13 @@ packages: dependencies: safe-buffer: 5.2.1 + /stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + dev: false + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -8266,6 +8756,12 @@ packages: engines: {node: '>=8'} dev: true + /style-to-object@1.0.8: + resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==} + dependencies: + inline-style-parser: 0.2.4 + dev: false + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -8356,6 +8852,14 @@ packages: engines: {node: '>=0.6'} dev: true + /trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + dev: false + + /trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + dev: false + /ts-api-utils@1.3.0(typescript@5.6.2): resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -8619,6 +9123,51 @@ packages: engines: {node: '>=4'} dev: true + /unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + dev: false + + /unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + dependencies: + '@types/unist': 3.0.3 + dev: false + + /unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + dependencies: + '@types/unist': 3.0.3 + dev: false + + /unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + dependencies: + '@types/unist': 3.0.3 + dev: false + + /unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + dev: false + + /unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + dev: false + /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -8675,6 +9224,20 @@ packages: spdx-expression-parse: 3.0.1 dev: true + /vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + dev: false + + /vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + dev: false + /victory-vendor@36.9.2: resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} dependencies: @@ -8976,3 +9539,7 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + dev: false