├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── pl-api.yaml
│ ├── pl-fe.yaml
│ └── pl-hooks.yaml
├── .gitignore
├── .husky
├── .gitignore
└── pre-commit
├── .npmrc
├── .vscode
├── extensions.json
├── pl-fe.code-snippets
└── settings.json
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── package.json
├── packages
├── pl-api
│ ├── .eslintignore
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── lib
│ │ ├── client.ts
│ │ ├── directory-client.ts
│ │ ├── entities
│ │ │ ├── account-warning.ts
│ │ │ ├── account.ts
│ │ │ ├── admin
│ │ │ │ ├── account.ts
│ │ │ │ ├── announcement.ts
│ │ │ │ ├── canonical-email-block.ts
│ │ │ │ ├── cohort.ts
│ │ │ │ ├── custom-emoji.ts
│ │ │ │ ├── dimension.ts
│ │ │ │ ├── domain-allow.ts
│ │ │ │ ├── domain-block.ts
│ │ │ │ ├── domain.ts
│ │ │ │ ├── email-domain-block.ts
│ │ │ │ ├── ip-block.ts
│ │ │ │ ├── ip.ts
│ │ │ │ ├── measure.ts
│ │ │ │ ├── moderation-log-entry.ts
│ │ │ │ ├── pleroma-config.ts
│ │ │ │ ├── relay.ts
│ │ │ │ ├── report.ts
│ │ │ │ ├── rule.ts
│ │ │ │ └── tag.ts
│ │ │ ├── announcement-reaction.ts
│ │ │ ├── announcement.ts
│ │ │ ├── antenna.ts
│ │ │ ├── application.ts
│ │ │ ├── backup.ts
│ │ │ ├── bookmark-folder.ts
│ │ │ ├── chat-message.ts
│ │ │ ├── chat.ts
│ │ │ ├── circle.ts
│ │ │ ├── context.ts
│ │ │ ├── conversation.ts
│ │ │ ├── custom-emoji.ts
│ │ │ ├── directory
│ │ │ │ ├── category.ts
│ │ │ │ ├── language.ts
│ │ │ │ ├── server.ts
│ │ │ │ └── statistics-period.ts
│ │ │ ├── domain-block.ts
│ │ │ ├── drive-file.ts
│ │ │ ├── drive-folder.ts
│ │ │ ├── emoji-reaction.ts
│ │ │ ├── extended-description.ts
│ │ │ ├── familiar-followers.ts
│ │ │ ├── featured-tag.ts
│ │ │ ├── filter-result.ts
│ │ │ ├── filter.ts
│ │ │ ├── group-member.ts
│ │ │ ├── group-relationship.ts
│ │ │ ├── group.ts
│ │ │ ├── grouped-notifications-results.ts
│ │ │ ├── index.ts
│ │ │ ├── instance.ts
│ │ │ ├── interaction-policy.ts
│ │ │ ├── interaction-request.ts
│ │ │ ├── list.ts
│ │ │ ├── location.ts
│ │ │ ├── marker.ts
│ │ │ ├── media-attachment.ts
│ │ │ ├── mention.ts
│ │ │ ├── notification-policy.ts
│ │ │ ├── notification-request.ts
│ │ │ ├── notification.ts
│ │ │ ├── oauth-token.ts
│ │ │ ├── poll.ts
│ │ │ ├── preview-card-author.ts
│ │ │ ├── preview-card.ts
│ │ │ ├── privacy-policy.ts
│ │ │ ├── quote.ts
│ │ │ ├── relationship-severance-event.ts
│ │ │ ├── relationship.ts
│ │ │ ├── report.ts
│ │ │ ├── role.ts
│ │ │ ├── rss-feed.ts
│ │ │ ├── rule.ts
│ │ │ ├── scheduled-status.ts
│ │ │ ├── scrobble.ts
│ │ │ ├── search.ts
│ │ │ ├── shout-message.ts
│ │ │ ├── status-edit.ts
│ │ │ ├── status-source.ts
│ │ │ ├── status.ts
│ │ │ ├── story-carousel-item.ts
│ │ │ ├── story-media.ts
│ │ │ ├── story-profile.ts
│ │ │ ├── streaming-event.ts
│ │ │ ├── subscription-details.ts
│ │ │ ├── subscription-invoice.ts
│ │ │ ├── subscription-option.ts
│ │ │ ├── suggestion.ts
│ │ │ ├── tag.ts
│ │ │ ├── terms-of-service.ts
│ │ │ ├── token.ts
│ │ │ ├── translation.ts
│ │ │ ├── trends-link.ts
│ │ │ ├── utils.ts
│ │ │ └── web-push-subscription.ts
│ │ ├── features.ts
│ │ ├── main.ts
│ │ ├── params
│ │ │ ├── accounts.ts
│ │ │ ├── admin.ts
│ │ │ ├── antennas.ts
│ │ │ ├── apps.ts
│ │ │ ├── chats.ts
│ │ │ ├── circles.ts
│ │ │ ├── common.ts
│ │ │ ├── drive.ts
│ │ │ ├── events.ts
│ │ │ ├── filtering.ts
│ │ │ ├── grouped-notifications.ts
│ │ │ ├── groups.ts
│ │ │ ├── index.ts
│ │ │ ├── instance.ts
│ │ │ ├── interaction-requests.ts
│ │ │ ├── lists.ts
│ │ │ ├── media.ts
│ │ │ ├── my-account.ts
│ │ │ ├── notifications.ts
│ │ │ ├── oauth.ts
│ │ │ ├── push-notifications.ts
│ │ │ ├── scheduled-statuses.ts
│ │ │ ├── search.ts
│ │ │ ├── settings.ts
│ │ │ ├── statuses.ts
│ │ │ ├── stories.ts
│ │ │ ├── timelines.ts
│ │ │ └── trends.ts
│ │ ├── request.ts
│ │ ├── responses.ts
│ │ └── utils
│ │ │ └── url.ts
│ ├── package.json
│ ├── tsconfig-build.json
│ ├── tsconfig.json
│ ├── typedoc.config.mjs
│ ├── vite.config.ts
│ └── yarn.lock
├── pl-fe
│ ├── .dockerignore
│ ├── .editorconfig
│ ├── .env.example
│ ├── .eslintignore
│ ├── .eslintrc.json
│ ├── .gitattributes
│ ├── .gitignore
│ ├── .lintstagedrc.json
│ ├── .stylelintrc.json
│ ├── .tool-versions
│ ├── CHANGELOG.md
│ ├── Dockerfile
│ ├── Dockerfile.dev
│ ├── LICENSE
│ ├── LICENSE-GPL-3.0
│ ├── README.md
│ ├── app.json
│ ├── compose-dev.yaml
│ ├── custom
│ │ ├── .gitkeep
│ │ ├── instance
│ │ │ └── .gitkeep
│ │ ├── locales
│ │ │ └── .gitkeep
│ │ └── modules
│ │ │ └── .gitkeep
│ ├── favicon.ico
│ ├── index.html
│ ├── installation
│ │ ├── docker.conf.template
│ │ └── mastodon.conf
│ ├── package.json
│ ├── postcss.config.cjs
│ ├── src
│ │ ├── __fixtures__
│ │ │ ├── account-moved.json
│ │ │ ├── account-with-emojis.json
│ │ │ ├── accounts.json
│ │ │ ├── accounts_counter_follow.json
│ │ │ ├── accounts_counter_initial.json
│ │ │ ├── accounts_counter_unfollow.json
│ │ │ ├── admin_api_frontend_config.json
│ │ │ ├── akkoma-instance.json
│ │ │ ├── announcements.json
│ │ │ ├── app.json
│ │ │ ├── blocks.json
│ │ │ ├── config_db.json
│ │ │ ├── fedibird-account.json
│ │ │ ├── fedibird-instance.json
│ │ │ ├── fedibird-quote-of-quote-post.json
│ │ │ ├── fedibird-quote-post.json
│ │ │ ├── friendica-instance.json
│ │ │ ├── friendica-status.json
│ │ │ ├── gotosocial-account.json
│ │ │ ├── gotosocial-instance.json
│ │ │ ├── gotosocial-status.json
│ │ │ ├── group-truthsocial.json
│ │ │ ├── intlMessages.json
│ │ │ ├── lain.json
│ │ │ ├── markers.json
│ │ │ ├── mastodon-3.0.0-instance.json
│ │ │ ├── mastodon-account.json
│ │ │ ├── mastodon-instance-rc.json
│ │ │ ├── mastodon-instance.json
│ │ │ ├── mastodon-reply-to-self.json
│ │ │ ├── mastodon_initial_state.json
│ │ │ ├── mitra-context.json
│ │ │ ├── mitra-instance.json
│ │ │ ├── mitra-status-with-attachments.json
│ │ │ ├── mk.json
│ │ │ ├── notification-favourite.json
│ │ │ ├── notification-follow.json
│ │ │ ├── notification-follow_request.json
│ │ │ ├── notification-mention.json
│ │ │ ├── notification-move.json
│ │ │ ├── notification-pleroma-chat_mention.json
│ │ │ ├── notification-pleroma-emoji_reaction.json
│ │ │ ├── notification-poll.json
│ │ │ ├── notification-reblog.json
│ │ │ ├── notification.json
│ │ │ ├── notifications.json
│ │ │ ├── patron-instance.json
│ │ │ ├── patron-user.json
│ │ │ ├── pixelfed-instance.json
│ │ │ ├── pl-fe.json
│ │ │ ├── pleroma-2.2.2-account.json
│ │ │ ├── pleroma-account.json
│ │ │ ├── pleroma-admin-config.json
│ │ │ ├── pleroma-instance.json
│ │ │ ├── pleroma-notification-move.json
│ │ │ ├── pleroma-quote-of-quote-post.json
│ │ │ ├── pleroma-quote-post.json
│ │ │ ├── pleroma-status-deleted.json
│ │ │ ├── pleroma-status-reply-with-mentions.json
│ │ │ ├── pleroma-status-vertical-video-without-metadata.json
│ │ │ ├── pleroma-status-with-attachments.json
│ │ │ ├── pleroma-status-with-poll-with-emojis.json
│ │ │ ├── pleroma-status-with-poll.json
│ │ │ ├── pleroma-status.json
│ │ │ ├── pleroma_initial_results.json
│ │ │ ├── relationship.json
│ │ │ ├── rules.json
│ │ │ ├── status-custom-emoji.json
│ │ │ ├── status-cw.json
│ │ │ ├── status-quotes.json
│ │ │ ├── status-unordered-mentions.json
│ │ │ ├── status-with-card.json
│ │ │ ├── status-with-poll.json
│ │ │ └── user.json
│ │ ├── actions
│ │ │ ├── account-notes.test.ts
│ │ │ ├── account-notes.ts
│ │ │ ├── accounts.test.ts
│ │ │ ├── accounts.ts
│ │ │ ├── admin.ts
│ │ │ ├── aliases.ts
│ │ │ ├── apps.ts
│ │ │ ├── auth.ts
│ │ │ ├── bookmarks.ts
│ │ │ ├── chats.ts
│ │ │ ├── circle.ts
│ │ │ ├── compose.test.ts
│ │ │ ├── compose.ts
│ │ │ ├── consumer-auth.ts
│ │ │ ├── conversations.ts
│ │ │ ├── draft-statuses.ts
│ │ │ ├── emoji-reacts.ts
│ │ │ ├── events.ts
│ │ │ ├── export-data.ts
│ │ │ ├── external-auth.ts
│ │ │ ├── favourites.ts
│ │ │ ├── filters.ts
│ │ │ ├── import-data.ts
│ │ │ ├── importer.ts
│ │ │ ├── instance.ts
│ │ │ ├── interactions.ts
│ │ │ ├── lists.ts
│ │ │ ├── markers.ts
│ │ │ ├── me.test.ts
│ │ │ ├── me.ts
│ │ │ ├── media.ts
│ │ │ ├── mfa.ts
│ │ │ ├── moderation.tsx
│ │ │ ├── mrf.ts
│ │ │ ├── notifications.test.ts
│ │ │ ├── notifications.ts
│ │ │ ├── oauth.ts
│ │ │ ├── onboarding.test.ts
│ │ │ ├── onboarding.ts
│ │ │ ├── pin-statuses.ts
│ │ │ ├── pl-fe.ts
│ │ │ ├── polls.ts
│ │ │ ├── preload.test.ts
│ │ │ ├── preload.ts
│ │ │ ├── push-notifications
│ │ │ │ ├── registerer.ts
│ │ │ │ └── setter.ts
│ │ │ ├── push-subscriptions.ts
│ │ │ ├── remote-timeline.ts
│ │ │ ├── reports.ts
│ │ │ ├── security.ts
│ │ │ ├── settings.ts
│ │ │ ├── shoutbox.ts
│ │ │ ├── statuses.test.ts
│ │ │ ├── statuses.ts
│ │ │ └── timelines.ts
│ │ ├── api
│ │ │ ├── __mocks__
│ │ │ │ └── index.ts
│ │ │ ├── hooks
│ │ │ │ ├── accounts
│ │ │ │ │ ├── use-account-lookup.ts
│ │ │ │ │ ├── use-account.ts
│ │ │ │ │ ├── use-follow.ts
│ │ │ │ │ ├── use-relationship.ts
│ │ │ │ │ └── use-relationships.ts
│ │ │ │ ├── admin
│ │ │ │ │ ├── use-suggest.ts
│ │ │ │ │ └── use-verify.ts
│ │ │ │ ├── groups
│ │ │ │ │ ├── use-create-group.ts
│ │ │ │ │ ├── use-delete-group.ts
│ │ │ │ │ ├── use-demote-group-member.ts
│ │ │ │ │ ├── use-group-membership-requests.ts
│ │ │ │ │ ├── use-group-relationship.ts
│ │ │ │ │ ├── use-group-relationships.ts
│ │ │ │ │ ├── use-group.test.ts
│ │ │ │ │ ├── use-group.ts
│ │ │ │ │ ├── use-groups.test.ts
│ │ │ │ │ ├── use-groups.ts
│ │ │ │ │ ├── use-join-group.ts
│ │ │ │ │ ├── use-leave-group.ts
│ │ │ │ │ ├── use-promote-group-member.ts
│ │ │ │ │ └── use-update-group.ts
│ │ │ │ └── streaming
│ │ │ │ │ ├── use-bubble-stream.ts
│ │ │ │ │ ├── use-community-stream.ts
│ │ │ │ │ ├── use-direct-stream.ts
│ │ │ │ │ ├── use-group-stream.ts
│ │ │ │ │ ├── use-hashtag-stream.ts
│ │ │ │ │ ├── use-list-stream.ts
│ │ │ │ │ ├── use-public-stream.ts
│ │ │ │ │ ├── use-remote-stream.ts
│ │ │ │ │ ├── use-timeline-stream.ts
│ │ │ │ │ └── use-user-stream.ts
│ │ │ └── index.ts
│ │ ├── assets
│ │ │ ├── images
│ │ │ │ ├── audio-placeholder.png
│ │ │ │ ├── avatar-missing.png
│ │ │ │ ├── header-missing.png
│ │ │ │ ├── video-placeholder.png
│ │ │ │ └── web-push
│ │ │ │ │ ├── web-push-icon_expand.png
│ │ │ │ │ ├── web-push-icon_favourite.png
│ │ │ │ │ └── web-push-icon_reblog.png
│ │ │ └── sounds
│ │ │ │ ├── LICENSE.md
│ │ │ │ ├── boop.mp3
│ │ │ │ ├── boop.ogg
│ │ │ │ ├── chat.mp3
│ │ │ │ └── chat.ogg
│ │ ├── build-config-compiletime.ts
│ │ ├── build-config.ts
│ │ ├── components
│ │ │ ├── account-hover-card.tsx
│ │ │ ├── account-search.tsx
│ │ │ ├── account.test.tsx
│ │ │ ├── account.tsx
│ │ │ ├── alt-indicator.tsx
│ │ │ ├── animated-number.tsx
│ │ │ ├── announcements
│ │ │ │ ├── announcement-content.tsx
│ │ │ │ ├── announcement.tsx
│ │ │ │ ├── announcements-panel.tsx
│ │ │ │ ├── emoji.tsx
│ │ │ │ ├── reaction.tsx
│ │ │ │ └── reactions-bar.tsx
│ │ │ ├── attachment-thumbs.tsx
│ │ │ ├── authorize-reject-buttons.tsx
│ │ │ ├── autosuggest-account-input.tsx
│ │ │ ├── autosuggest-emoji.test.tsx
│ │ │ ├── autosuggest-emoji.tsx
│ │ │ ├── autosuggest-input.tsx
│ │ │ ├── autosuggest-location.tsx
│ │ │ ├── avatar-stack.tsx
│ │ │ ├── badge.test.tsx
│ │ │ ├── badge.tsx
│ │ │ ├── big-card.tsx
│ │ │ ├── birthday-input.tsx
│ │ │ ├── birthday-panel.tsx
│ │ │ ├── blurhash.tsx
│ │ │ ├── copyable-input.tsx
│ │ │ ├── domain.tsx
│ │ │ ├── dropdown-menu
│ │ │ │ ├── dropdown-menu-item.tsx
│ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ └── index.ts
│ │ │ ├── event-preview.tsx
│ │ │ ├── extended-video-player.tsx
│ │ │ ├── fork-awesome-icon.tsx
│ │ │ ├── gdpr-banner.tsx
│ │ │ ├── group-card.tsx
│ │ │ ├── groups
│ │ │ │ ├── group-avatar.tsx
│ │ │ │ └── popover
│ │ │ │ │ └── group-popover.tsx
│ │ │ ├── hashtag-link.tsx
│ │ │ ├── hashtag.tsx
│ │ │ ├── hashtags-bar.tsx
│ │ │ ├── helmet.tsx
│ │ │ ├── hover-account-wrapper.tsx
│ │ │ ├── hover-status-wrapper.tsx
│ │ │ ├── icon-button.tsx
│ │ │ ├── icon-with-counter.tsx
│ │ │ ├── icon.tsx
│ │ │ ├── landing-gradient.tsx
│ │ │ ├── link.tsx
│ │ │ ├── list.tsx
│ │ │ ├── load-gap.tsx
│ │ │ ├── load-more.tsx
│ │ │ ├── loading-screen.tsx
│ │ │ ├── location-search.tsx
│ │ │ ├── markup.tsx
│ │ │ ├── media-gallery.tsx
│ │ │ ├── mention.tsx
│ │ │ ├── missing-indicator.tsx
│ │ │ ├── modal-root.tsx
│ │ │ ├── navlinks.tsx
│ │ │ ├── outline-box.tsx
│ │ │ ├── parsed-content.tsx
│ │ │ ├── parsed-mfm.tsx
│ │ │ ├── pending-items-row.tsx
│ │ │ ├── polls
│ │ │ │ ├── poll-footer.test.tsx
│ │ │ │ ├── poll-footer.tsx
│ │ │ │ ├── poll-option.tsx
│ │ │ │ └── poll.tsx
│ │ │ ├── preview-card.tsx
│ │ │ ├── progress-circle.tsx
│ │ │ ├── pull-to-refresh.tsx
│ │ │ ├── quoted-status-indicator.tsx
│ │ │ ├── quoted-status.test.tsx
│ │ │ ├── quoted-status.tsx
│ │ │ ├── radio.tsx
│ │ │ ├── relative-timestamp.tsx
│ │ │ ├── safe-embed.tsx
│ │ │ ├── scrobble.tsx
│ │ │ ├── scroll-top-button.test.tsx
│ │ │ ├── scroll-top-button.tsx
│ │ │ ├── scrollable-list.tsx
│ │ │ ├── search-input.tsx
│ │ │ ├── sentry-feedback-form.tsx
│ │ │ ├── sidebar-menu.tsx
│ │ │ ├── sidebar-navigation-link.tsx
│ │ │ ├── sidebar-navigation.tsx
│ │ │ ├── site-error-boundary.tsx
│ │ │ ├── site-logo.tsx
│ │ │ ├── status-action-bar.tsx
│ │ │ ├── status-action-button.tsx
│ │ │ ├── status-content.tsx
│ │ │ ├── status-hover-card.tsx
│ │ │ ├── status-language-picker.tsx
│ │ │ ├── status-list.tsx
│ │ │ ├── status-media.tsx
│ │ │ ├── status-mention.tsx
│ │ │ ├── status-reactions-bar.tsx
│ │ │ ├── status-reply-mentions.tsx
│ │ │ ├── status.test.tsx
│ │ │ ├── status.tsx
│ │ │ ├── statuses
│ │ │ │ ├── sensitive-content-overlay.test.tsx
│ │ │ │ ├── sensitive-content-overlay.tsx
│ │ │ │ └── status-info.tsx
│ │ │ ├── still-image.tsx
│ │ │ ├── thumb-navigation-link.tsx
│ │ │ ├── thumb-navigation.tsx
│ │ │ ├── tombstone.tsx
│ │ │ ├── translate-button.tsx
│ │ │ ├── trending-link.tsx
│ │ │ ├── ui
│ │ │ │ ├── accordion.tsx
│ │ │ │ ├── avatar.test.tsx
│ │ │ │ ├── avatar.tsx
│ │ │ │ ├── banner.tsx
│ │ │ │ ├── button
│ │ │ │ │ ├── index.test.tsx
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── useButtonStyles.ts
│ │ │ │ ├── card.test.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── checkbox.tsx
│ │ │ │ ├── column.test.tsx
│ │ │ │ ├── column.tsx
│ │ │ │ ├── combobox.css
│ │ │ │ ├── combobox.tsx
│ │ │ │ ├── counter.tsx
│ │ │ │ ├── divider.test.tsx
│ │ │ │ ├── divider.tsx
│ │ │ │ ├── emoji.test.tsx
│ │ │ │ ├── emoji.tsx
│ │ │ │ ├── file-input.tsx
│ │ │ │ ├── form-actions.test.tsx
│ │ │ │ ├── form-actions.tsx
│ │ │ │ ├── form-group.test.tsx
│ │ │ │ ├── form-group.tsx
│ │ │ │ ├── form.test.tsx
│ │ │ │ ├── form.tsx
│ │ │ │ ├── hstack.tsx
│ │ │ │ ├── icon-button.tsx
│ │ │ │ ├── icon.tsx
│ │ │ │ ├── inline-multiselect.tsx
│ │ │ │ ├── input.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── modal.test.tsx
│ │ │ │ ├── modal.tsx
│ │ │ │ ├── popover.tsx
│ │ │ │ ├── portal.tsx
│ │ │ │ ├── progress-bar.tsx
│ │ │ │ ├── select.tsx
│ │ │ │ ├── slider.tsx
│ │ │ │ ├── spinner.css
│ │ │ │ ├── spinner.tsx
│ │ │ │ ├── stack.tsx
│ │ │ │ ├── step-slider.tsx
│ │ │ │ ├── streamfield.tsx
│ │ │ │ ├── svg-icon.test.tsx
│ │ │ │ ├── svg-icon.tsx
│ │ │ │ ├── tabs.css
│ │ │ │ ├── tabs.tsx
│ │ │ │ ├── tag-input.tsx
│ │ │ │ ├── tag.tsx
│ │ │ │ ├── text.tsx
│ │ │ │ ├── textarea.tsx
│ │ │ │ ├── toast.tsx
│ │ │ │ ├── toggle.tsx
│ │ │ │ ├── tooltip.tsx
│ │ │ │ └── widget.tsx
│ │ │ ├── upload-progress.tsx
│ │ │ ├── upload.tsx
│ │ │ └── verification-badge.tsx
│ │ ├── containers
│ │ │ ├── account-container.tsx
│ │ │ └── status-container.tsx
│ │ ├── contexts
│ │ │ ├── chat-context.tsx
│ │ │ └── stat-context.tsx
│ │ ├── custom.ts
│ │ ├── entity-store
│ │ │ ├── actions.ts
│ │ │ ├── entities.ts
│ │ │ ├── hooks
│ │ │ │ ├── types.ts
│ │ │ │ ├── use-batched-entities.ts
│ │ │ │ ├── use-create-entity.ts
│ │ │ │ ├── use-delete-entity.ts
│ │ │ │ ├── use-dismiss-entity.ts
│ │ │ │ ├── use-entities.ts
│ │ │ │ ├── use-entity-lookup.ts
│ │ │ │ ├── use-entity.ts
│ │ │ │ ├── use-transaction.ts
│ │ │ │ └── utils.ts
│ │ │ ├── reducer.test.ts
│ │ │ ├── reducer.ts
│ │ │ ├── selectors.ts
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ ├── features
│ │ │ ├── account
│ │ │ │ └── components
│ │ │ │ │ └── header.tsx
│ │ │ ├── admin
│ │ │ │ ├── components
│ │ │ │ │ ├── admin-tabs.tsx
│ │ │ │ │ ├── counter.tsx
│ │ │ │ │ ├── dashcounter.tsx
│ │ │ │ │ ├── dimension.tsx
│ │ │ │ │ ├── latest-accounts-panel.tsx
│ │ │ │ │ ├── registration-mode-picker.tsx
│ │ │ │ │ ├── report-status.tsx
│ │ │ │ │ ├── report.tsx
│ │ │ │ │ ├── retention.tsx
│ │ │ │ │ └── unapproved-account.tsx
│ │ │ │ └── tabs
│ │ │ │ │ ├── awaiting-approval.tsx
│ │ │ │ │ ├── dashboard.tsx
│ │ │ │ │ └── reports.tsx
│ │ │ ├── audio
│ │ │ │ ├── index.tsx
│ │ │ │ └── visualizer.ts
│ │ │ ├── auth-login
│ │ │ │ └── components
│ │ │ │ │ ├── captcha.test.tsx
│ │ │ │ │ ├── captcha.tsx
│ │ │ │ │ ├── consumer-button.tsx
│ │ │ │ │ ├── consumers-list.tsx
│ │ │ │ │ ├── login-form.test.tsx
│ │ │ │ │ ├── login-form.tsx
│ │ │ │ │ ├── otp-auth-form.test.tsx
│ │ │ │ │ ├── otp-auth-form.tsx
│ │ │ │ │ └── registration-form.tsx
│ │ │ ├── birthdays
│ │ │ │ ├── account.tsx
│ │ │ │ └── date-picker.ts
│ │ │ ├── chats
│ │ │ │ └── components
│ │ │ │ │ ├── chat-composer.tsx
│ │ │ │ │ ├── chat-list-item.test.tsx
│ │ │ │ │ ├── chat-list-item.tsx
│ │ │ │ │ ├── chat-list-shoutbox.tsx
│ │ │ │ │ ├── chat-list.tsx
│ │ │ │ │ ├── chat-message-list.test.tsx
│ │ │ │ │ ├── chat-message-list.tsx
│ │ │ │ │ ├── chat-message.tsx
│ │ │ │ │ ├── chat-page
│ │ │ │ │ ├── chat-page.tsx
│ │ │ │ │ └── components
│ │ │ │ │ │ ├── blankslate-empty.tsx
│ │ │ │ │ │ ├── blankslate-with-chats.tsx
│ │ │ │ │ │ ├── chat-page-main.tsx
│ │ │ │ │ │ ├── chat-page-new.tsx
│ │ │ │ │ │ ├── chat-page-settings.tsx
│ │ │ │ │ │ ├── chat-page-shoutbox.tsx
│ │ │ │ │ │ └── chat-page-sidebar.tsx
│ │ │ │ │ ├── chat-pane-header.test.tsx
│ │ │ │ │ ├── chat-pane
│ │ │ │ │ ├── blankslate.tsx
│ │ │ │ │ ├── chat-pane.test.tsx
│ │ │ │ │ └── chat-pane.tsx
│ │ │ │ │ ├── chat-pending-upload.tsx
│ │ │ │ │ ├── chat-search
│ │ │ │ │ ├── blankslate.tsx
│ │ │ │ │ ├── chat-search.test.tsx
│ │ │ │ │ ├── chat-search.tsx
│ │ │ │ │ ├── empty-results-blankslate.tsx
│ │ │ │ │ └── results.tsx
│ │ │ │ │ ├── chat-textarea.tsx
│ │ │ │ │ ├── chat-upload-preview.tsx
│ │ │ │ │ ├── chat-upload.tsx
│ │ │ │ │ ├── chat-widget.test.tsx
│ │ │ │ │ ├── chat-widget
│ │ │ │ │ ├── chat-pane-header.tsx
│ │ │ │ │ ├── chat-settings.tsx
│ │ │ │ │ ├── chat-widget.tsx
│ │ │ │ │ ├── chat-window.tsx
│ │ │ │ │ ├── headers
│ │ │ │ │ │ └── chat-search-header.tsx
│ │ │ │ │ └── shoutbox-window.tsx
│ │ │ │ │ ├── chat.tsx
│ │ │ │ │ ├── shoutbox-composer.tsx
│ │ │ │ │ ├── shoutbox-message-list.tsx
│ │ │ │ │ ├── shoutbox.tsx
│ │ │ │ │ └── ui
│ │ │ │ │ └── pane.tsx
│ │ │ ├── compose-event
│ │ │ │ ├── components
│ │ │ │ │ └── upload-button.tsx
│ │ │ │ └── tabs
│ │ │ │ │ ├── edit-event.tsx
│ │ │ │ │ └── manage-pending-participants.tsx
│ │ │ ├── compose
│ │ │ │ ├── components
│ │ │ │ │ ├── autosuggest-account.tsx
│ │ │ │ │ ├── clear-link-suggestion.tsx
│ │ │ │ │ ├── compose-form-button.tsx
│ │ │ │ │ ├── compose-form.tsx
│ │ │ │ │ ├── content-type-button.tsx
│ │ │ │ │ ├── interaction-policy-button.tsx
│ │ │ │ │ ├── language-dropdown.tsx
│ │ │ │ │ ├── poll-button.tsx
│ │ │ │ │ ├── polls
│ │ │ │ │ │ ├── duration-selector.test.tsx
│ │ │ │ │ │ ├── duration-selector.tsx
│ │ │ │ │ │ └── poll-form.tsx
│ │ │ │ │ ├── privacy-dropdown.tsx
│ │ │ │ │ ├── reply-group-indicator.tsx
│ │ │ │ │ ├── reply-indicator.tsx
│ │ │ │ │ ├── reply-mentions.tsx
│ │ │ │ │ ├── schedule-button.tsx
│ │ │ │ │ ├── schedule-form.tsx
│ │ │ │ │ ├── sensitive-media-button.tsx
│ │ │ │ │ ├── spoiler-input.tsx
│ │ │ │ │ ├── text-character-counter.tsx
│ │ │ │ │ ├── upload-button.tsx
│ │ │ │ │ ├── upload-form.tsx
│ │ │ │ │ ├── upload-progress.tsx
│ │ │ │ │ ├── upload.tsx
│ │ │ │ │ ├── visual-character-counter.tsx
│ │ │ │ │ └── warning.tsx
│ │ │ │ ├── containers
│ │ │ │ │ ├── preview-compose-container.tsx
│ │ │ │ │ ├── quoted-status-container.tsx
│ │ │ │ │ ├── reply-indicator-container.tsx
│ │ │ │ │ ├── upload-button-container.tsx
│ │ │ │ │ └── warning-container.tsx
│ │ │ │ ├── editor
│ │ │ │ │ ├── LICENSE
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ ├── nodes
│ │ │ │ │ │ ├── emoji-node.tsx
│ │ │ │ │ │ ├── image-component.tsx
│ │ │ │ │ │ ├── image-node.tsx
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── mention-node.tsx
│ │ │ │ │ ├── plugins
│ │ │ │ │ │ ├── autosuggest-plugin.tsx
│ │ │ │ │ │ ├── floating-block-type-toolbar-plugin.tsx
│ │ │ │ │ │ ├── floating-link-editor-plugin.tsx
│ │ │ │ │ │ ├── floating-text-format-toolbar-plugin.tsx
│ │ │ │ │ │ ├── focus-plugin.tsx
│ │ │ │ │ │ ├── link-plugin.tsx
│ │ │ │ │ │ ├── ref-plugin.tsx
│ │ │ │ │ │ ├── state-plugin.tsx
│ │ │ │ │ │ └── submit-plugin.tsx
│ │ │ │ │ ├── transformers
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ └── utils
│ │ │ │ │ │ ├── get-dom-range-rect.ts
│ │ │ │ │ │ ├── get-selected-node.ts
│ │ │ │ │ │ ├── set-floating-elem-position.ts
│ │ │ │ │ │ └── url.ts
│ │ │ │ └── util
│ │ │ │ │ ├── counter.ts
│ │ │ │ │ └── url-regex.ts
│ │ │ ├── conversations
│ │ │ │ └── components
│ │ │ │ │ ├── conversation.tsx
│ │ │ │ │ └── conversations-list.tsx
│ │ │ ├── crypto-donate
│ │ │ │ ├── components
│ │ │ │ │ ├── crypto-address.tsx
│ │ │ │ │ ├── crypto-donate-panel.tsx
│ │ │ │ │ ├── crypto-icon.tsx
│ │ │ │ │ ├── detailed-crypto-address.tsx
│ │ │ │ │ ├── lightning-address.tsx
│ │ │ │ │ └── site-wallet.tsx
│ │ │ │ └── utils
│ │ │ │ │ ├── block-explorer.ts
│ │ │ │ │ ├── block-explorers.json
│ │ │ │ │ ├── coin-db.ts
│ │ │ │ │ └── manifest-map.ts
│ │ │ ├── developers
│ │ │ │ └── components
│ │ │ │ │ └── indicator.tsx
│ │ │ ├── draft-statuses
│ │ │ │ ├── builder.tsx
│ │ │ │ └── components
│ │ │ │ │ ├── draft-status-action-bar.tsx
│ │ │ │ │ └── draft-status.tsx
│ │ │ ├── edit-profile
│ │ │ │ └── components
│ │ │ │ │ ├── avatar-picker.tsx
│ │ │ │ │ └── header-picker.tsx
│ │ │ ├── embedded-status
│ │ │ │ └── index.tsx
│ │ │ ├── emoji
│ │ │ │ ├── components
│ │ │ │ │ ├── emoji-picker-dropdown.tsx
│ │ │ │ │ └── emoji-picker.tsx
│ │ │ │ ├── containers
│ │ │ │ │ └── emoji-picker-dropdown-container.tsx
│ │ │ │ ├── data.ts
│ │ │ │ ├── emojify.tsx
│ │ │ │ ├── index.test.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── mapping-compiletime.ts
│ │ │ │ ├── mapping.ts
│ │ │ │ ├── search.test.ts
│ │ │ │ └── search.ts
│ │ │ ├── event
│ │ │ │ └── components
│ │ │ │ │ ├── event-action-button.tsx
│ │ │ │ │ ├── event-date.tsx
│ │ │ │ │ └── event-header.tsx
│ │ │ ├── external-login
│ │ │ │ └── components
│ │ │ │ │ └── external-login-form.tsx
│ │ │ ├── federation-restrictions
│ │ │ │ └── components
│ │ │ │ │ ├── instance-restrictions.tsx
│ │ │ │ │ └── restricted-instance.tsx
│ │ │ ├── forms
│ │ │ │ └── index.tsx
│ │ │ ├── group
│ │ │ │ └── components
│ │ │ │ │ ├── group-action-button.test.tsx
│ │ │ │ │ ├── group-action-button.tsx
│ │ │ │ │ ├── group-header-image.tsx
│ │ │ │ │ ├── group-header.test.tsx
│ │ │ │ │ ├── group-header.tsx
│ │ │ │ │ ├── group-member-count.test.tsx
│ │ │ │ │ ├── group-member-count.tsx
│ │ │ │ │ ├── group-member-list-item.test.tsx
│ │ │ │ │ ├── group-member-list-item.tsx
│ │ │ │ │ ├── group-options-button.test.tsx
│ │ │ │ │ ├── group-options-button.tsx
│ │ │ │ │ ├── group-privacy.test.tsx
│ │ │ │ │ ├── group-privacy.tsx
│ │ │ │ │ ├── group-relationship.test.tsx
│ │ │ │ │ └── group-relationship.tsx
│ │ │ ├── groups
│ │ │ │ └── components
│ │ │ │ │ └── discover
│ │ │ │ │ ├── group-list-item.test.tsx
│ │ │ │ │ └── group-list-item.tsx
│ │ │ ├── notifications
│ │ │ │ └── components
│ │ │ │ │ ├── notification.tsx
│ │ │ │ │ └── notifications.test.tsx
│ │ │ ├── onboarding
│ │ │ │ ├── onboarding-wizard.tsx
│ │ │ │ └── steps
│ │ │ │ │ ├── avatar-selection-step.tsx
│ │ │ │ │ ├── bio-step.tsx
│ │ │ │ │ ├── completed-step.tsx
│ │ │ │ │ ├── cover-photo-selection-step.tsx
│ │ │ │ │ ├── display-name-step.tsx
│ │ │ │ │ ├── fediverse-step.tsx
│ │ │ │ │ └── suggested-accounts-step.tsx
│ │ │ ├── pl-fe-config
│ │ │ │ ├── components
│ │ │ │ │ ├── color-picker.tsx
│ │ │ │ │ ├── crypto-address-input.tsx
│ │ │ │ │ ├── footer-link-input.tsx
│ │ │ │ │ ├── icon-picker-dropdown.tsx
│ │ │ │ │ ├── icon-picker-menu.tsx
│ │ │ │ │ ├── icon-picker.tsx
│ │ │ │ │ ├── promo-panel-input.tsx
│ │ │ │ │ └── site-preview.tsx
│ │ │ │ └── forkawesome.json
│ │ │ ├── placeholder
│ │ │ │ ├── components
│ │ │ │ │ ├── placeholder-account.tsx
│ │ │ │ │ ├── placeholder-avatar.tsx
│ │ │ │ │ ├── placeholder-card.tsx
│ │ │ │ │ ├── placeholder-chat-message.tsx
│ │ │ │ │ ├── placeholder-chat.tsx
│ │ │ │ │ ├── placeholder-display-name.tsx
│ │ │ │ │ ├── placeholder-event-header.tsx
│ │ │ │ │ ├── placeholder-event-preview.tsx
│ │ │ │ │ ├── placeholder-group-card.tsx
│ │ │ │ │ ├── placeholder-group-search.tsx
│ │ │ │ │ ├── placeholder-hashtag.tsx
│ │ │ │ │ ├── placeholder-media-gallery.tsx
│ │ │ │ │ ├── placeholder-notification.tsx
│ │ │ │ │ ├── placeholder-sidebar-suggestions.tsx
│ │ │ │ │ ├── placeholder-sidebar-trends.tsx
│ │ │ │ │ ├── placeholder-status-content.tsx
│ │ │ │ │ └── placeholder-status.tsx
│ │ │ │ └── utils.ts
│ │ │ ├── preferences
│ │ │ │ └── index.tsx
│ │ │ ├── remote-timeline
│ │ │ │ └── components
│ │ │ │ │ └── pinned-hosts-picker.tsx
│ │ │ ├── reply-mentions
│ │ │ │ └── account.tsx
│ │ │ ├── scheduled-statuses
│ │ │ │ ├── builder.tsx
│ │ │ │ └── components
│ │ │ │ │ ├── scheduled-status-action-bar.tsx
│ │ │ │ │ └── scheduled-status.tsx
│ │ │ ├── security
│ │ │ │ ├── mfa-form.tsx
│ │ │ │ └── mfa
│ │ │ │ │ ├── disable-otp-form.tsx
│ │ │ │ │ ├── enable-otp-form.tsx
│ │ │ │ │ └── otp-confirm-form.tsx
│ │ │ ├── settings
│ │ │ │ └── components
│ │ │ │ │ ├── messages-settings.tsx
│ │ │ │ │ └── setting-toggle.tsx
│ │ │ ├── status
│ │ │ │ ├── components
│ │ │ │ │ ├── detailed-status.tsx
│ │ │ │ │ ├── status-interaction-bar.tsx
│ │ │ │ │ ├── status-type-icon.tsx
│ │ │ │ │ ├── thread-login-cta.tsx
│ │ │ │ │ ├── thread-status.tsx
│ │ │ │ │ └── thread.tsx
│ │ │ │ └── containers
│ │ │ │ │ └── quoted-status-container.tsx
│ │ │ ├── theme-editor
│ │ │ │ └── components
│ │ │ │ │ ├── color.tsx
│ │ │ │ │ └── palette.tsx
│ │ │ ├── ui
│ │ │ │ ├── components
│ │ │ │ │ ├── action-button.tsx
│ │ │ │ │ ├── background-shapes.tsx
│ │ │ │ │ ├── column-forbidden.tsx
│ │ │ │ │ ├── column-loading.tsx
│ │ │ │ │ ├── compose-button.test.tsx
│ │ │ │ │ ├── compose-button.tsx
│ │ │ │ │ ├── error-column.tsx
│ │ │ │ │ ├── hotkeys.tsx
│ │ │ │ │ ├── image-loader.tsx
│ │ │ │ │ ├── link-footer.tsx
│ │ │ │ │ ├── modal-loading.tsx
│ │ │ │ │ ├── modal-root.tsx
│ │ │ │ │ ├── panels
│ │ │ │ │ │ ├── account-note-panel.tsx
│ │ │ │ │ │ ├── group-media-panel.tsx
│ │ │ │ │ │ ├── instance-info-panel.tsx
│ │ │ │ │ │ ├── instance-moderation-panel.tsx
│ │ │ │ │ │ ├── my-groups-panel.tsx
│ │ │ │ │ │ ├── new-event-panel.tsx
│ │ │ │ │ │ ├── new-group-panel.tsx
│ │ │ │ │ │ ├── pinned-accounts-panel.tsx
│ │ │ │ │ │ ├── profile-fields-panel.tsx
│ │ │ │ │ │ ├── profile-info-panel.tsx
│ │ │ │ │ │ ├── profile-media-panel.tsx
│ │ │ │ │ │ ├── promo-panel.tsx
│ │ │ │ │ │ ├── sign-up-panel.test.tsx
│ │ │ │ │ │ ├── sign-up-panel.tsx
│ │ │ │ │ │ ├── trends-panel.test.tsx
│ │ │ │ │ │ ├── trends-panel.tsx
│ │ │ │ │ │ ├── user-panel.tsx
│ │ │ │ │ │ └── who-to-follow-panel.tsx
│ │ │ │ │ ├── pending-status.tsx
│ │ │ │ │ ├── poll-preview.tsx
│ │ │ │ │ ├── profile-dropdown.tsx
│ │ │ │ │ ├── profile-familiar-followers.tsx
│ │ │ │ │ ├── profile-field.tsx
│ │ │ │ │ ├── profile-stats.tsx
│ │ │ │ │ ├── subscribe-button.test.tsx
│ │ │ │ │ ├── subscription-button.tsx
│ │ │ │ │ ├── theme-selector.tsx
│ │ │ │ │ ├── theme-toggle.tsx
│ │ │ │ │ ├── timeline.tsx
│ │ │ │ │ └── zoomable-image.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── util
│ │ │ │ │ ├── async-components.ts
│ │ │ │ │ ├── fullscreen.ts
│ │ │ │ │ ├── global-hotkeys.tsx
│ │ │ │ │ ├── optional-motion.tsx
│ │ │ │ │ ├── pending-status-builder.ts
│ │ │ │ │ ├── react-router-helpers.tsx
│ │ │ │ │ └── reduced-motion.tsx
│ │ │ └── video
│ │ │ │ └── index.tsx
│ │ ├── hooks
│ │ │ ├── __mocks__
│ │ │ │ └── resize-observer.ts
│ │ │ ├── forms
│ │ │ │ ├── use-image-field.ts
│ │ │ │ ├── use-preview.ts
│ │ │ │ └── use-text-field.ts
│ │ │ ├── use-account-gallery.ts
│ │ │ ├── use-app-dispatch.ts
│ │ │ ├── use-app-selector.ts
│ │ │ ├── use-can-interact.ts
│ │ │ ├── use-click-outside.ts
│ │ │ ├── use-client.ts
│ │ │ ├── use-compose.ts
│ │ │ ├── use-debounce.ts
│ │ │ ├── use-dimensions.test.ts
│ │ │ ├── use-dimensions.ts
│ │ │ ├── use-dragged-files.ts
│ │ │ ├── use-features.ts
│ │ │ ├── use-get-state.ts
│ │ │ ├── use-instance.ts
│ │ │ ├── use-is-mobile.ts
│ │ │ ├── use-loading.ts
│ │ │ ├── use-locale.ts
│ │ │ ├── use-logged-in.ts
│ │ │ ├── use-logo.ts
│ │ │ ├── use-long-press.ts
│ │ │ ├── use-own-account.ts
│ │ │ ├── use-pl-fe-config.ts
│ │ │ ├── use-previous.ts
│ │ │ ├── use-registration-status.ts
│ │ │ ├── use-screen-width.ts
│ │ │ ├── use-settings.ts
│ │ │ ├── use-system-theme.ts
│ │ │ ├── use-theme-css.ts
│ │ │ └── use-theme.ts
│ │ ├── iframe.ts
│ │ ├── init
│ │ │ ├── pl-fe-head.tsx
│ │ │ ├── pl-fe-load.tsx
│ │ │ ├── pl-fe-mount.tsx
│ │ │ └── pl-fe.tsx
│ │ ├── instance
│ │ │ ├── about.example
│ │ │ │ ├── dmca.html
│ │ │ │ ├── index.html
│ │ │ │ ├── privacy.html
│ │ │ │ └── tos.html
│ │ │ ├── images
│ │ │ │ ├── logo.png
│ │ │ │ └── shortcuts
│ │ │ │ │ ├── chats.png
│ │ │ │ │ ├── notifications.png
│ │ │ │ │ └── search.png
│ │ │ └── pl-fe.example.json
│ │ ├── is-mobile.ts
│ │ ├── jest
│ │ │ ├── factory.ts
│ │ │ ├── fixtures
│ │ │ │ └── chats.json
│ │ │ ├── mock-stores.tsx
│ │ │ ├── test-helpers.tsx
│ │ │ └── test-setup.ts
│ │ ├── layouts
│ │ │ ├── admin-layout.tsx
│ │ │ ├── chats-layout.tsx
│ │ │ ├── default-layout.tsx
│ │ │ ├── empty-layout.tsx
│ │ │ ├── event-layout.tsx
│ │ │ ├── events-layout.tsx
│ │ │ ├── external-login-layout.tsx
│ │ │ ├── group-layout.tsx
│ │ │ ├── groups-layout.tsx
│ │ │ ├── home-layout.tsx
│ │ │ ├── landing-layout.tsx
│ │ │ ├── manage-groups-layout.tsx
│ │ │ ├── profile-layout.tsx
│ │ │ ├── remote-instance-layout.tsx
│ │ │ ├── search-layout.tsx
│ │ │ └── status-layout.tsx
│ │ ├── locales
│ │ │ ├── ar.json
│ │ │ ├── ast.json
│ │ │ ├── bg.json
│ │ │ ├── bn.json
│ │ │ ├── br.json
│ │ │ ├── bs.json
│ │ │ ├── ca.json
│ │ │ ├── co.json
│ │ │ ├── cs.json
│ │ │ ├── cy.json
│ │ │ ├── da.json
│ │ │ ├── de.json
│ │ │ ├── el.json
│ │ │ ├── en-Shaw.json
│ │ │ ├── en.json
│ │ │ ├── eo.json
│ │ │ ├── es-AR.json
│ │ │ ├── es.json
│ │ │ ├── et.json
│ │ │ ├── eu.json
│ │ │ ├── fa.json
│ │ │ ├── fi.json
│ │ │ ├── fr.json
│ │ │ ├── ga.json
│ │ │ ├── gl.json
│ │ │ ├── he.json
│ │ │ ├── hi.json
│ │ │ ├── hr.json
│ │ │ ├── hu.json
│ │ │ ├── hy.json
│ │ │ ├── id.json
│ │ │ ├── io.json
│ │ │ ├── is.json
│ │ │ ├── it.json
│ │ │ ├── ja.json
│ │ │ ├── jv.json
│ │ │ ├── ka.json
│ │ │ ├── kk.json
│ │ │ ├── ko.json
│ │ │ ├── lt.json
│ │ │ ├── lv.json
│ │ │ ├── mk.json
│ │ │ ├── ms.json
│ │ │ ├── nl.json
│ │ │ ├── nn.json
│ │ │ ├── no.json
│ │ │ ├── oc.json
│ │ │ ├── pl.json
│ │ │ ├── pt-BR.json
│ │ │ ├── pt.json
│ │ │ ├── ro.json
│ │ │ ├── ru.json
│ │ │ ├── sk.json
│ │ │ ├── sl.json
│ │ │ ├── sq.json
│ │ │ ├── sr-Latn.json
│ │ │ ├── sr.json
│ │ │ ├── sv.json
│ │ │ ├── ta.json
│ │ │ ├── te.json
│ │ │ ├── th.json
│ │ │ ├── tr.json
│ │ │ ├── uk.json
│ │ │ ├── zh-CN.json
│ │ │ ├── zh-HK.json
│ │ │ └── zh-TW.json
│ │ ├── main.tsx
│ │ ├── messages.ts
│ │ ├── middleware
│ │ │ ├── errors.ts
│ │ │ └── sounds.ts
│ │ ├── modals
│ │ │ ├── account-moderation-modal
│ │ │ │ ├── badge-input.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── staff-role-picker.tsx
│ │ │ ├── alt-text-modal.tsx
│ │ │ ├── birthdays-modal.tsx
│ │ │ ├── boost-modal.tsx
│ │ │ ├── compare-history-modal.tsx
│ │ │ ├── component-modal.tsx
│ │ │ ├── compose-interaction-policy-modal.tsx
│ │ │ ├── compose-modal.tsx
│ │ │ ├── confirmation-modal.tsx
│ │ │ ├── crypto-donate-modal.tsx
│ │ │ ├── dislikes-modal.tsx
│ │ │ ├── dropdown-menu-modal.tsx
│ │ │ ├── edit-announcement-modal.tsx
│ │ │ ├── edit-bookmark-folder-modal.tsx
│ │ │ ├── edit-domain-modal.tsx
│ │ │ ├── edit-federation-modal.tsx
│ │ │ ├── edit-rule-modal.tsx
│ │ │ ├── embed-modal.tsx
│ │ │ ├── event-map-modal.tsx
│ │ │ ├── event-participants-modal.tsx
│ │ │ ├── familiar-followers-modal.tsx
│ │ │ ├── favourites-modal.tsx
│ │ │ ├── hotkeys-modal.tsx
│ │ │ ├── join-event-modal.tsx
│ │ │ ├── list-adder-modal
│ │ │ │ ├── components
│ │ │ │ │ └── list.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── list-editor-modal
│ │ │ │ ├── components
│ │ │ │ │ ├── account.tsx
│ │ │ │ │ ├── edit-list-form.tsx
│ │ │ │ │ └── search.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── manage-group-modal
│ │ │ │ ├── index.tsx
│ │ │ │ └── steps
│ │ │ │ │ ├── confirmation-step.tsx
│ │ │ │ │ └── details-step.tsx
│ │ │ ├── media-modal.tsx
│ │ │ ├── mentions-modal.tsx
│ │ │ ├── missing-description-modal.tsx
│ │ │ ├── mute-modal.tsx
│ │ │ ├── reactions-modal.tsx
│ │ │ ├── reblogs-modal.tsx
│ │ │ ├── reply-mentions-modal.tsx
│ │ │ ├── report-modal
│ │ │ │ ├── components
│ │ │ │ │ └── status-check-box.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── steps
│ │ │ │ │ ├── confirmation-step.tsx
│ │ │ │ │ ├── other-actions-step.tsx
│ │ │ │ │ └── reason-step.tsx
│ │ │ ├── select-bookmark-folder-modal.tsx
│ │ │ ├── text-field-modal.tsx
│ │ │ ├── unauthorized-modal.test.tsx
│ │ │ ├── unauthorized-modal.tsx
│ │ │ └── video-modal.tsx
│ │ ├── normalizers
│ │ │ ├── account.ts
│ │ │ ├── admin-report.ts
│ │ │ ├── chat-message.ts
│ │ │ ├── group-member.ts
│ │ │ ├── group.ts
│ │ │ ├── notification.ts
│ │ │ ├── pl-fe
│ │ │ │ ├── pl-fe-config.test.ts
│ │ │ │ └── pl-fe-config.ts
│ │ │ └── status.ts
│ │ ├── pages
│ │ │ ├── account-lists
│ │ │ │ ├── circles.tsx
│ │ │ │ ├── directory.tsx
│ │ │ │ ├── follow-recommendations.tsx
│ │ │ │ ├── follow-requests.tsx
│ │ │ │ ├── followers.tsx
│ │ │ │ ├── following.tsx
│ │ │ │ ├── lists.tsx
│ │ │ │ └── outgoing-follow-requests.tsx
│ │ │ ├── accounts
│ │ │ │ ├── account-gallery.tsx
│ │ │ │ └── account-timeline.tsx
│ │ │ ├── auth
│ │ │ │ ├── external-login.tsx
│ │ │ │ ├── login.test.tsx
│ │ │ │ ├── login.tsx
│ │ │ │ ├── logout.tsx
│ │ │ │ ├── password-reset.tsx
│ │ │ │ ├── register-with-invite.tsx
│ │ │ │ └── registration.tsx
│ │ │ ├── chats
│ │ │ │ └── chats.tsx
│ │ │ ├── dashboard
│ │ │ │ ├── announcements.tsx
│ │ │ │ ├── dashboard.tsx
│ │ │ │ ├── domains.tsx
│ │ │ │ ├── moderation-log.tsx
│ │ │ │ ├── pl-fe-config.tsx
│ │ │ │ ├── relays.tsx
│ │ │ │ ├── rules.tsx
│ │ │ │ ├── theme-editor.tsx
│ │ │ │ └── user-index.tsx
│ │ │ ├── developers
│ │ │ │ ├── create-app.tsx
│ │ │ │ ├── developers.tsx
│ │ │ │ ├── service-worker-info.tsx
│ │ │ │ └── settings-store.tsx
│ │ │ ├── fun
│ │ │ │ └── circle.tsx
│ │ │ ├── groups
│ │ │ │ ├── edit-group.tsx
│ │ │ │ ├── group-blocked-members.tsx
│ │ │ │ ├── group-gallery.tsx
│ │ │ │ ├── group-members.tsx
│ │ │ │ ├── group-membership-requests.tsx
│ │ │ │ ├── groups.tsx
│ │ │ │ └── manage-group.tsx
│ │ │ ├── notifications
│ │ │ │ └── notifications.tsx
│ │ │ ├── search
│ │ │ │ └── search.tsx
│ │ │ ├── settings
│ │ │ │ ├── aliases.tsx
│ │ │ │ ├── auth-token-list.tsx
│ │ │ │ ├── backups.tsx
│ │ │ │ ├── blocks.tsx
│ │ │ │ ├── delete-account.tsx
│ │ │ │ ├── domain-blocks.tsx
│ │ │ │ ├── edit-email.tsx
│ │ │ │ ├── edit-filter.tsx
│ │ │ │ ├── edit-password.tsx
│ │ │ │ ├── edit-profile.tsx
│ │ │ │ ├── export-data.tsx
│ │ │ │ ├── filters.tsx
│ │ │ │ ├── import-data.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ ├── interaction-policies.tsx
│ │ │ │ ├── migration.tsx
│ │ │ │ ├── mutes.tsx
│ │ │ │ ├── settings.tsx
│ │ │ │ └── url-privacy.tsx
│ │ │ ├── status-lists
│ │ │ │ ├── bookmark-folders.tsx
│ │ │ │ ├── bookmarks.tsx
│ │ │ │ ├── conversations.tsx
│ │ │ │ ├── draft-statuses.tsx
│ │ │ │ ├── events.tsx
│ │ │ │ ├── favourited-statuses.tsx
│ │ │ │ ├── interaction-requests.tsx
│ │ │ │ ├── pinned-statuses.tsx
│ │ │ │ ├── quotes.tsx
│ │ │ │ └── scheduled-statuses.tsx
│ │ │ ├── statuses
│ │ │ │ ├── compose-event.tsx
│ │ │ │ ├── event-discussion.tsx
│ │ │ │ ├── event-information.tsx
│ │ │ │ └── status.tsx
│ │ │ ├── timelines
│ │ │ │ ├── bubble-timeline.tsx
│ │ │ │ ├── community-timeline.tsx
│ │ │ │ ├── group-timeline.tsx
│ │ │ │ ├── hashtag-timeline.tsx
│ │ │ │ ├── home-timeline.tsx
│ │ │ │ ├── landing-timeline.tsx
│ │ │ │ ├── link-timeline.tsx
│ │ │ │ ├── list-timeline.tsx
│ │ │ │ ├── public-timeline.tsx
│ │ │ │ ├── remote-timeline.tsx
│ │ │ │ └── test-timeline.tsx
│ │ │ └── utils
│ │ │ │ ├── about.tsx
│ │ │ │ ├── crypto-donate.tsx
│ │ │ │ ├── federation-restrictions.tsx
│ │ │ │ ├── generic-not-found.tsx
│ │ │ │ ├── intentional-error.tsx
│ │ │ │ ├── landing.tsx
│ │ │ │ ├── new-status.tsx
│ │ │ │ ├── server-info.tsx
│ │ │ │ └── share.tsx
│ │ ├── polyfills.ts
│ │ ├── precheck.ts
│ │ ├── queries
│ │ │ ├── __mocks__
│ │ │ │ └── client.ts
│ │ │ ├── account-lists
│ │ │ │ ├── use-blocks.ts
│ │ │ │ └── use-follows.ts
│ │ │ ├── accounts.ts
│ │ │ ├── accounts
│ │ │ │ ├── account-scrobble.ts
│ │ │ │ ├── use-birthday-reminders.ts
│ │ │ │ ├── use-circles.ts
│ │ │ │ ├── use-directory.ts
│ │ │ │ ├── use-endorsed-accounts.ts
│ │ │ │ ├── use-familiar-followers.ts
│ │ │ │ ├── use-follow-requests.ts
│ │ │ │ └── use-lists.ts
│ │ │ ├── admin
│ │ │ │ ├── use-announcements.ts
│ │ │ │ ├── use-domains.ts
│ │ │ │ ├── use-metrics.ts
│ │ │ │ ├── use-moderation-log.ts
│ │ │ │ ├── use-relays.ts
│ │ │ │ └── use-rules.ts
│ │ │ ├── announcements
│ │ │ │ └── use-announcements.ts
│ │ │ ├── chats.test.ts
│ │ │ ├── chats.ts
│ │ │ ├── client.ts
│ │ │ ├── embed.ts
│ │ │ ├── events
│ │ │ │ ├── use-event-participation-requests.ts
│ │ │ │ └── use-event-participations.ts
│ │ │ ├── groups
│ │ │ │ ├── use-group-blocks.ts
│ │ │ │ └── use-group-members.ts
│ │ │ ├── hashtags
│ │ │ │ ├── use-followed-tags.ts
│ │ │ │ └── use-hashtag.ts
│ │ │ ├── instance
│ │ │ │ ├── use-custom-emojis.ts
│ │ │ │ └── use-translation-languages.ts
│ │ │ ├── pl-fe
│ │ │ │ └── use-about-page.ts
│ │ │ ├── relationships.test.ts
│ │ │ ├── relationships.ts
│ │ │ ├── search
│ │ │ │ ├── use-search-accounts.ts
│ │ │ │ ├── use-search-location.ts
│ │ │ │ └── use-search.ts
│ │ │ ├── security
│ │ │ │ └── oauth-tokens.ts
│ │ │ ├── settings
│ │ │ │ ├── domain-blocks.ts
│ │ │ │ ├── use-backups.ts
│ │ │ │ └── use-interaction-policies.ts
│ │ │ ├── statuses
│ │ │ │ ├── scheduled-statuses.ts
│ │ │ │ ├── use-bookmark-folders.ts
│ │ │ │ ├── use-interaction-requests.ts
│ │ │ │ ├── use-status-history.ts
│ │ │ │ ├── use-status-interactions.ts
│ │ │ │ ├── use-status-quotes.ts
│ │ │ │ └── use-status-translation.ts
│ │ │ ├── suggestions.test.ts
│ │ │ ├── suggestions.ts
│ │ │ ├── timelines
│ │ │ │ ├── use-account-media-timeline.ts
│ │ │ │ └── use-events-lists.ts
│ │ │ ├── trends.test.ts
│ │ │ ├── trends.ts
│ │ │ ├── trends
│ │ │ │ ├── use-suggested-accounts.ts
│ │ │ │ ├── use-trending-links.ts
│ │ │ │ └── use-trending-statuses.ts
│ │ │ └── utils
│ │ │ │ ├── make-paginated-response-query-options.ts
│ │ │ │ ├── make-paginated-response-query.ts
│ │ │ │ ├── minify-list.ts
│ │ │ │ └── mutation-options.ts
│ │ ├── ready.ts
│ │ ├── reducers
│ │ │ ├── accounts-meta.ts
│ │ │ ├── admin-user-index.ts
│ │ │ ├── admin.test.ts
│ │ │ ├── admin.ts
│ │ │ ├── aliases.ts
│ │ │ ├── auth.test.ts
│ │ │ ├── auth.ts
│ │ │ ├── compose.test.ts
│ │ │ ├── compose.ts
│ │ │ ├── contexts.test.ts
│ │ │ ├── contexts.ts
│ │ │ ├── conversations.test.ts
│ │ │ ├── conversations.ts
│ │ │ ├── draft-statuses.ts
│ │ │ ├── filters.test.ts
│ │ │ ├── filters.ts
│ │ │ ├── index.test.ts
│ │ │ ├── index.ts
│ │ │ ├── instance.test.ts
│ │ │ ├── instance.ts
│ │ │ ├── list-adder.test.ts
│ │ │ ├── list-adder.ts
│ │ │ ├── list-editor.ts
│ │ │ ├── lists.test.ts
│ │ │ ├── lists.ts
│ │ │ ├── me.test.ts
│ │ │ ├── me.ts
│ │ │ ├── meta.test.ts
│ │ │ ├── meta.ts
│ │ │ ├── notifications.ts
│ │ │ ├── onboarding.test.ts
│ │ │ ├── onboarding.ts
│ │ │ ├── pending-statuses.ts
│ │ │ ├── pl-fe.test.ts
│ │ │ ├── pl-fe.ts
│ │ │ ├── polls.test.ts
│ │ │ ├── polls.ts
│ │ │ ├── push-notifications.test.ts
│ │ │ ├── push-notifications.ts
│ │ │ ├── security.ts
│ │ │ ├── shoutbox.ts
│ │ │ ├── status-lists.test.ts
│ │ │ ├── status-lists.ts
│ │ │ ├── statuses.test.ts
│ │ │ ├── statuses.ts
│ │ │ ├── timelines.test.ts
│ │ │ └── timelines.ts
│ │ ├── schemas
│ │ │ ├── pl-fe
│ │ │ │ └── settings.ts
│ │ │ ├── pleroma.ts
│ │ │ └── utils.ts
│ │ ├── selectors
│ │ │ └── index.ts
│ │ ├── sentry.ts
│ │ ├── service-worker
│ │ │ ├── sw.ts
│ │ │ └── web-push-locales.ts
│ │ ├── settings.ts
│ │ ├── storage
│ │ │ └── kv-store.ts
│ │ ├── store.ts
│ │ ├── stores
│ │ │ ├── account-hover-card.ts
│ │ │ ├── modals.ts
│ │ │ ├── settings.ts
│ │ │ ├── status-hover-card.ts
│ │ │ ├── status-meta.ts
│ │ │ └── ui.ts
│ │ ├── styles
│ │ │ ├── accessibility.scss
│ │ │ ├── application.scss
│ │ │ ├── basics.scss
│ │ │ ├── components
│ │ │ │ ├── columns.scss
│ │ │ │ ├── compose-form.scss
│ │ │ │ ├── datepicker.scss
│ │ │ │ ├── detailed-status.scss
│ │ │ │ ├── icon.scss
│ │ │ │ ├── media-gallery.scss
│ │ │ │ ├── modal.scss
│ │ │ │ ├── notification.scss
│ │ │ │ ├── status.scss
│ │ │ │ └── video-player.scss
│ │ │ ├── emoji-picker.scss
│ │ │ ├── forms.scss
│ │ │ ├── i18n
│ │ │ │ ├── arabic.css
│ │ │ │ └── javanese.css
│ │ │ ├── loading.scss
│ │ │ ├── markup.scss
│ │ │ ├── mfm.scss
│ │ │ ├── tailwind.css
│ │ │ ├── ui.scss
│ │ │ └── utilities.scss
│ │ ├── toast.test.tsx
│ │ ├── toast.tsx
│ │ ├── types
│ │ │ ├── colors.ts
│ │ │ ├── entities.ts
│ │ │ ├── history.ts
│ │ │ └── pl-fe.ts
│ │ └── utils
│ │ │ ├── accounts.test.ts
│ │ │ ├── accounts.ts
│ │ │ ├── auth.ts
│ │ │ ├── badges.test.ts
│ │ │ ├── badges.ts
│ │ │ ├── base64.test.ts
│ │ │ ├── base64.ts
│ │ │ ├── chats.test.ts
│ │ │ ├── chats.ts
│ │ │ ├── check-instance-capability.ts
│ │ │ ├── code-compiletime.ts
│ │ │ ├── code.ts
│ │ │ ├── colors.test.ts
│ │ │ ├── colors.ts
│ │ │ ├── comparators.test.ts
│ │ │ ├── comparators.ts
│ │ │ ├── config-db.test.ts
│ │ │ ├── config-db.ts
│ │ │ ├── console.ts
│ │ │ ├── copy.ts
│ │ │ ├── download.ts
│ │ │ ├── emoji-reacts.test.ts
│ │ │ ├── emoji-reacts.ts
│ │ │ ├── emoji.test.ts
│ │ │ ├── emoji.ts
│ │ │ ├── errors.ts
│ │ │ ├── favicon-service.ts
│ │ │ ├── html.test.ts
│ │ │ ├── html.ts
│ │ │ ├── input.test.ts
│ │ │ ├── input.ts
│ │ │ ├── media-aspect-ratio.ts
│ │ │ ├── media.test.ts
│ │ │ ├── media.ts
│ │ │ ├── normalizers.ts
│ │ │ ├── notification.ts
│ │ │ ├── numbers.test.tsx
│ │ │ ├── numbers.tsx
│ │ │ ├── nyaize.ts
│ │ │ ├── queries.test.ts
│ │ │ ├── queries.ts
│ │ │ ├── redirect.ts
│ │ │ ├── resize-image.ts
│ │ │ ├── rich-content.ts
│ │ │ ├── rtl.ts
│ │ │ ├── scopes.ts
│ │ │ ├── sounds.ts
│ │ │ ├── state.ts
│ │ │ ├── static.ts
│ │ │ ├── status.test.ts
│ │ │ ├── status.ts
│ │ │ ├── strings.ts
│ │ │ ├── suggestions.ts
│ │ │ ├── sw.ts
│ │ │ ├── tailwind.test.ts
│ │ │ ├── tailwind.ts
│ │ │ ├── theme.ts
│ │ │ ├── timelines.test.ts
│ │ │ ├── timelines.ts
│ │ │ ├── url-purify.ts
│ │ │ └── url.ts
│ ├── tailwind.config.ts
│ ├── tailwind
│ │ ├── colors.test.ts
│ │ └── colors.ts
│ ├── tsconfig.json
│ ├── vite.config.ts
│ └── yarn.lock
└── pl-hooks
│ ├── .eslintignore
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── lib
│ ├── contexts
│ │ ├── api-client.ts
│ │ └── query-client.ts
│ ├── hooks
│ │ ├── account-lists
│ │ │ └── use-directory.ts
│ │ ├── accounts
│ │ │ ├── use-account-lookup.ts
│ │ │ ├── use-account-relationship.ts
│ │ │ └── use-account.ts
│ │ ├── instance
│ │ │ ├── use-instance.ts
│ │ │ └── use-translation-languages.ts
│ │ ├── markers
│ │ │ ├── use-markers.ts
│ │ │ └── use-update-marker-mutation.ts
│ │ ├── notifications
│ │ │ ├── use-notification-list.ts
│ │ │ └── use-notification.ts
│ │ ├── polls
│ │ │ └── use-poll.ts
│ │ ├── search
│ │ │ └── use-search.ts
│ │ └── statuses
│ │ │ ├── use-status-history.ts
│ │ │ ├── use-status-quotes.ts
│ │ │ ├── use-status-translation.ts
│ │ │ └── use-status.ts
│ ├── importer.ts
│ ├── main.ts
│ ├── normalizers
│ │ ├── account.ts
│ │ ├── notification.ts
│ │ ├── status-edit.ts
│ │ ├── status-list.ts
│ │ └── status.ts
│ └── utils
│ │ └── queries.ts
│ ├── package.json
│ ├── tsconfig-build.json
│ ├── tsconfig.json
│ ├── vite.config.ts
│ └── yarn.lock
└── yarn.lock
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directories:
5 | - "/packages/pl-api"
6 | - "/packages/pl-fe"
7 | schedule:
8 | interval: "weekly"
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | yarn-error.log*
3 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn precommit
5 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | @transfem-org:registry=https://activitypub.software/api/v4/packages/npm/
2 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "bradlc.vscode-tailwindcss",
5 | "stylelint.vscode-stylelint",
6 | "wix.vscode-import-cost",
7 | "bradlc.vscode-tailwindcss"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "css.validate": false,
3 | "editor.insertSpaces": true,
4 | "editor.tabSize": 2,
5 | "files.associations": {
6 | "*.conf.template": "properties"
7 | },
8 | "files.eol": "\n",
9 | "files.insertFinalNewline": false,
10 | "json.schemas": [
11 | {
12 | "fileMatch": [".lintstagedrc.json"],
13 | "url": "https://json.schemastore.org/lintstagedrc.schema.json"
14 | },
15 | {
16 | "fileMatch": ["renovate.json"],
17 | "url": "https://docs.renovatebot.com/renovate-schema.json"
18 | }
19 | ],
20 | "scss.validate": false
21 | }
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "precommit": "lint-staged"
5 | },
6 | "devDependencies": {
7 | "husky": "^9.0.0",
8 | "lint-staged": ">=10"
9 | },
10 | "workspaces": ["pl-api", "pl-fe", "pl-hooks"],
11 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
12 | }
13 |
--------------------------------------------------------------------------------
/packages/pl-api/.eslintignore:
--------------------------------------------------------------------------------
1 | /node_modules/**
2 | /dist/**
3 | /static/**
4 | /public/**
5 | /tmp/**
6 | /coverage/**
7 | /custom/**
8 |
--------------------------------------------------------------------------------
/packages/pl-api/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | /.eslintcache
11 |
12 | node_modules
13 | dist
14 | dist-ssr
15 | docs
16 | *.local
17 |
18 | .idea
19 | .DS_Store
20 |
21 | # Editor directories and files
22 | .vscode/*
23 | !.vscode/extensions.json
24 | .idea
25 | .DS_Store
26 | *.suo
27 | *.ntvs*
28 | *.njsproj
29 | *.sln
30 | *.sw?
31 |
--------------------------------------------------------------------------------
/packages/pl-api/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | pl-api
7 |
8 |
9 |
10 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/announcement.ts:
--------------------------------------------------------------------------------
1 | import pick from 'lodash.pick';
2 | import * as v from 'valibot';
3 |
4 | import { announcementSchema } from '../announcement';
5 |
6 | /**
7 | * @category Admin schemas
8 | * @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminannouncements}
9 | */
10 | const adminAnnouncementSchema = v.pipe(
11 | v.any(),
12 | v.transform((announcement: any) => ({
13 | ...announcement,
14 | ...pick(announcement.pleroma, 'raw_content'),
15 | })),
16 | v.object({
17 | ...announcementSchema.entries,
18 | raw_content: v.fallback(v.string(), ''),
19 | }),
20 | );
21 |
22 | /**
23 | * @category Admin entity types
24 | */
25 | type AdminAnnouncement = v.InferOutput;
26 |
27 | export { adminAnnouncementSchema, type AdminAnnouncement };
28 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/canonical-email-block.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Admin schemas
5 | * @see {@link https://docs.joinmastodon.org/entities/Admin_CanonicalEmailBlock/}
6 | */
7 | const adminCanonicalEmailBlockSchema = v.object({
8 | id: v.string(),
9 | canonical_email_hash: v.string(),
10 | });
11 |
12 | /**
13 | * @category Admin entity types
14 | */
15 | type AdminCanonicalEmailBlock = v.InferOutput;
16 |
17 | export {
18 | adminCanonicalEmailBlockSchema,
19 | type AdminCanonicalEmailBlock,
20 | };
21 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/cohort.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { datetimeSchema } from '../utils';
4 |
5 | /**
6 | * @category Admin schemas
7 | * @see {@link https://docs.joinmastodon.org/entities/Admin_Cohort/}
8 | */
9 | const adminCohortSchema = v.object({
10 | period: datetimeSchema,
11 | frequency: v.picklist(['day', 'month']),
12 | data: v.array(v.object({
13 | date: datetimeSchema,
14 | rate: v.number(),
15 | value: v.pipe(v.unknown(), v.transform(Number)),
16 | })),
17 | });
18 |
19 | /**
20 | * @category Admin entity types
21 | */
22 | type AdminCohort = v.InferOutput;
23 |
24 | export {
25 | adminCohortSchema,
26 | type AdminCohort,
27 | };
28 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/dimension.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Admin schemas
5 | * @see {@link https://docs.joinmastodon.org/entities/Admin_Dimension/}
6 | */
7 | const adminDimensionSchema = v.object({
8 | key: v.string(),
9 | data: v.array(v.object({
10 | key: v.string(),
11 | human_key: v.string(),
12 | value: v.string(),
13 | unit: v.fallback(v.optional(v.string()), undefined),
14 | human_value: v.fallback(v.optional(v.string()), undefined),
15 | })),
16 | });
17 |
18 | /**
19 | * @category Admin entity types
20 | */
21 | type AdminDimension = v.InferOutput;
22 |
23 | export {
24 | adminDimensionSchema,
25 | type AdminDimension,
26 | };
27 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/domain-allow.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { datetimeSchema } from '../utils';
4 |
5 | /**
6 | * @category Admin schemas
7 | * @see {@link https://docs.joinmastodon.org/entities/Admin_DomainAllow/}
8 | */
9 | const adminDomainAllowSchema = v.object({
10 | id: v.string(),
11 | domain: v.string(),
12 | created_at: datetimeSchema,
13 | });
14 |
15 | /**
16 | * @category Admin entity types
17 | */
18 | type AdminDomainAllow = v.InferOutput;
19 |
20 | export {
21 | adminDomainAllowSchema,
22 | type AdminDomainAllow,
23 | };
24 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/domain.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { datetimeSchema } from '../utils';
4 |
5 | /**
6 | * @category Admin schemas
7 | */
8 | const adminDomainSchema = v.object({
9 | domain: v.fallback(v.string(), ''),
10 | id: v.pipe(v.unknown(), v.transform(String)),
11 | public: v.fallback(v.boolean(), false),
12 | resolves: v.fallback(v.boolean(), false),
13 | last_checked_at: v.fallback(v.nullable(datetimeSchema), null),
14 | });
15 |
16 | /**
17 | * @category Admin entity types
18 | */
19 | type AdminDomain = v.InferOutput
20 |
21 | export { adminDomainSchema, type AdminDomain };
22 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/email-domain-block.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { datetimeSchema } from '../utils';
4 |
5 | /**
6 | * @category Admin schemas
7 | * @see {@link https://docs.joinmastodon.org/entities/Admin_EmailDomainBlock/}
8 | */
9 | const adminEmailDomainBlockSchema = v.object({
10 | id: v.string(),
11 | domain: v.string(),
12 | created_at: datetimeSchema,
13 | history: v.array(v.object({
14 | day: v.pipe(v.unknown(), v.transform(String)),
15 | accounts: v.pipe(v.unknown(), v.transform(String)),
16 | uses: v.pipe(v.unknown(), v.transform(String)),
17 | })),
18 | });
19 |
20 | /**
21 | * @category Admin entity types
22 | */
23 | type AdminEmailDomainBlock = v.InferOutput;
24 |
25 | export {
26 | adminEmailDomainBlockSchema,
27 | type AdminEmailDomainBlock,
28 | };
29 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/ip-block.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { datetimeSchema } from '../utils';
4 |
5 | /**
6 | * @category Admin schemas
7 | * @see {@link https://docs.joinmastodon.org/entities/Admin_IpBlock/}
8 | */
9 | const adminIpBlockSchema = v.object({
10 | id: v.string(),
11 | ip: v.pipe(v.string(), v.ip()),
12 | severity: v.picklist(['sign_up_requires_approval', 'sign_up_block', 'no_access']),
13 | comment: v.fallback(v.string(), ''),
14 | created_at: datetimeSchema,
15 | expires_at: v.fallback(v.nullable(datetimeSchema), null),
16 | });
17 |
18 | /**
19 | * @category Admin entity types
20 | */
21 | type AdminIpBlock = v.InferOutput;
22 |
23 | export {
24 | adminIpBlockSchema,
25 | type AdminIpBlock,
26 | };
27 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/ip.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { datetimeSchema } from '../utils';
4 |
5 | /**
6 | * @category Admin schemas
7 | * @see {@link https://docs.joinmastodon.org/entities/Admin_Ip/}
8 | */
9 | const adminIpSchema = v.object({
10 | ip: v.pipe(v.string(), v.ip()),
11 | used_at: datetimeSchema,
12 | });
13 |
14 | /**
15 | * @category Admin entity types
16 | */
17 | type AdminIp = v.InferOutput;
18 |
19 | export {
20 | adminIpSchema,
21 | type AdminIp,
22 | };
23 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/moderation-log-entry.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Admin schemas
5 | * @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminmoderation_log}
6 | */
7 | const adminModerationLogEntrySchema = v.object({
8 | id: v.pipe(v.unknown(), v.transform(String)),
9 | data: v.fallback(v.record(v.string(), v.any()), {}),
10 | time: v.fallback(v.number(), 0),
11 | message: v.fallback(v.string(), ''),
12 | });
13 |
14 | /**
15 | * @category Admin entity types
16 | */
17 | type AdminModerationLogEntry = v.InferOutput
18 |
19 | export { adminModerationLogEntrySchema, type AdminModerationLogEntry };
20 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/pleroma-config.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Admin schemas
5 | */
6 | const pleromaConfigSchema = v.object({
7 | configs: v.array(v.object({
8 | value: v.any(),
9 | group: v.string(),
10 | key: v.string(),
11 | })),
12 | need_reboot: v.boolean(),
13 | });
14 |
15 | /**
16 | * @category Admin entity types
17 | */
18 | type PleromaConfig = v.InferOutput
19 |
20 | export { pleromaConfigSchema, type PleromaConfig };
21 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/relay.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Admin schemas
5 | */
6 | const adminRelaySchema = v.pipe(
7 | v.any(),
8 | v.transform((data: any) => ({ id: data.actor, ...data })),
9 | v.object({
10 | actor: v.fallback(v.string(), ''),
11 | id: v.string(),
12 | followed_back: v.fallback(v.boolean(), false),
13 | }),
14 | );
15 |
16 | /**
17 | * @category Admin entity types
18 | */
19 | type AdminRelay = v.InferOutput
20 |
21 | export { adminRelaySchema, type AdminRelay };
22 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/rule.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { datetimeSchema } from '../utils';
4 |
5 | /**
6 | * @category Admin schemas
7 | * @see {@link https://docs.pleroma.social/backend/development/API/admin_api/#get-apiv1pleromaadminrules}
8 | */
9 | const adminRuleSchema = v.object({
10 | id: v.string(),
11 | text: v.fallback(v.string(), ''),
12 | hint: v.fallback(v.string(), ''),
13 | priority: v.fallback(v.nullable(v.number()), null),
14 |
15 | created_at: v.fallback(v.optional(datetimeSchema), undefined),
16 | updated_at: v.fallback(v.optional(datetimeSchema), undefined),
17 | });
18 |
19 | /**
20 | * @category Admin entity types
21 | */
22 | type AdminRule = v.InferOutput;
23 |
24 | export { adminRuleSchema, type AdminRule };
25 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/admin/tag.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { tagSchema } from '../tag';
4 |
5 | /**
6 | * @category Admin schemas
7 | * @see {@link https://docs.joinmastodon.org/entities/Tag/#admin}
8 | */
9 | const adminTagSchema = v.object({
10 | ...tagSchema.entries,
11 | id: v.string(),
12 | trendable: v.boolean(),
13 | usable: v.boolean(),
14 | requires_review: v.boolean(),
15 | });
16 |
17 | /**
18 | * @category Admin entity types
19 | */
20 | type AdminTag = v.InferOutput;
21 |
22 | export {
23 | adminTagSchema,
24 | type AdminTag,
25 | };
26 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/announcement-reaction.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Schemas
5 | * @see {@link https://docs.joinmastodon.org/entities/announcement/}
6 | */
7 | const announcementReactionSchema = v.object({
8 | name: v.fallback(v.string(), ''),
9 | count: v.fallback(v.pipe(v.number(), v.integer(), v.minValue(0)), 0),
10 | me: v.fallback(v.boolean(), false),
11 | url: v.fallback(v.nullable(v.string()), null),
12 | static_url: v.fallback(v.nullable(v.string()), null),
13 | announcement_id: v.fallback(v.string(), ''),
14 | });
15 |
16 | /**
17 | * @category Entity types
18 | */
19 | type AnnouncementReaction = v.InferOutput;
20 |
21 | export { announcementReactionSchema, type AnnouncementReaction };
22 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/backup.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { datetimeSchema, mimeSchema } from './utils';
4 |
5 | /**
6 | * @category Schemas
7 | * @see {@link https://docs.pleroma.social/backend/development/API/pleroma_api/#post-apiv1pleromabackups}
8 | */
9 | const backupSchema = v.object({
10 | id: v.pipe(v.unknown(), v.transform(String)),
11 | content_type: mimeSchema,
12 | file_size: v.fallback(v.number(), 0),
13 | inserted_at: datetimeSchema,
14 | processed: v.fallback(v.boolean(), false),
15 | url: v.fallback(v.string(), ''),
16 | });
17 |
18 | /**
19 | * @category Entity types
20 | */
21 | type Backup = v.InferOutput;
22 |
23 | export { backupSchema, type Backup };
24 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/bookmark-folder.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Schemas
5 | */
6 | const bookmarkFolderSchema = v.pipe(v.any(), v.transform((data) => ({
7 | name: data.title,
8 | ...data,
9 | })), v.object({
10 | id: v.pipe(v.unknown(), v.transform(String)),
11 | name: v.fallback(v.string(), ''),
12 | emoji: v.fallback(v.nullable(v.string()), null),
13 | emoji_url: v.fallback(v.nullable(v.string()), null),
14 | }));
15 |
16 | /**
17 | * @category Entity types
18 | */
19 | type BookmarkFolder = v.InferOutput;
20 |
21 | export { bookmarkFolderSchema, type BookmarkFolder };
22 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/chat.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { accountSchema } from './account';
4 | import { chatMessageSchema } from './chat-message';
5 | import { datetimeSchema } from './utils';
6 |
7 | /**
8 | * @category Schemas
9 | * @see {@link https://docs.pleroma.social/backend/development/API/chats/#getting-a-list-of-chats}
10 | */
11 | const chatSchema = v.object({
12 | id: v.string(),
13 | account: accountSchema,
14 | unread: v.pipe(v.number(), v.integer()),
15 | last_message: v.fallback(v.nullable(chatMessageSchema), null),
16 | updated_at: datetimeSchema,
17 | });
18 |
19 | /**
20 | * @category Entity types
21 | */
22 | type Chat = v.InferOutput;
23 |
24 | export { chatSchema, type Chat };
25 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/circle.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Schemas
5 | */
6 | const circleSchema = v.object({
7 | id: v.string(),
8 | title: v.string(),
9 | });
10 |
11 | /**
12 | * @category Entity types
13 | */
14 | type Circle = v.InferOutput;
15 |
16 | export { circleSchema, type Circle };
17 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/context.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { statusSchema } from './status';
4 | import { filteredArray } from './utils';
5 |
6 | /**
7 | * @category Schemas
8 | * @see {@link https://docs.joinmastodon.org/entities/Context/}
9 | */
10 | const contextSchema = v.object({
11 | ancestors: filteredArray(statusSchema),
12 | descendants: filteredArray(statusSchema),
13 | references: v.fallback(filteredArray(statusSchema), []),
14 | });
15 |
16 | /**
17 | * @category Entity types
18 | */
19 | type Context = v.InferOutput;
20 |
21 | export { contextSchema, type Context };
22 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/conversation.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { accountSchema } from './account';
4 | import { statusSchema } from './status';
5 | import { filteredArray } from './utils';
6 |
7 | /**
8 | * @category Schemas
9 | * @see {@link https://docs.joinmastodon.org/entities/Conversation}
10 | */
11 | const conversationSchema = v.object({
12 | id: v.string(),
13 | unread: v.fallback(v.boolean(), false),
14 | accounts: filteredArray(accountSchema),
15 | last_status: v.fallback(v.nullable(statusSchema), null),
16 | });
17 |
18 | /**
19 | * @category Entity types
20 | */
21 | type Conversation = v.InferOutput;
22 |
23 | export { conversationSchema, type Conversation };
24 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/custom-emoji.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * Represents a custom emoji.
5 | *
6 | * @category Schemas
7 | * @see {@link https://docs.joinmastodon.org/entities/CustomEmoji/}
8 | */
9 | const customEmojiSchema = v.object({
10 | shortcode: v.string(),
11 | url: v.string(),
12 | static_url: v.fallback(v.string(), ''),
13 | visible_in_picker: v.fallback(v.boolean(), true),
14 | category: v.fallback(v.nullable(v.string()), null),
15 | });
16 |
17 | /**
18 | * @category Entity types
19 | */
20 | type CustomEmoji = v.InferOutput;
21 |
22 | export { customEmojiSchema, type CustomEmoji };
23 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/directory/category.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Directory schemas
5 | */
6 | const directoryCategorySchema = v.object({
7 | category: v.string(),
8 | servers_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null),
9 | });
10 |
11 | /**
12 | * @category Directory entity types
13 | */
14 | type DirectoryCategory = v.InferOutput;
15 |
16 | export { directoryCategorySchema, type DirectoryCategory };
17 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/directory/language.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Directory schemas
5 | */
6 | const directoryLanguageSchema = v.object({
7 | locale: v.string(),
8 | language: v.string(),
9 | servers_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null),
10 | });
11 |
12 | /**
13 | * @category Directory entity types
14 | */
15 | type DirectoryLanguage = v.InferOutput;
16 |
17 | export { directoryLanguageSchema, type DirectoryLanguage };
18 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/directory/statistics-period.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Directory schemas
5 | */
6 | const directoryStatisticsPeriodSchema = v.object({
7 | period: v.pipe(v.string(), v.isoDate()),
8 | server_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null),
9 | user_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null),
10 | active_user_count: v.fallback(v.nullable(v.pipe(v.unknown(), v.transform(Number))), null),
11 | });
12 |
13 | /**
14 | * @category Directory entity types
15 | */
16 | type DirectoryStatisticsPeriod = v.InferOutput;
17 |
18 | export { directoryStatisticsPeriodSchema, type DirectoryStatisticsPeriod };
19 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/domain-block.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Schemas
5 | * @see {@link https://docs.joinmastodon.org/entities/DomainBlock}
6 | */
7 | const domainBlockSchema = v.object({
8 | domain: v.string(),
9 | digest: v.string(),
10 | severity: v.picklist(['silence', 'suspend']),
11 | comment: v.fallback(v.optional(v.string()), undefined),
12 | });
13 |
14 | /**
15 | * @category Entity types
16 | */
17 | type DomainBlock = v.InferOutput;
18 |
19 | export { domainBlockSchema, type DomainBlock };
20 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/drive-file.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Schemas
5 | */
6 | const driveFileSchema = v.pipe(v.any(), v.transform((file) => ({
7 | ...file,
8 | thumbnail_url: file.thumbnailUrl,
9 | content_type: file.contentType,
10 | is_avatar: file.isAvatar,
11 | is_banner: file.isBanner,
12 | })), v.object({
13 | id: v.string(),
14 | url: v.string(),
15 | thumbnail_url: v.string(),
16 | filename: v.string(),
17 | content_type: v.string(),
18 | sensitive: v.boolean(),
19 | description: v.fallback(v.nullable(v.string()), null),
20 | is_avatar: v.boolean(),
21 | is_banner: v.boolean(),
22 | }));
23 |
24 | /**
25 | * @category Entity types
26 | */
27 | type DriveFile = v.InferOutput;
28 |
29 | export { driveFileSchema, type DriveFile };
30 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/drive-folder.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { bookmarkFolderSchema } from './bookmark-folder';
4 | import { driveFileSchema } from './drive-file';
5 | import { filteredArray } from './utils';
6 |
7 | /**
8 | * @category Schemas
9 | */
10 | const driveFolderSchema = v.pipe(v.any(), v.transform((folder) => ({
11 | ...folder,
12 | parent_id: folder.parentId,
13 | })), v.object({
14 | id: v.fallback(v.nullable(v.string()), null),
15 | name: v.fallback(v.nullable(v.string()), null),
16 | parent_id: v.fallback(v.nullable(v.string()), null),
17 | files: filteredArray(driveFileSchema),
18 | folders: filteredArray(bookmarkFolderSchema),
19 | }));
20 |
21 | /**
22 | * @category Entity types
23 | */
24 | type DriveFolder = v.InferOutput;
25 |
26 | export { driveFolderSchema, type DriveFolder };
27 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/extended-description.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { datetimeSchema } from './utils';
4 |
5 | /**
6 | * @category Schemas
7 | * @see {@link https://docs.joinmastodon.org/entities/ExtendedDescription}
8 | */
9 | const extendedDescriptionSchema = v.object({
10 | updated_at: datetimeSchema,
11 | content: v.string(),
12 | });
13 |
14 | /**
15 | * @category Entity types
16 | */
17 | type ExtendedDescription = v.InferOutput;
18 |
19 | export { extendedDescriptionSchema, type ExtendedDescription };
20 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/familiar-followers.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { accountSchema } from './account';
4 | import { filteredArray } from './utils';
5 |
6 | /**
7 | * @category Schemas
8 | * @see {@link https://docs.joinmastodon.org/entities/FamiliarFollowers/}
9 | */
10 | const familiarFollowersSchema = v.object({
11 | id: v.string(),
12 | accounts: filteredArray(accountSchema),
13 | });
14 |
15 | /**
16 | * @category Entity types
17 | */
18 | type FamiliarFollowers = v.InferOutput
19 |
20 | export { familiarFollowersSchema, type FamiliarFollowers };
21 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/featured-tag.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Schemas
5 | * @see {@link https://docs.joinmastodon.org/entities/FeaturedTag/}
6 | */
7 | const featuredTagSchema = v.object({
8 | id: v.string(),
9 | name: v.string(),
10 | url: v.fallback(v.optional(v.string()), undefined),
11 | statuses_count: v.number(),
12 | last_status_at: v.number(),
13 | });
14 |
15 | /**
16 | * @category Entity types
17 | */
18 | type FeaturedTag = v.InferOutput;
19 |
20 | export { featuredTagSchema, type FeaturedTag };
21 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/filter-result.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { baseFilterSchema } from './filter';
4 |
5 | const filterSchema = v.omit(baseFilterSchema, ['keywords', 'statuses']);
6 |
7 | /**
8 | * @category Schemas
9 | * @see {@link https://docs.joinmastodon.org/entities/FilterResult/}
10 | */
11 | const filterResultSchema = v.object({
12 | filter: filterSchema,
13 | keyword_matches: v.fallback(v.nullable(v.string()), null),
14 | status_matches: v.fallback(v.nullable(v.string()), null),
15 | });
16 |
17 | /**
18 | * @category Entity types
19 | */
20 | type FilterResult = v.InferOutput;
21 |
22 | export { filterResultSchema, type FilterResult };
23 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/group-relationship.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { GroupRoles } from './group-member';
4 |
5 | /**
6 | * @category Schemas
7 | */
8 | const groupRelationshipSchema = v.object({
9 | id: v.string(),
10 | member: v.fallback(v.boolean(), false),
11 | role: v.fallback(v.enum(GroupRoles), GroupRoles.USER),
12 | requested: v.fallback(v.boolean(), false),
13 | });
14 |
15 | /**
16 | * @category Entity types
17 | */
18 | type GroupRelationship = v.InferOutput;
19 |
20 | export { groupRelationshipSchema, type GroupRelationship };
21 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/list.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { antennaSchema } from './antenna';
4 | import { filteredArray } from './utils';
5 |
6 | /**
7 | * @category Schemas
8 | * @see {@link https://docs.joinmastodon.org/entities/List/}
9 | */
10 | const listSchema = v.object({
11 | id: v.pipe(v.unknown(), v.transform(String)),
12 | title: v.string(),
13 | replies_policy: v.fallback(v.optional(v.string()), undefined),
14 | exclusive: v.fallback(v.optional(v.boolean()), undefined),
15 | antennas: filteredArray(v.lazy(() => antennaSchema)),
16 | notify: v.fallback(v.optional(v.boolean()), undefined),
17 | favourite: v.fallback(v.optional(v.boolean()), undefined),
18 | });
19 |
20 | /**
21 | * @category Entity types
22 | */
23 | type List = v.InferOutput;
24 |
25 | export { listSchema, type List };
26 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/mention.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Schemas
5 | * @see {@link https://docs.joinmastodon.org/entities/Status/#Mention}
6 | */
7 | const mentionSchema = v.pipe(
8 | v.object({
9 | id: v.string(),
10 | username: v.fallback(v.string(), ''),
11 | url: v.fallback(v.pipe(v.string(), v.url()), ''),
12 | acct: v.string(),
13 | }),
14 | v.transform((mention) => {
15 | if (!mention.username) {
16 | mention.username = mention.acct.split('@')[0];
17 | }
18 |
19 | return mention;
20 | }),
21 | );
22 |
23 | /**
24 | * @category Entity types
25 | */
26 | type Mention = v.InferOutput;
27 |
28 | export { mentionSchema, type Mention };
29 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/notification-request.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { accountSchema } from './account';
4 | import { statusSchema } from './status';
5 | import { datetimeSchema } from './utils';
6 |
7 | /**
8 | * @category Schemas
9 | * @see {@link https://docs.joinmastodon.org/entities/NotificationRequest}
10 | */
11 | const notificationRequestSchema = v.object({
12 | id: v.string(),
13 | created_at: datetimeSchema,
14 | updated_at: datetimeSchema,
15 | account: accountSchema,
16 | notifications_count: v.pipe(v.unknown(), v.transform(String)),
17 | last_status: v.fallback(v.optional(statusSchema), undefined),
18 | });
19 |
20 | /**
21 | * @category Entity types
22 | */
23 | type NotificationRequest = v.InferOutput;
24 |
25 | export { notificationRequestSchema, type NotificationRequest };
26 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/preview-card-author.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { accountSchema } from './account';
4 |
5 | /**
6 | * @category Schemas
7 | * @see {@link https://docs.joinmastodon.org/entities/PreviewCardAuthor/}
8 | */
9 | const previewCardAuthorSchema = v.object({
10 | name: v.string(),
11 | url: v.pipe(v.string(), v.url()),
12 | account: v.fallback(v.nullable(accountSchema), null),
13 | });
14 |
15 | /**
16 | * @category Entity types
17 | */
18 | type PreviewCardAuthor = v.InferOutput;
19 |
20 | export { previewCardAuthorSchema, type PreviewCardAuthor };
21 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/privacy-policy.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { datetimeSchema } from './utils';
4 |
5 | /**
6 | * @category Schemas
7 | * @see {@link https://docs.joinmastodon.org/entities/PrivacyPolicy/}
8 | */
9 | const privacyPolicySchema = v.object({
10 | updated_at: datetimeSchema,
11 | content: v.string(),
12 | });
13 |
14 | /**
15 | * @category Entity types
16 | */
17 | type PrivacyPolicy = v.InferOutput;
18 |
19 | export { privacyPolicySchema, type PrivacyPolicy };
20 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/relationship-severance-event.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { datetimeSchema } from './utils';
4 |
5 | /**
6 | * @category Schemas
7 | * @see {@link https://docs.joinmastodon.org/entities/RelationshipSeveranceEvent/}
8 | */
9 | const relationshipSeveranceEventSchema = v.object({
10 | id: v.string(),
11 | type: v.picklist(['domain_block', 'user_domain_block', 'account_suspension']),
12 | purged: v.string(),
13 | relationships_count: v.fallback(v.optional(v.number()), undefined),
14 | created_at: datetimeSchema,
15 | });
16 |
17 | /**
18 | * @category Entity types
19 | */
20 | type RelationshipSeveranceEvent = v.InferOutput;
21 |
22 | export { relationshipSeveranceEventSchema, type RelationshipSeveranceEvent };
23 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/role.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | const hexSchema = v.pipe(v.string(), v.regex(/^#[a-f0-9]{6}$/i));
4 |
5 | /**
6 | * @category Schemas
7 | */
8 | const roleSchema = v.object({
9 | id: v.fallback(v.string(), ''),
10 | name: v.fallback(v.string(), ''),
11 | color: v.fallback(hexSchema, ''),
12 | permissions: v.fallback(v.string(), ''),
13 | highlighted: v.fallback(v.boolean(), true),
14 | });
15 |
16 | /**
17 | * @category Entity types
18 | */
19 | type Role = v.InferOutput;
20 |
21 | export {
22 | roleSchema,
23 | type Role,
24 | };
25 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/rss-feed.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Schemas
5 | */
6 | const rssFeedSchema = v.object({
7 | id: v.string(),
8 | url: v.string(),
9 | title: v.fallback(v.nullable(v.string()), null),
10 | description: v.fallback(v.nullable(v.string()), null),
11 | image_url: v.fallback(v.nullable(v.string()), null),
12 | });
13 |
14 | /**
15 | * @category Entity types
16 | */
17 | type RssFeed = v.InferOutput;
18 |
19 | export { rssFeedSchema, type RssFeed };
20 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/rule.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | const baseRuleSchema = v.object({
4 | id: v.string(),
5 | text: v.fallback(v.string(), ''),
6 | hint: v.fallback(v.string(), ''),
7 | translations: v.optional(v.record(v.string(), v.object({
8 | text: v.fallback(v.string(), ''),
9 | hint: v.fallback(v.string(), ''),
10 | })), undefined),
11 | });
12 |
13 | /**
14 | * @category Schemas
15 | * @see {@link https://docs.joinmastodon.org/entities/Rule/}
16 | */
17 | const ruleSchema = v.pipe(
18 | v.any(),
19 | v.transform((data: any) => ({
20 | ...data,
21 | hint: data.hint || data.subtext,
22 | })),
23 | baseRuleSchema,
24 | );
25 |
26 | /**
27 | * @category Entity types
28 | */
29 | type Rule = v.InferOutput;
30 |
31 | export { ruleSchema, type Rule };
32 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/search.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { accountSchema } from './account';
4 | import { groupSchema } from './group';
5 | import { statusSchema } from './status';
6 | import { tagSchema } from './tag';
7 | import { filteredArray } from './utils';
8 |
9 | /**
10 | * @category Schemas
11 | * @see {@link https://docs.joinmastodon.org/entities/Search}
12 | */
13 | const searchSchema = v.object({
14 | accounts: filteredArray(accountSchema),
15 | statuses: filteredArray(statusSchema),
16 | hashtags: filteredArray(tagSchema),
17 | groups: filteredArray(groupSchema),
18 | });
19 |
20 | /**
21 | * @category Entity types
22 | */
23 | type Search = v.InferOutput;
24 |
25 | export { searchSchema, type Search };
26 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/shout-message.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { accountSchema } from './account';
4 |
5 | /**
6 | * @category Schemas
7 | */
8 | const shoutMessageSchema = v.object({
9 | id: v.number(),
10 | text: v.string(),
11 | author: accountSchema,
12 | });
13 |
14 | /**
15 | * @category Entity types
16 | */
17 | type ShoutMessage = v.InferOutput;
18 |
19 | export { shoutMessageSchema, type ShoutMessage };
20 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/status-source.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { locationSchema } from './location';
4 |
5 | /**
6 | * @category Schemas
7 | * @see {@link https://docs.joinmastodon.org/entities/StatusSource/}
8 | */
9 | const statusSourceSchema = v.object({
10 | id: v.string(),
11 | text: v.fallback(v.string(), ''),
12 | spoiler_text: v.fallback(v.string(), ''),
13 |
14 | content_type: v.fallback(v.string(), 'text/plain'),
15 | location: v.fallback(v.nullable(locationSchema), null),
16 |
17 | text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null),
18 | spoiler_text_map: v.fallback(v.nullable(v.record(v.string(), v.string())), null),
19 | });
20 |
21 | /**
22 | * @category Entity types
23 | */
24 | type StatusSource = v.InferOutput;
25 |
26 | export { statusSourceSchema, type StatusSource };
27 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/story-carousel-item.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Schemas
5 | */
6 | const storyCarouselItemSchema = v.pipe(v.any(), v.transform((item) => ({
7 | account_id: item.pid,
8 | story_id: item.sid,
9 | ...item,
10 | })), v.object({
11 | account_id: v.string(),
12 | avatar: v.string(),
13 | local: v.boolean(),
14 | username: v.string(),
15 | latest: v.object({
16 | id: v.pipe(v.unknown(), v.transform(String)),
17 | type: v.string(),
18 | preview_url: v.string(),
19 | }),
20 | url: v.string(),
21 | seen: v.boolean(),
22 | story_id: v.pipe(v.unknown(), v.transform(String)),
23 | }));
24 |
25 | /**
26 | * @category Entity types
27 | */
28 | type StoryCarouselItem = v.InferOutput;
29 |
30 | export { storyCarouselItemSchema, type StoryCarouselItem };
31 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/story-media.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Schemas
5 | */
6 | const storyMediaSchema = v.pipe(v.any(), v.transform((media) => ({
7 | id: media.media_id,
8 | url: media.media_url,
9 | type: media.media_type,
10 | })), v.object({
11 | id: v.string(),
12 | url: v.string(),
13 | type: v.picklist(['photo', 'video']),
14 | }));
15 |
16 | /**
17 | * @category Entity types
18 | */
19 | type StoryMedia = v.InferOutput;
20 |
21 | export { storyMediaSchema, type StoryMedia };
22 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/subscription-details.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { datetimeSchema } from './utils';
4 |
5 | /**
6 | * @category Schemas
7 | */
8 | const subscriptionDetailsSchema = v.object({
9 | /** Subscription ID. */
10 | id: v.number(),
11 | /** The date when subscription expires. */
12 | expires_at: v.fallback(datetimeSchema, new Date().toISOString()),
13 | });
14 |
15 | /**
16 | * @category Entity types
17 | */
18 | type SubscriptionDetails = v.InferOutput;
19 |
20 | export {
21 | subscriptionDetailsSchema,
22 | type SubscriptionDetails,
23 | };
24 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/subscription-option.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Schemas
5 | */
6 | const subscriptionOptionSchema = v.object({
7 | /** Subscription type */
8 | type: v.picklist(['monero']),
9 | /** CAIP-2 chain ID. */
10 | chain_id: v.fallback(v.string(), ''),
11 | /** Subscription price (only for Monero) */
12 | price: v.fallback(v.nullable(v.number()), null),
13 | /** Payout address (only for Monero) */
14 | payout_address: v.fallback(v.string(), ''),
15 | });
16 |
17 | /**
18 | * @category Entity types
19 | */
20 | type SubscriptionOption = v.InferOutput;
21 |
22 | export {
23 | subscriptionOptionSchema,
24 | type SubscriptionOption,
25 | };
26 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/terms-of-service.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | import { dateSchema } from './utils';
4 |
5 | /**
6 | * @category Schemas
7 | * @see {@link https://docs.joinmastodon.org/entities/TermsOfService/}
8 | */
9 | const termsOfServiceSchema = v.object({
10 | effective_date: dateSchema,
11 | effective: v.boolean(),
12 | content: v.string(),
13 | succeeded_by: v.fallback(v.nullable(dateSchema), null),
14 | });
15 |
16 | /**
17 | * @category Entity types
18 | */
19 | type TermsOfService = v.InferOutput;
20 |
21 | export { termsOfServiceSchema, type TermsOfService };
22 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/token.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Schemas
5 | * @see {@link https://docs.joinmastodon.org/entities/Token/}
6 | */
7 | const tokenSchema = v.object({
8 | access_token: v.string(),
9 | token_type: v.string(),
10 | scope: v.string(),
11 | created_at: v.fallback(v.optional(v.number()), undefined),
12 |
13 | id: v.fallback(v.optional(v.pipe(v.unknown(), v.transform(String))), undefined),
14 | refresh_token: v.fallback(v.optional(v.string()), undefined),
15 | expires_in: v.fallback(v.optional(v.number()), undefined),
16 | me: v.fallback(v.optional(v.string()), undefined),
17 | });
18 |
19 | /**
20 | * @category Entity types
21 | */
22 | type Token = v.InferOutput;
23 |
24 | export { tokenSchema, type Token };
25 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/entities/web-push-subscription.ts:
--------------------------------------------------------------------------------
1 | import * as v from 'valibot';
2 |
3 | /**
4 | * @category Schemas
5 | * @see {@link https://docs.joinmastodon.org/entities/WebPushSubscription/}
6 | */
7 | const webPushSubscriptionSchema = v.object({
8 | id: v.pipe(v.unknown(), v.transform(String)),
9 | endpoint: v.string(),
10 | standard: v.fallback(v.boolean(), false),
11 | alerts: v.record(v.string(), v.boolean()),
12 | server_key: v.string(),
13 | });
14 |
15 | /**
16 | * @category Entity types
17 | */
18 | type WebPushSubscription = v.InferOutput;
19 |
20 | export { webPushSubscriptionSchema, type WebPushSubscription };
21 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/main.ts:
--------------------------------------------------------------------------------
1 | export { PlApiClient } from './client';
2 | export { PlApiDirectoryClient } from './directory-client';
3 | export { type Response as PlApiResponse } from './request';
4 | export * from './entities';
5 | export * from './features';
6 | export * from './params';
7 | export * from './responses';
8 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/params/antennas.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @category Request params
3 | */
4 | interface CreateAntennaParams {
5 | title: string;
6 | stl?: boolean;
7 | ltl?: boolean;
8 | insert_feeds?: boolean;
9 | with_media_only?: boolean;
10 | ignore_reblog?: boolean;
11 | favourite?: boolean;
12 | list_id?: string;
13 | }
14 |
15 | /**
16 | * @category Request params
17 | */
18 | type UpdateAntennaParams = Partial;
19 |
20 | export {
21 | type CreateAntennaParams,
22 | type UpdateAntennaParams,
23 | };
24 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/params/apps.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @category Request params
3 | */
4 | interface CreateApplicationParams {
5 | /** String. A name for your application */
6 | client_name: string;
7 | /** String. Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter. */
8 | redirect_uris: string;
9 | /** String. Space separated list of scopes. If none is provided, defaults to `read`. See [OAuth Scopes](https://docs.joinmastodon.org/api/oauth-scopes/) for a list of possible scopes. */
10 | scopes?: string;
11 | /** String. A URL to the homepage of your app */
12 | website?: string;
13 | }
14 |
15 | export type {
16 | CreateApplicationParams,
17 | };
18 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/params/chats.ts:
--------------------------------------------------------------------------------
1 | import { PaginationParams, WithMutedParam } from './common';
2 |
3 | /**
4 | * @category Request params
5 | */
6 | type GetChatsParams = PaginationParams & WithMutedParam;
7 |
8 | /**
9 | * @category Request params
10 | */
11 | type GetChatMessagesParams = PaginationParams;
12 |
13 | /**
14 | * @category Request params
15 | */
16 | type CreateChatMessageParams = {
17 | content?: string;
18 | media_id: string;
19 | } | {
20 | content: string;
21 | media_id?: string;
22 | };
23 |
24 | export type {
25 | GetChatsParams,
26 | GetChatMessagesParams,
27 | CreateChatMessageParams,
28 | };
29 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/params/circles.ts:
--------------------------------------------------------------------------------
1 | import { PaginationParams } from './common';
2 |
3 | /**
4 | * @category Request params
5 | */
6 | type GetCircleStatusesParams = PaginationParams;
7 |
8 | export type {
9 | GetCircleStatusesParams,
10 | };
11 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/params/drive.ts:
--------------------------------------------------------------------------------
1 | interface UpdateFileParams {
2 | filename: string;
3 | sensitive: boolean;
4 | description: string;
5 | }
6 |
7 | export type { UpdateFileParams };
8 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/params/index.ts:
--------------------------------------------------------------------------------
1 | export * from './accounts';
2 | export * from './admin';
3 | export * from './apps';
4 | export * from './chats';
5 | export type { PaginationParams } from './common';
6 | export * from './events';
7 | export * from './filtering';
8 | export * from './grouped-notifications';
9 | export * from './groups';
10 | export * from './instance';
11 | export * from './interaction-requests';
12 | export * from './lists';
13 | export * from './media';
14 | export * from './my-account';
15 | export * from './notifications';
16 | export * from './oauth';
17 | export * from './push-notifications';
18 | export * from './scheduled-statuses';
19 | export * from './search';
20 | export * from './settings';
21 | export * from './statuses';
22 | export * from './timelines';
23 | export * from './trends';
24 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/params/instance.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @category Request params
3 | */
4 | interface ProfileDirectoryParams {
5 | /** Number. Skip the first n results. */
6 | offset?: number;
7 | /** Number. How many accounts to load. Defaults to 40 accounts. Max 80 accounts. */
8 | limit?: number;
9 | /** String. Use active to sort by most recently posted statuses (default) or new to sort by most recently created profiles. */
10 | order?: string;
11 | /** Boolean. If true, returns only local accounts. */
12 | local?: boolean;
13 | }
14 |
15 | export type {
16 | ProfileDirectoryParams,
17 | };
18 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/params/lists.ts:
--------------------------------------------------------------------------------
1 | import type { PaginationParams } from './common';
2 |
3 | /**
4 | * @category Request params
5 | */
6 | interface CreateListParams {
7 | /** String. The title of the list to be created. */
8 | title: string;
9 | /** String. One of followed, list, or none. Defaults to list. */
10 | replies_policy?: 'followed' | 'list' | 'none';
11 | /** Boolean. Whether members of this list need to get removed from the “Home” feed */
12 | exclusive?: boolean;
13 | }
14 |
15 | /**
16 | * @category Request params
17 | */
18 | type UpdateListParams = CreateListParams;
19 |
20 | /**
21 | * @category Request params
22 | */
23 | type GetListAccountsParams = PaginationParams;
24 |
25 | export type {
26 | CreateListParams,
27 | UpdateListParams,
28 | GetListAccountsParams,
29 | };
30 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/params/scheduled-statuses.ts:
--------------------------------------------------------------------------------
1 | import type { PaginationParams } from './common';
2 |
3 | /**
4 | * @category Request params
5 | */
6 | type GetScheduledStatusesParams = PaginationParams;
7 |
8 | export type {
9 | GetScheduledStatusesParams,
10 | };
11 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/params/stories.ts:
--------------------------------------------------------------------------------
1 | interface CreateStoryPollParams {
2 | /** From 6 to 140 characters. */
3 | question: string;
4 | /** Between 2 and 4 answers. */
5 | answers: string;
6 | can_reply: boolean;
7 | can_react: boolean;
8 | }
9 |
10 | type StoryReportType = 'spam' | 'sensitive' | 'abusive' | 'underage' | 'copyright' | 'impersonation' | 'scam' | 'terrorism';
11 |
12 | interface CropStoryPhotoParams {
13 | width: number;
14 | height: number;
15 | x: number;
16 | y: number;
17 | }
18 |
19 | interface CreateStoryParams {
20 | /** Between 3 and 120 (in seconds). */
21 | duration: number;
22 | can_reply: boolean;
23 | can_react: boolean;
24 | }
25 |
26 | export type { CreateStoryPollParams, StoryReportType, CropStoryPhotoParams, CreateStoryParams };
27 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/params/trends.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @category Request params
3 | */
4 | interface GetTrends {
5 | /** Integer. Maximum number of results to return. */
6 | limit?: number;
7 | /** Integer. Skip the first n results. */
8 | offset?: number;
9 | }
10 |
11 | /**
12 | * @category Request params
13 | */
14 | type GetTrendingTags = GetTrends;
15 |
16 | /**
17 | * @category Request params
18 | */
19 | interface GetTrendingStatuses extends GetTrends {
20 | /**
21 | * Display trends from a given time range.
22 | *
23 | * Requires features{@link Features['trendingStatusesRange']}.
24 | */
25 | range?: 'daily' | 'monthly' | 'yearly';
26 | }
27 |
28 | /**
29 | * @category Request params
30 | */
31 | type GetTrendingLinks = GetTrends;
32 |
33 | export type {
34 | GetTrendingTags,
35 | GetTrendingStatuses,
36 | GetTrendingLinks,
37 | };
38 |
--------------------------------------------------------------------------------
/packages/pl-api/lib/responses.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @category Utils
3 | */
4 | interface PaginatedResponse {
5 | previous: (() => Promise>) | null;
6 | next: (() => Promise>) | null;
7 | items: IsArray extends true ? Array : T;
8 | partial: boolean;
9 | total?: number;
10 | }
11 |
12 | export type {
13 | PaginatedResponse,
14 | };
15 |
--------------------------------------------------------------------------------
/packages/pl-api/tsconfig-build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": ["lib"]
4 | }
--------------------------------------------------------------------------------
/packages/pl-api/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "moduleDetection": "force",
15 | "noEmit": true,
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src", "lib"]
24 | }
25 |
--------------------------------------------------------------------------------
/packages/pl-api/typedoc.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {Partial} */
2 |
3 | const config = {
4 | entryPoints: ['./lib/main.ts'],
5 | plugin: ['typedoc-material-theme', 'typedoc-plugin-valibot'],
6 | themeColor: '#d80482',
7 | navigation: {
8 | includeCategories: true,
9 | },
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/packages/pl-api/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path';
2 |
3 | import { defineConfig } from 'vite';
4 | import dts from 'vite-plugin-dts';
5 |
6 | import pkg from './package.json';
7 |
8 | export default defineConfig({
9 | plugins: [dts({ include: ['lib'], insertTypesEntry: true })],
10 | build: {
11 | copyPublicDir: false,
12 | lib: {
13 | entry: resolve(__dirname, 'lib/main.ts'),
14 | fileName: (format) => `main.${format}.js`,
15 | formats: ['es'],
16 | name: 'pl-api',
17 | },
18 | target: 'esnext',
19 | sourcemap: true,
20 | rollupOptions: {
21 | external: Object.keys(pkg.dependencies),
22 | },
23 | },
24 | });
25 |
--------------------------------------------------------------------------------
/packages/pl-fe/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 |
3 | /node_modules/
4 | /tmp/
5 | /build/
6 | /coverage/
7 | /.coverage/
8 | /.eslintcache
9 | /.env
10 | /deploy.sh
11 | /.vs/
12 | yarn-error.log*
13 | /junit.xml
14 |
15 | /dist/
16 | /static/
17 | /public/
18 | /dist/
19 | /pl-fe.zip
20 |
21 | .idea
22 | .DS_Store
23 |
24 | # Custom build files
25 | /custom/**/*
26 | !/custom/*
27 | /custom/*.*
28 | !/custom/.gitkeep
29 | !/custom/**/.gitkeep
30 |
31 | # surge.sh
32 | /CNAME
33 | /AUTH
34 | /CORS
35 | /ROUTER
36 |
--------------------------------------------------------------------------------
/packages/pl-fe/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 | root = true
3 |
4 | [*]
5 | end_of_line = lf
6 | insert_final_newline = true
7 | charset = utf-8
8 | indent_style = space
9 | indent_size = 2
10 |
--------------------------------------------------------------------------------
/packages/pl-fe/.env.example:
--------------------------------------------------------------------------------
1 | NODE_ENV=development
2 | # BACKEND_URL="https://example.com"
3 | # PROXY_HTTPS_INSECURE=false
4 |
--------------------------------------------------------------------------------
/packages/pl-fe/.eslintignore:
--------------------------------------------------------------------------------
1 | /node_modules/**
2 | /dist/**
3 | /static/**
4 | /public/**
5 | /tmp/**
6 | /coverage/**
7 | /custom/**
8 |
--------------------------------------------------------------------------------
/packages/pl-fe/.gitattributes:
--------------------------------------------------------------------------------
1 | CHANGELOG.md merge=union
--------------------------------------------------------------------------------
/packages/pl-fe/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /tmp/
3 | /build/
4 | /coverage/
5 | /.coverage/
6 | /.eslintcache
7 | /.env
8 | /deploy.sh
9 | /.vs/
10 | yarn-error.log*
11 | /junit.xml
12 | *.timestamp-*
13 | *.bundled_*
14 |
15 | /dist/
16 | /static/
17 | /public/
18 | /pl-fe.zip
19 |
20 | .idea
21 | .DS_Store
22 |
23 | # Custom build files
24 | /custom/**/*
25 | !/custom/*
26 | /custom/*.*
27 | !/custom/.gitkeep
28 | !/custom/**/.gitkeep
29 |
--------------------------------------------------------------------------------
/packages/pl-fe/.lintstagedrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "*.js": "eslint --cache",
3 | "*.cjs": "eslint --cache",
4 | "*.mjs": "eslint --cache",
5 | "*.ts": "eslint --cache",
6 | "*.tsx": "eslint --cache",
7 | "src/styles/**/*.scss": "stylelint"
8 | }
9 |
--------------------------------------------------------------------------------
/packages/pl-fe/.tool-versions:
--------------------------------------------------------------------------------
1 | nodejs 21.4.0
2 |
--------------------------------------------------------------------------------
/packages/pl-fe/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:21 as build
2 | WORKDIR /app
3 | COPY package.json .
4 | COPY yarn.lock .
5 | RUN yarn
6 | COPY . .
7 | ARG NODE_ENV=production
8 | RUN yarn build
9 |
10 | FROM nginx:stable-alpine
11 | EXPOSE 5000
12 | ENV PORT=5000
13 | ENV FALLBACK_PORT=4444
14 | ENV BACKEND_URL=http://localhost:4444
15 | ENV CSP=
16 | COPY installation/docker.conf.template /etc/nginx/templates/default.conf.template
17 | COPY --from=build /app/dist /usr/share/nginx/html
18 |
--------------------------------------------------------------------------------
/packages/pl-fe/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:21
2 |
3 | RUN apt-get update &&\
4 | apt-get install -y inotify-tools &&\
5 | # clean up apt
6 | rm -rf /var/lib/apt/lists/*
7 |
8 | WORKDIR /app
9 | ENV NODE_ENV=development
10 |
11 | COPY package.json .
12 | COPY yarn.lock .
13 | RUN yarn
14 |
15 | COPY . .
16 |
17 | ENV DEVSERVER_URL=http://0.0.0.0:3036
18 | CMD yarn dev
--------------------------------------------------------------------------------
/packages/pl-fe/README.md:
--------------------------------------------------------------------------------
1 | See [../../README.md](../../README.md) for more information on the project.
2 |
--------------------------------------------------------------------------------
/packages/pl-fe/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pl-fe",
3 | "description": "Mastodon-compatible social media front-end.",
4 | "keywords": ["fediverse"],
5 | "website": "https://github.com/mkljczk/pl-fe",
6 | "stack": "container"
7 | }
8 |
--------------------------------------------------------------------------------
/packages/pl-fe/compose-dev.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | app:
3 | build:
4 | context: .
5 | dockerfile: Dockerfile.dev
6 | image: pl-fe-dev
7 | ports:
8 | - "3036:3036"
9 | volumes:
10 | - .:/app
--------------------------------------------------------------------------------
/packages/pl-fe/custom/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/custom/.gitkeep
--------------------------------------------------------------------------------
/packages/pl-fe/custom/instance/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/custom/instance/.gitkeep
--------------------------------------------------------------------------------
/packages/pl-fe/custom/locales/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/custom/locales/.gitkeep
--------------------------------------------------------------------------------
/packages/pl-fe/custom/modules/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/custom/modules/.gitkeep
--------------------------------------------------------------------------------
/packages/pl-fe/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/favicon.ico
--------------------------------------------------------------------------------
/packages/pl-fe/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').ConfigFn} */
2 | const config = ({ env }) => ({
3 | plugins: {
4 | tailwindcss: {},
5 | autoprefixer: {},
6 | cssnano: env === 'production' ? {} : false,
7 | },
8 | });
9 |
10 | module.exports = config;
11 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/accounts_counter_follow.json:
--------------------------------------------------------------------------------
1 | {
2 | "9vMAje101ngtjlMj7w": {
3 | "followers_count": 2,
4 | "following_count": 3,
5 | "statuses_count": 2
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/accounts_counter_initial.json:
--------------------------------------------------------------------------------
1 | {
2 | "9vMAje101ngtjlMj7w": {
3 | "followers_count": 2,
4 | "following_count": 2,
5 | "statuses_count": 2
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/accounts_counter_unfollow.json:
--------------------------------------------------------------------------------
1 | {
2 | "9vMAje101ngtjlMj7w": {
3 | "followers_count": 2,
4 | "following_count": 1,
5 | "statuses_count": 2
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "vapid_key": "BHczIFh4Wn3Q_7wDgehaB8Ti3Uu8BoyOgXxkOVuEJRuEqxtd9TAno8K9ycz4myiQ1ruiyVfG6xT1JLeXtpxDzUs",
3 | "token_type": "Bearer",
4 | "client_secret": "cm_8Zip_UYyYq1DPQ-CRFUolrz894MmWYUC0aeVcklM",
5 | "redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
6 | "created_at": 1594764335,
7 | "name": "SoapboxFE_2020-07-14T22:05:17.054Z",
8 | "client_id": "bjiy8AxGKXXesfZcyp_iN-uQVE6Cnl03efWoSdOPh9M",
9 | "expires_in": 600,
10 | "scope": "read write follow push admin",
11 | "refresh_token": "IXoCKCsZi3ZCuCjIkeadvEoHRdqOYHklZmv9jvkJ5VA",
12 | "website": null,
13 | "id": "134",
14 | "access_token": "XSkQFSV1R_IvycQmw_uD5z6hQmNyuhh9PtMQbv8TgG8"
15 | }
16 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/blocks.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "22",
4 | "username": "twoods",
5 | "acct": "twoods",
6 | "display_name": "Tiger Woods"
7 | }
8 | ]
9 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/gotosocial-account.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "00YSECR4P7E64BD5MBA639PRVT",
3 | "username": "alex",
4 | "acct": "alex",
5 | "display_name": "Alex Gleason",
6 | "locked": false,
7 | "bot": false,
8 | "created_at": "2022-02-23T22:43:55Z",
9 | "note": "My GoToSocial profile
",
10 | "url": "http://localhost/@alex",
11 | "avatar": "",
12 | "avatar_static": "",
13 | "header": "",
14 | "header_static": "",
15 | "followers_count": 0,
16 | "following_count": 0,
17 | "statuses_count": 1,
18 | "last_status_at": "2022-02-23T22:54:14Z",
19 | "emojis": [],
20 | "fields": [],
21 | "source": {
22 | "privacy": "unlisted",
23 | "language": "en",
24 | "note": "My GoToSocial profile
",
25 | "fields": []
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/markers.json:
--------------------------------------------------------------------------------
1 | {
2 | "notifications": {
3 | "last_read_id": "35098814",
4 | "version": 361,
5 | "updated_at": "2019-11-26T22:37:25.239Z",
6 | "pleroma": {
7 | "unread_count": 3
8 | }
9 | },
10 | "home": {
11 | "last_read_id": "103206604258487607",
12 | "version": 468,
13 | "updated_at": "2019-11-26T22:37:25.235Z",
14 | "pleroma": {
15 | "unread_count": 32
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/mastodon-account.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "106801667066418367",
3 | "username": "benis911",
4 | "acct": "benis911",
5 | "display_name": "",
6 | "locked": false,
7 | "bot": false,
8 | "discoverable": null,
9 | "group": false,
10 | "created_at": "2021-08-22T00:00:00.000Z",
11 | "note": "",
12 | "url": "https://mastodon.social/@benis911",
13 | "avatar": "https://mastodon.social/avatars/original/missing.png",
14 | "avatar_static": "https://mastodon.social/avatars/original/missing.png",
15 | "header": "https://mastodon.social/headers/original/missing.png",
16 | "header_static": "https://mastodon.social/headers/original/missing.png",
17 | "followers_count": 1,
18 | "following_count": 0,
19 | "statuses_count": 5,
20 | "last_status_at": "2022-02-23",
21 | "emojis": [],
22 | "fields": []
23 | }
24 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/mitra-instance.json:
--------------------------------------------------------------------------------
1 | {
2 | "uri": "mitra.social",
3 | "title": "Mitra",
4 | "short_description": "Federated social network with smart contracts",
5 | "description": "This is an instance of [Mitra](https://codeberg.org/silverpill/mitra), federated social network built on [ActivityPub](https://activitypub.rocks/) protocol.\nRegistration is invitation-only.\nAdmin:\n - [@silverpill@mitra.social](https://mitra.social/profile/dd4ebc18-269d-4c7b-a310-03d29c6ab551)\n - Matrix: @silverpill:poa.st\n",
6 | "version": "3.0.0 (compatible; Mitra 0.4.0)",
7 | "registrations": false,
8 | "login_message": "Sign this message to log in to https://mitra.social. Do not sign this message on other sites!",
9 | "post_character_limit": 5000,
10 | "blockchain_explorer_url": null,
11 | "blockchain_contract_address": null,
12 | "ipfs_gateway_url": "https://ipfs.mitra.social"
13 | }
14 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/patron-instance.json:
--------------------------------------------------------------------------------
1 | {
2 | "funding": {
3 | "amount": 3500,
4 | "patrons": 3,
5 | "currency": "usd",
6 | "interval": "monthly"
7 | },
8 | "goals": [
9 | {
10 | "amount": 20000,
11 | "currency": "usd",
12 | "interval": "monthly",
13 | "text": "I'll be able to afford an avocado."
14 | }
15 | ],
16 | "url": "https://patron.gleasonator.com"
17 | }
18 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/patron-user.json:
--------------------------------------------------------------------------------
1 | {
2 | "is_patron": true,
3 | "url": "https://gleasonator.com/users/dave"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/relationship.json:
--------------------------------------------------------------------------------
1 | {
2 | "showing_reblogs": true,
3 | "followed_by": false,
4 | "subscribing": false,
5 | "blocked_by": false,
6 | "requested": false,
7 | "domain_blocking": false,
8 | "following": false,
9 | "endorsed": false,
10 | "blocking": true,
11 | "muting": false,
12 | "id": "9vMAje101ngtjlMj7w",
13 | "muting_notifications": true
14 | }
15 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/rules.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "1",
4 | "text": "Illegal activity and behavior",
5 | "subtext": "Content that depicts illegal or criminal acts, threats of violence."
6 | },
7 | {
8 | "id": "2",
9 | "text": "Intellectual property infringement",
10 | "subtext": "Impersonating another account or business, infringing on intellectual property rights."
11 | }
12 | ]
13 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/status-quotes.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "account": {
4 | "id": "ABDSjI3Q0R8aDaz1U0"
5 | },
6 | "content": "quoast",
7 | "id": "AJsajx9hY4Q7IKQXEe",
8 | "pleroma": {
9 | "quote": {
10 | "content": "10
",
11 | "id": "AJmoVikzI3SkyITyim"
12 | }
13 | }
14 | }
15 | ]
16 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/__fixtures__/user.json:
--------------------------------------------------------------------------------
1 | {
2 | "access_token": "UVBP2e17b4pTpb_h8fImIm3F5a66IBVb-JkyZHs4gLE",
3 | "expires_in": 600,
4 | "me": "https://social.teci.world/users/curtis",
5 | "refresh_token": "c2DpbVxYZBJDogNn-VBNFES72yXPNUYQCv0CrXGOplY",
6 | "scope": "read write follow push admin",
7 | "token_type": "Bearer"
8 | }
9 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/actions/account-notes.ts:
--------------------------------------------------------------------------------
1 | import { importEntities } from 'pl-fe/actions/importer';
2 |
3 | import { getClient } from '../api';
4 |
5 | import type { AppDispatch, RootState } from 'pl-fe/store';
6 |
7 | const submitAccountNote = (accountId: string, value: string) =>
8 | (dispatch: AppDispatch, getState: () => RootState) =>
9 | getClient(getState).accounts.updateAccountNote(accountId, value)
10 | .then(response => dispatch(importEntities({ relationships: [response] })));
11 |
12 | export { submitAccountNote };
13 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/actions/apps.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Apps: manage OAuth applications.
3 | * Particularly useful for auth.
4 | * https://docs.joinmastodon.org/methods/apps/
5 | * @module pl-fe/actions/apps
6 | * @see module:pl-fe/actions/auth
7 | */
8 |
9 | import { PlApiClient, type CreateApplicationParams } from 'pl-api';
10 |
11 | import * as BuildConfig from 'pl-fe/build-config';
12 |
13 | const createApp = (params: CreateApplicationParams, baseURL?: string) => {
14 | const client = new PlApiClient(baseURL || BuildConfig.BACKEND_URL || '');
15 |
16 | return client.apps.createApplication(params);
17 | };
18 |
19 | export {
20 | createApp,
21 | };
22 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/actions/chats.ts:
--------------------------------------------------------------------------------
1 | import { changeSetting } from 'pl-fe/actions/settings';
2 | import { useSettingsStore } from 'pl-fe/stores/settings';
3 |
4 | import type { AppDispatch, RootState } from 'pl-fe/store';
5 |
6 | const toggleMainWindow = () =>
7 | (dispatch: AppDispatch, getState: () => RootState) => {
8 | const main = useSettingsStore.getState().settings.chats.mainWindow;
9 | const state = main === 'minimized' ? 'open' : 'minimized';
10 | return dispatch(changeSetting(['chats', 'mainWindow'], state));
11 | };
12 |
13 | export {
14 | toggleMainWindow,
15 | };
16 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/actions/polls.ts:
--------------------------------------------------------------------------------
1 | import { getClient } from '../api';
2 |
3 | import { importEntities } from './importer';
4 |
5 | import type { AppDispatch, RootState } from 'pl-fe/store';
6 |
7 | const vote = (pollId: string, choices: number[]) =>
8 | (dispatch: AppDispatch, getState: () => RootState) =>
9 | getClient(getState()).polls.vote(pollId, choices).then((data) => {
10 | dispatch(importEntities({ polls: [data] }));
11 | });
12 |
13 | const fetchPoll = (pollId: string) =>
14 | (dispatch: AppDispatch, getState: () => RootState) =>
15 | getClient(getState()).polls.getPoll(pollId).then((data) => {
16 | dispatch(importEntities({ polls: [data] }));
17 | });
18 |
19 | export {
20 | vote,
21 | fetchPoll,
22 | };
23 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/actions/push-subscriptions.ts:
--------------------------------------------------------------------------------
1 | import { getClient } from '../api';
2 |
3 | import type { CreatePushNotificationsSubscriptionParams } from 'pl-api';
4 | import type { AppDispatch, RootState } from 'pl-fe/store';
5 |
6 | const createPushSubscription = (params: CreatePushNotificationsSubscriptionParams) =>
7 | (dispatch: AppDispatch, getState: () => RootState) =>
8 | getClient(getState).pushNotifications.createSubscription(params);
9 |
10 | export {
11 | createPushSubscription,
12 | };
13 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/api/hooks/groups/use-create-group.ts:
--------------------------------------------------------------------------------
1 | import { Entities } from 'pl-fe/entity-store/entities';
2 | import { useCreateEntity } from 'pl-fe/entity-store/hooks/use-create-entity';
3 | import { useClient } from 'pl-fe/hooks/use-client';
4 | import { normalizeGroup, type Group } from 'pl-fe/normalizers/group';
5 |
6 | import type { Group as BaseGroup, CreateGroupParams } from 'pl-api';
7 |
8 | const useCreateGroup = () => {
9 | const client = useClient();
10 |
11 | const { createEntity, ...rest } = useCreateEntity(
12 | [Entities.GROUPS, 'search', ''],
13 | (params: CreateGroupParams) => client.experimental.groups.createGroup(params),
14 | { transform: normalizeGroup },
15 | );
16 |
17 | return {
18 | createGroup: createEntity,
19 | ...rest,
20 | };
21 | };
22 |
23 | export { useCreateGroup };
24 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/api/hooks/groups/use-delete-group.ts:
--------------------------------------------------------------------------------
1 | import { Entities } from 'pl-fe/entity-store/entities';
2 | import { useDeleteEntity } from 'pl-fe/entity-store/hooks/use-delete-entity';
3 | import { useClient } from 'pl-fe/hooks/use-client';
4 |
5 | const useDeleteGroup = () => {
6 | const client = useClient();
7 |
8 | const { deleteEntity, isSubmitting } = useDeleteEntity(
9 | Entities.GROUPS,
10 | (groupId: string) => client.experimental.groups.deleteGroup(groupId),
11 | );
12 |
13 | return {
14 | mutate: deleteEntity,
15 | isSubmitting,
16 | };
17 | };
18 |
19 | export { useDeleteGroup };
20 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/api/hooks/groups/use-join-group.ts:
--------------------------------------------------------------------------------
1 | import { Entities } from 'pl-fe/entity-store/entities';
2 | import { useCreateEntity } from 'pl-fe/entity-store/hooks/use-create-entity';
3 | import { useClient } from 'pl-fe/hooks/use-client';
4 |
5 | import { useGroups } from './use-groups';
6 |
7 | import type { Group } from 'pl-api';
8 |
9 | const useJoinGroup = (group: Pick) => {
10 | const client = useClient();
11 | const { invalidate } = useGroups();
12 |
13 | const { createEntity, isSubmitting } = useCreateEntity(
14 | [Entities.GROUP_RELATIONSHIPS, group.id],
15 | () => client.experimental.groups.joinGroup(group.id),
16 | );
17 |
18 | return {
19 | mutate: createEntity,
20 | isSubmitting,
21 | invalidate,
22 | };
23 | };
24 |
25 | export { useJoinGroup };
26 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/api/hooks/groups/use-leave-group.ts:
--------------------------------------------------------------------------------
1 | import { Entities } from 'pl-fe/entity-store/entities';
2 | import { useCreateEntity } from 'pl-fe/entity-store/hooks/use-create-entity';
3 | import { useClient } from 'pl-fe/hooks/use-client';
4 |
5 | import { useGroups } from './use-groups';
6 |
7 | import type { Group } from 'pl-api';
8 |
9 | const useLeaveGroup = (group: Pick) => {
10 | const client = useClient();
11 | const { invalidate } = useGroups();
12 |
13 | const { createEntity, isSubmitting } = useCreateEntity(
14 | [Entities.GROUP_RELATIONSHIPS, group.id],
15 | () => client.experimental.groups.leaveGroup(group.id),
16 | );
17 |
18 | return {
19 | mutate: createEntity,
20 | isSubmitting,
21 | invalidate,
22 | };
23 | };
24 |
25 | export { useLeaveGroup };
26 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/api/hooks/streaming/use-bubble-stream.ts:
--------------------------------------------------------------------------------
1 | import { useTimelineStream } from './use-timeline-stream';
2 |
3 | interface UseBubbleStreamOpts {
4 | onlyMedia?: boolean;
5 | enabled?: boolean;
6 | }
7 |
8 | const useBubbleStream = ({ onlyMedia, enabled }: UseBubbleStreamOpts = {}) =>
9 | useTimelineStream(`bubble${onlyMedia ? ':media' : ''}`, {}, enabled);
10 |
11 | export { useBubbleStream };
12 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/api/hooks/streaming/use-community-stream.ts:
--------------------------------------------------------------------------------
1 | import { useTimelineStream } from './use-timeline-stream';
2 |
3 | interface UseCommunityStreamOpts {
4 | onlyMedia?: boolean;
5 | enabled?: boolean;
6 | }
7 |
8 | const useCommunityStream = ({ onlyMedia, enabled }: UseCommunityStreamOpts = {}) =>
9 | useTimelineStream(`public:local${onlyMedia ? ':media' : ''}`, {}, enabled);
10 |
11 | export { useCommunityStream };
12 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/api/hooks/streaming/use-direct-stream.ts:
--------------------------------------------------------------------------------
1 | import { useTimelineStream } from './use-timeline-stream';
2 |
3 | const useDirectStream = () => useTimelineStream('direct');
4 |
5 | export { useDirectStream };
6 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/api/hooks/streaming/use-group-stream.ts:
--------------------------------------------------------------------------------
1 | import { useTimelineStream } from './use-timeline-stream';
2 |
3 | const useGroupStream = (groupId: string) => useTimelineStream('group', { group: groupId } as any);
4 |
5 | export { useGroupStream };
6 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/api/hooks/streaming/use-hashtag-stream.ts:
--------------------------------------------------------------------------------
1 | import { useTimelineStream } from './use-timeline-stream';
2 |
3 | const useHashtagStream = (tag: string) => useTimelineStream('hashtag', { tag });
4 |
5 | export { useHashtagStream };
6 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/api/hooks/streaming/use-list-stream.ts:
--------------------------------------------------------------------------------
1 | import { useLoggedIn } from 'pl-fe/hooks/use-logged-in';
2 |
3 | import { useTimelineStream } from './use-timeline-stream';
4 |
5 | const useListStream = (listId: string) => {
6 | const { isLoggedIn } = useLoggedIn();
7 |
8 | return useTimelineStream('list', { list: listId }, isLoggedIn);
9 | };
10 |
11 | export { useListStream };
12 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/api/hooks/streaming/use-public-stream.ts:
--------------------------------------------------------------------------------
1 | import { useTimelineStream } from './use-timeline-stream';
2 |
3 | interface UsePublicStreamOpts {
4 | onlyMedia?: boolean;
5 | }
6 |
7 | const usePublicStream = ({ onlyMedia }: UsePublicStreamOpts = {}) =>
8 | useTimelineStream(`public${onlyMedia ? ':media' : ''}`);
9 |
10 | export { usePublicStream };
11 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/api/hooks/streaming/use-remote-stream.ts:
--------------------------------------------------------------------------------
1 | import { useTimelineStream } from './use-timeline-stream';
2 |
3 | interface UseRemoteStreamOpts {
4 | instance: string;
5 | onlyMedia?: boolean;
6 | }
7 |
8 | const useRemoteStream = ({ instance, onlyMedia }: UseRemoteStreamOpts) =>
9 | useTimelineStream(`public:remote${onlyMedia ? ':media' : ''}`, { instance } as any);
10 |
11 | export { useRemoteStream };
12 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/assets/images/audio-placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/src/assets/images/audio-placeholder.png
--------------------------------------------------------------------------------
/packages/pl-fe/src/assets/images/avatar-missing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/src/assets/images/avatar-missing.png
--------------------------------------------------------------------------------
/packages/pl-fe/src/assets/images/header-missing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/src/assets/images/header-missing.png
--------------------------------------------------------------------------------
/packages/pl-fe/src/assets/images/video-placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/src/assets/images/video-placeholder.png
--------------------------------------------------------------------------------
/packages/pl-fe/src/assets/images/web-push/web-push-icon_expand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/src/assets/images/web-push/web-push-icon_expand.png
--------------------------------------------------------------------------------
/packages/pl-fe/src/assets/images/web-push/web-push-icon_favourite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/src/assets/images/web-push/web-push-icon_favourite.png
--------------------------------------------------------------------------------
/packages/pl-fe/src/assets/images/web-push/web-push-icon_reblog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/src/assets/images/web-push/web-push-icon_reblog.png
--------------------------------------------------------------------------------
/packages/pl-fe/src/assets/sounds/LICENSE.md:
--------------------------------------------------------------------------------
1 | # Sound licenses
2 |
3 | - `chat.mp3`
4 | - `chat.ogg`
5 |
6 | © [notificationsounds.com](https://notificationsounds.com/notification-sounds/intuition-561), licensed under [CC BY 4.0](https://creativecommons.org/licenses/by-sa/4.0/).
7 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/assets/sounds/boop.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/src/assets/sounds/boop.mp3
--------------------------------------------------------------------------------
/packages/pl-fe/src/assets/sounds/boop.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/src/assets/sounds/boop.ogg
--------------------------------------------------------------------------------
/packages/pl-fe/src/assets/sounds/chat.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/src/assets/sounds/chat.mp3
--------------------------------------------------------------------------------
/packages/pl-fe/src/assets/sounds/chat.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mkljczk/pl-fe/08822a32d8d1fb38b786f98d8e9ff44bdf573af6/packages/pl-fe/src/assets/sounds/chat.ogg
--------------------------------------------------------------------------------
/packages/pl-fe/src/build-config.ts:
--------------------------------------------------------------------------------
1 | import type { PlFeEnv } from './build-config-compiletime';
2 |
3 | export const {
4 | NODE_ENV,
5 | BACKEND_URL,
6 | FE_SUBDIRECTORY,
7 | WITH_LANDING_PAGE,
8 | } = import.meta.compileTime('./build-config-compiletime.ts');
9 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/badge.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { render, screen } from 'pl-fe/jest/test-helpers';
4 |
5 | import Badge from './badge';
6 |
7 | describe('', () => {
8 | it('renders correctly', () => {
9 | render();
10 |
11 | expect(screen.getByTestId('badge')).toHaveTextContent('Patron');
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/dropdown-menu/index.ts:
--------------------------------------------------------------------------------
1 | export { default, type Menu } from './dropdown-menu';
2 | export type { MenuItem } from './dropdown-menu-item';
3 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/fork-awesome-icon.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * ForkAwesomeIcon: renders a ForkAwesome icon.
3 | * Full list: https://forkaweso.me/Fork-Awesome/icons/
4 | * @module pl-fe/components/fork_awesome_icon
5 | * @see pl-fe/components/icon
6 | */
7 |
8 | import clsx from 'clsx';
9 | import React from 'react';
10 |
11 | interface IForkAwesomeIcon extends React.HTMLAttributes {
12 | id: string;
13 | className?: string;
14 | fixedWidth?: boolean;
15 | }
16 |
17 | const ForkAwesomeIcon: React.FC = ({ id, className, fixedWidth, ...rest }) => (
18 |
24 | );
25 |
26 | export { ForkAwesomeIcon as default };
27 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/hashtag-link.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Link from './link';
4 |
5 | interface IHashtagLink {
6 | hashtag: string;
7 | }
8 |
9 | const HashtagLink: React.FC = ({ hashtag }) => (
10 | e.stopPropagation()}>
11 | #{hashtag}
12 |
13 | );
14 |
15 | export { HashtagLink as default };
16 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/icon-with-counter.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Icon, { IIcon } from 'pl-fe/components/icon';
4 | import Counter from 'pl-fe/components/ui/counter';
5 |
6 | interface IIconWithCounter extends React.HTMLAttributes {
7 | count: number;
8 | countMax?: number;
9 | icon?: string;
10 | src?: string;
11 | }
12 |
13 | const IconWithCounter: React.FC = ({ icon, count, countMax, ...rest }) => (
14 |
15 |
16 |
17 | {count > 0 && (
18 |
19 |
20 |
21 | )}
22 |
23 | );
24 |
25 | export { IconWithCounter as default };
26 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/icon.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Icon: abstract component to render SVG icons.
3 | * @module pl-fe/components/icon
4 | */
5 |
6 | import clsx from 'clsx';
7 | import React from 'react';
8 | import InlineSVG from 'react-inlinesvg'; // eslint-disable-line no-restricted-imports
9 |
10 | interface IIcon extends React.HTMLAttributes {
11 | src: string;
12 | id?: string;
13 | alt?: string;
14 | className?: string;
15 | }
16 |
17 | /**
18 | * @deprecated Use the UI Icon component directly.
19 | */
20 | const Icon: React.FC = ({ src, alt, className, ...rest }) => (
21 |
25 | >} />
26 |
27 | );
28 |
29 | export { type IIcon, Icon as default };
30 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/landing-gradient.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | /** Fullscreen gradient used as a backdrop to public pages. */
4 | const LandingGradient: React.FC = () => (
5 |
6 | );
7 |
8 | export { LandingGradient as default };
9 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/link.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link as Comp, LinkProps } from 'react-router-dom';
3 |
4 | const Link = (props: LinkProps) => (
5 |
9 | );
10 |
11 | export { Link as default };
12 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/load-more.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 |
4 | import Button from 'pl-fe/components/ui/button';
5 |
6 | interface ILoadMore {
7 | onClick: React.MouseEventHandler;
8 | disabled?: boolean;
9 | visible?: boolean;
10 | className?: string;
11 | }
12 |
13 | const LoadMore: React.FC = ({ onClick, disabled, visible = true, className }) => {
14 | if (!visible) {
15 | return null;
16 | }
17 |
18 | return (
19 |
22 | );
23 | };
24 |
25 | export { LoadMore as default };
26 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/loading-screen.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import LandingGradient from 'pl-fe/components/landing-gradient';
4 | import Spinner from 'pl-fe/components/ui/spinner';
5 |
6 | /** Fullscreen loading indicator. */
7 | const LoadingScreen: React.FC = React.memo(() => (
8 |
17 | ));
18 |
19 | export { LoadingScreen as default };
20 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/markup.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Text, { IText } from './ui/text';
4 |
5 | interface IMarkup extends IText {
6 | }
7 |
8 | /** Styles HTML markup returned by the API, such as in account bios and statuses. */
9 | const Markup = React.forwardRef((props, ref) => );
10 |
11 | export { Markup as default };
12 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/outline-box.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import React from 'react';
3 |
4 | interface IOutlineBox extends React.HTMLAttributes {
5 | children: React.ReactNode;
6 | className?: string;
7 | }
8 |
9 | /** Wraps children in a container with an outline. */
10 | const OutlineBox: React.FC = ({ children, className, ...rest }) => (
11 |
15 | {children}
16 |
17 | );
18 |
19 | export { OutlineBox as default };
20 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/avatar.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { render, screen } from 'pl-fe/jest/test-helpers';
4 |
5 | import Avatar from './avatar';
6 |
7 | const src = '/static/alice.jpg';
8 |
9 | describe('', () => {
10 | it('renders', () => {
11 | render();
12 |
13 | expect(screen.getByRole('img')).toBeInTheDocument();
14 | });
15 |
16 | it('handles size props', () => {
17 | render();
18 |
19 | expect(screen.getByTestId('still-image-container').getAttribute('style')).toMatch(/50px/i);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/banner.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import React from 'react';
3 |
4 | interface IBanner {
5 | theme: 'frosted' | 'opaque';
6 | children: React.ReactNode;
7 | className?: string;
8 | }
9 |
10 | /** Displays a sticky full-width banner at the bottom of the screen. */
11 | const Banner: React.FC = ({ theme, children, className }) => (
12 |
19 |
20 | {children}
21 |
22 |
23 | );
24 |
25 | export { Banner as default };
26 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface ICheckbox extends Pick, 'disabled' | 'id' | 'name' | 'onChange' | 'checked' | 'required'> { }
4 |
5 | /** A pretty checkbox input. */
6 | const Checkbox = React.forwardRef((props, ref) => (
7 |
13 | ));
14 |
15 | export { Checkbox as default };
16 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/column.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { render, screen } from 'pl-fe/jest/test-helpers';
4 |
5 | import { Column } from './column';
6 |
7 | describe('', () => {
8 | it('renders correctly with minimal props', () => {
9 | render();
10 |
11 | expect(screen.getByRole('button')).toHaveTextContent('Back');
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/combobox.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --reach-combobox: 1;
3 | }
4 |
5 | [data-reach-combobox-popover] {
6 | @apply rounded-md shadow-lg bg-white dark:bg-gray-900 dark:ring-2 dark:ring-primary-700 z-[100];
7 | }
8 |
9 | [data-reach-combobox-list] {
10 | @apply list-none m-0 py-1 px-0 select-none;
11 | }
12 |
13 | [data-reach-combobox-option] {
14 | @apply block px-4 py-2.5 text-sm text-gray-700 dark:text-gray-500 cursor-pointer;
15 | }
16 |
17 | [data-reach-combobox-option][aria-selected="true"] {
18 | @apply bg-gray-100 dark:bg-gray-800;
19 | }
20 |
21 | [data-reach-combobox-option]:hover {
22 | @apply bg-gray-100 dark:bg-gray-800;
23 | }
24 |
25 | [data-reach-combobox-option][aria-selected="true"]:hover {
26 | @apply bg-gray-100 dark:bg-gray-800;
27 | }
28 |
29 | [data-suggested-value] {
30 | @apply font-bold;
31 | }
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/combobox.tsx:
--------------------------------------------------------------------------------
1 | import './combobox.css';
2 |
3 | export {
4 | Combobox as default,
5 | Combobox,
6 | ComboboxInput,
7 | ComboboxPopover,
8 | ComboboxList,
9 | ComboboxOption,
10 | ComboboxOptionText,
11 | } from '@reach/combobox';
12 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/counter.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import AnimatedNumber from 'pl-fe/components/animated-number';
4 |
5 | interface ICounter {
6 | /** Number this counter should display. */
7 | count: number;
8 | /** Optional max number (ie: N+) */
9 | countMax?: number;
10 | }
11 |
12 | /** A simple counter for notifications, etc. */
13 | const Counter: React.FC = ({ count, countMax }) => (
14 |
15 |
16 |
17 | );
18 |
19 | export { Counter as default };
20 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/divider.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { render, screen } from 'pl-fe/jest/test-helpers';
4 |
5 | import Divider from './divider';
6 |
7 | describe('', () => {
8 | it('renders without text', () => {
9 | render();
10 |
11 | expect(screen.queryAllByTestId('divider-text')).toHaveLength(0);
12 | });
13 |
14 | it('renders text', () => {
15 | const text = 'Hello';
16 | render();
17 |
18 | expect(screen.getByTestId('divider-text')).toHaveTextContent(text);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/emoji.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { render, screen } from 'pl-fe/jest/test-helpers';
4 |
5 | import Emoji from './emoji';
6 |
7 | describe('', () => {
8 | it('renders a simple emoji', () => {
9 | render();
10 |
11 | const img = screen.getByRole('img');
12 | expect(img.getAttribute('src')).toBe('/packs/emoji/1f600.svg');
13 | expect(img.getAttribute('alt')).toBe('😀');
14 | });
15 |
16 | // https://emojipedia.org/emoji-flag-sequence/
17 | it('renders a sequence emoji', () => {
18 | render();
19 |
20 | const img = screen.getByRole('img');
21 | expect(img.getAttribute('src')).toBe('/packs/emoji/1f1fa-1f1f8.svg');
22 | expect(img.getAttribute('alt')).toBe('🇺🇸');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/file-input.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from 'react';
2 |
3 | interface IFileInput extends Pick, 'onChange' | 'required' | 'disabled' | 'name' | 'accept'> { }
4 |
5 | const FileInput = forwardRef((props, ref) => (
6 |
12 | ));
13 |
14 | export { FileInput as default };
15 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/form-actions.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { render, screen } from 'pl-fe/jest/test-helpers';
4 |
5 | import FormActions from './form-actions';
6 |
7 | describe('', () => {
8 | it('renders successfully', () => {
9 | render(child
);
10 |
11 | expect(screen.getByTestId('child')).toBeInTheDocument();
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/form-actions.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import HStack from './hstack';
4 |
5 | interface IFormActions {
6 | children: React.ReactNode;
7 | }
8 |
9 | /** Container element to house form actions. */
10 | const FormActions: React.FC = ({ children }) => (
11 |
12 | {children}
13 |
14 | );
15 |
16 | export { FormActions as default };
17 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/form.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { fireEvent, render, screen } from 'pl-fe/jest/test-helpers';
4 |
5 | import Form from './form';
6 |
7 | describe('', () => {
8 | it('renders children', () => {
9 | const onSubmitMock = vi.fn();
10 | render(
11 | ,
12 | );
13 |
14 | expect(screen.getByTestId('form')).toHaveTextContent('children');
15 | });
16 |
17 | it('handles onSubmit prop', () => {
18 | const onSubmitMock = vi.fn();
19 | render(
20 | ,
21 | );
22 |
23 | fireEvent.submit(
24 | screen.getByTestId('form'), {
25 | preventDefault: () => {},
26 | },
27 | );
28 | expect(onSubmitMock).toHaveBeenCalled();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/packages/pl-fe/src/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface IForm {
4 | /** Form submission event handler. */
5 | onSubmit?: (event: React.FormEvent) => void;
6 | /** Class name override for the