Showing preview only (2,200K chars total). Download the full file or copy to clipboard to get everything.
Repository: snapshot-labs/snapshot-v1
Branch: master
Commit: 23ef14e9e821
Files: 634
Total size: 2.0 MB
Directory structure:
gitextract_yfac7sqe/
├── .browserslistrc
├── .dockerignore
├── .eslintignore
├── .eslintrc-auto-import.json
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── BUG_REPORT.yml
│ │ └── FEATURE_REQUEST.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ ├── stale.yml
│ └── workflows/
│ ├── codeql.yml
│ ├── deploy.yml
│ ├── nodejs.yml
│ ├── test.yaml
│ ├── update-snapshot-packages.yml
│ └── update-snapshot-submodules.yml
├── .gitignore
├── .gitmodules
├── .gitpod.yml
├── .husky/
│ ├── post-checkout
│ ├── post-merge
│ ├── pre-commit
│ └── pre-push
├── Dockerfile
├── FUNDING.json
├── LICENSE
├── README.md
├── babel.config.js
├── crowdin.yml
├── index.html
├── package.json
├── postcss.config.js
├── public/
│ ├── .well-known/
│ │ ├── assetlinks.json
│ │ └── did.json
│ ├── manifest.json
│ └── service-worker.js
├── src/
│ ├── App.vue
│ ├── assets/
│ │ ├── css/
│ │ │ ├── main.scss
│ │ │ ├── tippy.scss
│ │ │ └── tune.scss
│ │ └── fonts/
│ │ ├── iconfont.css
│ │ └── iconfont.json
│ ├── components/
│ │ ├── AboutMembersListItem.vue
│ │ ├── AvatarOverlayEdit.vue
│ │ ├── AvatarSpace.vue
│ │ ├── AvatarToken.vue
│ │ ├── AvatarUser.vue
│ │ ├── Banner.vue
│ │ ├── BaseAvatar.vue
│ │ ├── BaseBadge.vue
│ │ ├── BaseBlock.vue
│ │ ├── BaseBreadcrumbs.vue
│ │ ├── BaseButtonIcon.vue
│ │ ├── BaseButtonRound.vue
│ │ ├── BaseCalendar.vue
│ │ ├── BaseCombobox.vue
│ │ ├── BaseContainer.vue
│ │ ├── BaseCounter.vue
│ │ ├── BaseIcon.vue
│ │ ├── BaseIndicator.vue
│ │ ├── BaseInput.vue
│ │ ├── BaseInterpunct.vue
│ │ ├── BaseLink.vue
│ │ ├── BaseListbox.vue
│ │ ├── BaseLoading.vue
│ │ ├── BaseMarkdown.vue
│ │ ├── BaseMenu.vue
│ │ ├── BaseMessage.vue
│ │ ├── BaseMessageBlock.vue
│ │ ├── BaseModal.vue
│ │ ├── BaseModalSelectItem.vue
│ │ ├── BaseNetworkItem.vue
│ │ ├── BaseNoResults.vue
│ │ ├── BasePill.vue
│ │ ├── BasePluginItem.vue
│ │ ├── BasePopover.vue
│ │ ├── BasePopoverHover.vue
│ │ ├── BaseProgressBar.vue
│ │ ├── BaseProgressRadial.vue
│ │ ├── BaseSearch.vue
│ │ ├── BaseSidebarNavigationItem.vue
│ │ ├── BaseSkinItem.vue
│ │ ├── BaseStrategyItem.vue
│ │ ├── BaseUser.vue
│ │ ├── BlockLink.vue
│ │ ├── BlockSpacesList.vue
│ │ ├── BlockSpacesListButtonMore.vue
│ │ ├── BlockSpacesListItem.vue
│ │ ├── BlockSpacesListSkeleton.vue
│ │ ├── ButtonBack.vue
│ │ ├── ButtonCard.vue
│ │ ├── ButtonFollow.vue
│ │ ├── ButtonPlayground.vue
│ │ ├── ButtonShare.vue
│ │ ├── ButtonSwitch.vue
│ │ ├── ButtonTheme.vue
│ │ ├── ComboboxNetwork.vue
│ │ ├── ContainerParallelInput.vue
│ │ ├── ExploreMenuCategories.vue
│ │ ├── ExploreSkeletonLoading.vue
│ │ ├── ExploreSpaces.vue
│ │ ├── FooterLinks.vue
│ │ ├── FooterLinksItem.vue
│ │ ├── FooterSocials.vue
│ │ ├── FooterSocialsItem.vue
│ │ ├── FooterTitle.vue
│ │ ├── FormArrayStrategies.vue
│ │ ├── FormObjectStrategyParams.vue
│ │ ├── IconDiscord.vue
│ │ ├── IconInformationTooltip.vue
│ │ ├── IconVerifiedSpace.vue
│ │ ├── IndicatorAssetsChange.spec.js
│ │ ├── IndicatorAssetsChange.vue
│ │ ├── InputCheckbox.vue
│ │ ├── InputComboboxToken.vue
│ │ ├── InputDate.vue
│ │ ├── InputEmail.vue
│ │ ├── InputNewsletter.vue
│ │ ├── InputSelect.vue
│ │ ├── InputSelectPrivacy.vue
│ │ ├── InputSelectVoteType.vue
│ │ ├── InputSelectVoteValidation.vue
│ │ ├── InputSwitch.vue
│ │ ├── InputUploadAvatar.vue
│ │ ├── LabelInput.vue
│ │ ├── LabelProposalState.vue
│ │ ├── LabelProposalVoted.vue
│ │ ├── LinkSpace.vue
│ │ ├── ListboxNetwork.vue
│ │ ├── LoadingList.vue
│ │ ├── LoadingPage.vue
│ │ ├── LoadingRow.vue
│ │ ├── LoadingSpinner.vue
│ │ ├── MenuAccount.vue
│ │ ├── MenuLanguages.vue
│ │ ├── MessageWarningFlagged.vue
│ │ ├── MessageWarningGnosisNetwork.vue
│ │ ├── MessageWarningHibernated.vue
│ │ ├── MessageWarningTestnet.vue
│ │ ├── MessageWarningValidation.vue
│ │ ├── ModalAccount.vue
│ │ ├── ModalConfirmAction.vue
│ │ ├── ModalConfirmLeave.vue
│ │ ├── ModalControllerEdit.vue
│ │ ├── ModalDelegate.vue
│ │ ├── ModalEmail.vue
│ │ ├── ModalEmailManagement.vue
│ │ ├── ModalEmailResend.vue
│ │ ├── ModalEmailSubscription.vue
│ │ ├── ModalLinkPreview.vue
│ │ ├── ModalMessage.vue
│ │ ├── ModalNotice.vue
│ │ ├── ModalOsnap.vue
│ │ ├── ModalPendingTransactions.vue
│ │ ├── ModalPlugins.vue
│ │ ├── ModalPostPayment.vue
│ │ ├── ModalPostVote.vue
│ │ ├── ModalProfileForm.vue
│ │ ├── ModalRevokeDelegate.vue
│ │ ├── ModalSelectDate.vue
│ │ ├── ModalSkins.vue
│ │ ├── ModalSnapshotTerms.vue
│ │ ├── ModalSpaces.vue
│ │ ├── ModalSpacesListItem.vue
│ │ ├── ModalStrategies.vue
│ │ ├── ModalStrategy.vue
│ │ ├── ModalTerms.vue
│ │ ├── ModalTokens.vue
│ │ ├── ModalTokensItem.vue
│ │ ├── ModalTransactionStatus.vue
│ │ ├── ModalTreasury.vue
│ │ ├── ModalValidation.vue
│ │ ├── ModalVote.vue
│ │ ├── ModalVoteValidation.vue
│ │ ├── ModalVotingPrivacy.vue
│ │ ├── ModalVotingType.vue
│ │ ├── ModalWrongNetwork.vue
│ │ ├── NavbarAccount.vue
│ │ ├── NavbarExtras.vue
│ │ ├── NavbarNotifications.vue
│ │ ├── PopoverHoverProfile.vue
│ │ ├── ProfileAboutBiography.vue
│ │ ├── ProfileAboutDelegate.vue
│ │ ├── ProfileAboutDelegateListItem.vue
│ │ ├── ProfileActivityList.vue
│ │ ├── ProfileActivityListItem.vue
│ │ ├── ProfileAddressCopy.vue
│ │ ├── ProfileName.vue
│ │ ├── ProfileSidebar.vue
│ │ ├── ProfileSidebarHeader.vue
│ │ ├── ProfileSidebarHeaderSkeleton.vue
│ │ ├── ProfileSidebarNavigation.vue
│ │ ├── ProposalsItem.vue
│ │ ├── ProposalsItemBody.vue
│ │ ├── ProposalsItemFooter.vue
│ │ ├── ProposalsItemResults.vue
│ │ ├── ProposalsItemTitle.vue
│ │ ├── SettingsBoostBlock.vue
│ │ ├── SettingsDangerzoneBlock.vue
│ │ ├── SettingsDelegationBlock.vue
│ │ ├── SettingsDomainBlock.vue
│ │ ├── SettingsLinkBlock.vue
│ │ ├── SettingsMembersBlock.vue
│ │ ├── SettingsMembersPopoverContent.vue
│ │ ├── SettingsPluginsBlock.vue
│ │ ├── SettingsProfileBlock.vue
│ │ ├── SettingsProposalBlock.vue
│ │ ├── SettingsStrategiesBlock.vue
│ │ ├── SettingsSubSpacesBlock.vue
│ │ ├── SettingsTreasuriesBlock.vue
│ │ ├── SettingsTreasuriesBlockItem.vue
│ │ ├── SettingsTreasuriesBlockItemButton.vue
│ │ ├── SettingsTreasuryActivateOsnapButton.vue
│ │ ├── SettingsValidationBlock.vue
│ │ ├── SettingsVotingBlock.vue
│ │ ├── SetupButtonBack.vue
│ │ ├── SetupButtonCreate.vue
│ │ ├── SetupButtonNext.vue
│ │ ├── SetupDomain.vue
│ │ ├── SetupDomainRegister.vue
│ │ ├── SetupExtras.vue
│ │ ├── SetupIntro.vue
│ │ ├── SetupMessageHelp.vue
│ │ ├── SetupProfile.vue
│ │ ├── SetupSidebarStepper.vue
│ │ ├── SetupStrategy.vue
│ │ ├── SetupStrategyAdvanced.vue
│ │ ├── SetupStrategyBasic.vue
│ │ ├── SetupStrategyVote.vue
│ │ ├── SidebarSpacesSkeleton.vue
│ │ ├── SidebarUnreadIndicator.vue
│ │ ├── SpaceBoostCardProposal.vue
│ │ ├── SpaceBoostDeposit.vue
│ │ ├── SpaceBreadcrumbs.vue
│ │ ├── SpaceCreateContent.vue
│ │ ├── SpaceCreateLegacyOsnap.vue
│ │ ├── SpaceCreateOsnap.vue
│ │ ├── SpaceCreatePlugins.vue
│ │ ├── SpaceCreateVoting.vue
│ │ ├── SpaceCreateVotingDateEnd.vue
│ │ ├── SpaceCreateVotingDateStart.vue
│ │ ├── SpaceCreateWarnings.vue
│ │ ├── SpaceDelegateEdit.vue
│ │ ├── SpaceDelegatesAccount.vue
│ │ ├── SpaceDelegatesCard.vue
│ │ ├── SpaceDelegatesDelegateModal.vue
│ │ ├── SpaceDelegatesSkeleton.vue
│ │ ├── SpaceDelegatesSplitDelegationModal.vue
│ │ ├── SpaceProposalBoost.vue
│ │ ├── SpaceProposalBoostClaim.vue
│ │ ├── SpaceProposalBoostClaimModal.vue
│ │ ├── SpaceProposalBoostClaimModalItem.vue
│ │ ├── SpaceProposalBoostClaimModalSuccess.vue
│ │ ├── SpaceProposalBoostItem.vue
│ │ ├── SpaceProposalBoostItemMenu.vue
│ │ ├── SpaceProposalBoostModalCreate.vue
│ │ ├── SpaceProposalBoostWinnersModal.vue
│ │ ├── SpaceProposalContent.vue
│ │ ├── SpaceProposalHeader.vue
│ │ ├── SpaceProposalInformation.vue
│ │ ├── SpaceProposalPage.vue
│ │ ├── SpaceProposalPlugins.vue
│ │ ├── SpaceProposalPluginsSidebar.vue
│ │ ├── SpaceProposalResults.vue
│ │ ├── SpaceProposalResultsList.vue
│ │ ├── SpaceProposalResultsListItem.vue
│ │ ├── SpaceProposalResultsProgressBar.vue
│ │ ├── SpaceProposalResultsQuorum.vue
│ │ ├── SpaceProposalResultsShutter.vue
│ │ ├── SpaceProposalVote.vue
│ │ ├── SpaceProposalVoteApproval.vue
│ │ ├── SpaceProposalVoteQuadratic.vue
│ │ ├── SpaceProposalVoteRankedChoice.vue
│ │ ├── SpaceProposalVoteSingleChoice.vue
│ │ ├── SpaceProposalVotes.vue
│ │ ├── SpaceProposalVotesFilters.vue
│ │ ├── SpaceProposalVotesItem.vue
│ │ ├── SpaceProposalVotesListItemChoice.vue
│ │ ├── SpaceProposalVotesModal.vue
│ │ ├── SpaceProposalVotesModalDownload.vue
│ │ ├── SpaceProposalsNotice.vue
│ │ ├── SpaceProposalsSearch.vue
│ │ ├── SpaceProposalsSearchFilter.vue
│ │ ├── SpaceSettingsMessageHibernated.vue
│ │ ├── SpaceSidebar.vue
│ │ ├── SpaceSidebarFooter.vue
│ │ ├── SpaceSidebarHeader.vue
│ │ ├── SpaceSidebarMenuThreeDot.vue
│ │ ├── SpaceSidebarNavigation.vue
│ │ ├── SpaceSidebarSubspaces.vue
│ │ ├── SpaceSplitDelegationRow.vue
│ │ ├── StrategiesBlockWarning.vue
│ │ ├── StrategiesListItem.vue
│ │ ├── TextAutolinker.vue
│ │ ├── TextareaArray.vue
│ │ ├── TextareaAutosize.vue
│ │ ├── TextareaJson.vue
│ │ ├── TheActionbar.vue
│ │ ├── TheFlashNotification.vue
│ │ ├── TheFooter.vue
│ │ ├── TheLayout.vue
│ │ ├── TheModalNotification.vue
│ │ ├── TheNavbar.vue
│ │ ├── TheSearchBar.vue
│ │ ├── TheSidebar.vue
│ │ ├── TreasuryAssetsList.vue
│ │ ├── TreasuryAssetsListItem.spec.js
│ │ ├── TreasuryAssetsListItem.vue
│ │ ├── TreasuryWalletsList.spec.js
│ │ ├── TreasuryWalletsList.vue
│ │ ├── TreasuryWalletsListItem.spec.js
│ │ ├── TreasuryWalletsListItem.vue
│ │ ├── Tune/
│ │ │ ├── TuneBlock.vue
│ │ │ ├── TuneBlockFooter.vue
│ │ │ ├── TuneBlockHeader.vue
│ │ │ ├── TuneButton.story.vue
│ │ │ ├── TuneButton.vue
│ │ │ ├── TuneButtonSelect.story.vue
│ │ │ ├── TuneButtonSelect.vue
│ │ │ ├── TuneCheckbox.story.vue
│ │ │ ├── TuneCheckbox.vue
│ │ │ ├── TuneCombobox.story.vue
│ │ │ ├── TuneCombobox.vue
│ │ │ ├── TuneComboboxMultiple.story.vue
│ │ │ ├── TuneComboboxMultiple.vue
│ │ │ ├── TuneErrorInput.story.vue
│ │ │ ├── TuneErrorInput.vue
│ │ │ ├── TuneForm.story.vue
│ │ │ ├── TuneForm.vue
│ │ │ ├── TuneIconHint.story.vue
│ │ │ ├── TuneIconHint.vue
│ │ │ ├── TuneInput.story.vue
│ │ │ ├── TuneInput.vue
│ │ │ ├── TuneInputDuration.story.vue
│ │ │ ├── TuneInputDuration.vue
│ │ │ ├── TuneInputSocial.vue
│ │ │ ├── TuneInputUrl.vue
│ │ │ ├── TuneLabelInput.story.vue
│ │ │ ├── TuneLabelInput.vue
│ │ │ ├── TuneListbox.story.vue
│ │ │ ├── TuneListbox.vue
│ │ │ ├── TuneListboxMultiple.story.vue
│ │ │ ├── TuneListboxMultiple.vue
│ │ │ ├── TuneLoadingSpinner.story.vue
│ │ │ ├── TuneLoadingSpinner.vue
│ │ │ ├── TuneMenu.story.vue
│ │ │ ├── TuneMenu.vue
│ │ │ ├── TuneModal.story.vue
│ │ │ ├── TuneModal.vue
│ │ │ ├── TuneModalDescription.vue
│ │ │ ├── TuneModalIndicator.vue
│ │ │ ├── TuneModalTitle.vue
│ │ │ ├── TunePopover.story.vue
│ │ │ ├── TunePopover.vue
│ │ │ ├── TuneRadio.story.vue
│ │ │ ├── TuneRadio.vue
│ │ │ ├── TuneSelect.vue
│ │ │ ├── TuneSwitch.story.vue
│ │ │ ├── TuneSwitch.vue
│ │ │ ├── TuneTag.story.vue
│ │ │ ├── TuneTag.vue
│ │ │ ├── TuneTextarea.story.vue
│ │ │ ├── TuneTextarea.vue
│ │ │ ├── TuneTextareaArray.story.vue
│ │ │ ├── TuneTextareaArray.vue
│ │ │ ├── TuneTextareaJson.story.vue
│ │ │ ├── TuneTextareaJson.vue
│ │ │ └── _Form/
│ │ │ ├── FormArray.vue
│ │ │ ├── FormBoolean.vue
│ │ │ ├── FormNumber.vue
│ │ │ └── FormString.vue
│ │ └── Ui/
│ │ ├── UiCollapsible.vue
│ │ ├── UiCollapsibleContent.vue
│ │ ├── UiCollapsibleText.vue
│ │ ├── UiInput.vue
│ │ └── UiSelect.vue
│ ├── composables/
│ │ ├── useAccount.ts
│ │ ├── useAliasAction.ts
│ │ ├── useApolloQuery.ts
│ │ ├── useApp.ts
│ │ ├── useBalances.ts
│ │ ├── useBoost.ts
│ │ ├── useChangeNetwork.ts
│ │ ├── useClient.ts
│ │ ├── useCopy.ts
│ │ ├── useDelegate.ts
│ │ ├── useDelegates.ts
│ │ ├── useEmailFetchClient.ts
│ │ ├── useEmailSubscription.ts
│ │ ├── useEns.ts
│ │ ├── useExtendedSpaces.ts
│ │ ├── useFlaggedMessageStatus.ts
│ │ ├── useFlashNotification.ts
│ │ ├── useFollowSpace.ts
│ │ ├── useFormSpaceProposal.ts
│ │ ├── useFormSpaceSettings.ts
│ │ ├── useFormValidation.ts
│ │ ├── useGnosis.ts
│ │ ├── useI18n.ts
│ │ ├── useImageUpload.ts
│ │ ├── useInfiniteLoader.ts
│ │ ├── useIntl.ts
│ │ ├── useMeta.ts
│ │ ├── useModal.ts
│ │ ├── useModalNotification.ts
│ │ ├── useNetworksFilter.ts
│ │ ├── useNotifications.ts
│ │ ├── usePayment.ts
│ │ ├── usePlugins.ts
│ │ ├── useProfiles.ts
│ │ ├── useProposalVotes.ts
│ │ ├── useProposals.ts
│ │ ├── useQuorum.ts
│ │ ├── useReportDownload.ts
│ │ ├── useResolveName.ts
│ │ ├── useSafe.ts
│ │ ├── useSharing.ts
│ │ ├── useShortUrls.ts
│ │ ├── useSkin.ts
│ │ ├── useSkinsFilter.ts
│ │ ├── useSnapshot.ts
│ │ ├── useSpaceController.ts
│ │ ├── useSpaceSubscription.ts
│ │ ├── useSpaces.ts
│ │ ├── useStatement.ts
│ │ ├── useStrategies.ts
│ │ ├── useTerms.ts
│ │ ├── useTreasury.ts
│ │ ├── useTxStatus.ts
│ │ ├── useUnseenProposals.ts
│ │ ├── useUsername.ts
│ │ └── useWeb3.ts
│ ├── env.d.ts
│ ├── helpers/
│ │ ├── alchemy/
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ ├── apollo.ts
│ │ ├── auth.ts
│ │ ├── b64.ts
│ │ ├── beams.ts
│ │ ├── boost/
│ │ │ ├── abi.json
│ │ │ ├── api.ts
│ │ │ ├── index.ts
│ │ │ ├── subgraph.ts
│ │ │ ├── tokens.ts
│ │ │ └── types.ts
│ │ ├── clientEIP712.ts
│ │ ├── connectors.ts
│ │ ├── constants.ts
│ │ ├── covalent.ts
│ │ ├── delegation.ts
│ │ ├── delegationV2/
│ │ │ ├── compound/
│ │ │ │ ├── index.ts
│ │ │ │ ├── queries.ts
│ │ │ │ ├── read.ts
│ │ │ │ └── write.ts
│ │ │ ├── index.ts
│ │ │ ├── splitDelegation/
│ │ │ │ ├── abi.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── read.ts
│ │ │ │ └── write.ts
│ │ │ └── types.ts
│ │ ├── ens.ts
│ │ ├── i18n.ts
│ │ ├── interfaces.ts
│ │ ├── pin.ts
│ │ ├── queries.ts
│ │ ├── shutter.ts
│ │ ├── sign.ts
│ │ ├── snapshot.ts
│ │ ├── transaction.ts
│ │ ├── utils.test.js
│ │ ├── utils.ts
│ │ ├── validation.ts
│ │ └── vitePlugins.ts
│ ├── locales/
│ │ ├── ar-SA.json
│ │ ├── de-DE.json
│ │ ├── default.json
│ │ ├── es-ES.json
│ │ ├── fil-PH.json
│ │ ├── fr-FR.json
│ │ ├── hi-IN.json
│ │ ├── id-ID.json
│ │ ├── it-IT.json
│ │ ├── ja-JP.json
│ │ ├── ko-KR.json
│ │ ├── languages.json
│ │ ├── pt-PT.json
│ │ ├── ro-RO.json
│ │ ├── ru-RU.json
│ │ ├── tr-TR.json
│ │ ├── uk-UA.json
│ │ ├── vi-VN.json
│ │ └── zh-CN.json
│ ├── main.ts
│ ├── plugins/
│ │ ├── README.md
│ │ ├── domino/
│ │ │ ├── ProposalSidebar.vue
│ │ │ ├── components/
│ │ │ │ └── CustomBlock.vue
│ │ │ └── plugin.json
│ │ ├── gnosis/
│ │ │ ├── Create.vue
│ │ │ ├── ProposalSidebar.vue
│ │ │ ├── components/
│ │ │ │ ├── Config.vue
│ │ │ │ └── CustomBlock.vue
│ │ │ ├── index.ts
│ │ │ └── plugin.json
│ │ ├── oSnap/
│ │ │ ├── Create.vue
│ │ │ ├── CreateSafe.vue
│ │ │ ├── Proposal.vue
│ │ │ ├── README.md
│ │ │ ├── components/
│ │ │ │ ├── BotSupportWarning.vue
│ │ │ │ ├── ExternalLink.vue
│ │ │ │ ├── HandleOutcome/
│ │ │ │ │ ├── HandleOutcome.vue
│ │ │ │ │ └── steps/
│ │ │ │ │ ├── CanProposeToOG.vue
│ │ │ │ │ ├── CanRequestTxExecution.vue
│ │ │ │ │ ├── InOOChallengePeriod.vue
│ │ │ │ │ ├── RejectedBySnapshotVote.vue
│ │ │ │ │ ├── TallyingSnapshotVotes.vue
│ │ │ │ │ └── TransactionsExecuted.vue
│ │ │ │ ├── Input/
│ │ │ │ │ ├── Address.vue
│ │ │ │ │ ├── Amount.vue
│ │ │ │ │ ├── FileInput/
│ │ │ │ │ │ ├── FileInput.vue
│ │ │ │ │ │ └── utils.ts
│ │ │ │ │ ├── MethodParameter/
│ │ │ │ │ │ ├── MethodParameter.vue
│ │ │ │ │ │ └── utils.ts
│ │ │ │ │ ├── ReadOnly.vue
│ │ │ │ │ ├── SelectSafe.vue
│ │ │ │ │ └── TransactionType.vue
│ │ │ │ ├── OsnapMarketingWidget.vue
│ │ │ │ ├── SafeLinkWithAvatar.vue
│ │ │ │ └── TransactionBuilder/
│ │ │ │ ├── ContractInteraction.vue
│ │ │ │ ├── ModalSafe.vue
│ │ │ │ ├── ModalTransactionType.vue
│ │ │ │ ├── RawTransaction.vue
│ │ │ │ ├── SafeImport.vue
│ │ │ │ ├── TenderlySimulation.vue
│ │ │ │ ├── TokensModal.vue
│ │ │ │ ├── TokensModalItem.vue
│ │ │ │ ├── Transaction.vue
│ │ │ │ ├── TransactionBuilder.vue
│ │ │ │ ├── TransactionImport.vue
│ │ │ │ ├── TransferFunds.vue
│ │ │ │ └── TransferNFT.vue
│ │ │ ├── constants.ts
│ │ │ ├── plugin.json
│ │ │ ├── types.ts
│ │ │ └── utils/
│ │ │ ├── abi.ts
│ │ │ ├── coins.ts
│ │ │ ├── events.ts
│ │ │ ├── getters.ts
│ │ │ ├── index.ts
│ │ │ ├── proposal.ts
│ │ │ ├── safeImport.ts
│ │ │ ├── tenderly.ts
│ │ │ ├── transactions.ts
│ │ │ └── validators.ts
│ │ ├── poap/
│ │ │ ├── ProposalSidebar.vue
│ │ │ ├── components/
│ │ │ │ └── CustomBlock.vue
│ │ │ ├── index.ts
│ │ │ └── plugin.json
│ │ ├── progress/
│ │ │ ├── ProposalSidebar.vue
│ │ │ ├── components/
│ │ │ │ └── CustomBlock.vue
│ │ │ ├── index.ts
│ │ │ ├── plugin.json
│ │ │ └── readme.md
│ │ ├── projectGalaxy/
│ │ │ ├── ProposalSidebar.vue
│ │ │ ├── README.md
│ │ │ ├── components/
│ │ │ │ └── CustomBlock.vue
│ │ │ ├── index.ts
│ │ │ └── plugin.json
│ │ ├── quorum/
│ │ │ ├── examples.json
│ │ │ └── plugin.json
│ │ └── safeSnap/
│ │ ├── Create.vue
│ │ ├── Proposal.vue
│ │ ├── components/
│ │ │ ├── Config.vue
│ │ │ ├── Form/
│ │ │ │ ├── ContractInteraction.vue
│ │ │ │ ├── ImportTransactionsButton.vue
│ │ │ │ ├── RawTransaction.vue
│ │ │ │ ├── SendAsset.vue
│ │ │ │ ├── TokensModal.vue
│ │ │ │ ├── TokensModalItem.vue
│ │ │ │ ├── Transaction.vue
│ │ │ │ ├── TransactionBatch.vue
│ │ │ │ └── TransferFunds.vue
│ │ │ ├── HandleOutcome.vue
│ │ │ ├── HandleOutcomeUma.vue
│ │ │ ├── Input/
│ │ │ │ ├── Address.vue
│ │ │ │ ├── Amount.vue
│ │ │ │ ├── ArrayType.vue
│ │ │ │ └── MethodParameter.vue
│ │ │ ├── Modal/
│ │ │ │ └── OptionApproval.vue
│ │ │ ├── SafeTransactions.vue
│ │ │ └── Tooltip.vue
│ │ ├── constants.ts
│ │ ├── index.ts
│ │ ├── plugin.json
│ │ ├── types/
│ │ │ └── index.ts
│ │ └── utils/
│ │ ├── abi.ts
│ │ ├── coins.ts
│ │ ├── decoder.ts
│ │ ├── events.ts
│ │ ├── index.ts
│ │ ├── multiSend.ts
│ │ ├── realityETH.ts
│ │ ├── realityModule.ts
│ │ ├── safe.ts
│ │ ├── transactions.ts
│ │ ├── umaModule.ts
│ │ └── validator.ts
│ ├── router/
│ │ └── index.ts
│ ├── sentry.ts
│ └── views/
│ ├── DelegateView.vue
│ ├── ExploreView.vue
│ ├── PlaygroundView.vue
│ ├── ProfileAbout.vue
│ ├── ProfileActivity.vue
│ ├── ProfileView.vue
│ ├── RankingView.vue
│ ├── SetupView.vue
│ ├── SpaceAbout.vue
│ ├── SpaceBoost.vue
│ ├── SpaceCreate.vue
│ ├── SpaceDelegate.vue
│ ├── SpaceDelegates.vue
│ ├── SpaceProposal.vue
│ ├── SpaceProposals.vue
│ ├── SpaceSettings.vue
│ ├── SpaceTreasury.vue
│ ├── SpaceView.vue
│ ├── StrategyView.vue
│ ├── TermsView.vue
│ └── TimelineView.vue
├── tailwind.config.js
├── tsconfig.json
├── vercel.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .browserslistrc
================================================
> 1%
last 2 versions
not dead
================================================
FILE: .dockerignore
================================================
node_modules
================================================
FILE: .eslintignore
================================================
src/plugins/*
================================================
FILE: .eslintrc-auto-import.json
================================================
{
"globals": {
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"DARK": true,
"EffectScope": true,
"InjectionKey": true,
"LIGHT": true,
"PropType": true,
"Ref": true,
"VNode": true,
"computed": true,
"createApp": true,
"customRef": true,
"defineAsyncComponent": true,
"defineComponent": true,
"effectScope": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"getRanking": true,
"h": true,
"inject": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeRouteLeave": true,
"onBeforeRouteUpdate": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onUnmounted": true,
"onUpdated": true,
"provide": true,
"reactive": true,
"readonly": true,
"ref": true,
"resolveComponent": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"toRaw": true,
"toRef": true,
"toRefs": true,
"triggerRef": true,
"unref": true,
"useAliasAction": true,
"useApolloQuery": true,
"useApp": true,
"useAttrs": true,
"useClient": true,
"useCopy": true,
"useCssModule": true,
"useCssVars": true,
"useDelegate": true,
"useEns": true,
"useExtendedSpaces": true,
"useFlashNotification": true,
"useFollowSpace": true,
"useFormSpaceProposal": true,
"useFormSpaceSettings": true,
"useFormValidation": true,
"useGnosis": true,
"useI18n": true,
"useImageUpload": true,
"useInfiniteLoader": true,
"useIntl": true,
"useLink": true,
"useMeta": true,
"useModal": true,
"useModalNotification": true,
"useNetworksFilter": true,
"useNotifications": true,
"usePlugins": true,
"useProfiles": true,
"useProposalVotes": true,
"useProposals": true,
"useQuorum": true,
"useReportDownload": true,
"useRoute": true,
"useRouter": true,
"useSafe": true,
"useSharing": true,
"useShortUrls": true,
"useSkin": true,
"useSkinsFilter": true,
"useSlots": true,
"useSnapshot": true,
"useSpaceController": true,
"useSpaceSubscription": true,
"useSpaces": true,
"useStrategies": true,
"useTerms": true,
"useTreasury": true,
"useTxStatus": true,
"useUnseenProposals": true,
"useUsername": true,
"useWeb3": true,
"useDelegates": true,
"useResolveName": true,
"watch": true,
"watchEffect": true,
"watchPostEffect": true,
"watchSyncEffect": true,
"watchTxStatus": true,
"toValue": true,
"useFlaggedMessageStatus": true,
"useEmailSubscription": true,
"useEmailFetchClient": true,
"useStatement": true,
"useBalances": true,
"useAccount": true,
"usePayment": true,
"useChangeNetwork": true,
"useBoost": true
}
}
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fabien@bonustrack.co. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: .github/CONTRIBUTING.md
================================================
### Opening an issue
You should usually open an issue in the following situations:
* Report an error you can’t solve yourself
* Discuss a high-level topic or idea (for example, community, vision or policies)
* Propose a new feature or other project idea
Tips for communicating on issues:
* **If you see an open issue that you want to tackle,** comment on the issue to let people know you’re on it. That way, people are less likely to duplicate your work.
* **If an issue was opened a while ago,** it’s possible that it’s being addressed somewhere else, or has already been resolved, so comment to ask for confirmation before starting work.
* **If you opened an issue, but figured out the answer later on your own,** comment on the issue to let people know, then close the issue. Even documenting that outcome is a contribution to the project.
### Opening a pull request
You should usually open a pull request in the following situations:
* Submit trivial fixes (for example, a typo, a broken link or an obvious error)
* Start work on a contribution that was already asked for, or that you’ve already discussed, in an issue
A pull request doesn’t have to represent finished work. It’s usually better to open a pull request early on, so others can watch or give feedback on your progress. Just mark it as a “WIP” (Work in Progress) in the subject line. You can always add more commits later.
If the project is on GitHub, here’s how to submit a pull request:
* **Fork the repository** and clone it locally. Connect your local to the original repository by adding it as a remote. Pull in changes from this repository often so that you stay up to date so that when you submit your pull request, merge conflicts will be less likely.
* **Create a branch** for your edits.
* **Reference any relevant issues** or supporting documentation in your PR (for example, “Closes #37.”)
* **Include screenshots of the before and after** if your changes include differences in HTML/CSS. Drag and drop the images into the body of your pull request.
* **Test your changes!** Run your changes against any existing tests if they exist and create new ones when needed. Whether tests exist or not, make sure your changes don’t break the existing project.
* **Contribute in the style of the project** to the best of your abilities. This may mean using indents, semi-colons or comments differently than you would in your own repository, but makes it easier for the maintainer to merge, others to understand and maintain in the future.
================================================
FILE: .github/ISSUE_TEMPLATE/BUG_REPORT.yml
================================================
name: Bug report
description: File a bug report
title: "[BUG] - "
labels: ["bug-report"]
body:
- type: markdown
attributes:
value: Thanks for making Snapshot awesome for everyone.
- type: input
id: title
attributes:
label: Briefly describe the bug.
description: A clear and concise description of what the bug is.
placeholder: ex. Unable to vote using metamask wallet on chrome.
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: How can we reproduce the bug?
placeholder: Steps to reproduce the behaviour.
value: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
id: expectation
attributes:
label: What is the expected behaviour?
description: A clear and concise description of what you expected to happen.
placeholder: ex. I should be able to vote using metamask wallet on chrome.
validations:
required: false
- type: textarea
id: screenshots
attributes:
label: Can you attach screenshots?
description: Please attach all the screenshots that can help us visually see the problem.
placeholder: You can drag-and-drop the image, copy paste the image in the field below.
validations:
required: false
- type: textarea
id: device
attributes:
label: Can you provide your device specific details below?
value: |
1. Device Type - [ex. Desktop or Mobile]
2. Device OS - [ex. Android, MacOS, Windows, iOS]
3. OS Version - [ex. iOS 11, Windows 10]
4. Browser - [ex. chrome, safari]
5. Browser Version - [ex. 101]
6. Wallet - [metamask, portis, walletconnect]
7. Space - [ex. ENS, Gitcoin]
8. Any additional information we should know -
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml
================================================
name: Feature request
description: Suggest an idea for this project
title: "[NEW FEATURE] - "
labels: ["feature-request"]
body:
- type: markdown
attributes:
value: Thanks for making Snapshot awesome for everyone.
- type: input
id: title
attributes:
label: Briefly describe the feature.
description: A clear and concise description of the feature you are requesting.
validations:
required: true
- type: textarea
id: problem
attributes:
label: Which problem is this feature trying to solve?
description: A clear description of the problem or frustration that will be solved with this feature.
placeholder: ex. I'm always frustrated when I am viewing the proposal metrics
validations:
required: true
- type: textarea
id: solution
attributes:
label: What is the expected solution?
description: A clear and concise description of what you expected to happen.
placeholder: ex. Having a chart to read the votes on the proposal will make it very easy to read the proposal metrics.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: How do you currently handle this problem?
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: false
- type: textarea
id: additional
attributes:
label: Anything else you'd like to mention?
description: Please mention any technical details, screenshots, mock-ups that you might have for us to better understand the feature.
validations:
required: false
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
### Summary
<!-- Related issues, a description or list of the changes and the motivation behind them -->
Closes: #
### How to test
1.
### To-Do
- [ ]
<!--
### Self-review checklist
- [ ] I have performed a full self-review of my changes
- [ ] I have tested my changes on a preview deployment
- [ ] I have tested my changes on different screen sizes (sm, md)
-->
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
ignore:
- dependency-name: "@snapshot-labs/*"
groups:
production-dependencies:
dependency-type: "production"
development-dependencies:
dependency-type: "development"
================================================
FILE: .github/stale.yml
================================================
daysUntilStale: 120
daysUntilClose: 14
exemptLabels:
- pinned
- enhancement
- bug
staleLabel: stale
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
closeComment: false
================================================
FILE: .github/workflows/codeql.yml
================================================
name: "CodeQL"
on:
push:
branches: [ 'master' ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ 'master' ]
schedule:
- cron: '36 5 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"
================================================
FILE: .github/workflows/deploy.yml
================================================
name: Deploy
on:
workflow_dispatch:
inputs:
target:
type: choice
description: Target
options:
- stable
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# Checks-out repo on develop branch
- uses: actions/checkout@v3
with:
ref: 'master'
# Overwrite target
- run: |
git checkout -b ${{ github.event.inputs.target }}
git push --set-upstream origin ${{ github.event.inputs.target }} --force
================================================
FILE: .github/workflows/nodejs.yml
================================================
name: Node CI
on:
push:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: yarn
- name: Build
run: yarn run build
- name: Lint
run: yarn run lint
================================================
FILE: .github/workflows/test.yaml
================================================
name: Tests
on: [push]
jobs:
test:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'yarn'
- run: yarn
- run: yarn test:unit:coverage
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
================================================
FILE: .github/workflows/update-snapshot-packages.yml
================================================
name: Update Snapshot packages
on: workflow_dispatch
jobs:
update-dep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '16.x'
- name: Update snapshot.js
run: |
yarn upgrade @snapshot-labs/snapshot.js --latest
- name: Update lock.js
run: |
yarn upgrade @snapshot-labs/lock --latest
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
commit-message: Update Snapshot packages
title: Update Snapshot packages
body: |
- Updates from snapshot.js or lock packages
Auto-generated by Github Actions
branch: update-snapshot-packages
================================================
FILE: .github/workflows/update-snapshot-submodules.yml
================================================
name: Update Snapshot submodules
on: workflow_dispatch
jobs:
update-dep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
- uses: actions/setup-node@v1
with:
node-version: '16.x'
- name: Update submodules
run: |
git submodule update --remote
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
title: Update Snapshot submodules
body: |
- Updates from submodules
Auto-generated by Github Actions
branch: update-snapshot-submodules
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
/dist
.yalc
components.d.ts
auto-imports.d.ts
# local env file
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Vitest
/coverage
# Sentry Auth Token
.env.sentry-build-plugin
================================================
FILE: .gitmodules
================================================
[submodule "snapshot-spaces"]
path = snapshot-spaces
url = https://github.com/snapshot-labs/snapshot-spaces
================================================
FILE: .gitpod.yml
================================================
tasks:
- name: Setup
init: |
yarn install --frozen-lockfile --silent --network-timeout 100000
command: yarn dev
ports:
- port: 3000
onOpen: open-preview
================================================
FILE: .husky/post-checkout
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn run init-submodules
================================================
FILE: .husky/post-merge
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn run init-submodules
================================================
FILE: .husky/pre-commit
================================================
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
================================================
FILE: .husky/pre-push
================================================
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
================================================
FILE: Dockerfile
================================================
FROM node:18-alpine
RUN apk update && apk upgrade && \
apk add --no-cache git py3-pip python3 gcc g++ make
WORKDIR /app
COPY . .
RUN yarn install --frozen-lockfile
RUN yarn install --frozen-lockfile
RUN yarn run build
EXPOSE 8080
CMD ["yarn", "run", "dev", "--host"]
================================================
FILE: FUNDING.json
================================================
{
"drips": {
"ethereum": {
"ownedBy": "0x8C28Cf33d9Fd3D0293f963b1cd27e3FF422B425c"
}
},
"opRetro": {
"projectId": "0x5e6c436e48e56d6d9622ba5d0be0035c314e2b29d2afc8f5f1ee8ac75cd42532"
}
}
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) Snapshot Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<div align="center">
<img src="public/icon.svg" height="70" alt="Snapshot Logo">
<h1>Snapshot</h1>
<strong>Snapshot is an off-chain gasless multi-governance client with easy to verify and hard to contest results.</strong>
</div>
<br>
<div align="center">
<a href="https://github.com/snapshot-labs/snapshot/actions/workflows/nodejs.yml">
<img src="https://github.com/snapshot-labs/snapshot/actions/workflows/nodejs.yml/badge.svg" alt="Node CI">
</a>
<img src="https://img.shields.io/github/commit-activity/w/snapshot-labs/snapshot" alt="GitHub commit activity">
<a href="https://github.com/snapshot-labs/snapshot/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22">
<img src="https://img.shields.io/github/issues/snapshot-labs/snapshot/help wanted" alt="GitHub issues help wanted">
</a>
<a href="https://discord.snapshot.org/">
<img src="https://img.shields.io/discord/707079246388133940.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2" alt="Discord">
</a>
<a href="https://x.com/SnapshotLabs">
<img src="https://img.shields.io/twitter/follow/SnapshotLabs?label=SnapshotLabs&style=social">
</a>
</div>
<div align="center">
<br>
<a href="https://snapshot.org"><b>snapshot.org »</b></a>
<br><br>
<a href="https://docs.snapshot.org"><b>Documentation</b></a>
•
<a href="https://features.snapshot.org/feature-requests"><b>Feature requests</b></a>
•
<a href="https://docs.snapshot.org/introduction#contributing"><b>Contribute</b></a>
</div>
## Project setup
```
yarn
```
### Compiles and hot-reloads for development
```
yarn dev
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn run lint
```
### Development Guide
Use `http://localhost:8080/#/fabien.eth` for testing your code.
By default your instance will connect to the hub at `https://testnet.hub.snapshot.org`. To change that (or other values) you can create a `.env.local` and overwrite the values from `.env`.
## Running service locally with Docker
1. Run `docker build -t snapshot .` to build the image
2. Run `docker run --name snapshot -p 8080:8080 snapshot` to run the container
3. Go to `http://localhost:8080/#/fabien.eth` to test your code
## License
Snapshot is open-sourced software licensed under the © [MIT license](LICENSE).
================================================
FILE: babel.config.js
================================================
module.exports = {
presets: ['@vue/cli-plugin-babel/preset']
};
================================================
FILE: crowdin.yml
================================================
files:
- source: /src/locales/default.json
translation: /src/locales/%locale%.json
================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.png" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
<title>Snapshot</title>
</head>
<body class="bg-skin-bg font-sans text-base text-skin-text antialiased">
<div id="app"></div>
<div id="modal" />
<script type="module" src="/src/main.ts"></script>
</body>
</html>
================================================
FILE: package.json
================================================
{
"name": "snapshot",
"version": "0.1.4",
"license": "MIT",
"repository": "snapshot-labs/snapshot",
"scripts": {
"dev": "vite --port=8080",
"build": "vite build",
"lint": "eslint \"*.{ts,js,vue,json}\" src/ --ext .ts,.js,.vue,.json",
"lint:fix": "yarn lint --fix",
"preinstall": "yarn run init-submodules",
"postinstall": "patch-package && husky install",
"init-submodules": "git submodule update --init",
"test:unit": "vitest run",
"test:unit:coverage": "vitest run --coverage"
},
"eslintConfig": {
"extends": [
"@snapshot-labs/vue",
"./.eslintrc-auto-import.json"
]
},
"prettier": "@snapshot-labs/prettier-config",
"dependencies": {
"@apollo/client": "^3.8.7",
"@braintree/sanitize-url": "^6.0.4",
"@ensdomains/eth-ens-namehash": "^2.0.15",
"@ethersproject/abi": "^5.7.0",
"@ethersproject/address": "^5.7.0",
"@ethersproject/bignumber": "^5.6.1",
"@ethersproject/bytes": "^5.7.0",
"@ethersproject/contracts": "^5.7.0",
"@ethersproject/providers": "^5.7.1",
"@ethersproject/random": "^5.7.0",
"@ethersproject/solidity": "^5.7.0",
"@ethersproject/strings": "^5.7.0",
"@ethersproject/units": "^5.7.0",
"@ethersproject/wallet": "^5.7.0",
"@headlessui-float/vue": "^0.13.0",
"@headlessui/vue": "^1.7.17",
"@pusher/push-notifications-web": "^1.1.0",
"@sentry/vite-plugin": "^2.10.1",
"@sentry/vue": "^7.80.1",
"@shutter-network/shutter-crypto": "1.0.1",
"@snapshot-labs/lock": "^0.2.11",
"@snapshot-labs/pineapple": "^1.1.0",
"@snapshot-labs/snapshot.js": "^0.14.18",
"@vue/apollo-composable": "4.0.0-beta.11",
"@vueuse/core": "^10.6.1",
"@vueuse/head": "^2.0.0",
"autolinker": "^4.0.0",
"bluebird": "^3.7.2",
"evm-proxy-detection": "^1.2.0",
"graphql": "16.6.0",
"graphql-tag": "^2.12.6",
"js-sha256": "^0.10.1",
"jsonexport": "^3.2.0",
"kubo-rpc-client": "^3.0.2",
"lodash": "^4.17.21",
"minisearch": "^6.2.0",
"remarkable": "^2.0.1",
"remove-markdown": "^0.5.0",
"typescript": "^5.2.2",
"v-viewer": "^3.0.11",
"vite-compatible-readable-stream": "^3.6.1",
"vue": "^3.3.8",
"vue-i18n": "^9.7.0",
"vue-router": "^4.2.5",
"vue-tippy": "^6.3.1",
"vuedraggable": "^4.0.2"
},
"devDependencies": {
"@iconify-json/heroicons-outline": "^1.1.7",
"@rushstack/eslint-patch": "^1.3.3",
"@snapshot-labs/eslint-config-vue": "^0.1.0-beta.15",
"@snapshot-labs/prettier-config": "0.1.0-beta.15",
"@tailwindcss/forms": "^0.5.6",
"@types/bluebird": "^3.5.38",
"@types/lodash": "^4.14.198",
"@types/node": "^20.5.0",
"@vitejs/plugin-vue": "^2.3.4",
"@vitest/coverage-v8": "^0.34.5",
"@vue/test-utils": "^2.4.1",
"autoprefixer": "^10.4.15",
"env-cmd": "^10.1.0",
"eslint": "^8.46.0",
"happy-dom": "^10.11.0",
"husky": "^8.0.3",
"patch-package": "^7.0.0",
"postcss": "^8.4.28",
"postinstall-postinstall": "^2.1.0",
"prettier": "^3.1.0",
"prettier-plugin-tailwindcss": "^0.5.7",
"rollup-plugin-visualizer": "^5.9.0",
"sass": "~1.62.1",
"sass-loader": "^13.2.2",
"start-server-and-test": "^2.0.0",
"tailwindcss": "^3.3.5",
"unplugin-auto-import": "^0.16.1",
"unplugin-icons": "^0.16.5",
"unplugin-vue-components": "^0.25.2",
"vite": "^2.9.14",
"vitest": "^0.33.0"
},
"resolutions": {
"ansi-regex": "^5.0.1",
"axios": "^0.21.1"
}
}
================================================
FILE: postcss.config.js
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};
================================================
FILE: public/.well-known/assetlinks.json
================================================
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "org.snapshot",
"sha256_cert_fingerprints": [
"44:5B:B1:EE:DF:5C:2A:90:7D:7A:10:DF:18:67:68:54:8E:8E:62:C1:DF:84:06:F7:8D:8E:AD:67:2E:B2:5C:E5"
]
}
}
]
================================================
FILE: public/.well-known/did.json
================================================
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:web:snapshot.org",
"verificationMethod": [
{
"id": "did:web:snapshot.org#wc-notify-subscribe-key",
"type": "JsonWebKey2020",
"controller": "did:web:snapshot.org",
"publicKeyJwk": {
"kty": "OKP",
"crv": "X25519",
"x": "gL37Y5hbjW4__xM7n4JrtAqc1OAGUWKiASb1W1cReA0"
}
},
{
"id": "did:web:snapshot.org#wc-notify-authentication-key",
"type": "JsonWebKey2020",
"controller": "did:web:snapshot.org",
"publicKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "iCZGVaUtesmG6RinZVff-GlW5qzap8uAI1opXKmuPA4"
}
}
],
"keyAgreement": [
"did:web:snapshot.org#wc-notify-subscribe-key"
],
"authentication": [
"did:web:snapshot.org#wc-notify-authentication-key"
]
}
================================================
FILE: public/manifest.json
================================================
{
"short_name": "Snapshot",
"name": "Snapshot",
"description": "Where decisions get made",
"iconPath": "icon.svg",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "avatar.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "avatar.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
================================================
FILE: public/service-worker.js
================================================
// importScripts('https://js.pusher.com/beams/service-worker.js');
================================================
FILE: src/App.vue
================================================
<script setup lang="ts">
const { domain, init, isReady, showSidebar } = useApp();
const route = useRoute();
const { restorePendingTransactions } = useTxStatus();
onMounted(async () => {
await init();
restorePendingTransactions();
});
</script>
<template>
<LoadingSpinner v-if="!isReady" class="overlay big animate-fade-in" />
<div v-else class="flex min-h-screen">
<div v-if="!domain" id="sidebar" class="flex flex-col">
<div
class="sticky top-0 z-40 h-screen overflow-hidden bg-skin-bg transition-all sm:w-[60px]"
:class="{ 'max-w-0 sm:max-w-none': !showSidebar }"
>
<TheSidebar class="border-r border-skin-border" />
</div>
</div>
<div
class="relative flex w-screen min-w-0 shrink-0 flex-col sm:w-auto sm:shrink sm:grow"
>
<div
class="absolute bottom-0 left-0 right-0 top-0 z-50 bg-skin-bg opacity-60"
:class="{ hidden: !showSidebar }"
@click="showSidebar = false"
/>
<div
id="navbar"
class="sticky top-0 z-40 border-b border-skin-border bg-skin-bg"
>
<TheNavbar />
</div>
<div id="content" class="pb-6 pt-4">
<router-view v-slot="{ Component }">
<KeepAlive :include="['ExploreView', 'RankingView']">
<component :is="Component" :key="route.path" />
</KeepAlive>
</router-view>
</div>
<footer
v-if="route.name === 'home' || route.name === 'terms-and-conditions'"
class="mt-auto"
>
<TheFooter />
</footer>
<div id="action-bar" />
</div>
</div>
<TheFlashNotification />
<TheModalNotification />
</template>
================================================
FILE: src/assets/css/main.scss
================================================
@import '../fonts/iconfont.css';
@import 'viewerjs/dist/viewer.css';
@import './tippy.scss';
@import './tune.scss';
@import '../snapshot-spaces/skins';
@tailwind base;
@tailwind components;
@tailwind utilities;
[data-color-scheme='dark'] {
color-scheme: dark;
--primary-color: #ffffff;
--bg-color: #1c1b20;
--text-color: #8b949e;
--link-color: #ffffff;
--heading-color: #ffffff;
--border-color: #343434;
--header-bg: #1c1b20;
--block-bg: transparent;
--shadow-color: rgba(255, 255, 255, 0.036);
--border-color-soft: rgba(45, 45, 45, 0.8);
--border-color-subtle: rgba(45, 45, 45, 0.5);
--border-color-faint: rgba(45, 45, 45, 0.3);
--network-blocks: url('@/assets/images/blocks-dark.svg');
}
[data-color-scheme='light'] {
color-scheme: light;
--primary-color: #000000;
--bg-color: white;
--text-color: #57606a;
--link-color: #444444;
--heading-color: #111111;
--border-color: #e0e0e0;
--header-bg: white;
--block-bg: transparent;
--shadow-color: #0001;
--border-color-soft: rgb(224, 224, 224, 0.6);
--border-color-subtle: rgb(224, 224, 224, 0.32);
--border-color-faint: rgb(224, 224, 224, 0.12);
--network-blocks: url('@/assets/images/blocks-light.svg');
}
@layer base {
@font-face {
font-family: 'Calibre';
src: url('../fonts/Calibre-Medium-Custom.woff2') format('woff2');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Calibre';
src: url('../fonts/Calibre-Semibold-Custom.woff2') format('woff2');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'SpaceMono';
src: url('../fonts/space-mono-v11-latin-700.woff2') format('woff2');
}
@font-face {
font-family: 'Space Mono';
font-style: normal;
font-weight: 700;
src: url('../fonts/SpaceMono-Bold.woff2') format('woff2');
}
.mono {
font-size: 44px;
font-family: 'Space Mono', Helvetica, Arial, sans-serif;
letter-spacing: -1px;
font-weight: 700;
line-height: 1.1em;
}
.eyebrow {
text-transform: uppercase;
letter-spacing: 1px;
font-size: 16px;
}
html {
scrollbar-gutter: stable;
overflow: visible !important;
}
*,
::before,
::after {
@apply border-skin-border;
}
body {
overflow-x: hidden;
}
h1 {
@apply text-2xl;
}
h2 {
@apply text-xl;
}
h3 {
@apply my-2 text-lg;
}
h4 {
@apply text-md;
}
h1,
h2,
h3,
h4,
h5,
h6 {
@apply font-semibold text-skin-heading;
}
a,
button,
input {
@apply transition-colors;
}
b {
@apply font-semibold;
}
select {
@apply bg-skin-bg;
}
}
@layer utilities {
// This allows us to use 'no-scrollbar' with Tailwind breakpoints like this 'md:no-scrollbar'
/* Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
}
// Don't prevent overscroll on touch devices
@media (pointer: fine) {
body {
overscroll-behavior-y: none;
}
}
// Fade transition for Vue's transition and transition-group
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
a,
.a {
color: var(--link-color);
&:hover {
cursor: pointer;
text-decoration: none;
}
}
.input {
@apply bg-transparent text-skin-link outline-none;
}
::placeholder {
color: var(--text-color);
opacity: 0.6 !important;
}
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type='time']::-webkit-calendar-picker-indicator {
background: none;
display: none;
}
// Pointer cursor for file input buttons
input[type=file], /* FF, IE7+, chrome (except button) */
input[type=file]::-webkit-file-upload-button {
/* chromes and blink button */
cursor: pointer;
}
input[type='number'] {
-moz-appearance: textfield;
}
.lazy-loading {
@apply animate-pulse-fast bg-skin-border;
}
// Shot UI kit
.s-input {
@apply block w-full rounded-full border border-skin-border bg-skin-bg px-3 py-2 text-skin-link outline-none focus-within:border-skin-text;
}
.s-error {
@apply flex items-center rounded-b-3xl bg-red px-3 pb-2 pt-4 text-sm text-white transition-all duration-200;
}
// Fix for chrome. Overwrites tailwinds break-words class.
// For some reason chrome doesn't break links with just overflow-wrap.
// It also needs word-break.
.break-words {
overflow-wrap: break-word;
word-break: break-word;
}
// v-viewer package styling
.viewer-backdrop {
background-color: rgba(0, 0, 0, 80%) !important;
}
.viewer-close {
scale: 1.5 !important;
margin-top: 50px !important;
margin-right: 66px !important;
width: 50px !important;
height: 50px !important;
background-color: transparent !important;
box-shadow: none !important;
}
@media (max-width: 544px) {
.viewer-close {
margin-right: 50px !important;
}
}
.network-blocks {
background-repeat: repeat-x;
background-position: top -40px center;
background-image: var(--network-blocks);
}
================================================
FILE: src/assets/css/tippy.scss
================================================
// V-Tippy tooltip package styling - could be moved somewhere else?
.tippy-box[data-animation='fade'][data-state='hidden'] {
opacity: 0;
}
[data-tippy-root] {
max-width: calc(100vw - 10px);
}
.tippy-box {
position: relative;
background-color: var(--text-color);
color: var(--header-bg);
border-radius: 7px;
font-size: 16px;
line-height: 1.4;
outline: 0;
transition-property: transform, visibility, opacity;
padding: 2px 4px;
text-align: center;
font-weight: 500;
}
.tippy-box[data-placement^='top'] > .tippy-arrow {
bottom: 0;
}
.tippy-box[data-placement^='top'] > .tippy-arrow:before {
bottom: -7px;
left: 0;
border-width: 8px 8px 0;
border-top-color: var(--text-color);
transform-origin: center top;
}
.tippy-box[data-placement^='bottom'] > .tippy-arrow {
top: 0;
}
.tippy-box[data-placement^='bottom'] > .tippy-arrow:before {
top: -7px;
left: 0;
border-width: 0 8px 8px;
border-bottom-color: var(--text-color);
transform-origin: center bottom;
}
.tippy-box[data-placement^='left'] > .tippy-arrow {
right: 0;
}
.tippy-box[data-placement^='left'] > .tippy-arrow:before {
border-width: 8px 0 8px 8px;
border-left-color: var(--text-color);
right: -7px;
transform-origin: center left;
}
.tippy-box[data-placement^='right'] > .tippy-arrow {
left: 0;
}
.tippy-box[data-placement^='right'] > .tippy-arrow:before {
left: -7px;
border-width: 8px 8px 8px 0;
border-right-color: var(--text-color);
transform-origin: center right;
}
.tippy-box[data-inertia][data-state='visible'] {
transition-timing-function: cubic-bezier(0.54, 1.5, 0.38, 1.11);
}
.tippy-arrow {
width: 16px;
height: 16px;
color: #333;
}
.tippy-arrow:before {
content: '';
position: absolute;
border-color: transparent;
border-style: solid;
}
.tippy-content {
position: relative;
padding: 5px 9px;
z-index: 1;
overflow-wrap: break-word;
}
.tippy-box[data-theme~='urlified'] {
max-width: 200px;
white-space: pre-wrap;
color: var(--header-bg);
a {
color: var(--header-bg);
}
}
================================================
FILE: src/assets/css/tune.scss
================================================
.tune-input {
@apply form-input rounded-full border-skin-border bg-transparent text-base text-skin-link focus:border-skin-text focus:outline-none focus:ring-0;
}
.tune-textarea {
@extend .tune-input;
@apply no-scrollbar rounded-3xl px-3;
&.disabled {
@apply cursor-not-allowed;
}
}
.tune-textarea-json {
@apply no-scrollbar overflow-x-scroll whitespace-pre;
&.disabled {
@apply cursor-not-allowed;
}
}
.tune-switch {
@apply border-transparent;
&.switched-on-bg {
@apply bg-green;
}
&.switched-off-bg {
@apply bg-skin-border;
}
&.switched-on-text {
@apply text-green;
}
&.switched-off-text {
@apply text-skin-text;
}
}
.tune-switch-button {
@apply bg-skin-bg;
}
.tune-input-loading {
@apply rounded-r-full bg-skin-bg;
}
.tune-button {
@apply h-[46px] cursor-pointer text-skin-link rounded-full bg-transparent border border-skin-border px-[22px] hover:border-skin-text;
&.disabled {
@apply border-skin-border bg-skin-bg text-skin-border cursor-not-allowed;
}
&.primary {
@apply border-skin-primary bg-skin-primary text-white hover:brightness-95;
&:disabled {
@apply border-skin-primary bg-skin-primary text-white opacity-40;
}
}
&.white-border {
@apply border-white/30 hover:border-white;
&:disabled {
@apply border-white text-white opacity-40;
}
}
&.danger {
@apply text-red hover:border-red border-red/40;
&:disabled {
@apply text-skin-border hover:border-skin-border;
}
}
}
.tune-button-select {
@apply relative h-[42px] w-full justify-start truncate pl-3 pr-5 text-left text-base text-skin-link rounded-full;
&.disabled {
@apply cursor-not-allowed border-skin-border;
}
}
.tune-input-duration {
@apply rounded-full border border-skin-border px-3 text-base text-skin-link focus-within:border-skin-text;
&.disabled {
@apply cursor-not-allowed;
}
}
.tune-input-duration-label {
@apply text-skin-text;
}
.tune-select {
@apply form-select text-left px-3 border border-skin-border w-full rounded-full outline-none focus:ring-0 focus:border-skin-text text-skin-link bg-skin-bg text-base;
&.disabled {
@apply cursor-not-allowed;
}
}
.tune-listbox-button {
@apply rounded-full border border-skin-border text-base text-skin-link hover:border-skin-text;
&.disabled {
@apply hover:border-skin-border;
}
&.error {
@apply border-red;
}
}
.tune-listbox-options {
@apply rounded-md border border-skin-border bg-skin-bg shadow-lg;
}
.tune-listbox-item {
&.selected {
@apply text-skin-link;
}
&.disabled {
@apply text-skin-border;
}
&.active {
@apply bg-skin-border;
}
}
.tune-listbox-placeholder {
@apply text-skin-text opacity-60;
}
.tune-label {
@apply font-sans text-skin-text;
}
.tune-sublabel {
@apply font-sans text-skin-text opacity-60;
}
.tune-icon-hint {
@apply text-xs hover:text-skin-link;
}
.tune-error-text {
@apply font-sans text-sm text-red;
}
.tune-error-border {
@apply border-red;
}
.tune-tag {
@apply rounded-lg border border-[--border-color-soft] bg-[--border-color-subtle] px-[6px] py-[3px] text-sm text-skin-text;
}
.tune-menu-list {
@apply rounded-md border border-skin-border bg-skin-header-bg shadow-lg;
}
.tune-menu-list-item {
@apply bg-skin-header-bg text-skin-text;
&.active {
@apply bg-skin-border text-skin-link;
}
}
.tune-popover {
@apply rounded-md border bg-skin-header-bg shadow-lg;
}
.tune-input-checkbox {
@apply form-checkbox h-[20px] w-[20px] rounded-lg border-skin-text text-skin-text focus:ring-0 focus:ring-offset-0 focus-visible:ring-offset-1 focus-visible:ring-offset-skin-text;
}
.tune-input-radio {
@apply form-radio h-[20px] w-[20px] rounded-full border-skin-text text-skin-text focus:ring-0 focus:ring-offset-0 focus-visible:ring-offset-1 focus-visible:ring-offset-skin-text;
}
.tune-form-array-objects {
@apply rounded-xl border p-3;
}
================================================
FILE: src/assets/fonts/iconfont.css
================================================
@font-face {
font-family: 'iconfont'; /* Project id 1946815 */
src: url('iconfont.woff2?t=1648383247623') format('woff2'),
url('iconfont.woff?t=1648383247623') format('woff'),
url('iconfont.ttf?t=1648383247623') format('truetype');
}
.iconfont {
font-family: 'iconfont' !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.iconnotificationsnone:before {
content: '\e708';
}
.iconearth:before {
content: '\e70d';
}
.iconmarkdown:before {
content: '\ec0f';
}
.iconcoins:before {
content: '\e6c6';
}
.iconcheck3:before {
content: '\e605';
}
.iconskip:before {
content: '\e8c5';
}
.iconclose2:before {
content: '\e6c3';
}
.iconcalendar:before {
content: '\e71f';
}
.icondraggable:before {
content: '\e617';
}
.iconpreview:before {
content: '\e637';
}
.iconfilter:before {
content: '\e74f';
}
.iconhome:before {
content: '\e673';
}
.iconapps:before {
content: '\e613';
}
.iconcheck1:before {
content: '\e607';
}
.iconremind:before {
content: '\e69e';
}
.iconemoji:before {
content: '\e709';
}
.iconflag_fill:before {
content: '\e711';
}
.iconmine:before {
content: '\e732';
}
.iconmore:before {
content: '\e736';
}
.iconpeople:before {
content: '\e73d';
}
.iconstealth_fill:before {
content: '\e75f';
}
.iconnotifications-on:before {
content: '\ec64';
}
.iconnotifications:before {
content: '\e630';
}
.iconnotifications-off:before {
content: '\e6e4';
}
.iconstamp:before {
content: '\e6de';
}
.iconinsertlink:before {
content: '\e621';
}
.iconupload:before {
content: '\e735';
}
.iconplay:before {
content: '\e628';
}
.iconfeed:before {
content: '\e6f6';
}
.icongitbook:before {
content: '\e604';
}
.iconreload:before {
content: '\e603';
}
.iconrefresh:before {
content: '\e78f';
}
.iconwarning1:before {
content: '\e602';
}
.icontoggle_off:before {
content: '\e9ad';
}
.icontoggle-on:before {
content: '\e8b2';
}
.iconthreedots:before {
content: '\e730';
}
.iconmoon:before {
content: '\e780';
}
.iconsun:before {
content: '\e634';
}
.iconinfo:before {
content: '\e93b';
}
.iconlogin:before {
content: '\e66a';
}
.iconcopy:before {
content: '\e638';
}
.iconsnapshot:before {
content: '\e6da';
}
.iconstars:before {
content: '\e6a3';
}
.iconfavorite-off:before {
content: '\e6d7';
}
.iconfavorite-on:before {
content: '\e6d8';
}
.iconloveit:before {
content: '\e60a';
}
.iconbyteball:before {
content: '\e6bc';
}
.iconandroid:before {
content: '\e601';
}
.icontwitter:before {
content: '\ec9c';
}
.iconreddit:before {
content: '\ea3c';
}
.icongoogle:before {
content: '\e723';
}
.iconchrome:before {
content: '\e6c2';
}
.iconapple:before {
content: '\e6bd';
}
.iconfacebook:before {
content: '\e6be';
}
.iconwindows:before {
content: '\e649';
}
.icondiscord:before {
content: '\e6fe';
}
.icontelegram:before {
content: '\e731';
}
.iconmacos:before {
content: '\e6ba';
}
.iconline:before {
content: '\e6c0';
}
.iconmenu1:before {
content: '\e6c8';
}
.iconclose1:before {
content: '\e6c9';
}
.icongear:before {
content: '\e6ca';
}
.iconbullet:before {
content: '\e619';
}
.iconplus:before {
content: '\e727';
}
.iconwallet:before {
content: '\e97c';
}
.iconsignature:before {
content: '\e600';
}
.iconreceipt-outlined:before {
content: '\e8eb';
}
.iconcheck:before {
content: '\e679';
}
.iconwarning:before {
content: '\e62c';
}
.iconsearch:before {
content: '\e6f2';
}
.iconmenu:before {
content: '\e609';
}
.iconclose:before {
content: '\e63e';
}
.icongithub:before {
content: '\e667';
}
.iconexternal-link:before {
content: '\e636';
}
.icongo:before {
content: '\e6cb';
}
.iconback:before {
content: '\e6cc';
}
.iconuser:before {
content: '\e66f';
}
.iconarrow-up:before {
content: '\e75c';
}
.iconarrow-down:before {
content: '\e75d';
}
================================================
FILE: src/assets/fonts/iconfont.json
================================================
{
"id": "1946815",
"name": "snapshot",
"font_family": "iconfont",
"css_prefix_text": "icon",
"description": "",
"glyphs": [
{
"icon_id": "736871",
"name": "notifications-none",
"font_class": "notificationsnone",
"unicode": "e708",
"unicode_decimal": 59144
},
{
"icon_id": "1444429",
"name": "earth",
"font_class": "earth",
"unicode": "e70d",
"unicode_decimal": 59149
},
{
"icon_id": "27365167",
"name": "markdown",
"font_class": "markdown",
"unicode": "ec0f",
"unicode_decimal": 60431
},
{
"icon_id": "1444287",
"name": "coins",
"font_class": "coins",
"unicode": "e6c6",
"unicode_decimal": 59078
},
{
"icon_id": "11398722",
"name": "check",
"font_class": "check3",
"unicode": "e605",
"unicode_decimal": 58885
},
{
"icon_id": "15617307",
"name": "skip-next",
"font_class": "skip",
"unicode": "e8c5",
"unicode_decimal": 59589
},
{
"icon_id": "980400",
"name": "close",
"font_class": "close2",
"unicode": "e6c3",
"unicode_decimal": 59075
},
{
"icon_id": "7239657",
"name": "calendar",
"font_class": "calendar",
"unicode": "e71f",
"unicode_decimal": 59167
},
{
"icon_id": "27154983",
"name": "draggable",
"font_class": "draggable",
"unicode": "e617",
"unicode_decimal": 58903
},
{
"icon_id": "20997029",
"name": "preview",
"font_class": "preview",
"unicode": "e637",
"unicode_decimal": 58935
},
{
"icon_id": "8555524",
"name": "filter-3-fill",
"font_class": "filter",
"unicode": "e74f",
"unicode_decimal": 59215
},
{
"icon_id": "14401724",
"name": "home",
"font_class": "home",
"unicode": "e673",
"unicode_decimal": 58995
},
{
"icon_id": "7988769",
"name": "apps",
"font_class": "apps",
"unicode": "e613",
"unicode_decimal": 58899
},
{
"icon_id": "583318",
"name": "check",
"font_class": "check1",
"unicode": "e607",
"unicode_decimal": 58887
},
{
"icon_id": "1410439",
"name": "notifications-none",
"font_class": "remind",
"unicode": "e69e",
"unicode_decimal": 59038
},
{
"icon_id": "2045201",
"name": "emoji",
"font_class": "emoji",
"unicode": "e709",
"unicode_decimal": 59145
},
{
"icon_id": "2045216",
"name": "flag_fill",
"font_class": "flag_fill",
"unicode": "e711",
"unicode_decimal": 59153
},
{
"icon_id": "2045276",
"name": "mine",
"font_class": "mine",
"unicode": "e732",
"unicode_decimal": 59186
},
{
"icon_id": "2045280",
"name": "more",
"font_class": "more",
"unicode": "e736",
"unicode_decimal": 59190
},
{
"icon_id": "2045289",
"name": "people",
"font_class": "people",
"unicode": "e73d",
"unicode_decimal": 59197
},
{
"icon_id": "2045337",
"name": "stealth_fill",
"font_class": "stealth_fill",
"unicode": "e75f",
"unicode_decimal": 59231
},
{
"icon_id": "490399",
"name": "notifications_active",
"font_class": "notifications-on",
"unicode": "ec64",
"unicode_decimal": 60516
},
{
"icon_id": "5226577",
"name": "notifications",
"font_class": "notifications",
"unicode": "e630",
"unicode_decimal": 58928
},
{
"icon_id": "7371992",
"name": "round-notifications_",
"font_class": "notifications-off",
"unicode": "e6e4",
"unicode_decimal": 59108
},
{
"icon_id": "24125360",
"name": "stamp",
"font_class": "stamp",
"unicode": "e6de",
"unicode_decimal": 59102
},
{
"icon_id": "11931885",
"name": "link",
"font_class": "insertlink",
"unicode": "e621",
"unicode_decimal": 58913
},
{
"icon_id": "1393807",
"name": "upload",
"font_class": "upload",
"unicode": "e735",
"unicode_decimal": 59189
},
{
"icon_id": "7428691",
"name": "play",
"font_class": "play",
"unicode": "e628",
"unicode_decimal": 58920
},
{
"icon_id": "7685091",
"name": "feed",
"font_class": "feed",
"unicode": "e6f6",
"unicode_decimal": 59126
},
{
"icon_id": "20531430",
"name": "gitbook",
"font_class": "gitbook",
"unicode": "e604",
"unicode_decimal": 58884
},
{
"icon_id": "88076",
"name": "reload",
"font_class": "reload",
"unicode": "e603",
"unicode_decimal": 58883
},
{
"icon_id": "2958463",
"name": "refresh",
"font_class": "refresh",
"unicode": "e78f",
"unicode_decimal": 59279
},
{
"icon_id": "18442559",
"name": "warning",
"font_class": "warning1",
"unicode": "e602",
"unicode_decimal": 58882
},
{
"icon_id": "11982163",
"name": "toggle_off",
"font_class": "toggle_off",
"unicode": "e9ad",
"unicode_decimal": 59821
},
{
"icon_id": "15617211",
"name": "toggle-on",
"font_class": "toggle-on",
"unicode": "e8b2",
"unicode_decimal": 59570
},
{
"icon_id": "1393793",
"name": "threedots",
"font_class": "threedots",
"unicode": "e730",
"unicode_decimal": 59184
},
{
"icon_id": "13896012",
"name": "moon",
"font_class": "moon",
"unicode": "e780",
"unicode_decimal": 59264
},
{
"icon_id": "18019049",
"name": "sun",
"font_class": "sun",
"unicode": "e634",
"unicode_decimal": 58932
},
{
"icon_id": "8827141",
"name": "info",
"font_class": "info",
"unicode": "e93b",
"unicode_decimal": 59707
},
{
"icon_id": "1567557",
"name": "login",
"font_class": "login",
"unicode": "e66a",
"unicode_decimal": 58986
},
{
"icon_id": "11968691",
"name": "copy",
"font_class": "copy",
"unicode": "e638",
"unicode_decimal": 58936
},
{
"icon_id": "17540404",
"name": "snapshot",
"font_class": "snapshot",
"unicode": "e6da",
"unicode_decimal": 59098
},
{
"icon_id": "10331571",
"name": "stars",
"font_class": "stars",
"unicode": "e6a3",
"unicode_decimal": 59043
},
{
"icon_id": "17018087",
"name": "favorite-off",
"font_class": "favorite-off",
"unicode": "e6d7",
"unicode_decimal": 59095
},
{
"icon_id": "17018088",
"name": "favorite-on",
"font_class": "favorite-on",
"unicode": "e6d8",
"unicode_decimal": 59096
},
{
"icon_id": "562650",
"name": "love it",
"font_class": "loveit",
"unicode": "e60a",
"unicode_decimal": 58890
},
{
"icon_id": "248947",
"name": "Vignetting",
"font_class": "byteball",
"unicode": "e6bc",
"unicode_decimal": 59068
},
{
"icon_id": "261002",
"name": "android",
"font_class": "android",
"unicode": "e601",
"unicode_decimal": 58881
},
{
"icon_id": "348956",
"name": "twitter",
"font_class": "twitter",
"unicode": "ec9c",
"unicode_decimal": 60572
},
{
"icon_id": "703493",
"name": "reddit",
"font_class": "reddit",
"unicode": "ea3c",
"unicode_decimal": 59964
},
{
"icon_id": "836629",
"name": "google",
"font_class": "google",
"unicode": "e723",
"unicode_decimal": 59171
},
{
"icon_id": "929164",
"name": "chrome",
"font_class": "chrome",
"unicode": "e6c2",
"unicode_decimal": 59074
},
{
"icon_id": "944996",
"name": "mac",
"font_class": "apple",
"unicode": "e6bd",
"unicode_decimal": 59069
},
{
"icon_id": "1192366",
"name": "facebook",
"font_class": "facebook",
"unicode": "e6be",
"unicode_decimal": 59070
},
{
"icon_id": "1275498",
"name": "windows",
"font_class": "windows",
"unicode": "e649",
"unicode_decimal": 58953
},
{
"icon_id": "1444399",
"name": "discord",
"font_class": "discord",
"unicode": "e6fe",
"unicode_decimal": 59134
},
{
"icon_id": "2612016",
"name": "mac os的icon",
"font_class": "macos",
"unicode": "e6ba",
"unicode_decimal": 59066
},
{
"icon_id": "3876347",
"name": "line",
"font_class": "line",
"unicode": "e6c0",
"unicode_decimal": 59072
},
{
"icon_id": "11262564",
"name": "menu",
"font_class": "menu1",
"unicode": "e6c8",
"unicode_decimal": 59080
},
{
"icon_id": "11262644",
"name": "close",
"font_class": "close1",
"unicode": "e6c9",
"unicode_decimal": 59081
},
{
"icon_id": "11262645",
"name": "gear",
"font_class": "gear",
"unicode": "e6ca",
"unicode_decimal": 59082
},
{
"icon_id": "6145465",
"name": "bullet",
"font_class": "bullet",
"unicode": "e619",
"unicode_decimal": 58905
},
{
"icon_id": "1176968",
"name": "plus",
"font_class": "plus",
"unicode": "e727",
"unicode_decimal": 59175
},
{
"icon_id": "924591",
"name": "wallet",
"font_class": "wallet",
"unicode": "e97c",
"unicode_decimal": 59772
},
{
"icon_id": "11399282",
"name": "signature",
"font_class": "signature",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "15617545",
"name": "receipt-outlined",
"font_class": "receipt-outlined",
"unicode": "e8eb",
"unicode_decimal": 59627
},
{
"icon_id": "10561798",
"name": "Check, label",
"font_class": "check",
"unicode": "e679",
"unicode_decimal": 59001
},
{
"icon_id": "59347",
"name": "warning",
"font_class": "warning",
"unicode": "e62c",
"unicode_decimal": 58924
},
{
"icon_id": "1262104",
"name": "search",
"font_class": "search",
"unicode": "e6f2",
"unicode_decimal": 59122
},
{
"icon_id": "1336076",
"name": "Menu",
"font_class": "menu",
"unicode": "e609",
"unicode_decimal": 58889
},
{
"icon_id": "2518398",
"name": "close",
"font_class": "close",
"unicode": "e63e",
"unicode_decimal": 58942
},
{
"icon_id": "5679219",
"name": "mark-github",
"font_class": "github",
"unicode": "e667",
"unicode_decimal": 58983
},
{
"icon_id": "7141712",
"name": "external-link",
"font_class": "external-link",
"unicode": "e636",
"unicode_decimal": 58934
},
{
"icon_id": "11262646",
"name": "go",
"font_class": "go",
"unicode": "e6cb",
"unicode_decimal": 59083
},
{
"icon_id": "11262647",
"name": "back",
"font_class": "back",
"unicode": "e6cc",
"unicode_decimal": 59084
},
{
"icon_id": "13519508",
"name": "user-solid",
"font_class": "user",
"unicode": "e66f",
"unicode_decimal": 58991
},
{
"icon_id": "13895880",
"name": "arrow-up",
"font_class": "arrow-up",
"unicode": "e75c",
"unicode_decimal": 59228
},
{
"icon_id": "14232782",
"name": "arrow-up",
"font_class": "arrow-down",
"unicode": "e75d",
"unicode_decimal": 59229
}
]
}
================================================
FILE: src/components/AboutMembersListItem.vue
================================================
<script setup lang="ts"></script>
<template>
<div class="flex justify-between border-t px-4 py-3 first:border-t-0">
<slot />
</div>
</template>
================================================
FILE: src/components/AvatarOverlayEdit.vue
================================================
<script setup lang="ts">
defineProps<{
loading: boolean;
avatar?: string;
isViewOnly?: boolean;
}>();
</script>
<template>
<transition name="fade">
<div
v-if="isViewOnly"
class="absolute bottom-0 left-0 right-0 top-0 cursor-not-allowed"
/>
<div
v-else
class="group absolute bottom-0 left-0 right-0 top-0 flex cursor-pointer items-center justify-center rounded-full transition-colors ease-out hover:bg-skin-border hover:opacity-80"
:class="{
'bg-skin-border opacity-80': loading
}"
>
<div
v-if="!loading"
class="hidden transition-all ease-out group-hover:block"
>
{{ avatar ? $t('edit') : $t('upload') }}
</div>
<LoadingSpinner v-if="loading" />
</div>
</transition>
</template>
================================================
FILE: src/components/AvatarSpace.vue
================================================
<script setup lang="ts">
import { sha256 } from 'js-sha256';
const props = withDefaults(
defineProps<{
space: { id: string; avatar?: string };
size?: string;
previewFile?: File;
}>(),
{
size: '20',
previewFile: undefined
}
);
const { env } = useApp();
const avatarHash = computed(() => {
if (!props.space?.avatar) return '';
const hash = sha256(props.space.avatar).slice(0, 16);
return `&cb=${hash}`;
});
</script>
<template>
<BaseAvatar
:preview-file="previewFile"
:size="size"
:src="`https://cdn.stamp.fyi/space/${env === 'demo' ? 's-tn' : 's'}:${
space.id
}?s=${Number(size) * 2}${avatarHash}`"
/>
</template>
================================================
FILE: src/components/AvatarToken.vue
================================================
<script setup lang="ts">
withDefaults(
defineProps<{
address: string;
size?: string;
}>(),
{
size: '22'
}
);
</script>
<template>
<img
:src="`https://cdn.stamp.fyi/token/eth:${address}?s=100`"
class="rounded-full bg-skin-border object-cover"
:style="{
width: `${Number(size)}px`,
height: `${Number(size)}px`,
minWidth: `${Number(size)}px`
}"
alt="Token logo"
@error="
($event.target as HTMLImageElement).src =
`https://cdn.stamp.fyi/token/eth:${address}?s=100`
"
/>
</template>
================================================
FILE: src/components/AvatarUser.vue
================================================
<script setup lang="ts">
import { getAddress } from '@ethersproject/address';
const props = withDefaults(
defineProps<{
address: string;
size?: string;
previewFile?: File | undefined;
}>(),
{
size: '22',
previewFile: undefined
}
);
const { profilesCreated } = useProfiles();
const normalizedAddress = computed(() => getAddress(props.address));
const timestamp = computed(() => {
if (
!normalizedAddress.value ||
!profilesCreated.value?.[normalizedAddress.value]
) {
return '';
}
return `&ts=${profilesCreated.value[normalizedAddress.value]}`;
});
</script>
<template>
<BaseAvatar
:src="`https://cdn.stamp.fyi/avatar/eth:${normalizedAddress}?s=${
Number(size) * 2
}${timestamp}`"
:preview-file="previewFile"
:size="size"
/>
</template>
================================================
FILE: src/components/Banner.vue
================================================
<script setup lang="ts">
const route = useRoute();
const { env, domain } = useApp();
const link = computed(() => {
const baseUrl =
env === 'demo'
? 'https://testnet.snapshot.box/#/'
: 'https://snapshot.box/#/';
let path = 'home';
const prefix = env === 'demo' ? `s-tn` : 's';
switch (route.name) {
case 'home': {
path = 'explore';
break;
}
case 'spaceProposals': {
path = `${prefix}:${route.params.key}`;
break;
}
case 'spaceSettings': {
path = `${prefix}:${route.params.key}/settings`;
break;
}
case 'spaceAbout': {
path = `${prefix}:${route.params.key}`;
break;
}
case 'spaceProposal': {
path = `${prefix}:${route.params.key}/proposal/${route.params.id}`;
break;
}
case 'spaceCreate': {
path = `${prefix}:${route.params.key}/create`;
break;
}
case 'spaceDelegates': {
path = `${prefix}:${route.params.key}/delegates`;
break;
}
case 'delegate': {
path = route.params.key
? `${prefix}:${route.params.key}/delegates`
: 'explore';
break;
}
case 'profileAbout': {
path = `profile/${route.params.address}`;
break;
}
case 'profileActivity': {
path = `profile/${route.params.address}`;
break;
}
}
return `${baseUrl}${path}`;
});
</script>
<template>
<a
v-if="!domain"
:href="link"
class="flex bg-blue-700 text-white rounded-full px-[10px] py-[2px] gap-1"
>
<div class="leading-6 hidden md:block">Switch to the new interface</div>
<div class="leading-6 md:hidden">Switch to v2</div>
<i-ho-arrow-narrow-right class="shrink-0 hidden xs:block" />
</a>
</template>
================================================
FILE: src/components/BaseAvatar.vue
================================================
<script setup lang="ts">
interface Props {
src: string;
size?: string;
previewFile?: File | undefined;
}
const props = withDefaults(defineProps<Props>(), {
size: '22',
previewFile: undefined
});
const avatarImage = ref<HTMLImageElement | null>(null);
watch(
() => props.previewFile,
() => {
// Preview can be used to show a local image instantly (f.e after uploading an image)
if (avatarImage.value && props.previewFile) {
return (avatarImage.value.src = URL.createObjectURL(props.previewFile));
}
// This removes the preview image if it's a blob and the previewFile is blank
if (avatarImage.value?.src.startsWith('blob') && !props.previewFile) {
return (avatarImage.value.src = '');
}
},
{ immediate: true }
);
</script>
<template>
<div>
<!-- Show local review image if previewFile is defined -->
<img
v-show="previewFile"
ref="avatarImage"
class="rounded-full bg-skin-border object-cover"
:style="{
width: `${Number(size)}px`,
height: `${Number(size)}px`,
minWidth: `${Number(size)}px`
}"
alt="avatar"
/>
<!-- else show image from ipfs or stamp -->
<img
v-show="!previewFile && src"
:src="src"
class="rounded-full bg-skin-border object-cover"
:style="{
width: `${Number(size)}px`,
height: `${Number(size)}px`,
minWidth: `${Number(size)}px`
}"
alt="avatar"
/>
<div
v-if="!src && !previewFile"
class="rounded-full bg-skin-border"
:style="{
width: `${Number(size)}px`,
height: `${Number(size)}px`,
minWidth: `${Number(size)}px`
}"
/>
</div>
</template>
================================================
FILE: src/components/BaseBadge.vue
================================================
<script setup lang="ts">
const props = defineProps<{ address: string; members?: string[] }>();
const isCore = computed(() => {
if (!props.members) return false;
const members = props.members.map(address => address.toLowerCase());
return members.includes(props.address.toLowerCase());
});
</script>
<template>
<div
v-if="isCore"
class="ml-1 rounded-full border px-[7px] text-xs text-skin-text leading-[22px]"
>
{{ $t('isCore') }}
</div>
</template>
================================================
FILE: src/components/BaseBlock.vue
================================================
<script setup lang="ts">
defineProps<{
title?: string;
counter?: number;
slim?: boolean;
loading?: boolean;
hideBottomBorder?: boolean;
label?: string;
labelTooltip?: string;
information?: string;
isCollapsable?: boolean;
showMoreButton?: boolean;
showMoreButtonLabel?: string;
loadingMore?: boolean;
}>();
defineEmits(['showMore']);
const isCollapsed = ref(true);
</script>
<template>
<div
class="border-y border-skin-border bg-skin-block-bg text-base md:rounded-xl md:border"
>
<div
v-if="title"
class="group flex h-[57px] justify-between rounded-t-none border-b border-skin-border px-4 pb-[12px] pt-3 md:rounded-t-lg"
:class="[
{
'border-b-0': hideBottomBorder || (isCollapsable && isCollapsed)
},
{ 'cursor-pointer': isCollapsable }
]"
@click="isCollapsable ? (isCollapsed = !isCollapsed) : null"
>
<h4 class="flex items-center">
<div>
{{ title }}
</div>
<IconInformationTooltip
:information="information"
class="ml-1 text-sm text-skin-text"
/>
<BaseCounter :counter="counter" class="ml-1 inline-block" />
</h4>
<div class="flex items-center">
<div
v-if="label"
v-tippy="{ content: labelTooltip ? labelTooltip : null }"
class="text-xs text-skin-link"
:class="{ 'cursor-help': labelTooltip }"
>
{{ label }}
</div>
</div>
<slot name="button" />
<BaseButtonIcon
v-if="isCollapsable"
class="pr-0 group-hover:text-skin-link"
>
<i-ho-chevron-up :class="[{ 'rotate-180': isCollapsed }]" />
</BaseButtonIcon>
</div>
<div v-if="loading" class="block px-4 py-4">
<LoadingList />
</div>
<Transition name="fade">
<div
v-if="!loading && (!isCollapsed || !isCollapsable)"
:class="!slim && 'p-4'"
class="leading-5 sm:leading-6 break-words"
>
<slot />
<div
v-if="showMoreButton"
class="block rounded-b-none border-t px-4 py-3 text-center md:rounded-b-md"
>
<LoadingSpinner v-if="loadingMore" />
<button v-else @click="$emit('showMore')">
<span v-text="$t(showMoreButtonLabel || 'seeMore')" />
</button>
</div>
</div>
</Transition>
</div>
</template>
================================================
FILE: src/components/BaseBreadcrumbs.vue
================================================
<script setup lang="ts">
defineProps<{
pages: { id?: string; name: string; to: string; current: boolean }[];
}>();
</script>
<template>
<div class="flex items-center overflow-x-scroll no-scrollbar">
<div v-for="(page, i) in pages" :key="i" class="flex items-center">
<router-link v-if="!page.current" :to="page.to" class="flex items-center">
<span class="text-skin-link truncate max-w-[180px]">{{
page.name
}}</span>
</router-link>
<div v-else class="flex cursor-default items-center">
<span
class="text-skin-link opacity-40 truncate max-w-[180px]"
:class="{
'!max-w-[320px]': page.id === 'proposal-title'
}"
>{{ page.name }}</span
>
</div>
<div class="mx-1 flex h-[20px] w-[20px] items-center justify-center">
<i-ho-chevron-right
v-if="i < pages.length - 1"
class="shrink-0 text-xs text-skin-text"
/>
</div>
</div>
</div>
</template>
================================================
FILE: src/components/BaseButtonIcon.vue
================================================
<script setup lang="ts">
defineProps<{
loading?: boolean;
isDisabled?: boolean;
}>();
</script>
<template>
<button
type="button"
:disabled="isDisabled"
:class="{ '!cursor-not-allowed': isDisabled }"
class="flex items-center rounded-full p-[6px] text-md text-skin-text transition-colors duration-200 hover:text-skin-link"
>
<LoadingSpinner v-if="loading" />
<slot v-else />
</button>
</template>
================================================
FILE: src/components/BaseButtonRound.vue
================================================
<script setup lang="ts">
withDefaults(
defineProps<{
isDisabled?: boolean;
size?: string;
}>(),
{
isDisabled: false,
size: '46px'
}
);
</script>
<template>
<button
:disabled="isDisabled"
:class="{ '!cursor-not-allowed': isDisabled }"
class="flex cursor-pointer select-none items-center justify-center rounded-full border hover:border-skin-text"
:style="`width: ${size}; height: ${size}`"
>
<slot />
</button>
</template>
================================================
FILE: src/components/BaseCalendar.vue
================================================
<script setup lang="ts">
const props = defineProps<{
modelValue: string;
}>();
const emit = defineEmits(['update:modelValue']);
const { currentLocale: locale } = useI18n();
const [
yearNow = new Date().getFullYear(),
monthNow = new Date().getMonth()
// dayNow = new Date().getDate()
] = props.modelValue ? props.modelValue.split('-') : [];
const input = ref(props.modelValue);
const year = ref(Number(yearNow));
const month = ref(Number(monthNow) - 1);
// const day = ref(dayNow);
const fullYear = computed(() =>
new Date(year.value, month.value).getFullYear()
);
const days = computed(() => new Date(year.value, month.value + 1, 0).getDate());
const emptyDays = computed(() => new Date(year.value, month.value, 1).getDay());
const today = computed(() => {
return formatDate(
new Date().getFullYear(),
new Date().getMonth(),
new Date().getDate()
);
});
const daysOfWeek = computed(() => {
const sunday = new Date(2017, 0, 0);
return [...Array(7)].map(() => {
sunday.setDate(sunday.getDate() + 1);
return sunday.toLocaleDateString(locale.value, {
weekday: 'short'
});
});
});
const monthName = computed(() => {
const name = new Date(year.value, month.value).toLocaleString(locale.value, {
month: 'long'
});
return `${name.charAt(0).toUpperCase()}${name.slice(1)}`;
});
function formatDate(year, month, day) {
let date = new Date(year, month, day);
const offset = date.getTimezoneOffset();
date = new Date(date.getTime() - offset * 60 * 1000);
return date.toISOString().split('T')[0];
}
function toggleDay(year, month, day) {
input.value = formatDate(year, month, day);
emit('update:modelValue', input.value);
}
function isSelectable(year, month, day) {
const date = new Date(year, month, day);
const dateNow = new Date().setHours(0, 0, 0, 0);
return !(dateNow - Number(date) > 0);
}
</script>
<template>
<div class="calendar">
<div class="mb-2 flex items-center">
<div class="w-1/4 text-left">
<button
class="iconfont iconback text-lg font-semibold text-skin-text"
@click="month--"
/>
</div>
<h4 class="h-full w-full text-center">{{ monthName }} {{ fullYear }}</h4>
<div class="w-1/4 text-right">
<button
class="iconfont icongo text-lg font-semibold text-skin-text"
@click="month++"
/>
</div>
</div>
<div class="overflow-hidden border-l border-t">
<div
v-for="dayOfWeek in daysOfWeek"
:key="dayOfWeek"
class="day border-b border-r text-skin-link"
v-text="dayOfWeek"
/>
<div
v-for="emptyDay in emptyDays"
:key="`empty-${emptyDay}`"
class="day border-b border-r"
/>
<div v-for="day in days" :key="day">
<a
v-if="isSelectable(year, month, day)"
class="day border-b border-r bg-transparent text-skin-link hover:bg-skin-link hover:text-skin-bg"
:class="{
'ring-1 ring-inset ring-skin-primary':
formatDate(year, month, day) === today,
'!bg-skin-link !text-skin-bg': input.includes(
formatDate(year, month, day)
)
}"
tabindex="0"
@click="toggleDay(year, month, day)"
@keypress="toggleDay(year, month, day)"
v-text="day"
/>
<div
v-else
class="day cursor-not-allowed border-b border-r text-skin-border"
v-text="day"
/>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.calendar {
width: 309px;
margin: 0 auto;
.day {
text-decoration: none;
font-size: 17px !important;
float: left;
text-align: center;
line-height: 44px;
width: 44px;
height: 44px;
}
}
</style>
================================================
FILE: src/components/BaseCombobox.vue
================================================
<script setup lang="ts">
import {
Combobox,
ComboboxInput,
ComboboxOptions,
ComboboxOption,
ComboboxLabel,
ComboboxButton
} from '@headlessui/vue';
const props = defineProps<{
label: string;
items: { id: number | string; name: string }[];
selectedId?: number | string;
information?: string;
isDisabled?: boolean;
}>();
const emit = defineEmits(['select', 'search']);
const selectedItem = ref<{ id: number | string; name: string }>(props.items[0]);
watch(selectedItem, () => emit('select', selectedItem.value));
watch(
() => props.selectedId,
() => {
const selected = props.items.find(item => item.id === props.selectedId);
selectedItem.value = selected ? selected : props.items[0];
},
{ immediate: true }
);
</script>
<template>
<Combobox
v-model="selectedItem"
:disabled="isDisabled"
as="div"
class="w-full"
>
<ComboboxLabel v-if="label" class="block">
<LabelInput :information="information">{{ label }}</LabelInput>
</ComboboxLabel>
<div class="relative">
<ComboboxButton class="w-full">
<ComboboxInput
class="s-input w-full py-2 !pr-[30px] pl-3 focus:outline-none"
spellcheck="false"
:display-value="(item: any) => item.name"
:class="{ 'cursor-not-allowed': isDisabled }"
:disabled="isDisabled"
@change="emit('search', $event.target.value)"
/>
</ComboboxButton>
<ComboboxButton
class="absolute inset-y-0 right-1 flex items-center px-2 focus:outline-none"
:class="{ 'cursor-not-allowed': isDisabled }"
>
<i-ho-chevron-down class="text-[14px] text-skin-link" />
</ComboboxButton>
<ComboboxOptions
v-if="items.length > 0"
class="absolute z-40 mt-1 w-full overflow-hidden rounded-md border border-skin-border bg-skin-bg text-base shadow-lg focus:outline-none sm:text-sm"
>
<div class="max-h-[180px] overflow-y-auto">
<ComboboxOption
v-for="item in items"
v-slot="{ active, selected, disabled }"
:key="item.id"
as="template"
:value="item"
>
<li
:class="[
{ 'bg-skin-border': active },
'relative cursor-default select-none truncate py-2 pl-3 pr-[50px]'
]"
>
<span
:class="[
selected ? 'font-semibold text-skin-link' : 'font-normal',
{ 'text-skin-border': disabled },
'flex w-full items-center truncate'
]"
>
<slot v-if="$slots.item" name="item" :item="item" />
<span v-else>
{{ item.name }}
</span>
</span>
<span
v-if="selected"
:class="['absolute inset-y-0 right-0 flex items-center pr-3']"
>
<i-ho-check class="text-green" />
</span>
</li>
</ComboboxOption>
</div>
</ComboboxOptions>
</div>
</Combobox>
</template>
================================================
FILE: src/components/BaseContainer.vue
================================================
<script setup lang="ts">
defineProps<{
slim?: boolean;
}>();
</script>
<template>
<div :class="slim ? 'px-0 md:px-4' : 'px-4'" class="mx-auto max-w-[1012px]">
<slot />
</div>
</template>
================================================
FILE: src/components/BaseCounter.vue
================================================
<script setup lang="ts">
const { formatNumber } = useIntl();
defineProps<{
counter?: number | string;
}>();
</script>
<template>
<div
v-if="(counter && counter >= 0) || typeof counter === 'string'"
class="h-[20px] min-w-[20px] rounded-full bg-skin-text px-1 text-center text-xs leading-normal text-white"
v-text="formatNumber(Number(counter))"
/>
</template>
================================================
FILE: src/components/BaseIcon.vue
================================================
<script setup lang="ts">
withDefaults(
defineProps<{
name: string;
size?: string;
}>(),
{
name: '',
size: '16'
}
);
</script>
<template>
<i
class="iconfont"
:class="`icon${name}`"
:style="size ? `font-size: ${size}px; line-height: ${size}px;` : ''"
/>
</template>
================================================
FILE: src/components/BaseIndicator.vue
================================================
<template>
<span class="inline-block h-[12px] w-[12px] rounded-full bg-skin-primary" />
</template>
================================================
FILE: src/components/BaseInput.vue
================================================
<script lang="ts">
export default {
inheritAttrs: false
};
</script>
<script setup lang="ts">
import { FormError } from '@/helpers/interfaces';
const props = withDefaults(
defineProps<{
type?: 'text' | 'number' | 'email';
modelValue?: string | number;
definition?: any;
error?: FormError | null;
focusOnMount?: boolean;
hideInput?: boolean;
placeholder?: string;
title?: string;
maxLength?: number;
readonly?: boolean;
information?: string;
loading?: boolean;
isDisabled?: boolean;
success?: boolean;
failed?: boolean;
}>(),
{
type: 'text',
modelValue: undefined,
definition: undefined,
error: null,
focusOnMount: false,
hideInput: false,
placeholder: undefined,
title: undefined,
maxLength: undefined,
readonly: false,
information: undefined,
loading: false,
isDisabled: false,
success: false,
failed: false
}
);
defineEmits(['update:modelValue']);
const BaseInputEL = ref<HTMLDivElement | undefined>(undefined);
const visited = ref(false);
const showErrorMessage = computed(() => visited.value || props.error?.push);
onMounted(() => {
if (props.focusOnMount) {
BaseInputEL?.value?.focus();
}
});
</script>
<template>
<div class="w-full">
<LabelInput v-if="title || definition?.title" :information="information">
{{ title ?? definition.title }}
</LabelInput>
<div class="group relative z-10">
<div
v-if="$slots.before"
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"
>
<slot name="before" />
</div>
<input
v-bind="$attrs"
ref="BaseInputEL"
:type="type"
:value="modelValue"
:class="[
's-input !h-[42px]',
{ '!border-red': error?.message && showErrorMessage },
{ 'cursor-not-allowed placeholder:!opacity-30': isDisabled }
]"
:maxlength="maxLength ?? definition?.maxLength"
:placeholder="placeholder ?? definition?.examples?.[0] ?? ''"
:readonly="readonly"
:disabled="isDisabled"
@blur="error?.message ? (visited = true) : null"
@focus="error?.message ? null : (visited = false)"
@input="
$emit('update:modelValue', ($event.target as HTMLInputElement).value)
"
/>
<div
v-if="loading || success || failed"
class="absolute inset-y-0 right-0 top-[1px] mr-1 hidden h-[40px] items-center overflow-hidden rounded-r-full bg-skin-bg pl-2 pr-2 group-focus-within:flex"
>
<LoadingSpinner v-if="loading" class="pb-[3px]" />
<i-ho-check v-if="success" class="text-md text-green" />
<i-ho-x v-if="failed" class="text-sm text-red" />
</div>
<div
v-else-if="$slots.after"
class="absolute inset-y-0 right-0 flex items-center pr-4"
>
<slot name="after" />
</div>
</div>
<div
:class="[
's-error',
!!error?.message && showErrorMessage
? '-mt-[21px] opacity-100'
: '-mt-[40px] h-6 opacity-0'
]"
>
<BaseIcon
v-if="error?.message && showErrorMessage"
name="warning"
class="mr-2 text-white"
/>
{{ error?.message }}
</div>
</div>
</template>
================================================
FILE: src/components/BaseInterpunct.vue
================================================
<template>
<div class="mx-2 bg-skin-text w-1 h-1 opacity-60 rounded-full" />
</template>
================================================
FILE: src/components/BaseLink.vue
================================================
<script setup lang="ts">
import { sanitizeUrl } from '@braintree/sanitize-url';
type Link = Record<string, any> | string;
defineProps<{
link: Link;
hideExternalIcon?: boolean;
disabled?: boolean;
}>();
</script>
<template>
<a
v-if="typeof link === 'string'"
:href="sanitizeUrl(link)"
target="_blank"
:class="['whitespace-nowrap', { 'pointer-events-none': disabled }]"
rel="noopener noreferrer"
>
<slot />
<i-ho-external-link
v-if="!hideExternalIcon"
class="mb-[2px] ml-1 inline-block text-xs"
/>
</a>
<router-link
v-else
:to="link"
:class="['whitespace-nowrap', { 'pointer-events-none': disabled }]"
>
<slot />
</router-link>
</template>
================================================
FILE: src/components/BaseListbox.vue
================================================
<script setup lang="ts">
import {
Listbox,
ListboxButton,
ListboxOptions,
ListboxOption,
ListboxLabel
} from '@headlessui/vue';
import isEqual from 'lodash/isEqual';
type ListboxItem = {
value: any;
title?: string;
extras?: Record<string, any>;
};
const props = defineProps<{
items: ListboxItem[];
modelValue: any;
label?: string;
disableInput?: boolean;
definition?: any;
information?: string;
}>();
const emit = defineEmits(['update:modelValue']);
const selectedItem = computed({
get: () =>
props.items.find(item => isEqual(item.value, props.modelValue)) ||
props.items[0],
set: newVal => emit('update:modelValue', newVal.value)
});
</script>
<template>
<Listbox v-model="selectedItem" as="div" :disabled="disableInput">
<ListboxLabel v-if="label || definition?.title">
<LabelInput :information="information || definition?.description">
{{ label || definition?.title }}
</LabelInput>
</ListboxLabel>
<div class="relative">
<ListboxButton
class="relative h-[46px] w-full truncate rounded-full border border-skin-border pl-3 pr-[40px] text-left text-skin-link hover:border-skin-text"
:class="{ 'cursor-not-allowed text-skin-border': disableInput }"
>
<slot
v-if="$slots.selected"
name="selected"
:selected-item="selectedItem"
/>
<span v-else-if="selectedItem">
{{ selectedItem?.title || selectedItem.value }}
</span>
<span
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-[12px]"
>
<i-ho-chevron-down class="text-[14px] text-skin-link" />
</span>
</ListboxButton>
<transition
enter-active-class="transition duration-100 ease-out"
enter-from-class="transform -translate-y-2 scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-75 ease-out"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<ListboxOptions
class="absolute z-40 mt-1 w-full overflow-hidden rounded-md border border-skin-border bg-skin-bg text-base shadow-lg focus:outline-none sm:text-sm"
>
<div class="max-h-[180px] overflow-y-auto">
<ListboxOption
v-for="item in items"
:key="item.value"
v-slot="{ active, selected, disabled }"
as="template"
:value="item"
>
<li
:class="[
{ 'bg-skin-border': active },
'relative cursor-default select-none py-2 pl-3 pr-[50px]'
]"
>
<span
:class="[
selected ? 'font-semibold text-skin-link' : 'font-normal',
{ 'text-skin-border': disabled },
'block truncate'
]"
>
<slot v-if="$slots.item" name="item" :item="item" />
<span v-else>
{{ item?.title || item.value }}
</span>
</span>
<span
v-if="selected"
:class="['absolute inset-y-0 right-0 flex items-center pr-3']"
>
<i-ho-check class="text-green" />
</span>
</li>
</ListboxOption>
</div>
</ListboxOptions>
</transition>
</div>
</Listbox>
</template>
================================================
FILE: src/components/BaseLoading.vue
================================================
<script setup lang="ts">
defineProps<{ block?: boolean }>();
</script>
<template>
<BaseBlock v-if="block" slim>
<slot />
</BaseBlock>
<div v-else>
<slot />
</div>
</template>
================================================
FILE: src/components/BaseMarkdown.vue
================================================
<script setup lang="ts">
import { Remarkable } from 'remarkable';
import { linkify } from 'remarkable/linkify';
// import sanitizeHtml from 'sanitize-html';
import { getIpfsUrl } from '@/helpers/utils';
import { isSnapshotUrl } from '@/helpers/utils';
const props = defineProps<{
body: string;
}>();
const { copyToClipboard } = useCopy();
const showModal = ref(false);
const clickedUrl = ref('');
const remarkable = new Remarkable({
html: false,
breaks: true,
typographer: false,
linkTarget: '_blank'
}).use(linkify);
const markdown = computed(() => {
let body = props.body;
// Add the ipfs gateway to markdown images that start with ipfs://
function replaceIpfsUrl(match, p1) {
return match.replace(p1, getIpfsUrl(p1));
}
body = body.replace(/!\[.*?\]\((ipfs:\/\/[a-zA-Z0-9]+?)\)/g, replaceIpfsUrl);
// if body contains a link that contain `_` , replace it with `\_` to escape it
body = body.replace(/(http.*?)(?=_)/g, '$1\\');
return remarkable.render(body);
});
function handleLinkClick(e, url) {
e.preventDefault();
clickedUrl.value = url;
if (isSnapshotUrl(url)) {
return handleConfirm();
}
showModal.value = true;
}
function handleConfirm() {
window.open(clickedUrl.value, '_blank', 'noopener,noreferrer');
}
onMounted(() => {
const body = document.querySelector('.markdown-body');
if (body !== null) {
body.querySelectorAll('pre>code').forEach(function (code) {
const parent = code.parentElement;
if (parent !== null) parent.classList.add('rounded-lg');
const copyButton = document.createElement('a');
const icon = document.createElement('i');
icon.classList.add('copy');
icon.classList.add('text-skin-text');
icon.classList.add('iconcopy');
icon.classList.add('iconfont');
copyButton.appendChild(icon);
copyButton.addEventListener('click', function () {
if (parent !== null) copyToClipboard(parent.innerText.trim());
});
code.appendChild(copyButton);
});
body.querySelectorAll('a[href]').forEach(function (link) {
link.addEventListener('click', function (e) {
handleLinkClick(e, link.getAttribute('href'));
});
});
}
});
</script>
<template>
<!-- eslint-disable vue/no-v-html -->
<div
v-viewer
v-bind="$attrs"
class="markdown-body break-words"
v-html="markdown"
/>
<Teleport to="#modal">
<ModalLinkPreview
:open="showModal"
:clicked-url="clickedUrl"
@close="showModal = false"
@confirm="handleConfirm"
/>
</Teleport>
</template>
<style lang="scss">
.markdown-body {
font-size: 19px;
line-height: 1.3;
word-wrap: break-word;
}
.markdown-body::before {
display: table;
content: '';
}
.markdown-body blockquote {
color: var(--text-color);
border-left-color: var(--text-color);
}
.markdown-body::after {
display: table;
clear: both;
content: '';
}
.markdown-body > *:first-child {
margin-top: 0 !important;
}
.markdown-body > *:last-child {
margin-bottom: 0 !important;
}
.markdown-body a:not([href]) {
color: inherit;
text-decoration: none;
}
.markdown-body .absent {
color: #cb2431;
}
.markdown-body .anchor {
float: left;
padding-right: 4px;
margin-left: -20px;
line-height: 1;
}
.markdown-body .anchor:focus {
outline: none;
}
.markdown-body p,
.markdown-body blockquote,
.markdown-body ul,
.markdown-body ol,
.markdown-body dl,
.markdown-body table,
.markdown-body pre {
margin-top: 0;
margin-bottom: 16px;
}
.markdown-body hr {
height: 0.25em;
padding: 0;
margin: 24px 0;
background-color: #e1e4e8;
border: 0;
}
.markdown-body blockquote {
padding: 0 1em;
color: #6a737d;
border-left: 0.25em solid #dfe2e5;
}
.markdown-body blockquote > :first-child {
margin-top: 0;
}
.markdown-body blockquote > :last-child {
margin-bottom: 0;
}
.markdown-body kbd {
display: inline-block;
padding: 3px 5px;
font-size: 11px;
line-height: 10px;
color: #444d56;
vertical-align: middle;
background-color: #fafbfc;
border: solid 1px #c6cbd1;
border-bottom-color: #959da5;
border-radius: 3px;
box-shadow: inset 0 -1px 0 #959da5;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 24px;
margin-bottom: 16px;
font-weight: 600;
line-height: 1.4 !important;
}
.markdown-body h1 .octicon-link,
.markdown-body h2 .octicon-link,
.markdown-body h3 .octicon-link,
.markdown-body h4 .octicon-link,
.markdown-body h5 .octicon-link,
.markdown-body h6 .octicon-link {
color: #1b1f23;
vertical-align: middle;
visibility: hidden;
}
.markdown-body h1:hover .anchor,
.markdown-body h2:hover .anchor,
.markdown-body h3:hover .anchor,
.markdown-body h4:hover .anchor,
.markdown-body h5:hover .anchor,
.markdown-body h6:hover .anchor {
text-decoration: none;
}
.markdown-body h1:hover .anchor .octicon-link,
.markdown-body h2:hover .anchor .octicon-link,
.markdown-body h3:hover .anchor .octicon-link,
.markdown-body h4:hover .anchor .octicon-link,
.markdown-body h5:hover .anchor .octicon-link,
.markdown-body h6:hover .anchor .octicon-link {
visibility: visible;
}
.markdown-body h1 tt,
.markdown-body h1 code,
.markdown-body h2 tt,
.markdown-body h2 code,
.markdown-body h3 tt,
.markdown-body h3 code,
.markdown-body h4 tt,
.markdown-body h4 code,
.markdown-body h5 tt,
.markdown-body h5 code,
.markdown-body h6 tt,
.markdown-body h6 code {
font-size: inherit;
}
.markdown-body h1 {
font-size: 1.5em;
}
.markdown-body h2 {
font-size: 1.25em;
}
.markdown-body h3 {
font-size: 1em;
}
.markdown-body h4 {
font-size: 0.875em;
}
.markdown-body h5 {
font-size: 0.85em;
}
.markdown-body h6 {
font-size: 0.8em;
}
.markdown-body ul,
.markdown-body ol {
padding-left: 2em;
}
.markdown-body ul.no-list,
.markdown-body ol.no-list {
padding: 0;
list-style-type: none;
}
.markdown-body ul {
list-style-type: disc;
}
.markdown-body ol {
list-style-type: decimal;
}
.markdown-body ul ul,
.markdown-body ul ol,
.markdown-body ol ol,
.markdown-body ol ul {
margin-top: 0;
margin-bottom: 0;
}
.markdown-body li {
word-wrap: break-all;
}
.markdown-body li > p {
margin-top: 16px;
}
.markdown-body li + li {
margin-top: 0.25em;
}
.markdown-body dl {
padding: 0;
}
.markdown-body dl dt {
padding: 0;
margin-top: 16px;
font-size: 1em;
font-style: italic;
font-weight: 600;
}
.markdown-body dl dd {
padding: 0 16px;
margin-bottom: 16px;
}
.markdown-body table {
display: block;
width: 100%;
overflow: auto;
}
.markdown-body table th {
font-weight: 600;
}
.markdown-body table th,
.markdown-body table td {
padding: 6px 13px;
border: 1px solid var(--border-color);
}
.markdown-body table thead tr,
.markdown-body table tbody tr:nth-child(2n) {
background-color: var(--bg-color);
border-top: 1px solid #c6cbd1;
}
.markdown-body table tbody tr {
background-color: var(--bg-color);
}
.markdown-body table img {
background-color: transparent;
}
.markdown-body img {
max-width: 100%;
box-sizing: content-box;
background-color: #fff;
cursor: pointer;
}
.markdown-body img[align='right'] {
padding-left: 20px;
}
.markdown-body img[align='left'] {
padding-right: 20px;
}
.markdown-body .emoji {
max-width: none;
vertical-align: text-top;
background-color: transparent;
}
.markdown-body span.frame {
display: block;
overflow: hidden;
}
.markdown-body span.frame > span {
display: block;
float: left;
width: auto;
padding: 7px;
margin: 13px 0 0;
overflow: hidden;
border: 1px solid #dfe2e5;
}
.markdown-body span.frame span img {
display: block;
float: left;
}
.markdown-body span.frame span span {
display: block;
padding: 5px 0 0;
clear: both;
color: #24292e;
}
.markdown-body span.align-center {
display: block;
overflow: hidden;
clear: both;
}
.markdown-body span.align-center > span {
display: block;
margin: 13px auto 0;
overflow: hidden;
text-align: center;
}
.markdown-body span.align-center span img {
margin: 0 auto;
text-align: center;
}
.markdown-body span.align-right {
display: block;
overflow: hidden;
clear: both;
}
.markdown-body span.align-right > span {
display: block;
margin: 13px 0 0;
overflow: hidden;
text-align: right;
}
.markdown-body span.align-right span img {
margin: 0;
text-align: right;
}
.markdown-body span.float-left {
display: block;
float: left;
margin-right: 13px;
overflow: hidden;
}
.markdown-body span.float-left span {
margin: 13px 0 0;
}
.markdown-body span.float-right {
display: block;
float: right;
margin-left: 13px;
overflow: hidden;
}
.markdown-body span.float-right > span {
display: block;
margin: 13px auto 0;
overflow: hidden;
text-align: right;
}
.markdown-body code,
.markdown-body tt {
padding: 0.2em 0.4em;
margin: 0;
background-color: rgba(27, 31, 35, 0.05);
border-radius: 3px;
font-size: 16px;
}
.markdown-body code br,
.markdown-body tt br {
display: none;
}
.markdown-body del code {
text-decoration: inherit;
}
.markdown-body pre {
font-size: 16px;
word-wrap: normal;
}
.markdown-body pre {
color: var(--link-color);
background-color: var(--border-color);
position: relative;
}
.markdown-body pre > code {
.copy {
font-size: 24px;
line-height: 24px;
position: absolute;
right: 1.2rem;
top: 1.2rem;
}
}
.markdown-body pre > code {
padding: 0;
margin: 0;
word-break: normal;
white-space: pre;
background: transparent;
border: 0;
}
.markdown-body .highlight {
margin-bottom: 16px;
}
.markdown-body .highlight pre {
margin-bottom: 0;
word-break: normal;
}
.markdown-body .highlight pre,
.markdown-body pre {
padding: 16px;
overflow: auto;
}
.markdown-body pre code,
.markdown-body pre tt {
display: inline;
max-width: auto;
padding: 0;
margin: 0;
overflow: visible;
line-height: inherit;
word-wrap: normal;
background-color: transparent;
border: 0;
}
.markdown-body .csv-data td,
.markdown-body .csv-data th {
padding: 5px;
overflow: hidden;
font-size: 12px;
line-height: 1;
text-align: left;
white-space: nowrap;
}
.markdown-body .csv-data .blob-num {
padding: 10px 8px 9px;
text-align: right;
background: #fff;
border: 0;
}
.markdown-body .csv-data tr {
border-top: 0;
}
.markdown-body .csv-data th {
font-weight: 600;
background: #f6f8fa;
border-top: 0;
}
@media (min-width: 544px) {
.markdown-body {
font-size: 22px;
line-height: 1.4;
}
.markdown-body h1 {
font-size: 2em;
}
.markdown-body h2 {
font-size: 1.5em;
}
.markdown-body h3 {
font-size: 1.25em;
}
.markdown-body h4 {
font-size: 1em;
}
.markdown-body h5 {
font-size: 0.875em;
}
.markdown-body h6 {
font-size: 0.85em;
}
}
</style>
================================================
FILE: src/components/BaseMenu.vue
================================================
<script setup lang="ts">
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';
import { Float } from '@headlessui-float/vue';
import type { Placement } from '@floating-ui/dom';
type Item = {
text: string;
action: string;
extras?: any;
};
withDefaults(
defineProps<{
items: Item[];
selected?: string;
placement?: Placement;
}>(),
{
selected: '',
placement: 'bottom-end'
}
);
const emit = defineEmits(['select']);
</script>
<template>
<Menu as="div" class="inline-block h-full text-left">
<Float
portal
enter="transition ease-out duration-100"
enter-from="transform opacity-0 scale-95"
enter-to="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leave-from="transform opacity-100 scale-100"
leave-to="transform opacity-0 scale-95"
:placement="placement"
:offset="8"
:shift="16"
:flip="16"
:z-index="50"
>
<MenuButton as="template">
<slot v-if="$slots.button" name="button" />
<TuneButton v-else class="flex items-center">
{{ selected }}
<i-ho-chevron-down
class="-mr-1 ml-1 text-xs text-skin-link"
aria-hidden="true"
/>
</TuneButton>
</MenuButton>
<MenuItems
class="overflow-hidden rounded-2xl border bg-skin-header-bg shadow-lg outline-none"
>
<div class="no-scrollbar max-h-[300px] overflow-auto">
<MenuItem v-for="item in items" :key="item.text" v-slot="{ active }">
<div
:class="[
active
? 'bg-skin-border text-skin-link'
: 'bg-skin-header-bg text-skin-text',
'cursor-pointer whitespace-nowrap px-3 py-2'
]"
@click="emit('select', item.action)"
>
<slot :key="item" name="item" :item="item">
{{ item.text }}
</slot>
</div>
</MenuItem>
</div>
</MenuItems>
</Float>
</Menu>
</template>
================================================
FILE: src/components/BaseMessage.vue
================================================
<script setup lang="ts">
defineProps<{
level: 'info' | 'warning' | 'warning-red';
}>();
</script>
<template>
<div>
<i-ho-information-circle
v-if="level === 'info'"
class="float-left mr-1 text-sm"
/>
<i-ho-exclamation-circle
v-else
class="float-left mr-1 text-sm"
:class="{ 'text-red': level === 'warning-red' }"
/>
<div class="leading-5" :class="{ 'text-red': level === 'warning-red' }">
<slot />
</div>
</div>
</template>
================================================
FILE: src/components/BaseMessageBlock.vue
================================================
<script setup lang="ts">
defineProps<{
level: 'info' | 'warning' | 'warning-red';
isResponsive?: boolean;
}>();
</script>
<template>
<BaseBlock
:class="[
'rounded-xl border text-skin-text',
{ '!border-skin-text': level === 'warning' },
{ '!border-red': level === 'warning-red' },
{
'!rounded-none border-x-0 md:!rounded-xl': isResponsive
}
]"
>
<BaseMessage :level="level">
<slot />
</BaseMessage>
</BaseBlock>
</template>
================================================
FILE: src/components/BaseModal.vue
================================================
<script setup lang="ts">
import { useWindowSize } from '@vueuse/core';
const props = withDefaults(
defineProps<{
open: boolean;
hideClose?: boolean;
maxHeight?: string;
}>(),
{
hideClose: false,
maxHeight: '420px'
}
);
const emit = defineEmits(['close']);
const { open } = toRefs(props);
const { height } = useWindowSize();
const heightStyle = computed(() => {
return `${height.value}px !important`;
});
function onKeydown(e: KeyboardEvent) {
if (e.key === 'Escape') emit('close');
}
watch(open, isOpen => {
document.body.classList[isOpen ? 'add' : 'remove']('overflow-hidden');
if (isOpen) document.addEventListener('keydown', onKeydown);
else document.removeEventListener('keydown', onKeydown);
});
onBeforeUnmount(() => {
document.body.classList.remove('overflow-hidden');
});
</script>
<template>
<Transition name="fade">
<div v-if="open" class="modal z-50 mx-auto w-screen">
<div class="backdrop" @click="emit('close')" />
<div class="shell relative overflow-hidden rounded-none md:rounded-3xl">
<div v-if="$slots.header" class="pt-3 text-center">
<slot name="header" />
</div>
<div class="modal-body">
<slot :max-height="maxHeight" />
</div>
<div v-if="$slots.footer" class="border-t p-4 text-center">
<slot name="footer" />
</div>
<BaseButtonIcon
v-if="!hideClose"
class="absolute right-[20px] top-[20px]"
@click="emit('close')"
>
<i-ho-x class="text-[17px]" />
</BaseButtonIcon>
</div>
</div>
</Transition>
</template>
<style lang="scss">
.modal {
position: fixed;
display: flex;
top: 0;
bottom: 0;
left: 0;
right: 0;
align-items: center;
justify-content: center;
z-index: 40;
.backdrop {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
background: rgba(0, 0, 0, 0.4);
}
.shell {
background-color: var(--bg-color);
padding-left: 0 !important;
padding-right: 0 !important;
max-width: 440px;
overflow-y: auto !important;
max-height: calc(100vh - 120px);
display: flex;
flex-direction: column;
z-index: 999;
margin: 0 auto;
width: 100%;
@media (max-width: 767px) {
border: 0;
width: 100% !important;
max-width: 100% !important;
height: 100% !important;
max-height: v-bind(heightStyle);
min-height: v-bind(heightStyle);
margin-bottom: 0 !important;
.modal-body {
max-height: 100% !important;
}
}
.modal-body {
max-height: v-bind(maxHeight);
flex: auto;
text-align: initial;
overflow-y: auto;
overflow-x: hidden;
}
}
}
</style>
================================================
FILE: src/components/BaseModalSelectItem.vue
================================================
<script setup lang="ts">
defineProps<{
selected?: boolean;
title: string;
tag?: string;
description?: string;
disabled?: boolean;
}>();
</script>
<template>
<BaseBlock
:class="[
'transition-colors hover:border-skin-text cursor-pointer',
{
'!border-skin-link': selected,
'hover:!border-skin-border hover:!cursor-not-allowed': disabled
}
]"
>
<div class="relative inset-y-0 flex items-center">
<div :class="['w-full text-left', { 'pr-[44px]': selected }]">
<div class="mb-2 flex items-center gap-2">
<h3
:class="[
'mb-0 truncate',
{ 'mt-0': description, 'text-skin-text': disabled }
]"
v-text="title"
/>
<BasePill>{{ tag }}</BasePill>
</div>
<span class="break-all text-skin-text" v-text="description" />
</div>
<i-ho-check v-if="selected" class="absolute right-0 text-md" />
</div>
</BaseBlock>
</template>
================================================
FILE: src/components/BaseNetworkItem.vue
================================================
<script setup lang="ts">
import { getIpfsUrl } from '@/helpers/utils';
const { formatCompactNumber } = useIntl();
const { networksSpacesCount } = useNetworksFilter();
defineProps<{
network: { logo: string; name: string; key: string };
}>();
</script>
<template>
<BaseBlock>
<div class="mb-3 flex items-start">
<BaseAvatar class="mr-2" :src="getIpfsUrl(network.logo)" size="28" />
<div class="overflow-hidden">
<h3 class="my-0 truncate leading-5" v-text="network.name" />
<div
class="text-xs leading-4 text-skin-text"
v-text="'Chain #' + network.key"
/>
</div>
</div>
<div class="text-skin-text">
{{
$tc('inSpaces', [
formatCompactNumber(networksSpacesCount?.[network.key] ?? 0)
])
}}
</div>
</BaseBlock>
</template>
================================================
FILE: src/components/BaseNoResults.vue
================================================
<template>
<div class="py-[20px] text-center text-skin-link md:py-5">
<i-ho-emoji-sad class="mx-auto" />
<div class="mt-2 text-base">
{{ $t('noResultsFound') }}
</div>
</div>
</template>
================================================
FILE: src/components/BasePill.vue
================================================
<script setup lang="ts"></script>
<template>
<span
class="rounded-full bg-skin-text px-2 text-center text-xs leading-5 text-white"
>
<slot />
</span>
</template>
================================================
FILE: src/components/BasePluginItem.vue
================================================
<script setup lang="ts">
import { getIpfsUrl } from '@/helpers/utils';
import { PluginIndex } from '@/helpers/interfaces';
const { formatCompactNumber } = useIntl();
const { pluginsSpacesCount } = usePlugins();
defineProps<{
plugin: PluginIndex;
}>();
</script>
<template>
<BaseBlock>
<div class="mb-2 flex items-center">
<BaseAvatar
v-if="plugin?.icon"
class="mr-2"
:src="getIpfsUrl(plugin.icon)"
size="28"
/>
<h3 class="m-0 truncate" v-text="plugin.name" />
<div class="ml-1">v{{ plugin.version }}</div>
</div>
<div class="flex items-end justify-between text-skin-text">
<div class="flex flex-col">
<div class="text-skin-text">
<BaseIcon name="github" class="mr-1" />
{{ plugin.author }}
</div>
{{
$tc('inSpaces', [
formatCompactNumber(pluginsSpacesCount?.[plugin.key] ?? 0)
])
}}
</div>
<BaseLink
:link="`https://github.com/snapshot-labs/snapshot/tree/master/src/plugins/${plugin.key}`"
@click.stop
>
{{ $t('learnMore') }}
</BaseLink>
</div>
</BaseBlock>
</template>
================================================
FILE: src/components/BasePopover.vue
================================================
<script setup lang="ts">
import {
Popover,
PopoverButton,
PopoverPanel,
FocusTrap
} from '@headlessui/vue';
import { Float } from '@headlessui-float/vue';
import type { Placement } from '@floating-ui/dom';
withDefaults(
defineProps<{
label?: string;
placement?: Placement;
disabled?: boolean;
}>(),
{
label: '',
placement: 'bottom-end',
disabled: false
}
);
</script>
<template>
<Popover>
<Float
enter="transition ease-out duration-100"
enter-from="transform opacity-0 scale-95"
enter-to="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leave-from="transform opacity-100 scale-100"
leave-to="transform opacity-0 scale-95"
:placement="placement"
:offset="4"
:shift="16"
:flip="16"
:z-index="50"
portal
>
<PopoverButton
as="template"
:disabled="disabled"
:class="[{ 'cursor-not-allowed': disabled }]"
>
<slot v-if="$slots.button" name="button" />
<TuneButton v-else class="flex items-center">
<span>{{ label }}</span>
<i-ho-chevron-down
class="ml-2 h-5 w-5 text-skin-link"
aria-hidden="true"
/>
</TuneButton>
</PopoverButton>
<PopoverPanel
v-slot="{ close }"
class="w-screen max-w-xs outline-none sm:max-w-sm"
>
<div
class="overflow-hidden rounded-2xl border bg-skin-header-bg shadow-lg"
>
<div
class="no-scrollbar max-h-[85vh] overflow-y-auto overscroll-contain"
>
<FocusTrap>
<span tabindex="0"></span>
<slot name="content" :close="close" />
</FocusTrap>
</div>
</div>
</PopoverPanel>
</Float>
</Popover>
</template>
================================================
FILE: src/components/BasePopoverHover.vue
================================================
<script setup lang="ts">
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue';
import { Float } from '@headlessui-float/vue';
import type { Placement } from '@floating-ui/dom';
withDefaults(
defineProps<{
placement?: Placement;
}>(),
{
placement: 'bottom-end'
}
);
const show = ref(false);
const timerOpen = ref<any>(null);
const timerClose = ref<any>(null);
const open = () => {
if (timerClose.value !== null) {
clearTimeout(timerClose.value);
timerClose.value = null;
}
timerOpen.value = setTimeout(() => {
show.value = true;
}, 200);
};
const delayClose = () => {
if (timerOpen.value !== null) {
clearTimeout(timerOpen.value);
timerOpen.value = null;
}
timerClose.value = setTimeout(() => {
show.value = false;
}, 150);
};
</script>
<template>
<Popover>
<Float
enter="transition ease-out duration-100"
enter-from="transform opacity-0 scale-95"
enter-to="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leave-from="transform opacity-100 scale-100"
leave-to="transform opacity-0 scale-95"
:show="show"
:placement="placement"
:offset="10"
:shift="16"
:flip="16"
:z-index="50"
portal
>
<PopoverButton
@mouseenter="open"
@mouseleave="delayClose"
@focus="open"
@focusout="delayClose"
>
<slot name="button" />
</PopoverButton>
<PopoverPanel
class="w-screen outline-none sm:max-w-sm"
static
@mouseenter="open"
@mouseleave="delayClose"
>
<div
class="overflow-hidden rounded-2xl border bg-skin-header-bg shadow-lg"
>
<div
class="no-scrollbar max-h-[85vh] overflow-y-auto overscroll-contain"
>
<slot name="content" />
</div>
</div>
</PopoverPanel>
</Float>
</Popover>
</template>
================================================
FILE: src/components/BaseProgressBar.vue
================================================
<script setup lang="ts">
defineProps<{ value: number }>();
</script>
<template>
<div class="relative mt-1 flex h-2 overflow-hidden rounded-full">
<div class="z-5 absolute h-full w-full bg-[color:var(--border-color)]" />
<div
:style="`width: ${value.toFixed(3)}%;`"
class="z-10 h-full bg-skin-primary opacity-80"
/>
</div>
</template>
================================================
FILE: src/components/BaseProgressRadial.vue
================================================
<script setup lang="ts">
defineProps<{ value: number }>();
</script>
<template>
<div
:style="{
backgroundImage: `conic-gradient(var(--text-color) ${value}%, transparent 0)`
}"
class="circle"
></div>
</template>
<style scoped>
.circle {
width: 18px;
height: 18px;
border-radius: 50%;
border: 1px solid var(--text-color);
}
</style>
================================================
FILE: src/components/BaseSearch.vue
================================================
<script setup lang="ts">
const props = defineProps<{
modelValue: string;
placeholder?: string;
modal?: boolean;
focusOnMount?: boolean;
isDisabled?: boolean;
}>();
const emit = defineEmits(['update:modelValue']);
const input = ref(props.modelValue || '');
const BaseInputEL = ref<HTMLDivElement | undefined>(undefined);
function handleInput(e) {
input.value = e.target.value;
emit('update:modelValue', e.target.value);
}
function clearInput() {
input.value = '';
emit('update:modelValue', '');
}
onMounted(() => {
if (props.focusOnMount) {
BaseInputEL?.value?.focus();
}
});
watch(
() => props.modelValue,
() => {
input.value = props.modelValue;
}
);
</script>
<template>
<div
class="flex h-[44px] items-center"
:class="{ 'border-b bg-skin-bg py-3 pl-4': modal }"
>
<i-ho-search class="mr-2 flex-shrink-0 text-[19px] text-skin-link" />
<input
ref="BaseInputEL"
:value="input"
:placeholder="placeholder"
type="text"
autocorrect="off"
autocapitalize="none"
class="input w-full border-none"
:class="{ '!cursor-not-allowed': isDisabled }"
:disabled="isDisabled"
@input="handleInput"
/>
<i-ho-x-circle
v-if="modelValue"
class="mr-[6px] flex-shrink-0 cursor-pointer text-[16px] ml-3 text-skin-link"
@click="clearInput"
/>
<slot name="after" class="flex-shrink-0" />
</div>
</template>
================================================
FILE: src/components/BaseSidebarNavigationItem.vue
================================================
<script setup lang="ts">
export interface Props {
isActive?: boolean;
}
withDefaults(defineProps<Props>(), {
isActive: false
});
</script>
<template>
<div
:class="[
'group relative block cursor-pointer whitespace-nowrap px-[20px] py-2 text-skin-text hover:bg-skin-bg lg:px-3',
{ ' !text-skin-heading': isActive }
]"
>
<slot />
<div class="absolute left-0 top-0 flex h-full w-full justify-center">
<div
class="lg:nav-left-border max-lg:nav-bottom-border lg:group-hover:nav-left-border-hovered"
:class="[
{
selected: isActive
}
]"
/>
</div>
</div>
</template>
<style scoped lang="scss">
@tailwind components;
@layer components {
.nav-left-border {
&.selected {
@apply absolute left-0 top-[4px] h-[32px] w-[4px] rounded-br rounded-tr bg-skin-text;
}
}
.nav-left-border-hovered {
&:not(.selected) {
@apply absolute left-0 top-[16px] h-[8px] w-[4px] rounded-br rounded-tr bg-skin-text;
}
}
.nav-bottom-border {
&.selected {
@apply absolute bottom-[0px] h-[4px] w-4/6 rounded-tl rounded-tr bg-skin-text;
}
}
}
</style>
================================================
FILE: src/components/BaseSkinItem.vue
================================================
<script setup lang="ts">
const { skinsSpacesCount } = useSkinsFilter();
const { formatCompactNumber } = useIntl();
defineProps<{
skin: string;
}>();
</script>
<template>
<BaseBlock class="cursor-pointer hover:border-skin-text">
<TuneButton :class="['mb-2', skin]" primary use-white-text>{{
skin
}}</TuneButton>
<div class="text-skin-text">
{{ $tc('inSpaces', [formatCompactNumber(skinsSpacesCount[skin] ?? 0)]) }}
</div>
</BaseBlock>
</template>
================================================
FILE: src/components/BaseStrategyItem.vue
================================================
<script setup lang="ts">
import { Strategy } from '@/helpers/interfaces';
const { formatCompactNumber } = useIntl();
defineProps<{
strategy: Strategy;
}>();
</script>
<template>
<BaseBlock class="cursor-pointer hover:border-skin-text">
<div class="flex items-baseline">
<h3 v-tippy="{ content: strategy.id }" class="mt-0 truncate">
{{ strategy.id }}
</h3>
<div class="ml-1">v{{ strategy.version }}</div>
</div>
<div class="text-skin-text">
<BaseIcon name="github" class="mr-1" />
{{ strategy.author }}
</div>
<div>
{{ $tc('inSpaces', [formatCompactNumber(strategy.spacesCount)]) }}
</div>
</BaseBlock>
</template>
================================================
FILE: src/components/BaseUser.vue
================================================
<script setup lang="ts">
import { Profile, ExtendedSpace, Proposal } from '@/helpers/interfaces';
const { domain } = useApp();
const props = defineProps<{
address: string;
space?: Partial<ExtendedSpace>;
proposal?: Proposal;
profile?: Profile;
hideAvatar?: boolean;
hideUsername?: boolean;
widthClass?: string;
textClass?: string;
}>();
const { getUsername } = useUsername();
const spaceMembers = computed(() => {
if (!props.space) return [];
return [
...(props.space.members || []),
...(props.space.moderators || []),
...(props.space.admins || [])
];
});
</script>
<template>
<PopoverHoverProfile
:address="address"
:profile="profile"
:proposal="proposal"
:space="space"
class="flex"
>
<BaseLink
:link="
domain
? `https://snapshot.org/#/profile/${address}`
: { name: 'profileActivity', params: { address } }
"
hide-external-icon
tabindex="-1"
@click.stop=""
>
<div :class="[widthClass, 'flex flex-nowrap items-center space-x-1']">
<AvatarUser v-if="!hideAvatar" :address="address" size="20" />
<span
v-if="!hideUsername"
class="w-full cursor-pointer truncate text-skin-link"
:class="textClass"
>
{{ getUsername(address, profile) }}
</span>
<BaseBadge
v-if="getUsername(address, profile) !== 'You'"
:address="address"
:members="spaceMembers"
/>
</div>
</BaseLink>
</PopoverHoverProfile>
</template>
================================================
FILE: src/components/BlockLink.vue
================================================
<script setup lang="ts">
import { debouncedWatch } from '@vueuse/core';
const props = withDefaults(
defineProps<{
link: string;
safeLinkPreview?: boolean;
}>(),
{
safeLinkPreview: true
}
);
const preview = ref<null | {
meta: {
title: string;
description: string;
};
links: {
icon: {
href: string;
}[];
};
}>(null);
const showModal = ref(false);
const loaded = ref(false);
const error = ref(false);
function handleConfirm() {
window.open(props.link, '_blank', 'noopener,noreferrer');
}
function handleClickLink() {
if (props.safeLinkPreview) {
showModal.value = true;
} else {
window.open(props.link, '_blank', 'noopener,noreferrer');
}
}
async function update(val: string) {
try {
error.value = false;
loaded.value = false;
preview.value = null;
new URL(val);
const IFRAMELY_API_KEY = 'd155718c86be7d5305ccb6';
const url = `https://cdn.iframe.ly/api/iframely?url=${encodeURI(
val
)}&api_key=${IFRAMELY_API_KEY}`;
const result = await fetch(url);
const json = await result.json();
if (json.status === 404) throw new Error('Error fetching link preview');
preview.value = json;
} catch (e) {
console.log(e);
error.value = true;
} finally {
loaded.value = true;
}
}
debouncedWatch(
() => props.link,
newLink => update(newLink),
{ debounce: 500, immediate: true }
);
</script>
<template>
<div v-if="!error">
<slot name="title" />
<button
type="button"
class="flex w-full items-center rounded-xl border hover:cursor-pointer hover:border-skin-text"
@click="handleClickLink"
>
<div class="shrink-0 px-4">
<div v-if="!loaded">
<div class="lazy-loading h-[32px] w-[32px] rounded-lg" />
</div>
<div v-else>
<div class="w-[32px]">
<IconDiscord
v-if="preview?.links.icon[0].href.includes('discord.com')"
/>
<img
v-else
:src="preview?.links.icon[0].href"
alt="logo"
width="32"
height="32"
class="rounded bg-white"
/>
</div>
</div>
</div>
<div class="overflow-hidden py-3 pr-3">
<div v-if="!loaded" class="flex h-[48px] flex-col justify-center">
<div class="lazy-loading h-[10px] w-[90px] rounded" />
<div class="lazy-loading mt-2 h-[10px] w-[160px] rounded" />
</div>
<div v-else>
<div class="line-clamp-1 text-left text-skin-link">
{{ preview?.meta.title }}
</div>
<div
v-if="preview?.meta.description"
class="line-clamp-1 text-left text-sm text-skin-text"
>
{{ preview?.meta.description }}
</div>
</div>
</div>
</button>
<Teleport to="#modal">
<ModalLinkPreview
:open="showModal"
:clicked-url="props.link"
@close="showModal = false"
@confirm="handleConfirm"
/>
</Teleport>
</div>
</template>
================================================
FILE: src/components/BlockSpacesList.vue
================================================
<script setup lang="ts">
import { useMediaQuery } from '@vueuse/core';
import { Space } from '@/helpers/interfaces';
defineProps<{
spaces: Space[];
title: string;
message?: string;
loading?: boolean;
}>();
const modalSpacesOpen = ref(false);
const isXSmallScreen = useMediaQuery('(max-width: 420px)');
const isSmallScreen = useMediaQuery('(max-width: 544px)');
const isMediumScreen = useMediaQuery('(max-width: 768px)');
const numberOfSpacesByScreenSize = computed(() => {
if (isXSmallScreen.value) {
return 3;
}
if (isSmallScreen.value) {
return 4;
}
if (isMediumScreen.value) {
return 5;
}
return 7;
});
</script>
<template>
<div>
<BaseBlock :title="title" :counter="spaces.length" hide-bottom-border slim>
<div v-if="loading || spaces.length" class="border-t px-4 py-4">
<BlockSpacesListSkeleton
v-if="loading"
:number-of-spaces="numberOfSpacesByScreenSize"
/>
<div v-else class="flex justify-between">
<div class="flex w-full overflow-x-hidden">
<div
v-for="space in spaces"
:key="space.id"
class="mx-2 min-w-[66px] max-w-[66px] text-center first:ml-0"
>
<BlockSpacesListItem :space="space" />
</div>
</div>
<BlockSpacesListButtonMore
v-if="numberOfSpacesByScreenSize < spaces.length"
@click="modalSpacesOpen = true"
/>
</div>
</div>
<div v-else class="border-t p-4">
{{ message || $t('noResultsFound') }}
</div>
</BaseBlock>
<teleport to="#modal">
<ModalSpaces
:spaces="spaces"
:open="modalSpacesOpen"
@close="modalSpacesOpen = false"
/>
</teleport>
</div>
</template>
================================================
FILE: src/components/BlockSpacesListButtonMore.vue
================================================
<template>
<button class="ml-4 h-full cursor-pointer bg-skin-bg text-skin-link">
<div class="flex justify-center">
<div
class="flex h-[54px] w-[54px] items-center justify-center rounded-full border hover:border-skin-text"
>
<i-ho-dots-horizontal class="text-sm" />
</div>
</div>
<div class="text-center text-xs">{{ $t('seeAll') }}</div>
</button>
</template>
================================================
FILE: src/components/BlockSpacesListItem.vue
================================================
<script setup lang="ts">
import { Space } from '@/helpers/interfaces';
defineProps<{
space: Space;
}>();
</script>
<template>
<router-link
:to="{
name: 'spaceProposals',
params: { key: space.id }
}"
>
<div class="flex justify-center">
<div
class="flex justify-center rounded-full !border-[1px] !border-skin-text p-[2px]"
>
<AvatarSpace :space="space" symbol-index="space" size="48" />
</div>
</div>
<div class="flex items-center justify-center">
<div class="truncate text-xs">
{{ space.name }}
</div>
<IconVerifiedSpace
v-if="space.verified"
:turbo="space.turbo"
size="14"
class="pl-1 text-skin-primary"
/>
</div>
</router-link>
</template>
================================================
FILE: src/components/BlockSpacesListSkeleton.vue
================================================
<script setup lang="ts">
defineProps<{
numberOfSpaces: number;
}>();
</script>
<template>
<div class="flex justify-between">
<div v-for="n in numberOfSpaces + 1" :key="n" class="animate-pulse">
<div class="flex justify-center">
<div class="flex rounded-full border p-[2px]">
<div class="h-[48px] w-[48px] rounded-full bg-skin-border" />
</div>
</div>
<div class="mt-[6px] h-[14px] w-[66px] rounded bg-skin-border" />
</div>
</div>
</template>
================================================
FILE: src/components/ButtonBack.vue
================================================
<script setup lang="ts">
defineProps<{
name?: string;
}>();
</script>
<template>
<button type="button">
<div
class="inline-flex items-center gap-1 text-skin-text hover:text-skin-link"
>
<i-ho-arrow-sm-left />
{{ name ? name : $t('back') }}
</div>
</button>
</template>
================================================
FILE: src/components/ButtonCard.vue
================================================
<script setup lang="ts">
defineProps<{
title?: string;
}>();
</script>
<template>
<button
class="relative w-full border-y border-skin-border p-4 py-[18px] pr-[80px] text-left hover:border-skin-text md:rounded-xl md:border-x"
>
<h4 class="leading-2 mb-1 mt-0">{{ title }}</h4>
<slot />
<i-ho-chevron-right class="absolute right-4 top-[calc(50%-17px)] text-xl" />
</button>
</template>
================================================
FILE: src/components/ButtonFollow.vue
================================================
<script setup lang="ts">
import { Space, RankedSpace, ExtendedSpace } from '@/helpers/interfaces';
const props = defineProps<{
space: Space | RankedSpace | ExtendedSpace;
primary?: boolean;
}>();
const { domain } = useApp();
const { isGnosisSafe } = useClient();
const { web3Account } = useWeb3();
const { modalTermsOpen, termsAccepted, acceptTerms } = useTerms(props.space.id);
const { clickFollow, loadingFollow, isFollowing, loadFollows } = useFollowSpace(
props.space.id
);
const canFollow = computed(() =>
props.space.terms ? termsAccepted.value || isFollowing.value : true
);
watch(
web3Account,
() => {
// Only for custom domain, else follows are loaded on sidebar
domain && loadFollows(props.space.id);
},
{ immediate: true }
);
</script>
<template>
<div
v-tippy="{
content: isGnosisSafe ? $t('walletNotSupported') : null
}"
v-bind="$attrs"
>
<TuneButton
v-bind="$attrs"
:loading="loadingFollow === space.id"
:disabled="isGnosisSafe"
class="group min-w-[125px]"
:class="{
'flex items-center justify-center hover:!border-red hover:!bg-red hover:!bg-opacity-5 hover:!text-red':
isFollowing
}"
:primary="primary"
@click.stop.prevent="
loadingFollow !== ''
? null
: canFollow
? clickFollow(space.id)
: (modalTermsOpen = true)
"
>
<span v-if="!isFollowing"> {{ $t('join') }} </span>
<span v-else>
<span class="flex items-center gap-2 group-hover:hidden">
<i-ho-check class="text-green" /> {{ $t('joined') }}
</span>
<span class="hidden group-hover:block">
{{ $t('leave') }}
</span>
</span>
</TuneButton>
</div>
<teleport to="#modal">
<ModalTerms
v-if="space"
:open="modalTermsOpen"
:space="space"
:action="$t('modalTerms.actionJoin')"
@close="modalTermsOpen = false"
@accept="acceptTerms(), clickFollow(space.id)"
/>
</teleport>
</template>
================================================
FILE: src/components/ButtonPlayground.vue
================================================
<script setup lang="ts">
import { encodeJson } from '@/helpers/b64';
const props = withDefaults(
defineProps<{
name: string;
network?: string;
params?: any;
snapshot?: string;
big?: boolean;
}>(),
{
name: '',
network: '',
params: {},
snapshot: '',
big: false
}
);
const router = useRouter();
const { domain } = useApp();
function clickPlayground() {
if (domain) {
return window.open(
`https://snapshot.org/#/playground/${props.name}?query=${encodeJson({
params: props.params,
network: props.network,
snapshot: props.snapshot
})}`,
'_blank'
);
}
const playgroundRoute = router.resolve({
name: 'playground',
query: {
query: encodeJson({
params: props.params,
network: props.network,
snapshot: props.snapshot
})
},
params: { name: props.name }
});
window.open(playgroundRoute.href, '_blank');
}
</script>
<template>
<TuneButton v-if="big" class="w-full" @click="clickPlayground">
{{ $t('settings.testInPlayground') }}
<i-ho-external-link class="mb-[2px] inline-block text-xs" />
</TuneButton>
<BaseButtonIcon
v-else
v-tippy="{ content: $t('playground') }"
@click="clickPlayground"
>
<i-ho-play />
</BaseButtonIcon>
</template>
================================================
FILE: src/components/ButtonShare.vue
================================================
<template>
<button
class="flex cursor-pointer h-full select-none items-center pr-1 hover:text-skin-link"
>
<i-ho-upload class="text-base" />
<span class="ml-1 hidden md:block">{{ $t('share') }}</span>
</button>
</template>
================================================
FILE: src/components/ButtonSwitch.vue
================================================
<script setup lang="ts">
interface State {
value: string | number | boolean;
name: string;
}
defineProps<{
modelValue: string | number | boolean;
state1: State;
state2: State;
}>();
defineEmits(['update:modelValue']);
</script>
<template>
<div class="inline-flex items-center rounded-full border p-1">
<div
:class="[
'w-full cursor-pointer px-3 py-1 text-center',
{
'rounded-full bg-skin-border text-skin-heading':
state1.value === modelValue
}
]"
@click="$emit('update:modelValue', state1.value)"
>
{{ state1.name }}
</div>
<div
:class="[
'w-full cursor-pointer px-3 py-1 text-center',
{
'rounded-full bg-skin-border text-skin-heading':
state2.value === modelValue
}
]"
@click="$emit('update:modelValue', state2.value)"
>
{{ state2.name }}
</div>
</div>
</template>
================================================
FILE: src/components/ButtonTheme.vue
================================================
<script setup lang="ts">
const { toggleUserTheme, getThemeIcon } = useSkin();
</script>
<template>
<BaseButtonRound
:aria-label="$t('toggleSkin')"
class="text-skin-text hover:text-skin-link"
@click="toggleUserTheme"
>
<i-ho-moon v-if="getThemeIcon() === 'moon'" />
<i-ho-sun v-if="getThemeIcon() === 'sun'" />
</BaseButtonRound>
</template>
================================================
FILE: src/components/ComboboxNetwork.vue
================================================
<script setup lang="ts">
defineProps<{
network: string;
hint?: string;
disabled?: boolean;
error?: string;
showErrors?: boolean;
}>();
const emit = defineEmits(['select']);
const { filterNetworks } = useNetworksFilter();
const { env } = useApp();
const networks = computed((): { id: string; name: string }[] => {
const filteredNetworks = filterNetworks().map(_n => ({
id: _n.key,
name: _n.name,
extras: {
hidden: env === 'production' ? _n.testnet : false
}
}));
return filteredNetworks;
});
</script>
<template>
<TuneCombobox
:label="$t('settings.network.label')"
:items="networks"
:model-value="network"
:hint="hint"
:disabled="disabled"
:error="error"
:show-errors="showErrors"
@update:model-value="value => emit('select', value)"
>
<template #item="{ item }">
<div class="flex items-center">
<div class="truncate pr-2">
{{ item.name }}
</div>
<BasePill class="leading-4"> #{{ item.id }} </BasePill>
</div>
</template>
</TuneCombobox>
</template>
================================================
FILE: src/components/ContainerParallelInput.vue
================================================
<template>
<div class="space-y-2 sm:flex sm:space-x-4 sm:space-y-0">
<slot />
</div>
</template>
================================================
FILE: src/components/ExploreMenuCategories.vue
================================================
<script setup lang="ts">
import { SPACE_CATEGORIES } from '@/helpers/constants';
const props = defineProps<{
metrics: Record<string, number>;
}>();
const emit = defineEmits(['update:category']);
const { tc } = useI18n();
const route = useRoute();
const router = useRouter();
const selected = ref((route.query.category as string) || undefined);
const routeQuery = computed(() => route.query || undefined);
const categoryItems = computed(() => {
return [
{
text: tc('explore.categories.all'),
action: 'all',
extras: {
count: props.metrics?.all || 0,
selected: !selected.value
}
},
...SPACE_CATEGORIES.map(c => ({
text: tc(`explore.categories.${c}`),
action: c,
extras: {
count: props.metrics?.[c] || 0,
selected: selected.value === c
}
})).sort((a, b) => b.extras.count - a.extras.count)
];
});
function selectCategory(c: string) {
selected.value = c;
emit('update:category', c);
router.push({
query: { ...routeQuery.value, category: c }
});
}
</script>
<template>
<BaseMenu
class="mt-2 w-full xs:w-auto sm:mr-2 md:ml-2 md:mt-0"
:items="categoryItems"
@select="selectCategory"
>
<template #button>
<TuneButton class="w-full whitespace-nowrap pr-3">
<div class="leading-2 flex items-center leading-3">
<i-ho-view-grid class="mr-2 text-xs" />
<span v-if="selected">
{{ $tc('explore.categories.' + selected) }}
</span>
<span v-else>
{{ $tc('explore.category') }}
</span>
<i-ho-chevron-down class="ml-1 text-xs text-skin-link" />
</div>
</TuneButton>
</template>
<template #item="{ item }">
<div class="flex">
<span class="mr-3">{{ item.text }}</span>
<span class="ml-auto mt-[-3px] flex">
<BaseCounter :counter="item.extras.count" class="my-auto" />
</span>
</div>
</template>
</BaseMenu>
</template>
================================================
FILE: src/components/ExploreSkeletonLoading.vue
================================================
<script setup lang="ts">
defineProps<{
isSpaces?: boolean;
}>();
const CARD_COUNT = 12;
</script>
<template>
<div
v-if="isSpaces"
class="grid gap-4 opacity-40 md:grid-cols-3 lg:grid-cols-4"
>
<div
v-for="i in CARD_COUNT"
:key="i"
class="min-h-[266px] animate-pulse bg-skin-border md:rounded-xl"
/>
</div>
<div v-else class="grid gap-4 opacity-40 md:grid-cols-2 lg:grid-cols-3">
<div
v-for="i in CARD_COUNT"
:key="i"
class="min-h-[124px] animate-pulse bg-skin-border md:rounded-xl"
/>
</div>
</template>
================================================
FILE: src/components/ExploreSpaces.vue
================================================
<script setup lang="ts">
import { shorten } from '@/helpers/utils';
import { useInfiniteScroll, watchDebounced } from '@vueuse/core';
const route = useRoute();
const { validEnsTlds } = useEns();
const { formatCompactNumber } = useIntl();
const { loadExtendedSpace, extendedSpaces, spaceLoading } = useExtendedSpaces();
const {
loadSpacesHome,
loadMoreSpacesHome,
loadingSpacesHome,
loadingMoreSpacesHome,
enableSpaceHomeScroll,
spacesHome,
spacesHomeMetrics
} = useSpaces();
const queryInput = ref({
search: (route.query.q as string) || '',
category: route.query.category || undefined
});
const isSearchInputTld = computed(() => {
if (!queryInput.value.search) return false;
return validEnsTlds.includes(queryInput.value.search.split('.').pop() ?? '');
});
const spaces = computed(() => {
if (isSearchInputTld.value) {
const space = extendedSpaces.value.find(
s => s.id === queryInput.value.search
);
return space ? [space] : [];
}
return spacesHome.value;
});
function handleClickMore() {
loadMoreSpacesHome(queryInput.value);
enableSpaceHomeScroll.value = true;
}
function loadSpaces() {
if (isSearchInputTld.value) return loadExtendedSpace(queryInput.value.search);
loadSpacesHome(queryInput.value);
}
useInfiniteScroll(
document,
() => {
if (enableSpaceHomeScroll.value) {
loadMoreSpacesHome(queryInput.value);
}
},
{ distance: 500 }
);
watchDebounced(
queryInput,
() => {
loadSpaces();
},
{ deep: true, debounce: 300 }
);
onMounted(() => {
loadSpaces();
});
</script>
<template>
<div class="relative">
<BaseContainer
class="mb-4 flex flex-col flex-wrap items-center xs:flex-row md:flex-nowrap"
>
<div tabindex="-1" class="w-full md:max-w-[420px]">
<TheSearchBar @update:input-search="queryInput.search = $event" />
</div>
<ExploreMenuCategories
:metrics="spacesHomeMetrics.categories"
@update:category="queryInput.category = $event"
/>
<div
v-if="spacesHomeMetrics.total"
class="mt-2 whitespace-nowrap text-right text-skin-text xs:ml-auto xs:mt-0"
>
{{ $tc('spaceCount', [formatCompactNumber(spacesHomeMetrics.total)]) }}
</div>
</BaseContainer>
<BaseContainer slim>
<ExploreSkeletonLoading
v-if="loadingSpacesHome || spaceLoading"
is-spaces
/>
<BaseNoResults v-else-if="spaces.length < 1" use-block />
<TransitionGroup
v-else-if="!loadingSpacesHome && !spaceLoading"
name="fade"
tag="div"
class="grid gap-4 md:grid-cols-3 lg:grid-cols-4"
>
<div v-for="space in spaces" :key="space.id">
<router-link
:to="{ name: 'spaceProposals', params: { key: space.id } }"
>
<BaseBlock
class="mb-0 flex items-center justify-center text-center transition-all hover:border-skin-text"
style="height: 266px"
>
<div class="relative mb-2 inline-block">
<AvatarSpace
:space="space"
symbol-index="space"
size="82"
class="mb-1"
/>
</div>
<div class="flex items-center justify-center gap-1 truncate">
<h3
class="mb-0 mt-0 !h-[32px] overflow-hidden pb-0 text-[22px]"
v-text="shorten(space.name, 16)"
/>
<IconVerifiedSpace
v-if="space.verified"
:turbo="space.turbo"
class="pt-[1px]"
/>
</div>
<div class="mb-[12px] text-skin-text">
{{
$tc('members', space.followersCount, {
count: formatCompactNumber(space.followersCount)
})
}}
</div>
<ButtonFollow :space="space" class="mx-auto" />
</BaseBlock>
</router-link>
</div>
</TransitionGroup>
<div
v-if="
!enableSpaceHomeScroll &&
spacesHomeMetrics.total > spacesHome.length &&
spaces.length >= 12
"
class="px-3 text-center md:px-0"
>
<TuneButton class="mt-4 w-full" @click="handleClickMore">
{{ $t('homeLoadmore') }}
</TuneButton>
</div>
<div v-else-if="loadingMoreSpacesHome" class="mt-4 flex h-[46px]">
<LoadingSpinner class="mx-auto" big />
</div>
</BaseContainer>
</div>
</template>
================================================
FILE: src/components/FooterLinks.vue
================================================
<script setup lang="ts"></script>
<template>
<div class="space-y-[12px]">
<slot />
</div>
</template>
================================================
FILE: src/components/FooterLinksItem.vue
================================================
<script setup lang="ts">
defineProps<{
link: any;
}>();
</script>
<template>
<BaseLink
:link="link"
hide-external-icon
class="flex items-center justify-center text-skin-text hover:text-skin-link md:justify-start"
>
<slot />
</BaseLink>
</template>
================================================
FILE: src/components/FooterSocials.vue
================================================
<script setup lang="ts">
const socials = [
{
icon: 'x',
link: 'https://x.com/SnapshotLabs'
},
{
icon: 'discord',
link: 'https://discord.snapshot.org/'
}
];
</script>
<template>
<div
class="flex items-center justify-center space-x-3 pt-2 md:mt-4 md:justify-start lg:mt-0 lg:justify-end"
>
<span v-for="social in socials" :key="social.icon">
<BaseLink :link="social.link" hide-external-icon>
<FooterSocialsItem v-if="social.icon === 'x'">
<i-s-x class="text-[23px]" />
</FooterSocialsItem>
<FooterSocialsItem v-else-if="social.icon === 'discord'">
<i-s-discord class="text-[23px]" />
</FooterSocialsItem>
</BaseLink>
</span>
</div>
</template>
================================================
FILE: src/components/FooterSocialsItem.vue
================================================
<script setup lang="ts"></script>
<template>
<div class="text-skin-text hover:text-skin-link">
<slot />
</div>
</template>
================================================
FILE: src/components/FooterTitle.vue
================================================
<script setup lang="ts"></script>
<template>
<h4 class="font-medium">
<slot />
</h4>
</template>
================================================
FILE: src/components/FormArrayStrategies.vue
================================================
<script setup lang="ts">
import { SpaceStrategy } from '@/helpers/interfaces';
import { clone } from '@snapshot-labs/snapshot.js/src/utils';
const props = defineProps<{
modelValue?: { name: string; network: string; params: any }[];
votingStrategies: SpaceStrategy[];
}>();
const emit = defineEmits(['update:modelValue', 'update:isValid']);
const updateIndex = ref(0);
const { filterStrategies, getStrategies, strategies } = useStrategies();
const input = ref<{ name: string; network: string; params: any }[]>(
props.modelValue || []
);
const strategyValidationStates = ref<boolean[]>([]);
const strategiesRef = ref();
function handleDelete(index: number) {
input.value.splice(index, 1);
strategyValidationStates.value.splice(index, 1);
}
function handleCopyStrategies() {
updateIndex.value++;
input.value = clone(props.votingStrategies);
}
function forceShowError() {
strategiesRef?.value?.forEach((ref: any) => {
if (ref?.forceShowError) {
ref?.forceShowError();
}
});
}
defineExpose({
forceShowError
});
watch(
strategyValidationStates,
() => {
const isValid = Object.values(strategyValidationStates.value).every(
v => v === true
);
emit('update:isValid', isValid);
},
{ immediate: true, deep: true }
);
watch(
input.value,
() => {
emit('update:modelValue', input.value);
},
{ deep: true }
);
onMounted(() => {
if (!strategies.value.length) getStrategies();
if (!input.value.length)
input.value.push({ name: 'ticket', network: '1', params: {} });
});
</script>
<template>
<div>
<div v-if="!strategies.length" class="mt-3 flex justify-center">
<LoadingSpinner />
</div>
<div v-else class="mt-3">
<LabelInput>
{{ $t('strategies') }}
</LabelInput>
<div class="space-y-3">
<div
v-for="(property, i) in input"
:key="i"
class="space-y-2 rounded-md border p-3"
>
<div class="mb-3 flex items-center justify-between">
<BasePill class="text-[16px]">
{{ i + 1 }}
</BasePill>
<BaseButtonIcon v-if="input.length > 1" @click="handleDelete(i)">
<i-ho-trash class="text-[17px]" />
</BaseButtonIcon>
</div>
<TuneCombobox
:label="$t('strategy')"
:items="filterStrategies().map(s => ({ id: s.id, name: s.id }))"
:model-value="input[i].name"
@update:model-value="value => (input[i].name = value)"
/>
<ComboboxNetwork
:network="input[i].network"
@select="value => (input[i].network = value)"
/>
<FormObjectStrategyParams
:key="updateIndex"
ref="strategiesRef"
v-model="input[i].params"
:strategy-name="input[i].name"
@update:is-valid="strategyValidationStates[i] = $event"
/>
</div>
<TuneButton
class="flex w-full items-center justify-center gap-2"
@click="input.push({ name: 'ticket', network: '1', params: {} })"
>
<i-ho-plus class="text-sm" />
{{ $t('addStrategy') }}
</TuneButton>
<TuneButton
v-if="votingStrategies.length"
class="flex w-full items-center justify-center gap-2"
@click="handleCopyStrategies"
>
<i-ho-duplicate />
{{ $t('copyVotingStrategies') }}
</TuneButton>
</div>
</div>
</div>
</template>
================================================
FILE: src/components/FormObjectStrategyParams.vue
================================================
<script setup lang="ts">
import { clone } from '@snapshot-labs/snapshot.js/src/utils';
import { validateForm } from '@/helpers/validation';
const props = defineProps<{
strategyName: string;
modelValue: any;
}>();
const emit = defineEmits(['update:modelValue', 'update:isValid']);
const {
getExtendedStrategy,
strategyDefinition,
loadingExtendedStrategy,
extendedStrategy
} = useStrategies();
const isValidJson = ref(true);
const formRef = ref();
const paramsComputed = computed({
get: () => props.modelValue,
set: value => {
emit('update:modelValue', value);
}
});
const validationErrors = computed(() => {
return validateForm(strategyDefinition.value || {}, paramsComputed.value);
});
const isValid = computed(() => {
return Object.values(validationErrors.value).length === 0;
});
function forceShowError() {
formRef?.value?.forceShowError();
}
defineExpose({
forceShowError
});
watch(
[isValidJson, isValid],
() => {
if (isValidJson.value && isValid.value) {
emit('update:isValid', true);
} else {
emit('update:isValid', false);
}
},
{ immediate: true }
);
watch(
() => props.strategyName,
async () => {
paramsComputed.value = {};
await getExtendedStrategy(props.strategyName);
if (
!strategyDefinition.value &&
extendedStrategy.value?.examples?.[0]?.strategy?.params
) {
paramsComputed.value = clone(
extendedStrategy.value.examples[0].strategy.params
);
}
}
);
onMounted(() => {
getExtendedStrategy(props.strategyName);
});
</script>
<template>
<div>
<div v-if="loadingExtendedStrategy" class="flex justify-center">
<LoadingSpinner />
</div>
<TuneForm
v-else-if="strategyDefinition"
ref="formRef"
v-model="paramsComputed"
:definition="strategyDefinition"
:error="validationErrors"
/>
<TuneTextareaJson
v-else
v-model="paramsComputed"
v-model:is-valid="isValidJson"
label="Params"
:placeholder="$t('strategyParameters')"
class="input text-left"
/>
</div>
</template>
================================================
FILE: src/components/IconDiscord.vue
================================================
<template>
<i-s-discord class="h-[32px] w-[32px] text-[#5865F2]" />
</template>
================================================
FILE: src/components/IconInformationTooltip.vue
================================================
<script setup lang="ts">
defineProps<{
information?: string;
}>();
</script>
<template>
<span
v-if="!!information"
v-tippy="{ content: information }"
class="text-xs hover:text-skin-link"
>
<i-ho-question-mark-circle />
</span>
</template>
================================================
FILE: src/components/IconVerifiedSpace.vue
================================================
<script setup lang="ts">
withDefaults(
defineProps<{
size?: string;
turbo: boolean;
}>(),
{
size: '20',
turbo: false
}
);
</script>
<template>
<div class="cursor-help">
<BaseIcon
v-tippy="{
content: $t('verifiedSpace'),
placement: 'right'
}"
name="check"
:size="size"
:class="[{ 'text-[#ffb503]': turbo }]"
/>
</div>
</template>
================================================
FILE: src/components/IndicatorAssetsChange.spec.js
================================================
import { describe, expect, it, afterEach } from 'vitest';
import { shallowMount } from '@vue/test-utils';
import IndicatorAssetsChange from './IndicatorAssetsChange.vue';
describe('IndicatorAssetsChange', () => {
let wrapper;
const findAssetChange = () =>
wrapper.find('[data-testid="asset-quote-change"]');
function createComponent(params = {}) {
wrapper = shallowMount(IndicatorAssetsChange, params);
}
afterEach(() => {
wrapper.unmount();
});
it('should render red if quote_24h is greater than quote', () => {
createComponent({
props: {
quote: {
quote: 2700,
quote_24h: 2800
}
}
});
expect(findAssetChange().classes()).toContain('text-red');
});
it('should render green if quote_24h is lower than quote', async () => {
createComponent({
props: {
quote: {
quote: 2900,
quote_24h: 2800
}
}
});
expect(findAssetChange().classes()).toContain('text-green');
});
it('should render correct 24h % change', () => {
const quote = {
quote: 2800,
quote_24h: 2800
};
createComponent({
props: {
quote
}
});
expect(
wrapper.find('[data-testid="asset-quote-change-percent"]').text()
).toContain('0%');
});
it('should render no indicator when both are zero', () => {
const quote = {
quote: 0,
quote_24h: 0
};
createComponent({
props: {
quote
}
});
expect(wrapper.find('[asset-quote-change"]').exists()).toBe(false);
});
it('should render the change in $', async () => {
const quote = {
quote: 2800,
quote_24h: 2800
};
createComponent({
props: {
quote
}
});
expect(
wrapper.find('[data-testid="asset-quote-change-usd"]').text()
).toContain(`$0`);
});
});
================================================
FILE: src/components/IndicatorAssetsChange.vue
================================================
<script setup lang="ts">
defineProps<{
quote: {
quote: number;
quote_24h: number;
};
}>();
const { formatPercentNumber, formatNumber } = useIntl();
</script>
<template>
<div v-if="quote.quote_24h || quote.quote">
<span
data-testid="asset-quote-change"
:class="[quote.quote_24h > quote.quote ? 'text-red' : 'text-green']"
>
<span class="pr-1" data-testid="asset-quote-change-percent">
{{
`${quote.quote_24h > quote.quote ? '' : '+'}${formatPercentNumber(
(quote.quote - quote.quote_24h) / quote.quote_24h
)}`
}}
</span>
<span data-testid="asset-quote-change-usd">
{{ `($${formatNumber(quote.quote - quote.quote_24h)})` }}
</span>
</span>
</div>
</template>
================================================
FILE: src/components/InputCheckbox.vue
================================================
<script setup lang="ts">
defineProps<{
modelValue: boolean;
name: string;
label: string;
}>();
const emit = defineEmits(['update:modelValue']);
</script>
<template>
<div class="flex items-center gap-[12px]">
<input
:checked="modelValue"
:name="name"
type="checkbox"
class="form-checkbox h-[19px] w-[19px] rounded-lg border-skin-text bg-skin-bg text-skin-primary !outline-none !ring-0"
@input="
emit('update:modelValue', ($event.target as HTMLInputElement).checked)
"
/>
<div class="text-skin-text">
{{ label }}
</div>
</div>
</template>
================================================
FILE: src/components/InputComboboxToken.vue
================================================
<script setup lang="ts">
import { Token } from '@/helpers/alchemy';
defineProps<{
label: string;
network: string;
tokens: Token[];
amount: string;
selectedToken?: Token;
loading?: boolean;
error?: string;
}>();
defineEmits(['update:selectedToken', 'update:amount']);
const { web3Account } = useWeb3();
const { modalAccountOpen } = useModal();
const isTokenModalOpen = ref(false);
function handleOpenTokenModal() {
if (!web3Account.value) return (modalAccountOpen.value = true);
isTokenModalOpen.value = true;
}
</script>
<template>
<TuneInput
:model-value="amount"
:label="label"
:error="error"
always-show-error
placeholder="0.0"
type="number"
class="pr-[142px]"
@update:model-value="$emit('update:amount', $event)"
>
<template #after>
<button
type="button"
label="Token"
class="-mr-[23px] h-[40px] hover:bg-[--border-color-subtle] rounded-r-full border-l"
@click="handleOpenTokenModal"
>
<div
class="flex flex-row space-x-2 items-center pr-[12px] pl-3 max-w-[150px]"
>
<template v-if="loading">
<TuneLoadingSpinner />
</template>
<template v-else-if="selectedToken?.contractAddress">
<AvatarToken :address="selectedToken.contractAddress" size="20" />
<span class="text-skin-link truncate">{{
selectedToken.symbol
}}</span>
</template>
<template v-else>
<div>Select token</div>
</template>
<i-ho-chevron-down class="text-sm text-skin-link shrink-0" />
</div>
</button>
</template>
</TuneInput>
<teleport to="#modal">
<ModalTokens
:selected-token="selectedToken"
:tokens="tokens"
:open="isTokenModalOpen"
:network="network"
@update:selected-token="$emit('update:selectedToken', $event)"
@close="isTokenModalOpen = false"
/>
</teleport>
</template>
================================================
FILE: src/components/InputDate.vue
================================================
<script setup lang="ts">
defineProps<{
title?: string;
information?: string;
dateString?: string;
date: number;
disabled?: boolean;
type?: string;
tooltip: string | null;
}>();
defineEmits(['update:date']);
const modalDateSelectOpen = ref(false);
</script>
<template>
<div class="w-full">
<LabelInput :information="information">{{ title }}</LabelInput>
<TuneButton
v-tippy="{ content: tooltip }"
class="relative inset-y-0 flex !h-[42px] w-full items-center truncate pl-[44px] pr-3 text-left"
:class="[disabled ? 'cursor-not-allowed' : 'cursor-pointer']"
@click="disabled ? null : (modalDateSelectOpen = true)"
>
<span :class="{ 'text-skin-text opacity-60': disabled }">
{{ dateString }}
</span>
<i-ho-calendar
class="absolute left-[16px] -mt-[1px] text-sm text-skin-text"
/>
</TuneButton>
</div>
<teleport to="#modal">
<ModalSelectDate
:type="type"
:open="modalDateSelectOpen"
:value="date"
@close="modalDateSelectOpen = false"
@input="$emit('update:date', $event)"
/>
</teleport>
</template>
================================================
FILE: src/components/InputEmail.vue
================================================
<script setup lang="ts">
defineProps<{
modelValue?: string;
focusOnMount?: boolean;
}>();
const emit = defineEmits(['update:modelValue']);
</script>
<template>
<BaseInput
v-bind="$attrs"
:model-value="modelValue"
:focus-on-mount="focusOnMount"
type="email"
:placeholder="$t('newsletter.yourEmail')"
class="!pr-[66px]"
@update:model-value="emit('update:modelValue', $event)"
>
<template #after>
<slot />
</template>
</BaseInput>
</template>
================================================
FILE: src/components/InputNewsletter.vue
================================================
<script setup lang="ts">
defineProps<{
tag: string;
}>();
defineEmits(['close']);
const action =
'https://snapshot.us17.list-manage.com/subscribe/post?u=1c820b717ffe37c1703e33f4b&id=f11d6e5df6';
</script>
<template>
<form
:action="action"
method="post"
target="_blank"
autocomplete="off"
class="flex"
>
<input type="hidden" name="tags" :value="tag" />
<InputEmail name="EMAIL" required>
<button
type="submit"
name="subscribe"
class="absolute right-0 h-[42px] rounded-r-full px-3"
>
<i-ho-paper-airplane class="rotate-90 text-skin-link" />
</button>
</InputEmail>
</form>
</template>
================================================
FILE: src/components/InputSelect.vue
================================================
<script setup lang="ts">
defineProps<{
modelValue: string;
title?: string;
information?: string;
isDisabled?: boolean;
tooltip?: string | null;
}>();
const emit = defineEmits(['select']);
</script>
<template>
<div class="w-full">
<LabelInput :information="information">{{ title }}</LabelInput>
<TuneButton
v-tippy="{ content: tooltip }"
:class="[
$attrs.class,
{ 'cursor-not-allowed !border-skin-border': isDisabled }
]"
class="relative !h-[42px] w-full truncate pl-3 pr-5 text-left"
:disabled="isDisabled"
@click="isDisabled ? null : emit('select')"
>
<span :class="{ 'text-skin-text ': isDisabled }">
{{ modelValue }}
</span>
<i-ho-chevron-down
class="absolute inset-y-[12px] right-[14px] text-xs text-skin-link"
/>
</TuneButton>
</div>
</template>
================================================
FILE: src/components/InputSelectPrivacy.vue
================================================
<script setup lang="ts">
withDefaults(
defineProps<{
privacy?: string;
hint?: string;
allowAny?: boolean;
disabled?: boolean;
}>(),
{
privacy: '',
hint: '',
allowAny: false,
disabled: false
}
);
const emit = defineEmits(['update:privacy']);
const modalVotingPrivacyOpen = ref(false);
</script>
<template>
<div>
<TuneButtonSelect
:label="$t(`privacy.label`)"
:hint="hint"
:model-value="
privacy ? $t(`privacy.${privacy}.label`) : $t('privacy.any')
"
:disabled="disabled"
@select="modalVotingPrivacyOpen = true"
/>
<teleport to="#modal">
<ModalVotingPrivacy
:selected="privacy"
:open="modalVotingPrivacyOpen"
:allow-any="allowAny"
@update:selected="emit('update:privacy', $event)"
@close="modalVotingPrivacyOpen = false"
/>
</teleport>
</div>
</template>
================================================
FILE: src/components/InputSelectVoteType.vue
================================================
<script setup lang="ts">
withDefaults(
defineProps<{
type?: string;
hint?: string;
allowAny?: boolean;
disabled?: boolean;
isDisabledSettings?: boolean;
}>(),
{
type: '',
hint: '',
allowAny: false,
disabled: false,
isDisabledSettings: false
}
);
const emit = defineEmits(['update:type']);
const modalVotingTypeOpen = ref(false);
</script>
<template>
<TuneButtonSelect
:label="$t(`settings.type.label`)"
:hint="hint"
:model-value="type ? $t(`voting.${type}.label`) : $t('settings.anyType')"
:disabled="disabled || isDisabledSettings"
:tooltip="
disabled
? $t('create.typeEnforced', { type: $t(`voting.${type}.label`) })
: null
"
@select="modalVotingTypeOpen = true"
/>
<teleport to="#modal">
<ModalVotingType
:selected="type"
:open="modalVotingTypeOpen"
:allow-any="allowAny"
@update:selected="emit('update:type', $event)"
@close="modalVotingTypeOpen = false"
/>
</teleport>
</template>
================================================
FILE: src/components/InputSelectVoteValidation.vue
================================================
<script setup lang="ts">
import { VoteValidation, SpaceStrategy } from '@/helpers/interfaces';
defineProps<{
validation: VoteValidation;
votingStrategies: SpaceStrategy[];
disabled?: boolean;
}>();
const emit = defineEmits(['add']);
const isModalOpen = ref(false);
</script>
<template>
<TuneButtonSelect
class="w-full"
:label="$t(`votingValidation.label`)"
:hint="$t(`votingValidation.information`)"
:model-value="$t(`votingValidation.${validation.name}.label`)"
:disabled="disabled"
@select="isModalOpen = true"
/>
<teleport to="#modal">
<ModalVoteValidation
:open="isModalOpen"
:validation="validation"
:voting-strategies="votingStrategies"
@close="isModalOpen = false"
@add="emit('add', $event)"
/>
</teleport>
</template>
================================================
FILE: src/components/InputSwitch.vue
================================================
<script setup lang="ts">
import { Switch } from '@headlessui/vue';
defineProps<{
modelValue?: boolean;
label?: string;
textRight?: string;
definition?: any;
information?: string;
isDisabled?: boolean;
}>();
const emit = defineEmits(['update:modelValue']);
</script>
<template>
<div class="flex items-center space-x-2 pr-2 pt-1">
<Switch
:model-value="modelValue"
:class="[
modelValue ? 'bg-green' : 'bg-skin-border',
{ '!cursor-not-allowed': isDisabled }
]"
:disabled="isDisabled"
class="relative inline-flex h-[22px] w-[38px] flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent outline-offset-2 transition-colors duration-200 ease-in-out"
@update:model-value="value => emit('update:modelValue', value)"
>
<span v-if="label" class="sr-only">{{ label }}</span>
<span
:class="[
modelValue ? 'translate-x-[16px]' : 'translate-x-0',
'shadow pointer-events-none inline-block h-[18px] w-[18px] transform rounded-full bg-skin-bg transition duration-200 ease-in-out'
]"
>
<span
:class="[
modelValue
? 'opacity-0 duration-100 ease-out'
: 'opacity-100 duration-200 ease-in',
'absolute inset-0 flex h-full w-full items-center justify-center text-skin-text transition-opacity'
]"
aria-hidden="true"
>
<svg class="h-[10px] w-[10px]" fill="none" viewBox="0 0 12 12">
<path
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
<span
:class="[
modelValue
? 'opacity-100 duration-200 ease-in'
: 'opacity-0 duration-100 ease-out',
'absolute inset-0 flex h-full w-full items-center justify-center text-green transition-opacity'
]"
aria-hidden="true"
>
<svg
class="h-[10px] w-[10px]"
fill="currentColor"
viewBox="0 0 12 12"
>
<path
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
/>
</svg>
</span>
</span>
</Switch>
<LabelInput :information="information || definition?.description">
{{ textRight || definition?.title }}
</LabelInput>
</div>
</template>
================================================
FILE: src/components/InputUploadAvatar.vue
================================================
<script setup lang="ts">
const props = defineProps<{
isViewOnly?: boolean;
}>();
const emit = defineEmits(['image-uploaded', 'image-remove']);
const fileInput = ref<HTMLInputElement | null>(null);
function openFilePicker() {
if (props.isViewOnly) return;
fileInput.value?.click();
}
const uploadSuccess = ref(false);
const previewFile = ref<File | undefined>(undefined);
const { upload, isUploadingImage, imageUploadError } = useImageUpload();
const { notify } = useFlashNotification();
function onFileChange(e) {
uploadSuccess.value = false;
if ((e.target as HTMLInputElement).files?.[0])
previewFile.value = (e.target as HTMLInputElement).files?.[0];
upload(previewFile.value, image => {
uploadSuccess.value = true;
emit('image-uploaded', image.url);
});
}
watch(
() => imageUploadError.value,
error => {
if (error !== '') {
notify(['red', error]);
}
}
);
</script>
<template>
<div @click="openFilePicker()">
<slot
name="avatar"
:uploading="isUploadingImage"
:preview-file="uploadSuccess ? previewFile : undefined"
/>
</div>
<input
v-bind="$attrs"
ref="fileInput"
type="file"
accept="image/jpg, image/jpeg, image/png"
style="display: none"
@change="onFileChange"
/>
</template>
================================================
FILE: src/components/LabelInput.vue
================================================
<script setup lang="ts">
defineProps<{
information?: string;
}>();
</script>
<template>
<span class="mb-[2px] flex items-center gap-1 text-skin-text">
<slot />
<IconInformationTooltip :information="information" />
</span>
</template>
================================================
FILE: src/components/LabelProposalState.vue
================================================
<script setup lang="ts">
const props = defineProps<{
state: string;
}>();
const stateClass = computed(() => {
if (props.state === 'closed') return 'bg-[#BB6BD9]';
if (props.state === 'active') return 'bg-green';
return 'bg-gray-500';
});
</script>
<template>
<div
class="text-white rounded-full px-[12px] text-sm h-[24px] w-fit leading-[23px]"
:class="stateClass"
>
{{ $t(`proposals.states.${state}`) }}
</div>
</template>
================================================
FILE: src/components/LabelProposalVoted.vue
================================================
<script setup lang="ts"></script>
<template>
<span
class="absolute inline-flex items-center gap-1 whitespace-nowrap py-[1px] text-sm text-skin-text"
>
<i-s-voted class="text-green" />
{{ $t('voted') }}
</span>
</template>
================================================
FILE: src/components/LinkSpace.vue
================================================
<script setup lang="ts">
import domains from '@/../snapshot-spaces/spaces/domains.json';
const props = defineProps<{
spaceId: string;
}>();
const { domain } = useApp();
const spaceLink = computed(() => {
// Check if proposal space id is a value in the domains.json file
if (domain && Object.values(domains).includes(props.spaceId)) {
// If so, find the key that matches the value
const key = Object.keys(domains).find(
key => domains[key] === props.spaceId
);
return `https://${key}`;
}
if (domain) return `https://snapshot.org/#/${props.spaceId}`;
return {
name: 'spaceProposals',
params: { key: props.spaceId }
};
});
</script>
<template>
<BaseLink :link="spaceLink" hide-external-icon>
<slot />
</BaseLink>
</template>
================================================
FILE: src/components/ListboxNetwork.vue
================================================
<script setup lang="ts">
import { getUrl } from '@snapshot-labs/snapshot.js/src/utils';
defineProps<{
modelValue: string;
networks: {
value: string;
name: string;
extras?: { icon?: string };
}[];
}>();
const emit = defineEmits(['update:modelValue']);
</script>
<template>
<TuneListbox
:model-value="modelValue"
label="Network"
:items="networks"
class="w-full"
@update:model-value="value => emit('update:modelValue', value)"
>
<template #item="{ item }">
<div class="flex items-center">
<BaseAvatar
v-if="item.extras?.icon"
:src="getUrl(item.extras.icon)"
class="mr-2"
/>
<div class="truncate pr-2">
{{ item.name }}
</div>
<BasePill class="leading-4"> #{{ item.value }} </BasePill>
</div>
</template>
<template #selected="{ selectedItem }">
<div class="flex items-center">
<BaseAvatar :src="getUrl(selectedItem.extras?.icon)" class="mr-2" />
<div class="truncate pr-2">
{{ selectedItem.name }}
</div>
</div>
</template>
</TuneListbox>
</template>
================================================
FILE: src/components/LoadingList.vue
================================================
<script setup lang="ts"></script>
<template>
<div>
<div
class="lazy-loading mb-2 rounded-md"
style="width: 80%; height: 20px"
/>
<div class="lazy-loading rounded-md" style="width: 50%; height: 20px" />
</div>
</template>
================================================
FILE: src/components/LoadingPage.vue
================================================
<template>
<BaseLoading>
<div class="space-y-3">
<div class="lazy-loading rounded-md" style="width: 100%; height: 34px" />
<div class="lazy-loading rounded-md" style="width: 40%; height: 34px" />
<div class="lazy-loading rounded-md" style="width: 65px; height: 28px" />
</div>
</BaseLoading>
</template>
================================================
FILE: src/components/LoadingRow.vue
================================================
<script setup lang="ts">
defineProps<{ block?: boolean }>();
</script>
<template>
<BaseLoading :block="block">
<div class="block px-4 py-4">
<div
class="lazy-loading mb-2 rounded-md"
style="width: 60%; height: 28px"
/>
<div class="lazy-loading rounded-md" style="width: 50%; height: 28px" />
</div>
</BaseLoading>
</template>
================================================
FILE: src/components/LoadingSpinner.vue
================================================
<script setup lang="ts">
defineProps({
fillWhite: {
type: Boolean,
default: false
},
small: {
type: Boolean,
default: false
},
big: {
type: Boolean,
default: false
}
});
</script>
<template>
<BaseLoading class="loading" :class="{ small, big }">
<svg
xmlns="http://www.w3.org/2000/svg"
width="50"
height="50"
viewBox="0 0 50 50"
>
<path
:class="{ '!fill-white': fillWhite }"
d="M25,5A20.14,20.14,0,0,1,45,22.88a2.51,2.51,0,0,0,2.49,2.26h0A2.52,2.52,0,0,0,50,22.33a25.14,25.14,0,0,0-50,0,2.52,2.52,0,0,0,2.5,2.81h0A2.51,2.51,0,0,0,5,22.88,20.14,20.14,0,0,1,25,5Z"
>
<animateTransform
attributeName="transform"
type="rotate"
from="0 25 25"
to="360 25 25"
dur="0.5s"
repeatCount="indefinite"
/>
</path>
</svg>
</BaseLoading>
</template>
<style lang="scss">
.loading {
span {
width: 100%;
}
&.small {
svg {
width: 18px;
height: 18px;
}
}
&.big {
svg {
width: 24px;
height: 24px;
}
}
svg {
display: inline-block;
vertical-align: middle;
width: 20px;
height: 20px;
path {
fill: var(--link-color);
}
}
&.overlay {
position: fixed;
text-align: center;
display: flex;
align-items: center;
align-content: center;
justify-content: center;
top: 0;
bottom: 80px;
left: 0;
right: 0;
width: 100%;
}
}
</style>
================================================
FILE: src/components/MenuAccount.vue
================================================
<script setup lang="ts">
import { openProfile } from '@/helpers/utils';
const props = defineProps<{
address: string;
}>();
const emit = defineEmits(['switchWallet']);
const { domain } = useApp();
const { logout } = useWeb3();
const { modalEmailOpen } = useModal();
const router = useRouter();
function handleAction(e) {
if (e === 'viewProfile') return openProfile(props.address, domain, router);
if (e === 'switchWallet') return emit('switchWallet');
if (e === 'subscribeEmail') {
modalEmailOpen.value = true;
return true;
}
return logout();
}
</script>
<template>
<div>
<BaseMenu
:items="[
{
text: 'View profile',
action: 'viewProfile',
extras: { icon: 'profile' }
},
{
text: 'Switch wallet',
action: 'switchWallet',
extras: { icon: 'switch' }
},
{
text: 'Email notifications',
action: 'subscribeEmail',
extras: { icon: 'mail' }
},
{ text: 'Log out', action: 'logout', extras: { icon: 'logout' } }
]"
@select="handleAction($event)"
>
<template #button>
<slot />
</template>
<template #item="{ item }">
<div class="flex items-center space-x-2">
<div class="w-[24px]">
<i-ho-user-circle
v-if="item.extras.icon === 'profile'"
class="text-[19px]"
/>
<i-ho-user-add
v-if="item.extras.icon === 'user-add'"
class="ml-[2px]"
/>
<i-ho-refresh v-if="item.extras.icon === 'switch'" />
<i-ho-mail v-if="item.extras.icon === 'mail'" />
<i-ho-logout
v-if="item.extras.icon === 'logout'"
class="ml-[2px]"
/>
</div>
<div>
{{ item.text }}
</div>
</div>
</template>
</BaseMenu>
</div>
<teleport to="#modal">
<ModalEmail :open="modalEmailOpen" @close="modalEmailOpen = false" />
</teleport>
</template>
================================================
FILE: src/components/MenuLanguages.vue
================================================
<script setup lang="ts">
import languages from '@/locales/languages.json';
const { setLocale } = useI18n();
function selectLang(locale) {
setLocale(locale);
}
const localeItems = computed<{ text: string; action: string }[]>(() => {
return Object.keys(languages).map(locale => ({
text:
locale === 'en-US'
? languages[locale].name
: languages[locale].nativeName,
action: locale
}));
});
</script>
<template>
<BaseMenu :items="localeItems" @select="selectLang($event)">
<template #button>
<TuneButton
class="flex !h-[44px] w-full items-center !text-skin-text hover:!text-skin-link"
>
<i-ho-globe class="mr-2" />
{{
languages[$i18n.locale]?.nativeName ?? languages[$i18n.locale]?.name
}}
</TuneButton>
</template>
</BaseMenu>
</template>
================================================
FILE: src/components/MessageWarningFlagged.vue
================================================
<script setup lang="ts">
const props = defineProps<{
type: string;
responsive?: boolean;
}>();
const emit = defineEmits(['forceShow']);
const { t } = useI18n();
const warningText = computed(() => {
if (props.type === 'proposal') {
return t('warningFlaggedProposal');
}
return t('warningFlaggedSpace');
});
</script>
<template>
<div
class="flex justify-between py-3 pl-4"
:class="[
{ 'rounded-xl border': !responsive },
{ 'rounded-none border-y md:rounded-xl md:border': responsive }
]"
>
<div class="flex items-center">
{{ warningText }}
</div>
<div>
<button @click.prevent="emit('forceShow')">
<div class="px-4 py-3 hover:text-skin-link">
{{ $t('warningFlaggedActionShow') }}
</div>
</button>
</div>
</div>
</template>
================================================
FILE: src/components/MessageWarningGnosisNetwork.vue
================================================
<script setup lang="ts">
import networks from '@snapshot-labs/snapshot.js/src/networks.json';
import { ExtendedSpace } from '@/helpers/interfaces';
const defaultNetwork = import.meta.env.VITE_DEFAULT_NETWORK;
const props = defineProps<{
space: ExtendedSpace;
action: 'vote' | 'create' | 'settings';
isResponsive?: boolean;
}>();
const networkKey = computed(() =>
props.action === 'settings' ? defaultNetwork : props.space.network
);
</script>
<template>
<BaseMessageBlock level="warning" :is-responsive="isResponsive">
{{
$t('settings.gnosisWrongNetwork.base', {
network: networks?.[networkKey]?.name,
action: $t(`settings.gnosisWrongNetwork.${action}`)
})
}}
</BaseMessageBlock>
</template>
================================================
FILE: src/components/MessageWarningHibernated.vue
================================================
<script setup lang="ts">
import { ExtendedSpace } from '@/helpers/interfaces';
const props = defineProps<{
space: ExtendedSpace;
}>();
const { web3Account } = useWeb3();
const { loadSpaceController, isSpaceController } = useSpaceController();
const isAuthorized = computed(() => {
const admins = (props.space.admins || []).map(admin => admin.toLowerCase());
return (
admins.includes(web3Account.value?.toLowerCase()) || isSpaceController.value
);
});
onMounted(async () => {
await loadSpaceController();
});
</script>
<template>
<BaseMessageBlock v-if="space.hibernated" level="warning-red" is-responsive>
{{
isAuthorized
? $t('create.errorSpaceHibernatedAdmin')
: $t('create.errorSpaceHibernatedUsers')
}}
<BaseLink
v-if="isAuthorized"
link="https://docs.snapshot.org/user-guides/spaces/space-hibernation"
>
{{ $t('learnMore') }}
</BaseLink>
<p v-if="isAuthorized" class="mt-3">
<router-link :to="{ name: 'spaceSettings' }">
<TuneButton> Go to Settings </TuneButton>
</router-link>
</p>
</BaseMessageBlock>
</template>
================================================
FILE: src/components/MessageWarningTestnet.vue
================================================
<script setup lang="ts">
const props = defineProps<{
context: 'Treasury' | 'Strategy';
error?: string | Record<string, any>;
}>();
const strategyTestnetErrors = computed(() => {
if (typeof props.error === 'object') {
const entries = Object.entries(props.error).filter(e => e[1].network);
return entries.filter(e => e[1].network === 'Testnet not allowed.');
}
});
</script>
<template>
<BaseMessageBlock
v-if="strategyTestnetErrors"
level="warning-red"
class="mt-3"
>
{{ context }}
#{{ strategyTestnetErrors.map(e => Number(e[0]) + 1).join(', ') }}
is using a test network which is no longer supported. If you are looking to
setup a space for testing, please checkout
<BaseLink link="https://testnet.snapshot.org">
testnet.snapshot.org</BaseLink
>
</BaseMessageBlock>
</template>
================================================
FILE: src/components/MessageWarningValidation.vue
================================================
<script setup lang="ts">
const props = defineProps<{
context: 'voting' | 'proposal';
spaceId: string;
validationName: string;
validationParams: Record<string, any>;
minScore: number;
symbol: string;
}>();
const { formatCompactNumber } = useIntl();
const tPath = computed(() => {
if (props.context === 'voting') {
return 'votingValidation';
}
return 'proposalValidation';
});
</script>
<template>
<BaseMessageBlock level="warning">
<template v-if="validationName === 'basic'">
{{
$t(`${tPath}.basic.invalidMessage`, {
amount: formatCompactNumber(minScore),
symbol
})
}}
<BaseLink :link="{ name: 'spaceAbout', params: { key: spaceId } }">
{{ $t('learnMore') }}
</BaseLink>
</template>
<template v-else-if="validationName === 'passport-gated'">
{{
$t(`${tPath}.passport-gated.invalidMessage`, {
scoreThreshold: validationParams?.scoreThreshold || 0
})
}}<span v-if="props.validationParams?.operator === 'NONE'">. </span>
<template v-else>
{{
$t(`${tPath}.passport-gated.invalidMessageStamps`, {
operator:
props.validationParams?.operator === 'AND' ? 'all' : 'one',
stamps:
validationParams.stamps && validationParams.stamps.join(', ')
})
}}
</template>
<BaseLink link="https://passport.gitcoin.co/#/dashboard">
Gitcoin Passport</BaseLink
>
</template>
<template v-else>
{{ $t(`${tPath}.notValidMessage`) }}
<BaseLink :link="{ name: 'spaceAbout', params: { key: spaceId } }">
{{ $t('learnMore') }}
</BaseLink>
</template>
</BaseMessageBlock>
</template>
================================================
FILE: src/components/ModalAccount.vue
================================================
<script setup lang="ts">
import { getInjected } from '@snapshot-labs/lock/src/utils';
import connectors from '@/helpers/connectors';
import { getIpfsUrl } from '@/helpers/utils';
const props = defineProps<{
open: boolean;
}>();
defineEmits(['login', 'close', 'openTerms']);
const { open } = toRefs(props);
const isShowingAllConnectors = ref(false);
const injected = computed(() => getInjected());
const filteredConnectors = computed(() => {
const baseConnectors = ['injected', 'walletconnect', 'walletlink'];
// If injected is Coinbase, hide WalletLink
if (injected.value?.name === 'Coinbase') connectors.walletlink.hidden = true;
if (isShowingAllConnectors.value) return Object.keys(connectors);
return Object.keys(connectors).filter(cId => baseConnectors.includes(cId));
});
watch(open, () => {
isShowingAllConnectors.value = false;
});
</script>
<template>
<TuneModal :open="open" @close="$emit('close')">
<TuneModalTitle as="h4" class="mx-3 mt-3">
Connect to Snapshot
</TuneModalTitle>
<!-- TODO: Enable when TOS ready and remember to enable disconnect in useApp -->
<!-- <TuneModalDescription class="mx-3 pb-3">
By connecting, you agree to
<a
role="button"
tabindex="0"
class="font-semibold"
@click="$emit('openTerms')"
@keyup.enter="$emit('openTerms')"
>
Snapshot Labs' Terms of Service</a
>.
</TuneModalDescription> -->
<div>
<div class="m-3 space-y-2">
<div
v-for="cId in filteredConnectors"
:key="cId"
class="block"
@click="$emit('login', connectors[cId].id)"
>
<TuneButton
v-if="cId === 'injected' && injected"
class="flex w-full items-center justify-center"
data-testid="button-connnect-wallet-injected"
>
<img
:src="getIpfsUrl(injected.icon)"
height="28"
width="28"
class="-mt-1 mr-2"
:alt="injected.name"
/>
{{ injected.name }}
</TuneButton>
<TuneButton
v-else-if="cId !== 'injected' && !connectors[cId].hidden"
class="flex w-full items-center justify-center gap-2"
>
<img
:src="getIpfsUrl(connectors[cId].icon)"
height="25"
width="25"
:alt="connectors[cId].name"
/>
<span>{{ connectors[cId].name }}</span>
</TuneButton>
</div>
<TuneButton
v-if="!isShowingAllConnectors"
class="flex w-full items-center justify-center gap-1"
@click="isShowingAllConnectors = true"
>
{{ $t('showMore') }}
<i-ho-chevron-down class="text-sm text-skin-link" />
</TuneButton>
</div>
</div>
</TuneModal>
</template>
================================================
FILE: src/components/ModalConfirmAction.vue
================================================
<script setup lang="ts">
defineProps<{
open: boolean;
title?: string;
showCancel?: boolean;
disabled?: boolean;
}>();
defineEmits(['close', 'confirm']);
</script>
<template>
<BaseModal :open="open" @close="$emit('close')">
<template #header>
<div class="flex flex-row items-center justify-center">
<h3>{{ title ? title : $t('confirmAction') }}</h3>
</div>
</template>
<slot />
<template #footer>
<div class="flex gap-3">
<TuneButton v-if="showCancel" class="w-full" @click="$emit('close')">
{{ $t('cancel') }}
</TuneButton>
<TuneButton
class="w-full"
primary
:disabled="disabled"
@click="$emit('confirm'), $emit('close')"
>
{{ $t('confirm') }}
</TuneButton>
</div>
</template>
</BaseModal>
</template>
================================================
FILE: src/components/ModalConfirmLeave.vue
================================================
<script setup lang="ts">
defineProps<{
open: boolean;
title?: string;
disabled?: boolean;
}>();
defineEmits(['close', 'save', 'leave']);
</script>
<template>
<BaseModal :open="open" @close="$emit('close')">
<template #header>
<div class="flex flex-row items-center justify-center">
<h3>{{ title ? title : 'Unsaved changes' }}</h3>
</div>
</template>
<BaseMessageBlock level="warning" class="m-4">
You have unsaved changes. Would you like to save them before leaving?
</BaseMessageBlock>
<template #footer>
<div class="flex gap-3">
<TuneButton class="w-full" @click="$emit('leave'), $emit('close')">
Leave
</TuneButton>
<TuneButton
class="w-full"
primary
:disabled="disabled"
@click="$emit('save'), $emit('close')"
>
Save
</TuneButton>
</div>
</template>
</BaseModal>
</template>
================================================
FILE: src/components/ModalControllerEdit.vue
================================================
<script setup lang="ts">
import { shorten } from '@/helpers/utils';
import { isAddress } from '@ethersproject/address';
const {
spaceControllerInput,
settingENSRecord,
spaceController,
confirmSetRecord
} = useSpaceController();
defineProps<{
open: boolean;
ensAddress: string;
}>();
const controllerInputIsValid = computed(() =>
isAddress(spaceControllerInput.value)
);
defineEmits(['close']);
</script>
<template>
<BaseModal :open="open" @close="$emit('close')">
<template #header>
<div class="flex flex-row items-center justify-center">
<h3>{{ $t('settings.editController') }}</h3>
</div>
</template>
<div class="p-4">
<BaseMessageBlock v-if="spaceController" level="info" class="mb-3">
{{
$tc('settings.currentSpaceControllerIs', {
address: shorten(spaceController)
})
}}
<div>
<BaseLink :link="`https://app.ens.domains/name/${ensAddress}`">
{{ $t('setup.seeOnEns') }}
</BaseLink>
</div>
</BaseMessageBlock>
<BaseInput
v-model.trim="spaceControllerInput"
:title="$t('settings.newController')"
:placeholder="
$t('setup.spaceOwnerAddressPlaceHolder', {
address:
spaceController ?? '0x3901D0fDe202aF1427216b79f5243f8A022d68cf'
})
"
focus-on-mount
>
</BaseInput>
</div>
<template #footer>
<TuneButton
class="my-2 w-full"
primary
:disabled="!controllerInputIsValid"
:loading="settingENSRecord"
@click="confirmSetRecord(), $emit('close')"
>
{{ $t('settings.set') }}
</TuneButton>
</template>
</BaseModal>
</template>
================================================
FILE: src/components/ModalDelegate.vue
================================================
<script setup lang="ts">
import { sleep } from '@snapshot-labs/snapshot.js/src/utils';
const props = defineProps<{
open: boolean;
userAddress: string;
spa
gitextract_yfac7sqe/ ├── .browserslistrc ├── .dockerignore ├── .eslintignore ├── .eslintrc-auto-import.json ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ ├── BUG_REPORT.yml │ │ └── FEATURE_REQUEST.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ ├── stale.yml │ └── workflows/ │ ├── codeql.yml │ ├── deploy.yml │ ├── nodejs.yml │ ├── test.yaml │ ├── update-snapshot-packages.yml │ └── update-snapshot-submodules.yml ├── .gitignore ├── .gitmodules ├── .gitpod.yml ├── .husky/ │ ├── post-checkout │ ├── post-merge │ ├── pre-commit │ └── pre-push ├── Dockerfile ├── FUNDING.json ├── LICENSE ├── README.md ├── babel.config.js ├── crowdin.yml ├── index.html ├── package.json ├── postcss.config.js ├── public/ │ ├── .well-known/ │ │ ├── assetlinks.json │ │ └── did.json │ ├── manifest.json │ └── service-worker.js ├── src/ │ ├── App.vue │ ├── assets/ │ │ ├── css/ │ │ │ ├── main.scss │ │ │ ├── tippy.scss │ │ │ └── tune.scss │ │ └── fonts/ │ │ ├── iconfont.css │ │ └── iconfont.json │ ├── components/ │ │ ├── AboutMembersListItem.vue │ │ ├── AvatarOverlayEdit.vue │ │ ├── AvatarSpace.vue │ │ ├── AvatarToken.vue │ │ ├── AvatarUser.vue │ │ ├── Banner.vue │ │ ├── BaseAvatar.vue │ │ ├── BaseBadge.vue │ │ ├── BaseBlock.vue │ │ ├── BaseBreadcrumbs.vue │ │ ├── BaseButtonIcon.vue │ │ ├── BaseButtonRound.vue │ │ ├── BaseCalendar.vue │ │ ├── BaseCombobox.vue │ │ ├── BaseContainer.vue │ │ ├── BaseCounter.vue │ │ ├── BaseIcon.vue │ │ ├── BaseIndicator.vue │ │ ├── BaseInput.vue │ │ ├── BaseInterpunct.vue │ │ ├── BaseLink.vue │ │ ├── BaseListbox.vue │ │ ├── BaseLoading.vue │ │ ├── BaseMarkdown.vue │ │ ├── BaseMenu.vue │ │ ├── BaseMessage.vue │ │ ├── BaseMessageBlock.vue │ │ ├── BaseModal.vue │ │ ├── BaseModalSelectItem.vue │ │ ├── BaseNetworkItem.vue │ │ ├── BaseNoResults.vue │ │ ├── BasePill.vue │ │ ├── BasePluginItem.vue │ │ ├── BasePopover.vue │ │ ├── BasePopoverHover.vue │ │ ├── BaseProgressBar.vue │ │ ├── BaseProgressRadial.vue │ │ ├── BaseSearch.vue │ │ ├── BaseSidebarNavigationItem.vue │ │ ├── BaseSkinItem.vue │ │ ├── BaseStrategyItem.vue │ │ ├── BaseUser.vue │ │ ├── BlockLink.vue │ │ ├── BlockSpacesList.vue │ │ ├── BlockSpacesListButtonMore.vue │ │ ├── BlockSpacesListItem.vue │ │ ├── BlockSpacesListSkeleton.vue │ │ ├── ButtonBack.vue │ │ ├── ButtonCard.vue │ │ ├── ButtonFollow.vue │ │ ├── ButtonPlayground.vue │ │ ├── ButtonShare.vue │ │ ├── ButtonSwitch.vue │ │ ├── ButtonTheme.vue │ │ ├── ComboboxNetwork.vue │ │ ├── ContainerParallelInput.vue │ │ ├── ExploreMenuCategories.vue │ │ ├── ExploreSkeletonLoading.vue │ │ ├── ExploreSpaces.vue │ │ ├── FooterLinks.vue │ │ ├── FooterLinksItem.vue │ │ ├── FooterSocials.vue │ │ ├── FooterSocialsItem.vue │ │ ├── FooterTitle.vue │ │ ├── FormArrayStrategies.vue │ │ ├── FormObjectStrategyParams.vue │ │ ├── IconDiscord.vue │ │ ├── IconInformationTooltip.vue │ │ ├── IconVerifiedSpace.vue │ │ ├── IndicatorAssetsChange.spec.js │ │ ├── IndicatorAssetsChange.vue │ │ ├── InputCheckbox.vue │ │ ├── InputComboboxToken.vue │ │ ├── InputDate.vue │ │ ├── InputEmail.vue │ │ ├── InputNewsletter.vue │ │ ├── InputSelect.vue │ │ ├── InputSelectPrivacy.vue │ │ ├── InputSelectVoteType.vue │ │ ├── InputSelectVoteValidation.vue │ │ ├── InputSwitch.vue │ │ ├── InputUploadAvatar.vue │ │ ├── LabelInput.vue │ │ ├── LabelProposalState.vue │ │ ├── LabelProposalVoted.vue │ │ ├── LinkSpace.vue │ │ ├── ListboxNetwork.vue │ │ ├── LoadingList.vue │ │ ├── LoadingPage.vue │ │ ├── LoadingRow.vue │ │ ├── LoadingSpinner.vue │ │ ├── MenuAccount.vue │ │ ├── MenuLanguages.vue │ │ ├── MessageWarningFlagged.vue │ │ ├── MessageWarningGnosisNetwork.vue │ │ ├── MessageWarningHibernated.vue │ │ ├── MessageWarningTestnet.vue │ │ ├── MessageWarningValidation.vue │ │ ├── ModalAccount.vue │ │ ├── ModalConfirmAction.vue │ │ ├── ModalConfirmLeave.vue │ │ ├── ModalControllerEdit.vue │ │ ├── ModalDelegate.vue │ │ ├── ModalEmail.vue │ │ ├── ModalEmailManagement.vue │ │ ├── ModalEmailResend.vue │ │ ├── ModalEmailSubscription.vue │ │ ├── ModalLinkPreview.vue │ │ ├── ModalMessage.vue │ │ ├── ModalNotice.vue │ │ ├── ModalOsnap.vue │ │ ├── ModalPendingTransactions.vue │ │ ├── ModalPlugins.vue │ │ ├── ModalPostPayment.vue │ │ ├── ModalPostVote.vue │ │ ├── ModalProfileForm.vue │ │ ├── ModalRevokeDelegate.vue │ │ ├── ModalSelectDate.vue │ │ ├── ModalSkins.vue │ │ ├── ModalSnapshotTerms.vue │ │ ├── ModalSpaces.vue │ │ ├── ModalSpacesListItem.vue │ │ ├── ModalStrategies.vue │ │ ├── ModalStrategy.vue │ │ ├── ModalTerms.vue │ │ ├── ModalTokens.vue │ │ ├── ModalTokensItem.vue │ │ ├── ModalTransactionStatus.vue │ │ ├── ModalTreasury.vue │ │ ├── ModalValidation.vue │ │ ├── ModalVote.vue │ │ ├── ModalVoteValidation.vue │ │ ├── ModalVotingPrivacy.vue │ │ ├── ModalVotingType.vue │ │ ├── ModalWrongNetwork.vue │ │ ├── NavbarAccount.vue │ │ ├── NavbarExtras.vue │ │ ├── NavbarNotifications.vue │ │ ├── PopoverHoverProfile.vue │ │ ├── ProfileAboutBiography.vue │ │ ├── ProfileAboutDelegate.vue │ │ ├── ProfileAboutDelegateListItem.vue │ │ ├── ProfileActivityList.vue │ │ ├── ProfileActivityListItem.vue │ │ ├── ProfileAddressCopy.vue │ │ ├── ProfileName.vue │ │ ├── ProfileSidebar.vue │ │ ├── ProfileSidebarHeader.vue │ │ ├── ProfileSidebarHeaderSkeleton.vue │ │ ├── ProfileSidebarNavigation.vue │ │ ├── ProposalsItem.vue │ │ ├── ProposalsItemBody.vue │ │ ├── ProposalsItemFooter.vue │ │ ├── ProposalsItemResults.vue │ │ ├── ProposalsItemTitle.vue │ │ ├── SettingsBoostBlock.vue │ │ ├── SettingsDangerzoneBlock.vue │ │ ├── SettingsDelegationBlock.vue │ │ ├── SettingsDomainBlock.vue │ │ ├── SettingsLinkBlock.vue │ │ ├── SettingsMembersBlock.vue │ │ ├── SettingsMembersPopoverContent.vue │ │ ├── SettingsPluginsBlock.vue │ │ ├── SettingsProfileBlock.vue │ │ ├── SettingsProposalBlock.vue │ │ ├── SettingsStrategiesBlock.vue │ │ ├── SettingsSubSpacesBlock.vue │ │ ├── SettingsTreasuriesBlock.vue │ │ ├── SettingsTreasuriesBlockItem.vue │ │ ├── SettingsTreasuriesBlockItemButton.vue │ │ ├── SettingsTreasuryActivateOsnapButton.vue │ │ ├── SettingsValidationBlock.vue │ │ ├── SettingsVotingBlock.vue │ │ ├── SetupButtonBack.vue │ │ ├── SetupButtonCreate.vue │ │ ├── SetupButtonNext.vue │ │ ├── SetupDomain.vue │ │ ├── SetupDomainRegister.vue │ │ ├── SetupExtras.vue │ │ ├── SetupIntro.vue │ │ ├── SetupMessageHelp.vue │ │ ├── SetupProfile.vue │ │ ├── SetupSidebarStepper.vue │ │ ├── SetupStrategy.vue │ │ ├── SetupStrategyAdvanced.vue │ │ ├── SetupStrategyBasic.vue │ │ ├── SetupStrategyVote.vue │ │ ├── SidebarSpacesSkeleton.vue │ │ ├── SidebarUnreadIndicator.vue │ │ ├── SpaceBoostCardProposal.vue │ │ ├── SpaceBoostDeposit.vue │ │ ├── SpaceBreadcrumbs.vue │ │ ├── SpaceCreateContent.vue │ │ ├── SpaceCreateLegacyOsnap.vue │ │ ├── SpaceCreateOsnap.vue │ │ ├── SpaceCreatePlugins.vue │ │ ├── SpaceCreateVoting.vue │ │ ├── SpaceCreateVotingDateEnd.vue │ │ ├── SpaceCreateVotingDateStart.vue │ │ ├── SpaceCreateWarnings.vue │ │ ├── SpaceDelegateEdit.vue │ │ ├── SpaceDelegatesAccount.vue │ │ ├── SpaceDelegatesCard.vue │ │ ├── SpaceDelegatesDelegateModal.vue │ │ ├── SpaceDelegatesSkeleton.vue │ │ ├── SpaceDelegatesSplitDelegationModal.vue │ │ ├── SpaceProposalBoost.vue │ │ ├── SpaceProposalBoostClaim.vue │ │ ├── SpaceProposalBoostClaimModal.vue │ │ ├── SpaceProposalBoostClaimModalItem.vue │ │ ├── SpaceProposalBoostClaimModalSuccess.vue │ │ ├── SpaceProposalBoostItem.vue │ │ ├── SpaceProposalBoostItemMenu.vue │ │ ├── SpaceProposalBoostModalCreate.vue │ │ ├── SpaceProposalBoostWinnersModal.vue │ │ ├── SpaceProposalContent.vue │ │ ├── SpaceProposalHeader.vue │ │ ├── SpaceProposalInformation.vue │ │ ├── SpaceProposalPage.vue │ │ ├── SpaceProposalPlugins.vue │ │ ├── SpaceProposalPluginsSidebar.vue │ │ ├── SpaceProposalResults.vue │ │ ├── SpaceProposalResultsList.vue │ │ ├── SpaceProposalResultsListItem.vue │ │ ├── SpaceProposalResultsProgressBar.vue │ │ ├── SpaceProposalResultsQuorum.vue │ │ ├── SpaceProposalResultsShutter.vue │ │ ├── SpaceProposalVote.vue │ │ ├── SpaceProposalVoteApproval.vue │ │ ├── SpaceProposalVoteQuadratic.vue │ │ ├── SpaceProposalVoteRankedChoice.vue │ │ ├── SpaceProposalVoteSingleChoice.vue │ │ ├── SpaceProposalVotes.vue │ │ ├── SpaceProposalVotesFilters.vue │ │ ├── SpaceProposalVotesItem.vue │ │ ├── SpaceProposalVotesListItemChoice.vue │ │ ├── SpaceProposalVotesModal.vue │ │ ├── SpaceProposalVotesModalDownload.vue │ │ ├── SpaceProposalsNotice.vue │ │ ├── SpaceProposalsSearch.vue │ │ ├── SpaceProposalsSearchFilter.vue │ │ ├── SpaceSettingsMessageHibernated.vue │ │ ├── SpaceSidebar.vue │ │ ├── SpaceSidebarFooter.vue │ │ ├── SpaceSidebarHeader.vue │ │ ├── SpaceSidebarMenuThreeDot.vue │ │ ├── SpaceSidebarNavigation.vue │ │ ├── SpaceSidebarSubspaces.vue │ │ ├── SpaceSplitDelegationRow.vue │ │ ├── StrategiesBlockWarning.vue │ │ ├── StrategiesListItem.vue │ │ ├── TextAutolinker.vue │ │ ├── TextareaArray.vue │ │ ├── TextareaAutosize.vue │ │ ├── TextareaJson.vue │ │ ├── TheActionbar.vue │ │ ├── TheFlashNotification.vue │ │ ├── TheFooter.vue │ │ ├── TheLayout.vue │ │ ├── TheModalNotification.vue │ │ ├── TheNavbar.vue │ │ ├── TheSearchBar.vue │ │ ├── TheSidebar.vue │ │ ├── TreasuryAssetsList.vue │ │ ├── TreasuryAssetsListItem.spec.js │ │ ├── TreasuryAssetsListItem.vue │ │ ├── TreasuryWalletsList.spec.js │ │ ├── TreasuryWalletsList.vue │ │ ├── TreasuryWalletsListItem.spec.js │ │ ├── TreasuryWalletsListItem.vue │ │ ├── Tune/ │ │ │ ├── TuneBlock.vue │ │ │ ├── TuneBlockFooter.vue │ │ │ ├── TuneBlockHeader.vue │ │ │ ├── TuneButton.story.vue │ │ │ ├── TuneButton.vue │ │ │ ├── TuneButtonSelect.story.vue │ │ │ ├── TuneButtonSelect.vue │ │ │ ├── TuneCheckbox.story.vue │ │ │ ├── TuneCheckbox.vue │ │ │ ├── TuneCombobox.story.vue │ │ │ ├── TuneCombobox.vue │ │ │ ├── TuneComboboxMultiple.story.vue │ │ │ ├── TuneComboboxMultiple.vue │ │ │ ├── TuneErrorInput.story.vue │ │ │ ├── TuneErrorInput.vue │ │ │ ├── TuneForm.story.vue │ │ │ ├── TuneForm.vue │ │ │ ├── TuneIconHint.story.vue │ │ │ ├── TuneIconHint.vue │ │ │ ├── TuneInput.story.vue │ │ │ ├── TuneInput.vue │ │ │ ├── TuneInputDuration.story.vue │ │ │ ├── TuneInputDuration.vue │ │ │ ├── TuneInputSocial.vue │ │ │ ├── TuneInputUrl.vue │ │ │ ├── TuneLabelInput.story.vue │ │ │ ├── TuneLabelInput.vue │ │ │ ├── TuneListbox.story.vue │ │ │ ├── TuneListbox.vue │ │ │ ├── TuneListboxMultiple.story.vue │ │ │ ├── TuneListboxMultiple.vue │ │ │ ├── TuneLoadingSpinner.story.vue │ │ │ ├── TuneLoadingSpinner.vue │ │ │ ├── TuneMenu.story.vue │ │ │ ├── TuneMenu.vue │ │ │ ├── TuneModal.story.vue │ │ │ ├── TuneModal.vue │ │ │ ├── TuneModalDescription.vue │ │ │ ├── TuneModalIndicator.vue │ │ │ ├── TuneModalTitle.vue │ │ │ ├── TunePopover.story.vue │ │ │ ├── TunePopover.vue │ │ │ ├── TuneRadio.story.vue │ │ │ ├── TuneRadio.vue │ │ │ ├── TuneSelect.vue │ │ │ ├── TuneSwitch.story.vue │ │ │ ├── TuneSwitch.vue │ │ │ ├── TuneTag.story.vue │ │ │ ├── TuneTag.vue │ │ │ ├── TuneTextarea.story.vue │ │ │ ├── TuneTextarea.vue │ │ │ ├── TuneTextareaArray.story.vue │ │ │ ├── TuneTextareaArray.vue │ │ │ ├── TuneTextareaJson.story.vue │ │ │ ├── TuneTextareaJson.vue │ │ │ └── _Form/ │ │ │ ├── FormArray.vue │ │ │ ├── FormBoolean.vue │ │ │ ├── FormNumber.vue │ │ │ └── FormString.vue │ │ └── Ui/ │ │ ├── UiCollapsible.vue │ │ ├── UiCollapsibleContent.vue │ │ ├── UiCollapsibleText.vue │ │ ├── UiInput.vue │ │ └── UiSelect.vue │ ├── composables/ │ │ ├── useAccount.ts │ │ ├── useAliasAction.ts │ │ ├── useApolloQuery.ts │ │ ├── useApp.ts │ │ ├── useBalances.ts │ │ ├── useBoost.ts │ │ ├── useChangeNetwork.ts │ │ ├── useClient.ts │ │ ├── useCopy.ts │ │ ├── useDelegate.ts │ │ ├── useDelegates.ts │ │ ├── useEmailFetchClient.ts │ │ ├── useEmailSubscription.ts │ │ ├── useEns.ts │ │ ├── useExtendedSpaces.ts │ │ ├── useFlaggedMessageStatus.ts │ │ ├── useFlashNotification.ts │ │ ├── useFollowSpace.ts │ │ ├── useFormSpaceProposal.ts │ │ ├── useFormSpaceSettings.ts │ │ ├── useFormValidation.ts │ │ ├── useGnosis.ts │ │ ├── useI18n.ts │ │ ├── useImageUpload.ts │ │ ├── useInfiniteLoader.ts │ │ ├── useIntl.ts │ │ ├── useMeta.ts │ │ ├── useModal.ts │ │ ├── useModalNotification.ts │ │ ├── useNetworksFilter.ts │ │ ├── useNotifications.ts │ │ ├── usePayment.ts │ │ ├── usePlugins.ts │ │ ├── useProfiles.ts │ │ ├── useProposalVotes.ts │ │ ├── useProposals.ts │ │ ├── useQuorum.ts │ │ ├── useReportDownload.ts │ │ ├── useResolveName.ts │ │ ├── useSafe.ts │ │ ├── useSharing.ts │ │ ├── useShortUrls.ts │ │ ├── useSkin.ts │ │ ├── useSkinsFilter.ts │ │ ├── useSnapshot.ts │ │ ├── useSpaceController.ts │ │ ├── useSpaceSubscription.ts │ │ ├── useSpaces.ts │ │ ├── useStatement.ts │ │ ├── useStrategies.ts │ │ ├── useTerms.ts │ │ ├── useTreasury.ts │ │ ├── useTxStatus.ts │ │ ├── useUnseenProposals.ts │ │ ├── useUsername.ts │ │ └── useWeb3.ts │ ├── env.d.ts │ ├── helpers/ │ │ ├── alchemy/ │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── apollo.ts │ │ ├── auth.ts │ │ ├── b64.ts │ │ ├── beams.ts │ │ ├── boost/ │ │ │ ├── abi.json │ │ │ ├── api.ts │ │ │ ├── index.ts │ │ │ ├── subgraph.ts │ │ │ ├── tokens.ts │ │ │ └── types.ts │ │ ├── clientEIP712.ts │ │ ├── connectors.ts │ │ ├── constants.ts │ │ ├── covalent.ts │ │ ├── delegation.ts │ │ ├── delegationV2/ │ │ │ ├── compound/ │ │ │ │ ├── index.ts │ │ │ │ ├── queries.ts │ │ │ │ ├── read.ts │ │ │ │ └── write.ts │ │ │ ├── index.ts │ │ │ ├── splitDelegation/ │ │ │ │ ├── abi.ts │ │ │ │ ├── index.ts │ │ │ │ ├── read.ts │ │ │ │ └── write.ts │ │ │ └── types.ts │ │ ├── ens.ts │ │ ├── i18n.ts │ │ ├── interfaces.ts │ │ ├── pin.ts │ │ ├── queries.ts │ │ ├── shutter.ts │ │ ├── sign.ts │ │ ├── snapshot.ts │ │ ├── transaction.ts │ │ ├── utils.test.js │ │ ├── utils.ts │ │ ├── validation.ts │ │ └── vitePlugins.ts │ ├── locales/ │ │ ├── ar-SA.json │ │ ├── de-DE.json │ │ ├── default.json │ │ ├── es-ES.json │ │ ├── fil-PH.json │ │ ├── fr-FR.json │ │ ├── hi-IN.json │ │ ├── id-ID.json │ │ ├── it-IT.json │ │ ├── ja-JP.json │ │ ├── ko-KR.json │ │ ├── languages.json │ │ ├── pt-PT.json │ │ ├── ro-RO.json │ │ ├── ru-RU.json │ │ ├── tr-TR.json │ │ ├── uk-UA.json │ │ ├── vi-VN.json │ │ └── zh-CN.json │ ├── main.ts │ ├── plugins/ │ │ ├── README.md │ │ ├── domino/ │ │ │ ├── ProposalSidebar.vue │ │ │ ├── components/ │ │ │ │ └── CustomBlock.vue │ │ │ └── plugin.json │ │ ├── gnosis/ │ │ │ ├── Create.vue │ │ │ ├── ProposalSidebar.vue │ │ │ ├── components/ │ │ │ │ ├── Config.vue │ │ │ │ └── CustomBlock.vue │ │ │ ├── index.ts │ │ │ └── plugin.json │ │ ├── oSnap/ │ │ │ ├── Create.vue │ │ │ ├── CreateSafe.vue │ │ │ ├── Proposal.vue │ │ │ ├── README.md │ │ │ ├── components/ │ │ │ │ ├── BotSupportWarning.vue │ │ │ │ ├── ExternalLink.vue │ │ │ │ ├── HandleOutcome/ │ │ │ │ │ ├── HandleOutcome.vue │ │ │ │ │ └── steps/ │ │ │ │ │ ├── CanProposeToOG.vue │ │ │ │ │ ├── CanRequestTxExecution.vue │ │ │ │ │ ├── InOOChallengePeriod.vue │ │ │ │ │ ├── RejectedBySnapshotVote.vue │ │ │ │ │ ├── TallyingSnapshotVotes.vue │ │ │ │ │ └── TransactionsExecuted.vue │ │ │ │ ├── Input/ │ │ │ │ │ ├── Address.vue │ │ │ │ │ ├── Amount.vue │ │ │ │ │ ├── FileInput/ │ │ │ │ │ │ ├── FileInput.vue │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── MethodParameter/ │ │ │ │ │ │ ├── MethodParameter.vue │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── ReadOnly.vue │ │ │ │ │ ├── SelectSafe.vue │ │ │ │ │ └── TransactionType.vue │ │ │ │ ├── OsnapMarketingWidget.vue │ │ │ │ ├── SafeLinkWithAvatar.vue │ │ │ │ └── TransactionBuilder/ │ │ │ │ ├── ContractInteraction.vue │ │ │ │ ├── ModalSafe.vue │ │ │ │ ├── ModalTransactionType.vue │ │ │ │ ├── RawTransaction.vue │ │ │ │ ├── SafeImport.vue │ │ │ │ ├── TenderlySimulation.vue │ │ │ │ ├── TokensModal.vue │ │ │ │ ├── TokensModalItem.vue │ │ │ │ ├── Transaction.vue │ │ │ │ ├── TransactionBuilder.vue │ │ │ │ ├── TransactionImport.vue │ │ │ │ ├── TransferFunds.vue │ │ │ │ └── TransferNFT.vue │ │ │ ├── constants.ts │ │ │ ├── plugin.json │ │ │ ├── types.ts │ │ │ └── utils/ │ │ │ ├── abi.ts │ │ │ ├── coins.ts │ │ │ ├── events.ts │ │ │ ├── getters.ts │ │ │ ├── index.ts │ │ │ ├── proposal.ts │ │ │ ├── safeImport.ts │ │ │ ├── tenderly.ts │ │ │ ├── transactions.ts │ │ │ └── validators.ts │ │ ├── poap/ │ │ │ ├── ProposalSidebar.vue │ │ │ ├── components/ │ │ │ │ └── CustomBlock.vue │ │ │ ├── index.ts │ │ │ └── plugin.json │ │ ├── progress/ │ │ │ ├── ProposalSidebar.vue │ │ │ ├── components/ │ │ │ │ └── CustomBlock.vue │ │ │ ├── index.ts │ │ │ ├── plugin.json │ │ │ └── readme.md │ │ ├── projectGalaxy/ │ │ │ ├── ProposalSidebar.vue │ │ │ ├── README.md │ │ │ ├── components/ │ │ │ │ └── CustomBlock.vue │ │ │ ├── index.ts │ │ │ └── plugin.json │ │ ├── quorum/ │ │ │ ├── examples.json │ │ │ └── plugin.json │ │ └── safeSnap/ │ │ ├── Create.vue │ │ ├── Proposal.vue │ │ ├── components/ │ │ │ ├── Config.vue │ │ │ ├── Form/ │ │ │ │ ├── ContractInteraction.vue │ │ │ │ ├── ImportTransactionsButton.vue │ │ │ │ ├── RawTransaction.vue │ │ │ │ ├── SendAsset.vue │ │ │ │ ├── TokensModal.vue │ │ │ │ ├── TokensModalItem.vue │ │ │ │ ├── Transaction.vue │ │ │ │ ├── TransactionBatch.vue │ │ │ │ └── TransferFunds.vue │ │ │ ├── HandleOutcome.vue │ │ │ ├── HandleOutcomeUma.vue │ │ │ ├── Input/ │ │ │ │ ├── Address.vue │ │ │ │ ├── Amount.vue │ │ │ │ ├── ArrayType.vue │ │ │ │ └── MethodParameter.vue │ │ │ ├── Modal/ │ │ │ │ └── OptionApproval.vue │ │ │ ├── SafeTransactions.vue │ │ │ └── Tooltip.vue │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── plugin.json │ │ ├── types/ │ │ │ └── index.ts │ │ └── utils/ │ │ ├── abi.ts │ │ ├── coins.ts │ │ ├── decoder.ts │ │ ├── events.ts │ │ ├── index.ts │ │ ├── multiSend.ts │ │ ├── realityETH.ts │ │ ├── realityModule.ts │ │ ├── safe.ts │ │ ├── transactions.ts │ │ ├── umaModule.ts │ │ └── validator.ts │ ├── router/ │ │ └── index.ts │ ├── sentry.ts │ └── views/ │ ├── DelegateView.vue │ ├── ExploreView.vue │ ├── PlaygroundView.vue │ ├── ProfileAbout.vue │ ├── ProfileActivity.vue │ ├── ProfileView.vue │ ├── RankingView.vue │ ├── SetupView.vue │ ├── SpaceAbout.vue │ ├── SpaceBoost.vue │ ├── SpaceCreate.vue │ ├── SpaceDelegate.vue │ ├── SpaceDelegates.vue │ ├── SpaceProposal.vue │ ├── SpaceProposals.vue │ ├── SpaceSettings.vue │ ├── SpaceTreasury.vue │ ├── SpaceView.vue │ ├── StrategyView.vue │ ├── TermsView.vue │ └── TimelineView.vue ├── tailwind.config.js ├── tsconfig.json ├── vercel.json └── vite.config.ts
SYMBOL INDEX (572 symbols across 117 files)
FILE: src/components/IndicatorAssetsChange.spec.js
function createComponent (line 11) | function createComponent(params = {}) {
FILE: src/components/TreasuryAssetsListItem.spec.js
function createComponent (line 28) | function createComponent(params = {}) {
FILE: src/components/TreasuryWalletsList.spec.js
function createComponent (line 14) | function createComponent(params = {}) {
FILE: src/components/TreasuryWalletsListItem.spec.js
function createComponent (line 24) | function createComponent(params = {}) {
FILE: src/composables/useAccount.ts
function getERC20Account (line 5) | async function getERC20Account(
function useAccount (line 18) | function useAccount() {
FILE: src/composables/useAliasAction.ts
function useAliasAction (line 19) | function useAliasAction() {
FILE: src/composables/useApolloQuery.ts
function useApolloQuery (line 5) | function useApolloQuery() {
FILE: src/composables/useApp.ts
function useApp (line 24) | function useApp() {
FILE: src/composables/useBalances.ts
constant COINGECKO_API_URL (line 11) | const COINGECKO_API_URL = 'https://api.coingecko.com/api/v3/simple';
constant COINGECKO_PARAMS (line 12) | const COINGECKO_PARAMS = '&vs_currencies=usd&include_24hr_change=true';
function useBalances (line 14) | function useBalances() {
FILE: src/composables/useBoost.ts
function useBoost (line 9) | function useBoost() {
FILE: src/composables/useChangeNetwork.ts
function useChangeNetwork (line 4) | function useChangeNetwork() {
FILE: src/composables/useClient.ts
function useClient (line 4) | function useClient() {
FILE: src/composables/useCopy.ts
function useCopy (line 3) | function useCopy() {
FILE: src/composables/useDelegate.ts
function useDelegate (line 12) | function useDelegate() {
FILE: src/composables/useDelegates.ts
type DelegatesStats (line 10) | type DelegatesStats = Record<string, { votes: number; proposals: number }>;
constant DELEGATES_LIMIT (line 12) | const DELEGATES_LIMIT = 18;
function useDelegates (line 14) | function useDelegates(space: ExtendedSpace) {
FILE: src/composables/useEmailFetchClient.ts
function useEmailFetchClient (line 29) | function useEmailFetchClient() {
FILE: src/composables/useEmailSubscription.ts
type SubscriptionType (line 4) | type SubscriptionType = (typeof subscriptionTypes)[number];
type SubscriptionStatus (line 5) | type SubscriptionStatus = 'NOT_SUBSCRIBED' | 'VERIFIED' | 'UNVERIFIED';
function useEmailSubscriptionComposable (line 7) | function useEmailSubscriptionComposable() {
FILE: src/composables/useEns.ts
constant VALID_ENS_TLDS (line 6) | const VALID_ENS_TLDS = ['eth', 'xyz', 'com', 'org', 'io', 'app', 'art', ...
function useEns (line 8) | function useEns() {
FILE: src/composables/useExtendedSpaces.ts
function useExtendedSpaces (line 7) | function useExtendedSpaces() {
FILE: src/composables/useFlaggedMessageStatus.ts
function useFlaggedMessageStatus (line 23) | function useFlaggedMessageStatus(pageId: Ref<string> | string) {
FILE: src/composables/useFlashNotification.ts
type Notification (line 1) | interface Notification {
function useFlashNotification (line 10) | function useFlashNotification() {
FILE: src/composables/useFollowSpace.ts
function useFollowSpace (line 9) | function useFollowSpace(spaceId: any = {}) {
FILE: src/composables/useFormSpaceProposal.ts
type ProposalForm (line 7) | interface ProposalForm {
constant EMPTY_PROPOSAL (line 25) | const EMPTY_PROPOSAL: ProposalForm = {
constant EMPTY_PROPOSAL_DRAFT (line 43) | const EMPTY_PROPOSAL_DRAFT = {
function useFormSpaceProposal (line 58) | function useFormSpaceProposal({ spaceType = 'default' } = {}) {
FILE: src/composables/useFormSpaceSettings.ts
constant DEFAULT_PROPOSAL_VALIDATION (line 8) | const DEFAULT_PROPOSAL_VALIDATION = { name: 'any', params: {} };
constant DEFAULT_VOTE_VALIDATION (line 9) | const DEFAULT_VOTE_VALIDATION = { name: 'any', params: {} };
constant DEFAULT_DELEGATION (line 10) | const DEFAULT_DELEGATION = {
constant EMPTY_SPACE_FORM (line 16) | const EMPTY_SPACE_FORM = {
function useFormSpaceSettings (line 68) | function useFormSpaceSettings(
FILE: src/composables/useFormValidation.ts
function useFormValidation (line 5) | function useFormValidation(schema, form) {
FILE: src/composables/useGnosis.ts
function useGnosis (line 23) | function useGnosis(space?: ExtendedSpace) {
FILE: src/composables/useI18n.ts
function useI18n (line 10) | function useI18n() {
FILE: src/composables/useImageUpload.ts
function useImageUpload (line 6) | function useImageUpload() {
FILE: src/composables/useInfiniteLoader.ts
function useInfiniteLoader (line 1) | function useInfiniteLoader(loadBy = 6) {
FILE: src/composables/useIntl.ts
function useIntl (line 52) | function useIntl() {
FILE: src/composables/useMeta.ts
type metaInfo (line 3) | type metaInfo = {
function useMeta (line 14) | function useMeta(metaInfo: metaInfo) {
FILE: src/composables/useModal.ts
function useModal (line 5) | function useModal() {
FILE: src/composables/useModalNotification.ts
type Notification (line 1) | interface Notification {
function useModalNotification (line 10) | function useModalNotification() {
FILE: src/composables/useNetworksFilter.ts
function useNetworksFilter (line 11) | function useNetworksFilter() {
FILE: src/composables/useNotifications.ts
type Notification (line 10) | interface Notification {
function useNotifications (line 28) | function useNotifications() {
FILE: src/composables/usePayment.ts
constant BASE_PRICE (line 6) | const BASE_PRICE = 1666.66666667;
constant BASE_UNIT (line 7) | const BASE_UNIT = 1;
constant BASE_CURRENCY (line 8) | const BASE_CURRENCY = {
constant PLANS (line 12) | const PLANS = {
constant CURRENCIES (line 16) | const CURRENCIES = {
constant DEFAULT_CURRENCY (line 48) | const DEFAULT_CURRENCY = 'ethereum';
constant DEFAULT_PLAN (line 49) | const DEFAULT_PLAN = 'y1';
constant TRANSFER_ABI (line 50) | const TRANSFER_ABI = [
constant SNAPSHOT_WALLET (line 69) | const SNAPSHOT_WALLET = '0x01e8CEC73B020AB9f822fD0dee3Aa4da2fe39e38';
constant COINGECKO_API_URL (line 74) | const COINGECKO_API_URL = 'https://api.coingecko.com/api/v3/simple';
constant COINGECKO_PARAMS (line 75) | const COINGECKO_PARAMS = '&vs_currencies=usd&include_24hr_change=true';
function usePayment (line 77) | function usePayment(network: number) {
FILE: src/composables/usePlugins.ts
function usePlugins (line 92) | function usePlugins() {
FILE: src/composables/useProfiles.ts
function useProfiles (line 13) | function useProfiles() {
FILE: src/composables/useProposalVotes.ts
type QueryParams (line 4) | type QueryParams = {
function useProposalVotes (line 8) | function useProposalVotes(proposal: Proposal, loadBy = 6) {
FILE: src/composables/useProposals.ts
type ProposalsStore (line 4) | interface ProposalsStore {
function useProposals (line 24) | function useProposals() {
FILE: src/composables/useQuorum.ts
type QuorumProps (line 7) | interface QuorumProps {
function useQuorum (line 15) | function useQuorum(props: QuorumProps) {
FILE: src/composables/useReportDownload.ts
function useReportDownload (line 3) | function useReportDownload() {
FILE: src/composables/useResolveName.ts
function useResolveName (line 4) | function useResolveName() {
FILE: src/composables/useSafe.ts
type ExecutionStatus (line 1) | interface ExecutionStatus {
function useSafe (line 14) | function useSafe() {
FILE: src/composables/useSharing.ts
function useSharing (line 4) | function useSharing() {
FILE: src/composables/useShortUrls.ts
function useShortUrls (line 3) | function useShortUrls() {
FILE: src/composables/useSkin.ts
constant DARK (line 4) | const DARK = 'dark';
constant LIGHT (line 5) | const LIGHT = 'light';
function toggleUserTheme (line 11) | function toggleUserTheme() {
function useSkin (line 27) | function useSkin() {
FILE: src/composables/useSkinsFilter.ts
function useSkinsFilter (line 11) | function useSkinsFilter() {
FILE: src/composables/useSnapshot.ts
function useSnapshot (line 7) | function useSnapshot() {
FILE: src/composables/useSpaceController.ts
function useSpaceController (line 21) | function useSpaceController() {
FILE: src/composables/useSpaceSubscription.ts
function useSpaceSubscription (line 8) | function useSpaceSubscription(spaceId: any) {
FILE: src/composables/useSpaces.ts
type Metrics (line 4) | interface Metrics {
function useSpaces (line 9) | function useSpaces() {
FILE: src/composables/useStatement.ts
constant SET_STATEMENT_ACTION (line 5) | const SET_STATEMENT_ACTION = 'set-statement';
function useStatement (line 10) | function useStatement() {
FILE: src/composables/useStrategies.ts
function useStrategies (line 12) | function useStrategies() {
FILE: src/composables/useTerms.ts
function useTerms (line 3) | function useTerms(spaceKey) {
FILE: src/composables/useTreasury.ts
constant TOKEN_LIST_URL (line 5) | const TOKEN_LIST_URL = 'https://ipfs.io/ipns/tokens.uniswap.org';
function useTreasury (line 10) | function useTreasury() {
FILE: src/composables/useTxStatus.ts
constant PENDING_TRANSACTIONS_STORAGE_KEY (line 5) | const PENDING_TRANSACTIONS_STORAGE_KEY = 'snapshot.pendingTransactions';
function useTxStatus (line 11) | function useTxStatus() {
FILE: src/composables/useUnseenProposals.ts
type proposal (line 4) | type proposal = { id: string; created: number; space: { id: string } };
function useUnseenProposals (line 10) | function useUnseenProposals() {
FILE: src/composables/useUsername.ts
function useUsername (line 4) | function useUsername() {
FILE: src/composables/useWeb3.ts
function useWeb3 (line 22) | function useWeb3() {
FILE: src/env.d.ts
type ImportMetaEnv (line 1) | interface ImportMetaEnv {
FILE: src/helpers/alchemy/index.ts
constant NETWORKS (line 11) | const NETWORKS = {
function getApiUrl (line 19) | function getApiUrl(networkId: number) {
function request (line 25) | async function request(
function batchRequest (line 45) | async function batchRequest(
function getBalance (line 72) | async function getBalance(
function getTokenBalances (line 86) | async function getTokenBalances(
function getTokensMetadata (line 99) | async function getTokensMetadata(
function getBalances (line 118) | async function getBalances(
FILE: src/helpers/alchemy/types.ts
type BalanceData (line 1) | type BalanceData = { contractAddress: string; tokenBalance: string };
type Metadata (line 2) | type Metadata = {
type Token (line 7) | type Token = BalanceData &
type GetTokenBalancesResponse (line 14) | type GetTokenBalancesResponse = {
type GetTokensMetadataResponse (line 19) | type GetTokensMetadataResponse = Metadata[];
type GetBalancesResponse (line 21) | type GetBalancesResponse = Token[];
FILE: src/helpers/b64.ts
constant B64U_LOOKUP (line 3) | const B64U_LOOKUP = {
FILE: src/helpers/boost/api.ts
constant GUARD_URL (line 8) | const GUARD_URL = 'https://boost.snapshot.org';
function getRewards (line 10) | async function getRewards(
function getVouchers (line 31) | async function getVouchers(
function getWinners (line 52) | async function getWinners(
FILE: src/helpers/boost/index.ts
constant BOOST_VERSION (line 8) | const BOOST_VERSION = '0.0.1';
constant BOOST_CONTRACTS (line 10) | const BOOST_CONTRACTS = {
constant SUPPORTED_NETWORKS (line 17) | const SUPPORTED_NETWORKS = Object.keys(BOOST_CONTRACTS);
constant SNAPSHOT_GUARD_ADDRESS (line 19) | const SNAPSHOT_GUARD_ADDRESS =
function createBoost (line 22) | async function createBoost(
function claimTokens (line 52) | async function claimTokens(
function claimAllTokens (line 68) | async function claimAllTokens(
function getStrategyURI (line 88) | async function getStrategyURI(strategy: BoostStrategy) {
function withdrawAndBurn (line 93) | async function withdrawAndBurn(
function getFees (line 104) | async function getFees(web3: Web3Provider, networkId: string) {
FILE: src/helpers/boost/subgraph.ts
constant SUBGRAPH_URLS (line 5) | const SUBGRAPH_URLS = {
function getClaims (line 15) | async function getClaims(recipient: string) {
function getBoosts (line 48) | async function getBoosts(proposalIds: string[]) {
FILE: src/helpers/boost/tokens.ts
constant EXCLUDED_TOKENS (line 1) | const EXCLUDED_TOKENS = [
function isExcludedToken (line 103) | function isExcludedToken(chainId: string, contractAddress: string) {
FILE: src/helpers/boost/types.ts
type BoostClaimSubgraph (line 1) | type BoostClaimSubgraph = {
type BoostRewardGuard (line 11) | type BoostRewardGuard = {
type BoostVoucherGuard (line 17) | type BoostVoucherGuard = {
type BoostWinnersGuard (line 24) | type BoostWinnersGuard = {
type BoostStrategy (line 29) | interface BoostStrategy {
type BoostSubgraph (line 51) | type BoostSubgraph = {
FILE: src/helpers/constants.ts
constant ETH_CONTRACT (line 1) | const ETH_CONTRACT = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
constant DEFAULT_ETH_ADDRESS (line 3) | const DEFAULT_ETH_ADDRESS = '0x0000000000000000000000000000000000000000';
constant SNAPSHOT_BREAKPOINTS (line 5) | const SNAPSHOT_BREAKPOINTS = {
constant KNOWN_HOSTS (line 14) | const KNOWN_HOSTS = [
constant KNOWN_DOMAINS (line 29) | const KNOWN_DOMAINS = ['blockscout.com'];
constant SPACE_CATEGORIES (line 31) | const SPACE_CATEGORIES = [
constant ERC20ABI (line 52) | const ERC20ABI = [
constant COINGECKO_ASSET_PLATFORMS (line 69) | const COINGECKO_ASSET_PLATFORMS = {
constant COINGECKO_BASE_ASSETS (line 74) | const COINGECKO_BASE_ASSETS = {
type ChainCurrency (line 79) | type ChainCurrency = {
constant CHAIN_CURRENCIES (line 86) | const CHAIN_CURRENCIES: Record<string, ChainCurrency> = {
constant TWO_WEEKS (line 119) | const TWO_WEEKS = 1209600;
constant ONE_DAY (line 120) | const ONE_DAY = 86400;
constant SNAPSHOT_HELP_LINK (line 122) | const SNAPSHOT_HELP_LINK = 'https://help.snapshot.org/en';
constant BOOST_ENABLED_VOTING_TYPES (line 124) | const BOOST_ENABLED_VOTING_TYPES = [
constant STRATEGIES_LIMITS (line 130) | const STRATEGIES_LIMITS = {
constant PROPOSAL_BODY_LIMITS (line 136) | const PROPOSAL_BODY_LIMITS = {
FILE: src/helpers/covalent.ts
constant API_URL (line 3) | const API_URL = 'https://api.covalenthq.com/v1';
constant API_KEY (line 4) | const API_KEY = 'ckey_2d082caf47f04a46947f4f212a8';
constant ETHER_CONTRACT (line 5) | const ETHER_CONTRACT = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
function getTokenBalances (line 7) | async function getTokenBalances(
function getTokenPrices (line 37) | async function getTokenPrices(
FILE: src/helpers/delegation.ts
function getDelegates (line 8) | async function getDelegates(network: string, address: string) {
function getDelegators (line 24) | async function getDelegators(network: string, address: string) {
FILE: src/helpers/delegationV2/compound/read.ts
type Governance (line 11) | type Governance = {
type Delegate (line 17) | type Delegate = {
function adjustUrl (line 23) | function adjustUrl(apiUrl: string) {
FILE: src/helpers/delegationV2/index.ts
type DelegationTypes (line 9) | enum DelegationTypes {
function setupDelegation (line 14) | function setupDelegation(
FILE: src/helpers/delegationV2/splitDelegation/read.ts
constant SPLIT_DELEGATE_BACKEND_URL (line 8) | const SPLIT_DELEGATE_BACKEND_URL = 'https://delegate-api.gnosisguild.org';
type DelegateFromSD (line 10) | type DelegateFromSD = {
type AddressResponse (line 18) | type AddressResponse = {
FILE: src/helpers/delegationV2/splitDelegation/write.ts
constant DELEGATION_CONTRACT (line 7) | const DELEGATION_CONTRACT = '0xDE1e8A7E184Babd9F0E3af18f40634e9Ed6F0905';
FILE: src/helpers/delegationV2/types.ts
type DelegationReader (line 3) | type DelegationReader = {
type DelegationWriter (line 14) | type DelegationWriter = {
type DelegatingTo (line 23) | type DelegatingTo = {
type DelegateTreeItem (line 28) | type DelegateTreeItem = {
type DelegatorTreeItem (line 35) | type DelegatorTreeItem = {
FILE: src/helpers/i18n.ts
function getBrowserLocale (line 8) | function getBrowserLocale() {
function setI18nLanguage (line 24) | function setI18nLanguage(i18n, locale) {
function loadLocaleMessages (line 33) | async function loadLocaleMessages(i18n, locale) {
FILE: src/helpers/interfaces.ts
type Strategy (line 5) | interface Strategy {
type StrategyExample (line 15) | interface StrategyExample {
type StrategySchema (line 24) | interface StrategySchema {
type StrategyDefinitionProperties (line 32) | interface StrategyDefinitionProperties {
type StrategyDefinition (line 42) | interface StrategyDefinition {
type Profile (line 52) | interface Profile {
type ProfileActivity (line 61) | interface ProfileActivity {
type TreasuryAsset (line 74) | interface TreasuryAsset {
type TreasuryWallet (line 86) | interface TreasuryWallet {
type ExploreSpace (line 92) | interface ExploreSpace {
type Space (line 109) | interface Space {
type RankedSpace (line 122) | interface RankedSpace {
type ExtendedSpace (line 140) | interface ExtendedSpace {
type DelegatesConfig (line 190) | interface DelegatesConfig {
type SpaceValidation (line 196) | interface SpaceValidation {
type SpaceStrategy (line 201) | interface SpaceStrategy {
type ProposalSpace (line 207) | interface ProposalSpace {
type Proposal (line 217) | interface Proposal {
type VoteValidation (line 248) | interface VoteValidation {
type Results (line 253) | interface Results {
type Choice (line 259) | type Choice = number | number[] | Record<string, any>;
type Vote (line 261) | interface Vote {
type VoteFilters (line 273) | interface VoteFilters {
type ABI (line 280) | type ABI = string | Array<Fragment | JsonFragment | string>;
type PendingTransaction (line 282) | interface PendingTransaction {
type SafeTransaction (line 289) | interface SafeTransaction {
type RealityOracleProposal (line 297) | interface RealityOracleProposal {
type UmaOracleProposal (line 314) | interface UmaOracleProposal {
type SafeAsset (line 330) | interface SafeAsset {
type CollectableAsset (line 336) | interface CollectableAsset extends SafeAsset {
type TokenAsset (line 341) | interface TokenAsset extends SafeAsset {
type CollectableAssetTransaction (line 349) | interface CollectableAssetTransaction extends SafeTransaction {
type TokenAssetTransaction (line 355) | interface TokenAssetTransaction extends SafeTransaction {
type CustomContractTransaction (line 362) | interface CustomContractTransaction extends SafeTransaction {
type SafeModuleTransactionBatch (line 367) | interface SafeModuleTransactionBatch {
type SafeExecutionData (line 372) | interface SafeExecutionData {
type Plugin (line 379) | interface Plugin {
type PluginIndex (line 389) | interface PluginIndex extends Plugin {
type FormError (line 393) | interface FormError {
type Delegate (line 398) | interface Delegate {
type DelegateWithPercent (line 404) | interface DelegateWithPercent extends Delegate {
type Statement (line 409) | interface Statement {
type DelegatesVote (line 418) | type DelegatesVote = {
type DelegatesProposal (line 425) | type DelegatesProposal = {
FILE: src/helpers/pin.ts
function pinGraph (line 6) | async function pinGraph(payload: any) {
function pinPineapple (line 15) | async function pinPineapple(payload: any) {
FILE: src/helpers/queries.ts
constant VOTES_QUERY (line 3) | const VOTES_QUERY = gql`
constant PROPOSAL_QUERY (line 40) | const PROPOSAL_QUERY = gql`
constant PROPOSALS_QUERY (line 86) | const PROPOSALS_QUERY = gql`
constant NOTIFICATION_PROPOSALS_QUERY (line 143) | const NOTIFICATION_PROPOSALS_QUERY = gql`
constant FOLLOWS_QUERY (line 168) | const FOLLOWS_QUERY = gql`
constant SUBSCRIPTIONS_QUERY (line 183) | const SUBSCRIPTIONS_QUERY = gql`
constant ALIASES_QUERY (line 195) | const ALIASES_QUERY = gql`
constant ENS_DOMAINS_BY_ACCOUNT_QUERY (line 206) | const ENS_DOMAINS_BY_ACCOUNT_QUERY = gql`
constant ENS_DOMAIN_BY_HASH_QUERY (line 221) | const ENS_DOMAIN_BY_HASH_QUERY = gql`
constant SPACE_SKIN_QUERY (line 232) | const SPACE_SKIN_QUERY = gql`
constant SPACE_DELEGATE_QUERY (line 240) | const SPACE_DELEGATE_QUERY = gql`
constant SKINS_COUNT_QUERY (line 255) | const SKINS_COUNT_QUERY = gql`
constant NETWORKS_COUNT_QUERY (line 264) | const NETWORKS_COUNT_QUERY = gql`
constant PLUGINS_COUNT_QUERY (line 273) | const PLUGINS_COUNT_QUERY = gql`
constant VALIDATIONS_COUNT_QUERY (line 282) | const VALIDATIONS_COUNT_QUERY = gql`
constant STRATEGIES_QUERY (line 291) | const STRATEGIES_QUERY = gql`
constant EXTENDED_STRATEGY_QUERY (line 302) | const EXTENDED_STRATEGY_QUERY = gql`
constant ACTIVITY_VOTES_QUERY (line 316) | const ACTIVITY_VOTES_QUERY = gql`
constant PROFILES_QUERY (line 348) | const PROFILES_QUERY = gql`
constant USER_VOTED_PROPOSAL_IDS_QUERY (line 360) | const USER_VOTED_PROPOSAL_IDS_QUERY = gql`
constant SPACES_RANKING_QUERY (line 370) | const SPACES_RANKING_QUERY = gql`
constant SPACES_QUERY (line 409) | const SPACES_QUERY = gql`
constant STATEMENTS_QUERY (line 426) | const STATEMENTS_QUERY = gql`
constant SPACE_QUERY (line 442) | const SPACE_QUERY = gql`
constant LEADERBOARD_QUERY (line 541) | const LEADERBOARD_QUERY = gql`
FILE: src/helpers/shutter.ts
function encryptChoice (line 8) | async function encryptChoice(
FILE: src/helpers/sign.ts
type DataType (line 10) | type DataType = Record<string, { name: string; type: string }[]>;
type ISubscribe (line 12) | type ISubscribe = {
function sign (line 16) | async function sign(
FILE: src/helpers/snapshot.ts
function getProposalVotes (line 8) | async function getProposalVotes(
function getProposal (line 44) | async function getProposal(id) {
function getPower (line 72) | async function getPower(space, address, proposal) {
function voteValidation (line 88) | async function voteValidation(
function proposalValidation (line 119) | async function proposalValidation(
FILE: src/helpers/transaction.ts
function sendApprovalTransaction (line 4) | function sendApprovalTransaction(
FILE: src/helpers/utils.ts
function shortenAddress (line 9) | function shortenAddress(str = '') {
function shorten (line 13) | function shorten(str: string, key?: any): string {
function getChoiceString (line 25) | function getChoiceString(proposal, selected) {
function jsonParse (line 30) | function jsonParse(input, fallback?) {
function lsSet (line 41) | function lsSet(key: string, value: any) {
function lsGet (line 45) | function lsGet(key: string, fallback?: any) {
function lsRemove (line 50) | function lsRemove(key: string) {
function mapOldPluginNames (line 54) | function mapOldPluginNames(space) {
function formatAmount (line 65) | function formatAmount(amount, maxDecimals) {
function parseAmount (line 76) | function parseAmount(input) {
function parseValueInput (line 80) | function parseValueInput(input) {
function getNumberWithOrdinal (line 88) | function getNumberWithOrdinal(n) {
function explorerUrl (line 94) | function explorerUrl(network, str: string, type = 'address'): string {
function openProfile (line 98) | function openProfile(address: string, domain: string, router: any) {
function calcFromSeconds (line 107) | function calcFromSeconds(value, unit) {
function calcToSeconds (line 113) | function calcToSeconds(value, unit) {
function getIpfsUrl (line 119) | function getIpfsUrl(url: string) {
function clearStampCache (line 125) | async function clearStampCache(id: string, type = 'space') {
function resolveHandle (line 133) | async function resolveHandle(handle: string) {
function lookupAddress (line 150) | async function lookupAddress(
function isSnapshotUrl (line 180) | function isSnapshotUrl(url: string) {
function toChecksumAddress (line 196) | function toChecksumAddress(address: string) {
function addressEqual (line 204) | function addressEqual(address1: string, address2: string) {
FILE: src/helpers/validation.ts
function getErrorMessage (line 5) | function getErrorMessage(errorObject): string {
function validateForm (line 29) | function validateForm(
type ValidationErrorOutput (line 44) | interface ValidationErrorOutput {
function transformAjvErrors (line 47) | function transformAjvErrors(errors): ValidationErrorOutput {
function extractPathFromError (line 77) | function extractPathFromError(error): string[] {
function findOrCreateNestedObject (line 84) | function findOrCreateNestedObject(
FILE: src/main.ts
method setup (line 39) | setup() {
FILE: src/plugins/gnosis/index.ts
constant UNISWAP_V2_SUBGRAPH_URL (line 4) | const UNISWAP_V2_SUBGRAPH_URL = {
constant OMEN_SUBGRAPH_URL (line 10) | const OMEN_SUBGRAPH_URL = {
constant WETH_ADDRESS (line 16) | const WETH_ADDRESS = {
constant OMEN_GQL_QUERY (line 22) | const OMEN_GQL_QUERY = {
constant UNISWAP_V2_GQL_QUERY (line 37) | const UNISWAP_V2_GQL_QUERY = {
class Plugin (line 489) | class Plugin {
method getTokenInfo (line 496) | async getTokenInfo(web3: any, tokenAddress: string) {
method getOmenCondition (line 510) | async getOmenCondition(network: string, conditionId: any) {
method getUniswapPair (line 523) | async getUniswapPair(network: string, token0: any, token1: any) {
FILE: src/plugins/oSnap/components/Input/FileInput/utils.ts
function isFileOfType (line 1) | function isFileOfType(file: File, type: File['type']) {
function getFilesFromEvent (line 5) | function getFilesFromEvent(event: DragEvent | Event) {
FILE: src/plugins/oSnap/components/Input/MethodParameter/utils.ts
function getTypes (line 5) | function getTypes(param: ParamType): MaybeNestedArrays<InputTypes> {
function extractTypes (line 22) | function extractTypes(param: ParamType) {
function reduceInt (line 50) | function reduceInt(value: string) {
function getParamPlaceholder (line 54) | function getParamPlaceholder(param: ParamType) {
function getParamLabel (line 69) | function getParamLabel(param: ParamType) {
function convertToStrings (line 85) | function convertToStrings(value: unknown) {
function parseInputArray (line 93) | function parseInputArray(
function preprocessInputToJson (line 109) | function preprocessInputToJson(input: string): string {
FILE: src/plugins/oSnap/constants.ts
constant EXPLORER_API_URLS (line 1100) | const EXPLORER_API_URLS = {
constant GNOSIS_SAFE_TRANSACTION_API_URLS (line 1115) | const GNOSIS_SAFE_TRANSACTION_API_URLS = {
constant SAFE_APP_URLS (line 1130) | const SAFE_APP_URLS = {
constant OPTIMISTIC_GOVERNOR_ABI (line 1145) | const OPTIMISTIC_GOVERNOR_ABI = [
constant OPTIMISTIC_ORACLE_V3_ABI (line 1203) | const OPTIMISTIC_ORACLE_V3_ABI = [
constant VOTING_ABI (line 1238) | const VOTING_ABI = [
constant UMA_FINDER_ABI (line 1342) | const UMA_FINDER_ABI = [
constant ERC20_ABI (line 1353) | const ERC20_ABI = [
constant ERC721_ABI (line 1365) | const ERC721_ABI = [
type ContractData (line 1370) | type ContractData = {
FILE: src/plugins/oSnap/types.ts
type Networks (line 14) | type Networks = typeof networks;
type Network (line 20) | type Network = keyof Networks;
type SafeNetworkPrefixes (line 26) | type SafeNetworkPrefixes = typeof safePrefixes;
type SafeNetworkPrefix (line 32) | type SafeNetworkPrefix = SafeNetworkPrefixes[keyof SafeNetworkPrefixes];
type TransactionTypes (line 42) | type TransactionTypes = typeof transactionTypes;
type TransactionType (line 52) | type TransactionType = TransactionTypes[number];
type OptimisticGovernorTransaction (line 63) | type OptimisticGovernorTransaction = [
type Transaction (line 77) | type Transaction =
type BaseTransaction (line 91) | type BaseTransaction = {
type SafeImportTransaction (line 108) | type SafeImportTransaction = BaseTransaction & {
type RawTransaction (line 118) | type RawTransaction = BaseTransaction & {
type ContractInteractionTransaction (line 131) | type ContractInteractionTransaction = BaseTransaction & {
type TransferNftTransaction (line 146) | type TransferNftTransaction = BaseTransaction & {
type TransferFundsTransaction (line 161) | type TransferFundsTransaction = BaseTransaction & {
type Asset (line 180) | type Asset = {
type Token (line 196) | type Token = Asset & {
type NFT (line 210) | type NFT = Asset & {
type BalanceResponse (line 222) | type BalanceResponse = {
type GnosisSafe (line 244) | type GnosisSafe = {
type OsnapPluginData (line 261) | type OsnapPluginData = MultiSafe;
type LegacyOsnapPluginData (line 263) | type LegacyOsnapPluginData = SingleSafe;
type MultiSafe (line 265) | type MultiSafe = {
type SingleSafe (line 269) | type SingleSafe = {
type AssertionGql (line 284) | type AssertionGql = {
type OGModuleDetails (line 303) | type OGModuleDetails = {
type CollateralDetails (line 319) | type CollateralDetails = {
type AssertionMadeEvent (line 341) | type AssertionMadeEvent = Event & {
type TransactionsProposedEvent (line 369) | type TransactionsProposedEvent = Event & {
type ProposalExecutedEvent (line 391) | type ProposalExecutedEvent = Event & {
type AssertionTransactionDetails (line 403) | type AssertionTransactionDetails = {
type OGProposalState (line 421) | type OGProposalState =
type ResultUrl (line 437) | interface ResultUrl {
type TenderlySimulationResult (line 442) | interface TenderlySimulationResult {
type ErrorWithMessage (line 449) | type ErrorWithMessage = InstanceType<typeof Error> & {
function isErrorWithMessage (line 454) | function isErrorWithMessage(error: unknown): error is ErrorWithMessage {
type Status (line 466) | type Status = keyof typeof Status;
type SpaceConfigResponse (line 468) | type SpaceConfigResponse =
type ProposedTransaction (line 480) | interface ProposedTransaction {
type ContractInterface (line 496) | interface ContractInterface {
type Batch (line 500) | interface Batch {
type BatchFile (line 506) | interface BatchFile {
type BatchFileMeta (line 514) | interface BatchFileMeta {
type BatchTransaction (line 523) | interface BatchTransaction {
type ContractMethod (line 531) | interface ContractMethod {
type ContractInput (line 537) | interface ContractInput {
type InputTypes (line 545) | type InputTypes =
type Integer (line 553) | type Integer = `int${number}` | `uint${number}`;
function isIntegerType (line 555) | function isIntegerType(type: InputTypes): type is Integer {
function nonNullable (line 559) | function nonNullable<T>(value: T): value is NonNullable<T> {
function isLegacySingleSafe (line 564) | function isLegacySingleSafe(
FILE: src/plugins/oSnap/utils/abi.ts
function isArrayParameter (line 17) | function isArrayParameter(parameter: string): boolean {
function getContractABI (line 43) | async function getContractABI(
function isWriteFunction (line 82) | function isWriteFunction(method: FunctionFragment) {
function getABIWriteFunctions (line 90) | function getABIWriteFunctions(abi: string) {
function extractMethodArgs (line 109) | function extractMethodArgs(values: string[]) {
function encodeMethodAndParams (line 122) | function encodeMethodAndParams(
function transformSafeMethodToFunctionFragment (line 132) | function transformSafeMethodToFunctionFragment(
function initializeSafeImportTransaction (line 143) | function initializeSafeImportTransaction(
function encodeSafeMethodAndParams (line 161) | function encodeSafeMethodAndParams(
function getERC20TokenTransferTransactionData (line 186) | function getERC20TokenTransferTransactionData(
function getERC721TokenTransferTransactionData (line 200) | function getERC721TokenTransferTransactionData(
FILE: src/plugins/oSnap/utils/coins.ts
constant ETHEREUM_COIN (line 3) | const ETHEREUM_COIN = {
constant MATIC_COIN (line 12) | const MATIC_COIN = {
constant EWC_COIN (line 21) | const EWC_COIN = {
constant XDAI_COIN (line 30) | const XDAI_COIN = {
constant BNB_COIN (line 38) | const BNB_COIN = {
constant CORE_COIN (line 46) | const CORE_COIN = {
constant OPTIMISM_COIN (line 55) | const OPTIMISM_COIN = {
function getNativeAsset (line 64) | function getNativeAsset(network: Network) {
function isNativeAsset (line 84) | function isNativeAsset(token: Token | undefined) {
FILE: src/plugins/oSnap/utils/events.ts
type RangeState (line 6) | type RangeState = {
function rangeStart (line 23) | function rangeStart(
function rangeSuccessDescending (line 78) | function rangeSuccessDescending(state: RangeState): RangeState {
function rangeFailureDescending (line 110) | function rangeFailureDescending(state: RangeState): RangeState {
function pageEvents (line 130) | async function pageEvents<E>(
function getPagedEvents (line 153) | async function getPagedEvents<EventType = Event>(params: {
FILE: src/plugins/oSnap/utils/getters.ts
function callGnosisSafeTransactionApi (line 49) | async function callGnosisSafeTransactionApi<TResult = any>(
type Result (line 82) | type Result = {
function getDeployBlock (line 96) | function getDeployBlock(params: { network: Network; name: string }): num...
class ConfigError (line 105) | class ConfigError extends Error {
method constructor (line 106) | constructor(message: string, responsibleVar: string) {
function logIfErrorMessage (line 112) | function logIfErrorMessage(e: unknown, overrideMessage: string) {
function getContractSubgraph (line 119) | function getContractSubgraph(params: { network: Network; name: string }) {
function getOptimisticGovernorSubgraph (line 142) | function getOptimisticGovernorSubgraph(network: Network): string {
function getOracleV3Subgraph (line 149) | function getOracleV3Subgraph(network: Network): string {
type Result (line 208) | type Result = {
type Result (line 242) | type Result = {
function makeConfigureOsnapUrl (line 279) | function makeConfigureOsnapUrl(params: {
function getAssertionGql (line 314) | async function getAssertionGql(params: {
function getOgProposalGql (line 348) | async function getOgProposalGql(params: {
function getCollateralDetailsForProposal (line 388) | async function getCollateralDetailsForProposal(
function getUserCollateralAllowance (line 414) | async function getUserCollateralAllowance(
function getUserCollateralBalance (line 425) | async function getUserCollateralBalance(
function getOGModuleDetails (line 437) | async function getOGModuleDetails(params: {
function getOgProposalStateFromChain (line 479) | async function getOgProposalStateFromChain(params: {
function getOGProposalStateGql (line 634) | async function getOGProposalStateGql(params: {
function assertionIdIsNotZero (line 719) | function assertionIdIsNotZero(assertionId: string) {
function getOGProposalState (line 728) | async function getOGProposalState(params: {
function getProposalHashFromTransactions (line 761) | function getProposalHashFromTransactions(
function getSafeNetworkPrefix (line 777) | function getSafeNetworkPrefix(network: Network): SafeNetworkPrefix {
function getSafeAppLink (line 784) | function getSafeAppLink(
function getOracleUiLink (line 799) | function getOracleUiLink(
function fetchImplementationAddress (line 810) | async function fetchImplementationAddress(
function isConfigCompliant (line 826) | async function isConfigCompliant(safeAddress: string, chainId: string) {
function fetchBalances (line 837) | async function fetchBalances(network: Network, safeAddress: string) {
function fetchTokens (line 863) | async function fetchTokens(url: string): Promise<Token[]> {
function enhanceTokensWithBalances (line 873) | function enhanceTokensWithBalances(
function enhanceTokenWithBalance (line 896) | function enhanceTokenWithBalance(
function getVerifiedToken (line 913) | function getVerifiedToken(tokenAddress: string, tokens: Token[]) {
function fetchCollectibles (line 919) | async function fetchCollectibles(
FILE: src/plugins/oSnap/utils/safeImport.ts
function parseGnosisSafeFile (line 5) | async function parseGnosisSafeFile(
function isCreatedFromSafe (line 36) | function isCreatedFromSafe(
FILE: src/plugins/oSnap/utils/tenderly.ts
constant SIMULATION_ENDPOINT (line 13) | const SIMULATION_ENDPOINT =
constant OSNAP_GAS_SUBSIDY (line 16) | const OSNAP_GAS_SUBSIDY = 500_000;
function validatePayload (line 18) | function validatePayload(data: OsnapPluginData): void | never {
function prepareTenderlySimulationPayload (line 37) | function prepareTenderlySimulationPayload(props: {
function exceedsOsnapGasSubsidy (line 60) | function exceedsOsnapGasSubsidy(res: TenderlySimulationResult): boolean {
FILE: src/plugins/oSnap/utils/transactions.ts
function createFormattedOptimisticGovernorTransaction (line 24) | function createFormattedOptimisticGovernorTransaction({
function createRawTransaction (line 41) | function createRawTransaction(params: {
function createTransferNftTransaction (line 61) | function createTransferNftTransaction(params: {
function createTransferFundsTransaction (line 92) | function createTransferFundsTransaction(params: {
function createContractInteractionTransaction (line 131) | function createContractInteractionTransaction(params: {
function parseAmount (line 162) | function parseAmount(input: string) {
function parseValueInput (line 166) | function parseValueInput(input: string) {
function createSafeImportTransaction (line 173) | function createSafeImportTransaction(
FILE: src/plugins/oSnap/utils/validators.ts
function validateTransaction (line 55) | function validateTransaction(transaction: BaseTransaction) {
function validateModuleAddress (line 69) | async function validateModuleAddress(
function isTransferFundsValid (line 87) | function isTransferFundsValid(params: {
function isTransferNftValid (line 105) | function isTransferNftValid(params: {
function amountPositive (line 124) | function amountPositive(amount: string, decimals = 18) {
function isBytesLikeSafe (line 134) | function isBytesLikeSafe(value: string): boolean {
function allTransactionsValid (line 142) | function allTransactionsValid(transactions: Transaction[]): boolean {
function isContractAddress (line 146) | async function isContractAddress(
function isBool (line 160) | function isBool(value: string): boolean {
function validateInput (line 167) | function validateInput(inputValue: string, type: InputTypes): boolean {
type Integer (line 186) | type Integer = `int${number}` | `uint${number}`;
function isValidInt (line 188) | function isValidInt(value: string, type: Integer) {
type MaybeNestedArrays (line 224) | type MaybeNestedArrays<T> = T | T[] | MaybeNestedArrays<T>[];
function validateMaybeArray (line 226) | function validateMaybeArray(
function validateSingleOrArray (line 259) | function validateSingleOrArray(
FILE: src/plugins/poap/index.ts
constant API_BASE_URL (line 4) | const API_BASE_URL = 'https://api.poap.tech';
constant APP_BASE_URL (line 5) | const APP_BASE_URL = 'https://app.poap.xyz';
class Plugin (line 7) | class Plugin {
method openScanPage (line 15) | openScanPage(address) {
method getCurrentState (line 18) | async getCurrentState(snapshot, address) {
method claim (line 71) | async claim(snapshot, address) {
FILE: src/plugins/progress/index.ts
class Plugin (line 1) | class Plugin {
FILE: src/plugins/projectGalaxy/index.ts
class Plugin (line 1) | class Plugin {
method constructor (line 4) | constructor(apiBaseUrl) {
method fetchGQL (line 10) | async fetchGQL({ query, variables }) {
method getOATImage (line 23) | async getOATImage(campaignId) {
method claim (line 42) | async claim(address, campaignID) {
method getCurrentState (line 67) | async getCurrentState(snapshot, address, campaign) {
FILE: src/plugins/safeSnap/constants.ts
constant EIP712_TYPES (line 3) | const EIP712_TYPES = {
constant EIP3770_PREFIXES (line 13) | const EIP3770_PREFIXES = {
constant EXPLORER_API_URLS (line 26) | const EXPLORER_API_URLS = {
constant GNOSIS_SAFE_TRANSACTION_API_URLS (line 39) | const GNOSIS_SAFE_TRANSACTION_API_URLS = {
constant REALITY_MODULE_ABI (line 54) | const REALITY_MODULE_ABI = [
constant ORACLE_ABI (line 74) | const ORACLE_ABI = [
constant UMA_MODULE_ABI (line 110) | const UMA_MODULE_ABI = [
constant UMA_ORACLE_ABI (line 168) | const UMA_ORACLE_ABI = [
constant UMA_VOTING_ABI (line 203) | const UMA_VOTING_ABI = [
constant UMA_FINDER_ABI (line 307) | const UMA_FINDER_ABI = [
constant ERC20_ABI (line 318) | const ERC20_ABI = [
constant ERC721_ABI (line 330) | const ERC721_ABI = [
constant MULTI_SEND_ABI (line 334) | const MULTI_SEND_ABI = [
constant MULTI_SEND_V1_3_0 (line 340) | const MULTI_SEND_V1_3_0 = {
constant MULTI_SEND_V1_2_0 (line 372) | const MULTI_SEND_V1_2_0 = {
constant MULTI_SEND_V1_1_1 (line 381) | const MULTI_SEND_V1_1_1 = {
constant MULTI_SEND_VERSIONS (line 390) | const MULTI_SEND_VERSIONS: Record<
type ContractData (line 400) | type ContractData = {
FILE: src/plugins/safeSnap/index.ts
class Plugin (line 50) | class Plugin {
method validateTransaction (line 51) | validateTransaction(transaction: SafeTransaction) {
method calcTransactionHash (line 63) | calcTransactionHash(
method calcTransactionHashes (line 76) | calcTransactionHashes(
method getExecutionDetailsWithHashes (line 94) | async getExecutionDetailsWithHashes(
method getModuleDetailsReality (line 140) | async getModuleDetailsReality(network: string, moduleAddress: string) {
method validateUmaModule (line 147) | async validateUmaModule(network: string, umaAddress: string) {
method getExecutionDetailsUma (line 161) | async getExecutionDetailsUma(
method approveBondUma (line 182) | async *approveBondUma(
method getModuleDetailsUma (line 209) | async getModuleDetailsUma(
method submitProposalWithHashes (line 240) | async *submitProposalWithHashes(
method submitProposalUma (line 258) | async *submitProposalUma(
method loadClaimBondData (line 278) | async loadClaimBondData(
method claimBond (line 386) | async *claimBond(
method executeProposalWithHashes (line 427) | async *executeProposalWithHashes(
method executeProposalUma (line 455) | async *executeProposalUma(
method voteForQuestion (line 472) | async *voteForQuestion(
FILE: src/plugins/safeSnap/types/index.ts
type Networks (line 3) | type Networks = typeof networks;
type Network (line 5) | type Network = keyof Networks;
FILE: src/plugins/safeSnap/utils/abi.ts
function isArrayParameter (line 14) | function isArrayParameter(parameter: string): boolean {
function parseMethodToABI (line 37) | function parseMethodToABI(method: FunctionFragment) {
function getContractABI (line 41) | async function getContractABI(
function isWriteFunction (line 75) | function isWriteFunction(method: FunctionFragment) {
function getABIWriteFunctions (line 80) | function getABIWriteFunctions(abi: Fragment[]) {
function extractMethodArgs (line 94) | function extractMethodArgs(values: string[]) {
function getContractTransactionData (line 104) | function getContractTransactionData(
function getAbiFirstFunctionName (line 114) | function getAbiFirstFunctionName(abi: ABI): string {
function getERC20TokenTransferTransactionData (line 119) | function getERC20TokenTransferTransactionData(
function getERC721TokenTransferTransactionData (line 130) | function getERC721TokenTransferTransactionData(
FILE: src/plugins/safeSnap/utils/coins.ts
constant ETHEREUM_COIN (line 4) | const ETHEREUM_COIN: TokenAsset = {
constant MATIC_COIN (line 12) | const MATIC_COIN: TokenAsset = {
constant EWC_COIN (line 20) | const EWC_COIN: TokenAsset = {
constant XDAI_COIN (line 28) | const XDAI_COIN: TokenAsset = {
constant BNB_COIN (line 36) | const BNB_COIN: TokenAsset = {
constant CORE_COIN (line 44) | const CORE_COIN: TokenAsset = {
function getNativeAsset (line 53) | function getNativeAsset(network: Network) {
FILE: src/plugins/safeSnap/utils/decoder.ts
class InterfaceDecoder (line 9) | class InterfaceDecoder extends Interface {
method decodeFunction (line 10) | public decodeFunction(
method getMethodFragment (line 34) | public getMethodFragment(
method formatParameter (line 47) | private formatParameter(parameter, value, deep = 0): string {
method formatArrayValue (line 54) | private formatArrayValue(paramType, value, deep = 0) {
FILE: src/plugins/safeSnap/utils/events.ts
type RangeState (line 4) | type RangeState = {
function rangeStart (line 21) | function rangeStart(
function rangeSuccessDescending (line 76) | function rangeSuccessDescending(state: RangeState): RangeState {
function rangeFailureDescending (line 108) | function rangeFailureDescending(state: RangeState): RangeState {
function pageEvents (line 128) | async function pageEvents<E>(
FILE: src/plugins/safeSnap/utils/index.ts
function formatBatchTransaction (line 30) | function formatBatchTransaction(
function createBatch (line 42) | function createBatch(
function getBatchHash (line 61) | function getBatchHash(
function getSafeHash (line 78) | function getSafeHash(safe: SafeExecutionData) {
function validateSafeData (line 85) | function validateSafeData(safe: SafeExecutionData) {
function isValidInput (line 95) | function isValidInput<Input extends { safes: SafeExecutionData[] }>(
function coerceConfig (line 101) | function coerceConfig(config, network) {
function fetchTextSignatures (line 164) | async function fetchTextSignatures(
FILE: src/plugins/safeSnap/utils/multiSend.ts
type MULTI_SEND_VERSION (line 8) | enum MULTI_SEND_VERSION {
function getMultiSend (line 14) | function getMultiSend(
function encodeTransactions (line 21) | function encodeTransactions(transactions: SafeTransaction[]) {
function createMultiSendTx (line 41) | function createMultiSendTx(
FILE: src/plugins/safeSnap/utils/safe.ts
function callGnosisSafeTransactionApi (line 5) | async function callGnosisSafeTransactionApi(
FILE: src/plugins/safeSnap/utils/transactions.ts
function rawToModuleTransaction (line 20) | function rawToModuleTransaction({
function sendAssetToModuleTransaction (line 40) | function sendAssetToModuleTransaction({
function transferFundsToModuleTransaction (line 63) | function transferFundsToModuleTransaction({
function contractInteractionToModuleTransaction (line 102) | function contractInteractionToModuleTransaction(
function decodeContractTransaction (line 130) | async function decodeContractTransaction(
function getMethodSignature (line 169) | function getMethodSignature(data: string) {
function isERC20TransferTransaction (line 177) | function isERC20TransferTransaction(transaction: SafeTransaction) {
function decodeERC721TransferTransaction (line 182) | function decodeERC721TransferTransaction(transaction: SafeTransaction) {
function decodeTransactionData (line 191) | async function decodeTransactionData(
function parseAmount (line 241) | function parseAmount(input: string) {
function parseValueInput (line 245) | function parseValueInput(input: string) {
FILE: src/plugins/safeSnap/utils/umaModule.ts
function getDeployBlock (line 19) | function getDeployBlock(network: string, name: string): number {
function getContractSubgraph (line 24) | function getContractSubgraph(search: {
function getOptimisticGovernorSubgraph (line 43) | function getOptimisticGovernorSubgraph(network: string): string {
function getOracleV3Subgraph (line 46) | function getOracleV3Subgraph(network: string): string {
function updateCurrentUserBondInfo (line 141) | async function updateCurrentUserBondInfo() {
FILE: src/router/index.ts
method beforeRouteEnter (line 156) | beforeRouteEnter() {
method scrollBehavior (line 179) | scrollBehavior(to, _from, savedPosition) {
Condensed preview — 634 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,265K chars).
[
{
"path": ".browserslistrc",
"chars": 30,
"preview": "> 1%\nlast 2 versions\nnot dead\n"
},
{
"path": ".dockerignore",
"chars": 13,
"preview": "node_modules\n"
},
{
"path": ".eslintignore",
"chars": 13,
"preview": "src/plugins/*"
},
{
"path": ".eslintrc-auto-import.json",
"chars": 3182,
"preview": "{\n \"globals\": {\n \"Component\": true,\n \"ComponentPublicInstance\": true,\n \"ComputedRef\": true,\n \"DARK\": true,\n"
},
{
"path": ".github/CODE_OF_CONDUCT.md",
"chars": 3217,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 2518,
"preview": "### Opening an issue\n\nYou should usually open an issue in the following situations:\n\n* Report an error you can’t solve y"
},
{
"path": ".github/ISSUE_TEMPLATE/BUG_REPORT.yml",
"chars": 1951,
"preview": "name: Bug report\ndescription: File a bug report\ntitle: \"[BUG] - \"\nlabels: [\"bug-report\"]\nbody:\n - type: markdown\n at"
},
{
"path": ".github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml",
"chars": 1673,
"preview": "name: Feature request\ndescription: Suggest an idea for this project\ntitle: \"[NEW FEATURE] - \"\nlabels: [\"feature-request\""
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 369,
"preview": "### Summary\n\n<!-- Related issues, a description or list of the changes and the motivation behind them -->\n\nCloses: #\n\n##"
},
{
"path": ".github/dependabot.yml",
"chars": 315,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"npm\"\n directory: \"/\"\n schedule:\n interval: \"weekly\"\n ignore:\n "
},
{
"path": ".github/stale.yml",
"chars": 320,
"preview": "daysUntilStale: 120\ndaysUntilClose: 14\nexemptLabels:\n - pinned\n - enhancement\n - bug\nstaleLabel: stale\nmarkComment: >"
},
{
"path": ".github/workflows/codeql.yml",
"chars": 2637,
"preview": "name: \"CodeQL\"\n\non:\n push:\n branches: [ 'master' ]\n pull_request:\n # The branches below must be a subset of the "
},
{
"path": ".github/workflows/deploy.yml",
"chars": 502,
"preview": "name: Deploy\n\non:\n workflow_dispatch:\n inputs:\n target:\n type: choice\n description: Target\n "
},
{
"path": ".github/workflows/nodejs.yml",
"chars": 513,
"preview": "name: Node CI\n\non:\n push:\n\njobs:\n build:\n runs-on: ubuntu-latest\n\n strategy:\n matrix:\n node-version:"
},
{
"path": ".github/workflows/test.yaml",
"chars": 415,
"preview": "name: Tests\non: [push]\njobs:\n test:\n runs-on: ubuntu-20.04\n steps:\n - uses: actions/checkout@v3\n - uses"
},
{
"path": ".github/workflows/update-snapshot-packages.yml",
"chars": 798,
"preview": "name: Update Snapshot packages\n\non: workflow_dispatch\n\njobs:\n update-dep:\n runs-on: ubuntu-latest\n steps:\n -"
},
{
"path": ".github/workflows/update-snapshot-submodules.yml",
"chars": 665,
"preview": "name: Update Snapshot submodules\n\non: workflow_dispatch\n\njobs:\n update-dep:\n runs-on: ubuntu-latest\n steps:\n "
},
{
"path": ".gitignore",
"chars": 335,
"preview": ".DS_Store\nnode_modules\n/dist\n.yalc\ncomponents.d.ts\nauto-imports.d.ts\n\n# local env file\n.env.local\n.env.*.local\n\n# Log fi"
},
{
"path": ".gitmodules",
"chars": 110,
"preview": "[submodule \"snapshot-spaces\"]\n\tpath = snapshot-spaces\n\turl = https://github.com/snapshot-labs/snapshot-spaces\n"
},
{
"path": ".gitpod.yml",
"chars": 176,
"preview": "tasks:\n - name: Setup\n init: |\n yarn install --frozen-lockfile --silent --network-timeout 100000\n command: y"
},
{
"path": ".husky/post-checkout",
"chars": 67,
"preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nyarn run init-submodules\n"
},
{
"path": ".husky/post-merge",
"chars": 67,
"preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nyarn run init-submodules\n"
},
{
"path": ".husky/pre-commit",
"chars": 52,
"preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n"
},
{
"path": ".husky/pre-push",
"chars": 52,
"preview": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n"
},
{
"path": "Dockerfile",
"chars": 278,
"preview": "FROM node:18-alpine\n\nRUN apk update && apk upgrade && \\\n apk add --no-cache git py3-pip python3 gcc g++ make\n\nWORKDIR"
},
{
"path": "FUNDING.json",
"chars": 213,
"preview": "{\n \"drips\": {\n \"ethereum\": {\n \"ownedBy\": \"0x8C28Cf33d9Fd3D0293f963b1cd27e3FF422B425c\"\n }\n },\n \"opRetro\": {"
},
{
"path": "LICENSE",
"chars": 1075,
"preview": "The MIT License (MIT)\n\nCopyright (c) Snapshot Labs\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "README.md",
"chars": 2391,
"preview": "<div align=\"center\">\n <img src=\"public/icon.svg\" height=\"70\" alt=\"Snapshot Logo\">\n <h1>Snapshot</h1>\n <strong>S"
},
{
"path": "babel.config.js",
"chars": 66,
"preview": "module.exports = {\n presets: ['@vue/cli-plugin-babel/preset']\n};\n"
},
{
"path": "crowdin.yml",
"chars": 89,
"preview": "files:\n - source: /src/locales/default.json\n translation: /src/locales/%locale%.json\n"
},
{
"path": "index.html",
"chars": 479,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <link rel=\"icon\" href=\"/favicon.png\" />\n <"
},
{
"path": "package.json",
"chars": 3506,
"preview": "{\n \"name\": \"snapshot\",\n \"version\": \"0.1.4\",\n \"license\": \"MIT\",\n \"repository\": \"snapshot-labs/snapshot\",\n \"scripts\":"
},
{
"path": "postcss.config.js",
"chars": 81,
"preview": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {}\n }\n};\n"
},
{
"path": "public/.well-known/assetlinks.json",
"chars": 320,
"preview": "[\n {\n \"relation\": [\"delegate_permission/common.handle_all_urls\"],\n \"target\": {\n \"namespace\": \"android_app\",\n"
},
{
"path": "public/.well-known/did.json",
"chars": 912,
"preview": "{\n \"@context\": [\n \"https://www.w3.org/ns/did/v1\",\n \"https://w3id.org/security/suites/jws-2020/v1\"\n ],\n \"id\": \"d"
},
{
"path": "public/manifest.json",
"chars": 545,
"preview": "{\n \"short_name\": \"Snapshot\",\n \"name\": \"Snapshot\",\n \"description\": \"Where decisions get made\",\n \"iconPath\": \"icon.svg"
},
{
"path": "public/service-worker.js",
"chars": 67,
"preview": "// importScripts('https://js.pusher.com/beams/service-worker.js');\n"
},
{
"path": "src/App.vue",
"chars": 1676,
"preview": "<script setup lang=\"ts\">\nconst { domain, init, isReady, showSidebar } = useApp();\nconst route = useRoute();\nconst { rest"
},
{
"path": "src/assets/css/main.scss",
"chars": 5162,
"preview": "@import '../fonts/iconfont.css';\n@import 'viewerjs/dist/viewer.css';\n@import './tippy.scss';\n@import './tune.scss';\n@imp"
},
{
"path": "src/assets/css/tippy.scss",
"chars": 2042,
"preview": "// V-Tippy tooltip package styling - could be moved somewhere else?\n.tippy-box[data-animation='fade'][data-state='hidden"
},
{
"path": "src/assets/css/tune.scss",
"chars": 3961,
"preview": ".tune-input {\n @apply form-input rounded-full border-skin-border bg-transparent text-base text-skin-link focus:border-s"
},
{
"path": "src/assets/fonts/iconfont.css",
"chars": 3967,
"preview": "@font-face {\n font-family: 'iconfont'; /* Project id 1946815 */\n src: url('iconfont.woff2?t=1648383247623') format('wo"
},
{
"path": "src/assets/fonts/iconfont.json",
"chars": 12119,
"preview": "{\n \"id\": \"1946815\",\n \"name\": \"snapshot\",\n \"font_family\": \"iconfont\",\n \"css_prefix_text\": \"icon\",\n \"description\": \"\""
},
{
"path": "src/components/AboutMembersListItem.vue",
"chars": 153,
"preview": "<script setup lang=\"ts\"></script>\n\n<template>\n <div class=\"flex justify-between border-t px-4 py-3 first:border-t-0\">\n "
},
{
"path": "src/components/AvatarOverlayEdit.vue",
"chars": 801,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n loading: boolean;\n avatar?: string;\n isViewOnly?: boolean;\n}>();\n</script>\n\n<"
},
{
"path": "src/components/AvatarSpace.vue",
"chars": 677,
"preview": "<script setup lang=\"ts\">\nimport { sha256 } from 'js-sha256';\n\nconst props = withDefaults(\n defineProps<{\n space: { i"
},
{
"path": "src/components/AvatarToken.vue",
"chars": 563,
"preview": "<script setup lang=\"ts\">\nwithDefaults(\n defineProps<{\n address: string;\n size?: string;\n }>(),\n {\n size: '22"
},
{
"path": "src/components/AvatarUser.vue",
"chars": 815,
"preview": "<script setup lang=\"ts\">\nimport { getAddress } from '@ethersproject/address';\n\nconst props = withDefaults(\n defineProps"
},
{
"path": "src/components/Banner.vue",
"chars": 1732,
"preview": "<script setup lang=\"ts\">\nconst route = useRoute();\nconst { env, domain } = useApp();\n\nconst link = computed(() => {\n co"
},
{
"path": "src/components/BaseAvatar.vue",
"chars": 1714,
"preview": "<script setup lang=\"ts\">\ninterface Props {\n src: string;\n size?: string;\n previewFile?: File | undefined;\n}\n\nconst pr"
},
{
"path": "src/components/BaseBadge.vue",
"chars": 474,
"preview": "<script setup lang=\"ts\">\nconst props = defineProps<{ address: string; members?: string[] }>();\n\nconst isCore = computed("
},
{
"path": "src/components/BaseBlock.vue",
"chars": 2425,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n title?: string;\n counter?: number;\n slim?: boolean;\n loading?: boolean;\n hi"
},
{
"path": "src/components/BaseBreadcrumbs.vue",
"chars": 1016,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n pages: { id?: string; name: string; to: string; current: boolean }[];\n}>();\n</s"
},
{
"path": "src/components/BaseButtonIcon.vue",
"chars": 430,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n loading?: boolean;\n isDisabled?: boolean;\n}>();\n</script>\n\n<template>\n <butto"
},
{
"path": "src/components/BaseButtonRound.vue",
"chars": 472,
"preview": "<script setup lang=\"ts\">\nwithDefaults(\n defineProps<{\n isDisabled?: boolean;\n size?: string;\n }>(),\n {\n isDi"
},
{
"path": "src/components/BaseCalendar.vue",
"chars": 3809,
"preview": "<script setup lang=\"ts\">\nconst props = defineProps<{\n modelValue: string;\n}>();\n\nconst emit = defineEmits(['update:mode"
},
{
"path": "src/components/BaseCombobox.vue",
"chars": 3153,
"preview": "<script setup lang=\"ts\">\nimport {\n Combobox,\n ComboboxInput,\n ComboboxOptions,\n ComboboxOption,\n ComboboxLabel,\n C"
},
{
"path": "src/components/BaseContainer.vue",
"chars": 198,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n slim?: boolean;\n}>();\n</script>\n\n<template>\n <div :class=\"slim ? 'px-0 md:px-4"
},
{
"path": "src/components/BaseCounter.vue",
"chars": 379,
"preview": "<script setup lang=\"ts\">\nconst { formatNumber } = useIntl();\n\ndefineProps<{\n counter?: number | string;\n}>();\n</script>"
},
{
"path": "src/components/BaseIcon.vue",
"chars": 305,
"preview": "<script setup lang=\"ts\">\nwithDefaults(\n defineProps<{\n name: string;\n size?: string;\n }>(),\n {\n name: '',\n "
},
{
"path": "src/components/BaseIndicator.vue",
"chars": 102,
"preview": "<template>\n <span class=\"inline-block h-[12px] w-[12px] rounded-full bg-skin-primary\" />\n</template>\n"
},
{
"path": "src/components/BaseInput.vue",
"chars": 3326,
"preview": "<script lang=\"ts\">\nexport default {\n inheritAttrs: false\n};\n</script>\n\n<script setup lang=\"ts\">\nimport { FormError } fr"
},
{
"path": "src/components/BaseInterpunct.vue",
"chars": 91,
"preview": "<template>\n <div class=\"mx-2 bg-skin-text w-1 h-1 opacity-60 rounded-full\" />\n</template>\n"
},
{
"path": "src/components/BaseLink.vue",
"chars": 719,
"preview": "<script setup lang=\"ts\">\nimport { sanitizeUrl } from '@braintree/sanitize-url';\n\ntype Link = Record<string, any> | strin"
},
{
"path": "src/components/BaseListbox.vue",
"chars": 3622,
"preview": "<script setup lang=\"ts\">\nimport {\n Listbox,\n ListboxButton,\n ListboxOptions,\n ListboxOption,\n ListboxLabel\n} from '"
},
{
"path": "src/components/BaseLoading.vue",
"chars": 192,
"preview": "<script setup lang=\"ts\">\ndefineProps<{ block?: boolean }>();\n</script>\n\n<template>\n <BaseBlock v-if=\"block\" slim>\n <"
},
{
"path": "src/components/BaseMarkdown.vue",
"chars": 10849,
"preview": "<script setup lang=\"ts\">\nimport { Remarkable } from 'remarkable';\nimport { linkify } from 'remarkable/linkify';\n// impor"
},
{
"path": "src/components/BaseMenu.vue",
"chars": 2096,
"preview": "<script setup lang=\"ts\">\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';\nimport { Float } from "
},
{
"path": "src/components/BaseMessage.vue",
"chars": 490,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n level: 'info' | 'warning' | 'warning-red';\n}>();\n</script>\n\n<template>\n <div>\n"
},
{
"path": "src/components/BaseMessageBlock.vue",
"chars": 494,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n level: 'info' | 'warning' | 'warning-red';\n isResponsive?: boolean;\n}>();\n</sc"
},
{
"path": "src/components/BaseModal.vue",
"chars": 2784,
"preview": "<script setup lang=\"ts\">\nimport { useWindowSize } from '@vueuse/core';\n\nconst props = withDefaults(\n defineProps<{\n "
},
{
"path": "src/components/BaseModalSelectItem.vue",
"chars": 1009,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n selected?: boolean;\n title: string;\n tag?: string;\n description?: string;\n "
},
{
"path": "src/components/BaseNetworkItem.vue",
"chars": 842,
"preview": "<script setup lang=\"ts\">\nimport { getIpfsUrl } from '@/helpers/utils';\n\nconst { formatCompactNumber } = useIntl();\n\ncons"
},
{
"path": "src/components/BaseNoResults.vue",
"chars": 209,
"preview": "<template>\n <div class=\"py-[20px] text-center text-skin-link md:py-5\">\n <i-ho-emoji-sad class=\"mx-auto\" />\n <div "
},
{
"path": "src/components/BasePill.vue",
"chars": 177,
"preview": "<script setup lang=\"ts\"></script>\n\n<template>\n <span\n class=\"rounded-full bg-skin-text px-2 text-center text-xs lead"
},
{
"path": "src/components/BasePluginItem.vue",
"chars": 1190,
"preview": "<script setup lang=\"ts\">\nimport { getIpfsUrl } from '@/helpers/utils';\nimport { PluginIndex } from '@/helpers/interfaces"
},
{
"path": "src/components/BasePopover.vue",
"chars": 1854,
"preview": "<script setup lang=\"ts\">\nimport {\n Popover,\n PopoverButton,\n PopoverPanel,\n FocusTrap\n} from '@headlessui/vue';\nimpo"
},
{
"path": "src/components/BasePopoverHover.vue",
"chars": 1966,
"preview": "<script setup lang=\"ts\">\nimport { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue';\nimport { Float } from '"
},
{
"path": "src/components/BaseProgressBar.vue",
"chars": 363,
"preview": "<script setup lang=\"ts\">\ndefineProps<{ value: number }>();\n</script>\n\n<template>\n <div class=\"relative mt-1 flex h-2 ov"
},
{
"path": "src/components/BaseProgressRadial.vue",
"chars": 363,
"preview": "<script setup lang=\"ts\">\ndefineProps<{ value: number }>();\n</script>\n\n<template>\n <div\n :style=\"{\n backgroundIm"
},
{
"path": "src/components/BaseSearch.vue",
"chars": 1438,
"preview": "<script setup lang=\"ts\">\nconst props = defineProps<{\n modelValue: string;\n placeholder?: string;\n modal?: boolean;\n "
},
{
"path": "src/components/BaseSidebarNavigationItem.vue",
"chars": 1184,
"preview": "<script setup lang=\"ts\">\nexport interface Props {\n isActive?: boolean;\n}\n\nwithDefaults(defineProps<Props>(), {\n isActi"
},
{
"path": "src/components/BaseSkinItem.vue",
"chars": 483,
"preview": "<script setup lang=\"ts\">\nconst { skinsSpacesCount } = useSkinsFilter();\n\nconst { formatCompactNumber } = useIntl();\n\ndef"
},
{
"path": "src/components/BaseStrategyItem.vue",
"chars": 691,
"preview": "<script setup lang=\"ts\">\nimport { Strategy } from '@/helpers/interfaces';\n\nconst { formatCompactNumber } = useIntl();\n\nd"
},
{
"path": "src/components/BaseUser.vue",
"chars": 1564,
"preview": "<script setup lang=\"ts\">\nimport { Profile, ExtendedSpace, Proposal } from '@/helpers/interfaces';\n\nconst { domain } = us"
},
{
"path": "src/components/BlockLink.vue",
"chars": 3102,
"preview": "<script setup lang=\"ts\">\nimport { debouncedWatch } from '@vueuse/core';\n\nconst props = withDefaults(\n defineProps<{\n "
},
{
"path": "src/components/BlockSpacesList.vue",
"chars": 1810,
"preview": "<script setup lang=\"ts\">\nimport { useMediaQuery } from '@vueuse/core';\nimport { Space } from '@/helpers/interfaces';\n\nde"
},
{
"path": "src/components/BlockSpacesListButtonMore.vue",
"chars": 409,
"preview": "<template>\n <button class=\"ml-4 h-full cursor-pointer bg-skin-bg text-skin-link\">\n <div class=\"flex justify-center\">"
},
{
"path": "src/components/BlockSpacesListItem.vue",
"chars": 785,
"preview": "<script setup lang=\"ts\">\nimport { Space } from '@/helpers/interfaces';\n\ndefineProps<{\n space: Space;\n}>();\n</script>\n\n<"
},
{
"path": "src/components/BlockSpacesListSkeleton.vue",
"chars": 502,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n numberOfSpaces: number;\n}>();\n</script>\n\n<template>\n <div class=\"flex justify-"
},
{
"path": "src/components/ButtonBack.vue",
"chars": 306,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n name?: string;\n}>();\n</script>\n\n<template>\n <button type=\"button\">\n <div\n "
},
{
"path": "src/components/ButtonCard.vue",
"chars": 409,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n title?: string;\n}>();\n</script>\n\n<template>\n <button\n class=\"relative w-ful"
},
{
"path": "src/components/ButtonFollow.vue",
"chars": 2051,
"preview": "<script setup lang=\"ts\">\nimport { Space, RankedSpace, ExtendedSpace } from '@/helpers/interfaces';\n\nconst props = define"
},
{
"path": "src/components/ButtonPlayground.vue",
"chars": 1323,
"preview": "<script setup lang=\"ts\">\nimport { encodeJson } from '@/helpers/b64';\n\nconst props = withDefaults(\n defineProps<{\n na"
},
{
"path": "src/components/ButtonShare.vue",
"chars": 241,
"preview": "<template>\n <button\n class=\"flex cursor-pointer h-full select-none items-center pr-1 hover:text-skin-link\"\n >\n <"
},
{
"path": "src/components/ButtonSwitch.vue",
"chars": 948,
"preview": "<script setup lang=\"ts\">\ninterface State {\n value: string | number | boolean;\n name: string;\n}\n\ndefineProps<{\n modelV"
},
{
"path": "src/components/ButtonTheme.vue",
"chars": 368,
"preview": "<script setup lang=\"ts\">\nconst { toggleUserTheme, getThemeIcon } = useSkin();\n</script>\n\n<template>\n <BaseButtonRound\n "
},
{
"path": "src/components/ComboboxNetwork.vue",
"chars": 1085,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n network: string;\n hint?: string;\n disabled?: boolean;\n error?: string;\n sho"
},
{
"path": "src/components/ContainerParallelInput.vue",
"chars": 105,
"preview": "<template>\n <div class=\"space-y-2 sm:flex sm:space-x-4 sm:space-y-0\">\n <slot />\n </div>\n</template>\n"
},
{
"path": "src/components/ExploreMenuCategories.vue",
"chars": 2010,
"preview": "<script setup lang=\"ts\">\nimport { SPACE_CATEGORIES } from '@/helpers/constants';\n\nconst props = defineProps<{\n metrics:"
},
{
"path": "src/components/ExploreSkeletonLoading.vue",
"chars": 577,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n isSpaces?: boolean;\n}>();\n\nconst CARD_COUNT = 12;\n</script>\n\n<template>\n <div\n"
},
{
"path": "src/components/ExploreSpaces.vue",
"chars": 4605,
"preview": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\nimport { useInfiniteScroll, watchDebounced } from '@"
},
{
"path": "src/components/FooterLinks.vue",
"chars": 111,
"preview": "<script setup lang=\"ts\"></script>\n\n<template>\n <div class=\"space-y-[12px]\">\n <slot />\n </div>\n</template>\n"
},
{
"path": "src/components/FooterLinksItem.vue",
"chars": 273,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n link: any;\n}>();\n</script>\n\n<template>\n <BaseLink\n :link=\"link\"\n hide-ex"
},
{
"path": "src/components/FooterSocials.vue",
"chars": 749,
"preview": "<script setup lang=\"ts\">\nconst socials = [\n {\n icon: 'x',\n link: 'https://x.com/SnapshotLabs'\n },\n {\n icon: "
},
{
"path": "src/components/FooterSocialsItem.vue",
"chars": 132,
"preview": "<script setup lang=\"ts\"></script>\n\n<template>\n <div class=\"text-skin-text hover:text-skin-link\">\n <slot />\n </div>\n"
},
{
"path": "src/components/FooterTitle.vue",
"chars": 106,
"preview": "<script setup lang=\"ts\"></script>\n\n<template>\n <h4 class=\"font-medium\">\n <slot />\n </h4>\n</template>\n"
},
{
"path": "src/components/FormArrayStrategies.vue",
"chars": 3530,
"preview": "<script setup lang=\"ts\">\nimport { SpaceStrategy } from '@/helpers/interfaces';\nimport { clone } from '@snapshot-labs/sna"
},
{
"path": "src/components/FormObjectStrategyParams.vue",
"chars": 2107,
"preview": "<script setup lang=\"ts\">\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { validateForm } from '@/h"
},
{
"path": "src/components/IconDiscord.vue",
"chars": 82,
"preview": "<template>\n <i-s-discord class=\"h-[32px] w-[32px] text-[#5865F2]\" />\n</template>\n"
},
{
"path": "src/components/IconInformationTooltip.vue",
"chars": 264,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n information?: string;\n}>();\n</script>\n\n<template>\n <span\n v-if=\"!!informati"
},
{
"path": "src/components/IconVerifiedSpace.vue",
"chars": 412,
"preview": "<script setup lang=\"ts\">\nwithDefaults(\n defineProps<{\n size?: string;\n turbo: boolean;\n }>(),\n {\n size: '20'"
},
{
"path": "src/components/IndicatorAssetsChange.spec.js",
"chars": 1897,
"preview": "import { describe, expect, it, afterEach } from 'vitest';\nimport { shallowMount } from '@vue/test-utils';\nimport Indicat"
},
{
"path": "src/components/IndicatorAssetsChange.vue",
"chars": 778,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n quote: {\n quote: number;\n quote_24h: number;\n };\n}>();\n\nconst { formatPe"
},
{
"path": "src/components/InputCheckbox.vue",
"chars": 615,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n modelValue: boolean;\n name: string;\n label: string;\n}>();\n\nconst emit = defin"
},
{
"path": "src/components/InputComboboxToken.vue",
"chars": 1995,
"preview": "<script setup lang=\"ts\">\nimport { Token } from '@/helpers/alchemy';\n\ndefineProps<{\n label: string;\n network: string;\n "
},
{
"path": "src/components/InputDate.vue",
"chars": 1136,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n title?: string;\n information?: string;\n dateString?: string;\n date: number;\n"
},
{
"path": "src/components/InputEmail.vue",
"chars": 494,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n modelValue?: string;\n focusOnMount?: boolean;\n}>();\n\nconst emit = defineEmits("
},
{
"path": "src/components/InputNewsletter.vue",
"chars": 683,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n tag: string;\n}>();\n\ndefineEmits(['close']);\n\nconst action =\n 'https://snapshot"
},
{
"path": "src/components/InputSelect.vue",
"chars": 875,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n modelValue: string;\n title?: string;\n information?: string;\n isDisabled?: bo"
},
{
"path": "src/components/InputSelectPrivacy.vue",
"chars": 912,
"preview": "<script setup lang=\"ts\">\nwithDefaults(\n defineProps<{\n privacy?: string;\n hint?: string;\n allowAny?: boolean;\n"
},
{
"path": "src/components/InputSelectVoteType.vue",
"chars": 1033,
"preview": "<script setup lang=\"ts\">\nwithDefaults(\n defineProps<{\n type?: string;\n hint?: string;\n allowAny?: boolean;\n "
},
{
"path": "src/components/InputSelectVoteValidation.vue",
"chars": 807,
"preview": "<script setup lang=\"ts\">\nimport { VoteValidation, SpaceStrategy } from '@/helpers/interfaces';\n\ndefineProps<{\n validati"
},
{
"path": "src/components/InputSwitch.vue",
"chars": 2693,
"preview": "<script setup lang=\"ts\">\nimport { Switch } from '@headlessui/vue';\n\ndefineProps<{\n modelValue?: boolean;\n label?: stri"
},
{
"path": "src/components/InputUploadAvatar.vue",
"chars": 1292,
"preview": "<script setup lang=\"ts\">\nconst props = defineProps<{\n isViewOnly?: boolean;\n}>();\n\nconst emit = defineEmits(['image-upl"
},
{
"path": "src/components/LabelInput.vue",
"chars": 249,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n information?: string;\n}>();\n</script>\n\n<template>\n <span class=\"mb-[2px] flex "
},
{
"path": "src/components/LabelProposalState.vue",
"chars": 451,
"preview": "<script setup lang=\"ts\">\nconst props = defineProps<{\n state: string;\n}>();\n\nconst stateClass = computed(() => {\n if (p"
},
{
"path": "src/components/LabelProposalVoted.vue",
"chars": 241,
"preview": "<script setup lang=\"ts\"></script>\n\n<template>\n <span\n class=\"absolute inline-flex items-center gap-1 whitespace-nowr"
},
{
"path": "src/components/LinkSpace.vue",
"chars": 780,
"preview": "<script setup lang=\"ts\">\nimport domains from '@/../snapshot-spaces/spaces/domains.json';\n\nconst props = defineProps<{\n "
},
{
"path": "src/components/ListboxNetwork.vue",
"chars": 1148,
"preview": "<script setup lang=\"ts\">\nimport { getUrl } from '@snapshot-labs/snapshot.js/src/utils';\n\ndefineProps<{\n modelValue: str"
},
{
"path": "src/components/LoadingList.vue",
"chars": 250,
"preview": "<script setup lang=\"ts\"></script>\n\n<template>\n <div>\n <div\n class=\"lazy-loading mb-2 rounded-md\"\n style=\"w"
},
{
"path": "src/components/LoadingPage.vue",
"chars": 334,
"preview": "<template>\n <BaseLoading>\n <div class=\"space-y-3\">\n <div class=\"lazy-loading rounded-md\" style=\"width: 100%; he"
},
{
"path": "src/components/LoadingRow.vue",
"chars": 373,
"preview": "<script setup lang=\"ts\">\ndefineProps<{ block?: boolean }>();\n</script>\n\n<template>\n <BaseLoading :block=\"block\">\n <d"
},
{
"path": "src/components/LoadingSpinner.vue",
"chars": 1525,
"preview": "<script setup lang=\"ts\">\ndefineProps({\n fillWhite: {\n type: Boolean,\n default: false\n },\n small: {\n type: Bo"
},
{
"path": "src/components/MenuAccount.vue",
"chars": 2079,
"preview": "<script setup lang=\"ts\">\nimport { openProfile } from '@/helpers/utils';\n\nconst props = defineProps<{\n address: string;\n"
},
{
"path": "src/components/MenuLanguages.vue",
"chars": 846,
"preview": "<script setup lang=\"ts\">\nimport languages from '@/locales/languages.json';\n\nconst { setLocale } = useI18n();\n\nfunction s"
},
{
"path": "src/components/MessageWarningFlagged.vue",
"chars": 828,
"preview": "<script setup lang=\"ts\">\nconst props = defineProps<{\n type: string;\n responsive?: boolean;\n}>();\n\nconst emit = defineE"
},
{
"path": "src/components/MessageWarningGnosisNetwork.vue",
"chars": 744,
"preview": "<script setup lang=\"ts\">\nimport networks from '@snapshot-labs/snapshot.js/src/networks.json';\nimport { ExtendedSpace } f"
},
{
"path": "src/components/MessageWarningHibernated.vue",
"chars": 1132,
"preview": "<script setup lang=\"ts\">\nimport { ExtendedSpace } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n space: Ext"
},
{
"path": "src/components/MessageWarningTestnet.vue",
"chars": 844,
"preview": "<script setup lang=\"ts\">\nconst props = defineProps<{\n context: 'Treasury' | 'Strategy';\n error?: string | Record<strin"
},
{
"path": "src/components/MessageWarningValidation.vue",
"chars": 1760,
"preview": "<script setup lang=\"ts\">\nconst props = defineProps<{\n context: 'voting' | 'proposal';\n spaceId: string;\n validationNa"
},
{
"path": "src/components/ModalAccount.vue",
"chars": 2906,
"preview": "<script setup lang=\"ts\">\nimport { getInjected } from '@snapshot-labs/lock/src/utils';\nimport connectors from '@/helpers/"
},
{
"path": "src/components/ModalConfirmAction.vue",
"chars": 870,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n open: boolean;\n title?: string;\n showCancel?: boolean;\n disabled?: boolean;\n"
},
{
"path": "src/components/ModalConfirmLeave.vue",
"chars": 955,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n open: boolean;\n title?: string;\n disabled?: boolean;\n}>();\n\ndefineEmits(['clo"
},
{
"path": "src/components/ModalControllerEdit.vue",
"chars": 1761,
"preview": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\nimport { isAddress } from '@ethersproject/address';\n"
},
{
"path": "src/components/ModalDelegate.vue",
"chars": 1306,
"preview": "<script setup lang=\"ts\">\nimport { sleep } from '@snapshot-labs/snapshot.js/src/utils';\n\nconst props = defineProps<{\n op"
},
{
"path": "src/components/ModalEmail.vue",
"chars": 1207,
"preview": "<script setup lang=\"ts\">\nconst props = defineProps<{\n open: boolean;\n}>();\nconst emit = defineEmits(['close']);\n\nconst "
},
{
"path": "src/components/ModalEmailManagement.vue",
"chars": 1927,
"preview": "<script setup lang=\"ts\">\nconst emit = defineEmits(['close']);\n\nconst { loading, error, clientSubscriptions, updateSubscr"
},
{
"path": "src/components/ModalEmailResend.vue",
"chars": 303,
"preview": "<script setup lang=\"ts\">\nconst emit = defineEmits(['close']);\n</script>\n\n<template>\n <div class=\"m-4 flex flex-col gap-"
},
{
"path": "src/components/ModalEmailSubscription.vue",
"chars": 2237,
"preview": "<script setup lang=\"ts\">\nconst emit = defineEmits(['close']);\n\ntype ModalView = 'SUBSCRIBE' | 'SUCCESS';\n\nconst { web3Ac"
},
{
"path": "src/components/ModalLinkPreview.vue",
"chars": 611,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n open: boolean;\n clickedUrl: string;\n}>();\n\ndefineEmits<{\n (event: 'close'): v"
},
{
"path": "src/components/ModalMessage.vue",
"chars": 671,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n open: boolean;\n title: string;\n level: 'info' | 'warning' | 'warning-red';\n}>"
},
{
"path": "src/components/ModalNotice.vue",
"chars": 535,
"preview": "<script setup lang=\"ts\">\ndefineProps<{ open: boolean; title: string }>();\ndefineEmits(['close']);\n</script>\n\n<template>\n"
},
{
"path": "src/components/ModalOsnap.vue",
"chars": 1918,
"preview": "<script setup lang=\"ts\">\nimport { TreasuryWallet } from '@/helpers/interfaces';\nimport { Network } from '@/plugins/oSnap"
},
{
"path": "src/components/ModalPendingTransactions.vue",
"chars": 814,
"preview": "<script setup lang=\"ts\">\nimport { explorerUrl, shorten } from '@/helpers/utils';\n\ndefineProps<{\n open: boolean;\n}>();\n\n"
},
{
"path": "src/components/ModalPlugins.vue",
"chars": 2931,
"preview": "<script setup lang=\"ts\">\nimport { PluginIndex } from '@/helpers/interfaces';\n\nconst props = defineProps<{\n open: boolea"
},
{
"path": "src/components/ModalPostPayment.vue",
"chars": 1291,
"preview": "<script setup lang=\"ts\">\nimport { explorerUrl } from '@/helpers/utils';\n\ndefineProps<{\n open: boolean;\n tx: any;\n}>();"
},
{
"path": "src/components/ModalPostVote.vue",
"chars": 3749,
"preview": "<script setup lang=\"ts\">\nimport { getChoiceString } from '@/helpers/utils';\nimport { ExtendedSpace, Proposal } from '@/h"
},
{
"path": "src/components/ModalProfileForm.vue",
"chars": 3159,
"preview": "<script setup lang=\"ts\">\nimport schemas from '@snapshot-labs/snapshot.js/src/schemas';\nimport client from '@/helpers/cli"
},
{
"path": "src/components/ModalRevokeDelegate.vue",
"chars": 2564,
"preview": "<script setup lang=\"ts\">\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport { sendTransaction, sleep"
},
{
"path": "src/components/ModalSelectDate.vue",
"chars": 3369,
"preview": "<script setup lang=\"ts\">\nconst props = defineProps<{\n open: boolean;\n value?: number;\n type?: string;\n}>();\n\nconst em"
},
{
"path": "src/components/ModalSkins.vue",
"chars": 1525,
"preview": "<script setup>\nconst props = defineProps({\n open: {\n type: Boolean,\n required: true\n }\n});\n\nconst emit = defineE"
},
{
"path": "src/components/ModalSnapshotTerms.vue",
"chars": 3098,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n open: boolean;\n}>();\n\ndefineEmits(['close']);\n// TODO: Add correct TOS text\n</s"
},
{
"path": "src/components/ModalSpaces.vue",
"chars": 567,
"preview": "<script setup lang=\"ts\">\nimport { Space } from '@/helpers/interfaces';\n\ndefineProps<{\n open: boolean;\n spaces: Space[]"
},
{
"path": "src/components/ModalSpacesListItem.vue",
"chars": 1169,
"preview": "<script setup lang=\"ts\">\nimport { Space } from '@/helpers/interfaces';\n\nconst { formatCompactNumber } = useIntl();\n\ndefi"
},
{
"path": "src/components/ModalStrategies.vue",
"chars": 673,
"preview": "<script setup lang=\"ts\">\nimport { Proposal, SpaceStrategy } from '@/helpers/interfaces';\n\ndefineProps<{\n open: boolean;"
},
{
"path": "src/components/ModalStrategy.vue",
"chars": 4395,
"preview": "<script setup lang=\"ts\">\nimport { clone } from '@snapshot-labs/snapshot.js/src/utils';\nimport { validateForm } from '@/h"
},
{
"path": "src/components/ModalTerms.vue",
"chars": 1423,
"preview": "<script setup lang=\"ts\">\nimport { getUrl } from '@snapshot-labs/snapshot.js/src/utils';\nimport { Space, RankedSpace, Ext"
},
{
"path": "src/components/ModalTokens.vue",
"chars": 4388,
"preview": "<script setup lang=\"ts\">\nimport { isAddress } from '@ethersproject/address';\nimport { ERC20ABI } from '@/helpers/constan"
},
{
"path": "src/components/ModalTokensItem.vue",
"chars": 1706,
"preview": "<script setup lang=\"ts\">\nimport { shorten, explorerUrl } from '@/helpers/utils';\nimport { Token } from '@/helpers/alchem"
},
{
"path": "src/components/ModalTransactionStatus.vue",
"chars": 1810,
"preview": "<script setup lang=\"ts\">\nimport { explorerUrl } from '@/helpers/utils';\n\nconst props = defineProps<{\n open: boolean;\n "
},
{
"path": "src/components/ModalTreasury.vue",
"chars": 2652,
"preview": "<script setup lang=\"ts\">\nimport { clone, validateSchema } from '@snapshot-labs/snapshot.js/src/utils';\nimport schemas fr"
},
{
"path": "src/components/ModalValidation.vue",
"chars": 6120,
"preview": "<script setup lang=\"ts\">\nimport { SpaceValidation, SpaceStrategy } from '@/helpers/interfaces';\nimport { clone } from '@"
},
{
"path": "src/components/ModalVote.vue",
"chars": 10870,
"preview": "<script setup lang=\"ts\">\nimport { shorten, getChoiceString, explorerUrl } from '@/helpers/utils';\nimport { getPower, vot"
},
{
"path": "src/components/ModalVoteValidation.vue",
"chars": 6013,
"preview": "<script setup lang=\"ts\">\nimport { VoteValidation, SpaceStrategy } from '@/helpers/interfaces';\nimport { clone } from '@s"
},
{
"path": "src/components/ModalVotingPrivacy.vue",
"chars": 1243,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n open: boolean;\n selected?: string;\n allowAny?: boolean;\n allowNone?: boolean"
},
{
"path": "src/components/ModalVotingType.vue",
"chars": 1066,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n open: boolean;\n selected: string;\n allowAny: boolean;\n}>();\n\nconst emit = def"
},
{
"path": "src/components/ModalWrongNetwork.vue",
"chars": 2440,
"preview": "<script setup lang=\"ts\">\nimport { getInstance } from '@snapshot-labs/lock/plugins/vue3';\nimport networks from '@snapshot"
},
{
"path": "src/components/NavbarAccount.vue",
"chars": 2287,
"preview": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\nimport { getInstance } from '@snapshot-labs/lock/plu"
},
{
"path": "src/components/NavbarExtras.vue",
"chars": 2136,
"preview": "<script setup lang=\"ts\">\nimport pkg from '@/../package.json';\nimport { PopoverButton } from '@headlessui/vue';\n\nconst { "
},
{
"path": "src/components/NavbarNotifications.vue",
"chars": 4408,
"preview": "<script setup lang=\"ts\">\nimport { PopoverButton } from '@headlessui/vue';\n\nconst {\n notificationsLoading,\n Notificatio"
},
{
"path": "src/components/PopoverHoverProfile.vue",
"chars": 2077,
"preview": "<script setup lang=\"ts\">\nimport { explorerUrl } from '@/helpers/utils';\nimport { useMediaQuery } from '@vueuse/core';\nim"
},
{
"path": "src/components/ProfileAboutBiography.vue",
"chars": 198,
"preview": "<script setup lang=\"ts\">\ndefineProps<{ about: string }>();\n</script>\n\n<template>\n <BaseBlock :title=\"$t('profile.about."
},
{
"path": "src/components/ProfileAboutDelegate.vue",
"chars": 3108,
"preview": "<script setup lang=\"ts\">\nimport { getDelegators } from '@/helpers/delegation';\nimport uniqBy from 'lodash/uniqBy';\nimpor"
},
{
"path": "src/components/ProfileAboutDelegateListItem.vue",
"chars": 1654,
"preview": "<script setup lang=\"ts\">\nimport { Space } from '@/helpers/interfaces';\n\ndefineProps<{\n spaces: Space[];\n delegators: {"
},
{
"path": "src/components/ProfileActivityList.vue",
"chars": 276,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n title: string;\n}>();\n</script>\n\n<template>\n <div>\n <span class=\"px-4 text-x"
},
{
"path": "src/components/ProfileActivityListItem.vue",
"chars": 1738,
"preview": "<script setup lang=\"ts\">\nimport { ProfileActivity } from '@/helpers/interfaces';\n\nconst { formatRelativeTime, longRelati"
},
{
"path": "src/components/ProfileAddressCopy.vue",
"chars": 794,
"preview": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\n\ndefineProps<{\n profile?: {\n ens?: string;\n n"
},
{
"path": "src/components/ProfileName.vue",
"chars": 428,
"preview": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\n\ndefineProps<{\n address: string;\n profile?: {\n "
},
{
"path": "src/components/ProfileSidebar.vue",
"chars": 1210,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n userAddress: string;\n profiles: {\n [address: string]: {\n ens: string;\n"
},
{
"path": "src/components/ProfileSidebarHeader.vue",
"chars": 411,
"preview": "<script setup lang=\"ts\">\ndefineProps<{\n userAddress: string;\n profile: { ens: string; name?: string; avatar?: string }"
},
{
"path": "src/components/ProfileSidebarHeaderSkeleton.vue",
"chars": 354,
"preview": "<template>\n <div>\n <div class=\"lazy-loading h-[48px] w-[48px] rounded-full\" />\n <div class=\"mt-2 flex flex-col it"
},
{
"path": "src/components/ProfileSidebarNavigation.vue",
"chars": 563,
"preview": "<template>\n <div class=\"no-scrollbar flex pb-0 pt-4 lg:block lg:pb-4\">\n <router-link v-slot=\"{ isExactActive }\" :to="
},
{
"path": "src/components/ProposalsItem.vue",
"chars": 3346,
"preview": "<script setup lang=\"ts\">\nimport removeMd from 'remove-markdown';\nimport { Proposal, ExtendedSpace, Profile } from '@/hel"
},
{
"path": "src/components/ProposalsItemBody.vue",
"chars": 139,
"preview": "<script setup lang=\"ts\"></script>\n\n<template>\n <p class=\"line-clamp-2 break-words text-md font-semibold\">\n <slot />\n"
},
{
"path": "src/components/ProposalsItemFooter.vue",
"chars": 1406,
"preview": "<script setup lang=\"ts\">\nimport capitalize from 'lodash/capitalize';\nimport { Proposal } from '@/helpers/interfaces';\n\nc"
},
{
"path": "src/components/ProposalsItemResults.vue",
"chars": 1485,
"preview": "<script setup lang=\"ts\">\nimport { Proposal } from '@/helpers/interfaces';\nimport { shorten } from '@/helpers/utils';\n\nco"
},
{
"path": "src/components/ProposalsItemTitle.vue",
"chars": 344,
"preview": "<script setup lang=\"ts\">\nimport { Proposal } from '@/helpers/interfaces';\n\ndefineProps<{\n proposal: Proposal;\n voted: "
},
{
"path": "src/components/SettingsBoostBlock.vue",
"chars": 715,
"preview": "<script setup lang=\"ts\">\nconst props = defineProps<{\n context: 'setup' | 'settings';\n isViewOnly?: boolean;\n}>();\n\ncon"
},
{
"path": "src/components/SettingsDangerzoneBlock.vue",
"chars": 2124,
"preview": "<script setup lang=\"ts\">\nimport { shorten } from '@/helpers/utils';\n\nconst props = defineProps<{\n isController: boolean"
}
]
// ... and 434 more files (download for full content)
About this extraction
This page contains the full source code of the snapshot-labs/snapshot-v1 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 634 files (2.0 MB), approximately 552.8k tokens, and a symbol index with 572 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.