├── .easignore
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── new_feature.md
├── pull_request_template.md
└── workflows
│ ├── build-app.yml
│ ├── delete-build-jet-cache.yml
│ ├── e2e-test-android.yml
│ ├── main.yml
│ ├── path-filters.yml
│ ├── push-app.yml
│ └── test.yml
├── .gitignore
├── .tool-versions
├── LICENSE
├── PROJECT.md
├── README.md
├── documentation
├── .gitignore
├── README.md
├── babel.config.js
├── beta_docs
│ └── discourse-features.md
├── check-node-version.sh
├── docs
│ ├── app-store.md
│ ├── assets.md
│ ├── commercial-support.md
│ ├── concepts.md
│ ├── contributing.md
│ ├── customize.md
│ ├── discourse-features.md
│ ├── discourse-login-setup.md
│ ├── discourse-plugin-enable.md
│ ├── discourse-plugin-installation.md
│ ├── discourse-plugin.md
│ ├── email-deep-linking
│ │ ├── intro.md
│ │ └── setup
│ │ │ ├── enable-email-deep-linking.md
│ │ │ └── verify-email-deep-linking.md
│ ├── env-mobile.md
│ ├── intro.md
│ ├── lexicon-updates.md
│ ├── optimal.md
│ ├── play-store.md
│ ├── publish-app.md
│ ├── push-notifications
│ │ ├── introduction.md
│ │ ├── plugin-interaction.md
│ │ └── setup
│ │ │ ├── enable-push-notifications.md
│ │ │ └── verify-push-notifications.md
│ ├── quick-start.md
│ ├── rationale.md
│ ├── setup.md
│ ├── supported-devices.md
│ ├── technologies.md
│ ├── theming.md
│ ├── troubleshooting-build.md
│ ├── tutorial
│ │ ├── building.md
│ │ ├── intro.md
│ │ ├── publishing.md
│ │ ├── setup-cloud-server.md
│ │ ├── setup-discourse.md
│ │ ├── setup-mobile.md
│ │ ├── setup.md
│ │ ├── updating.md
│ │ └── white-label.md
│ └── white-labeling.md
├── docusaurus.config.js
├── package.json
├── sidebars.js
├── src
│ ├── css
│ │ ├── custom.css
│ │ └── image.css
│ └── pages
│ │ ├── index.js
│ │ ├── playground.tsx
│ │ └── styles.module.css
├── static
│ ├── .nojekyll
│ └── img
│ │ ├── favicon.ico
│ │ ├── guides
│ │ ├── playStore
│ │ │ ├── build-artifact.png
│ │ │ └── builds.png
│ │ └── testFlight
│ │ │ ├── add-app.png
│ │ │ ├── app-connect.png
│ │ │ ├── build-artifact.png
│ │ │ ├── builds.png
│ │ │ ├── new-app.png
│ │ │ ├── register-app-id.png
│ │ │ ├── review-contact.png
│ │ │ ├── review-information.png
│ │ │ └── review-signin.png
│ │ ├── lexicon-architecture.png
│ │ ├── lexicon-architecture.svg
│ │ ├── logo.svg
│ │ ├── push-notifications
│ │ └── push-notifications-flowchart.svg
│ │ ├── screenshot
│ │ ├── Android_Comment.png
│ │ ├── Android_DarkMode.png
│ │ ├── Android_Home.png
│ │ ├── Android_Login.png
│ │ ├── Android_Message.png
│ │ ├── Android_MessageReply.gif
│ │ ├── Android_NewMessage.gif
│ │ ├── Android_NewPost.gif
│ │ ├── Android_NewPost.png
│ │ ├── Android_Notifications.png
│ │ ├── Android_PostDetail.png
│ │ ├── Android_PostReply.gif
│ │ ├── Android_Profile.png
│ │ ├── Android_SignUp.png
│ │ ├── IOS_Comment.png
│ │ ├── IOS_DarkMode.png
│ │ ├── IOS_Home.png
│ │ ├── IOS_Login.png
│ │ ├── IOS_Message.png
│ │ ├── IOS_MessageReply.gif
│ │ ├── IOS_NewMessage.gif
│ │ ├── IOS_NewPost.gif
│ │ ├── IOS_NewPost.png
│ │ ├── IOS_Notification.png
│ │ ├── IOS_PostDetail.png
│ │ ├── IOS_PostReply.gif
│ │ ├── IOS_Profile.png
│ │ ├── IOS_SignUp.png
│ │ ├── Mobile-LoginWithApple.png
│ │ ├── Please_connect_network_error.png
│ │ ├── Website_SignUp.png
│ │ ├── add-ssh-key.png
│ │ ├── authenticating-droplet.png
│ │ ├── choose-a-plan.png
│ │ ├── choose-image.png
│ │ ├── control-panel.png
│ │ ├── droplet-ip.png
│ │ ├── droplets.png
│ │ ├── finalizing-creating-droplet.png
│ │ ├── playground.png
│ │ ├── plugins
│ │ │ ├── Discourse-Plugin-Email-notification.png
│ │ │ ├── Discourse-Plugin-EmailDeepLinking-Settings.png
│ │ │ ├── Discourse-Plugin-Enable.png
│ │ │ ├── Discourse-Plugin-PushNotif-Settings.png
│ │ │ ├── Discourse-Plugin-Settings.png
│ │ │ ├── Mobile-PushNotification.png
│ │ │ ├── version-2.2.0
│ │ │ │ ├── Discourse-Plugin-ActivationWithLink-Email.png
│ │ │ │ ├── Discourse-Plugin-EmailDeepLinking-Settings.png
│ │ │ │ ├── Discourse-Plugin-Enable-ActivationWithLink.png
│ │ │ │ ├── Discourse-Plugin-Enable.png
│ │ │ │ ├── Discourse-Plugin-Login-With-Apple-App-ID.png
│ │ │ │ ├── Discourse-Plugin-Login-With-Apple.png
│ │ │ │ ├── Discourse-Plugin-Login-With-Link.png
│ │ │ │ ├── Discourse-Plugin-LoginWithLink-Email.png
│ │ │ │ ├── Discourse-Plugin-PushNotif-Settings.png
│ │ │ │ ├── Discourse-Plugin-Settings.png
│ │ │ │ ├── Mobile-ActivationWithLink-Redirect.png
│ │ │ │ ├── Mobile-LoginWithLink-Redirect.png
│ │ │ │ └── Mobile-LoginWithLink.png
│ │ │ └── version-3.0.0
│ │ │ │ ├── Discourse-Plugin-EmailDeepLinking-Settings.png
│ │ │ │ ├── Discourse-Plugin-PushNotif-Settings.png
│ │ │ │ └── Discourse-Plugin-Settings.png
│ │ ├── region.jpeg
│ │ ├── tablet
│ │ │ ├── Tablet_Android_Landscape_Home.gif
│ │ │ ├── Tablet_Android_Landscape_NewPost.gif
│ │ │ ├── Tablet_Android_Landscape_Profile.gif
│ │ │ ├── Tablet_Android_Landscape_ReplyMessage.gif
│ │ │ ├── Tablet_Android_Portrait_Home.gif
│ │ │ ├── Tablet_Android_Portrait_NewPost.gif
│ │ │ ├── Tablet_Android_Portrait_Profile.gif
│ │ │ ├── Tablet_Android_Portrait_ReplyMessage.gif
│ │ │ ├── Tablet_IOS_Landscape_Home.gif
│ │ │ ├── Tablet_IOS_Landscape_NewPost.gif
│ │ │ ├── Tablet_IOS_Landscape_Profile.gif
│ │ │ ├── Tablet_IOS_Landscape_ReplyMessage.gif
│ │ │ ├── Tablet_IOS_Portrait_Home.gif
│ │ │ ├── Tablet_IOS_Portrait_NewPost.gif
│ │ │ ├── Tablet_IOS_Portrait_Profile.gif
│ │ │ └── Tablet_IOS_Portrait_ReplyMessage.gif
│ │ ├── terminal.png
│ │ ├── user-api-keys
│ │ │ ├── Discourse-App-Settings.png
│ │ │ ├── Discourse-Profile-Security-Settings.png
│ │ │ ├── Discourse-Settings-Auth-Redirect.png
│ │ │ ├── Discourse-Settings-New-Auth-Redirect.png
│ │ │ ├── Mobile-Authorize.png
│ │ │ ├── Mobile-Discourse-Login.png
│ │ │ └── Mobile-Welcome-Page.png
│ │ ├── vscode-env.png
│ │ └── vscode-file.png
│ │ ├── undraw_docusaurus_mountain.svg
│ │ ├── undraw_docusaurus_react.svg
│ │ ├── undraw_docusaurus_tree.svg
│ │ ├── version-3.0.0
│ │ ├── new-lexicon-architecture.png
│ │ └── new-lexicon-architecture.svg
│ │ └── version-3.2.0
│ │ └── push-notifications
│ │ └── push-notifications-flowchart.svg
├── versioned_docs
│ ├── version-1.0.0
│ │ ├── app-store.md
│ │ ├── assets.md
│ │ ├── commercial-support.md
│ │ ├── concepts.md
│ │ ├── contributing.md
│ │ ├── customize.md
│ │ ├── dedicated.md
│ │ ├── deployment.md
│ │ ├── discourse-features.md
│ │ ├── env-mobile.md
│ │ ├── env-prose.md
│ │ ├── intro.md
│ │ ├── lexicon-updates.md
│ │ ├── optimal.md
│ │ ├── play-store.md
│ │ ├── publish-app.md
│ │ ├── quick-start.md
│ │ ├── rationale.md
│ │ ├── setup.md
│ │ ├── supported-devices.md
│ │ ├── technologies.md
│ │ ├── theming.md
│ │ ├── troubleshooting-build.md
│ │ ├── tutorial
│ │ │ ├── building.md
│ │ │ ├── install-prose.md
│ │ │ ├── intro.md
│ │ │ ├── publishing.md
│ │ │ ├── setup-cloud-server.md
│ │ │ ├── setup-discourse.md
│ │ │ ├── setup-mobile.md
│ │ │ ├── setup.md
│ │ │ ├── updating.md
│ │ │ └── white-label.md
│ │ └── white-labeling.md
│ ├── version-2.0.0
│ │ ├── app-store.md
│ │ ├── assets.md
│ │ ├── commercial-support.md
│ │ ├── concepts.md
│ │ ├── contributing.md
│ │ ├── customize.md
│ │ ├── dedicated.md
│ │ ├── deployment.md
│ │ ├── discourse-features.md
│ │ ├── discourse-plugin-enable.md
│ │ ├── discourse-plugin-installation.md
│ │ ├── discourse-plugin.md
│ │ ├── email-deep-linking
│ │ │ ├── intro.md
│ │ │ └── setup
│ │ │ │ ├── enable-email-deep-linking.md
│ │ │ │ └── verify-email-deep-linking.md
│ │ ├── env-mobile.md
│ │ ├── env-prose.md
│ │ ├── intro.md
│ │ ├── lexicon-updates.md
│ │ ├── optimal.md
│ │ ├── play-store.md
│ │ ├── publish-app.md
│ │ ├── push-notifications
│ │ │ ├── introduction.md
│ │ │ ├── plugin-interaction.md
│ │ │ └── setup
│ │ │ │ ├── enable-push-notifications.md
│ │ │ │ └── verify-push-notifications.md
│ │ ├── quick-start.md
│ │ ├── rationale.md
│ │ ├── setup.md
│ │ ├── supported-devices.md
│ │ ├── technologies.md
│ │ ├── theming.md
│ │ ├── troubleshooting-build.md
│ │ ├── tutorial
│ │ │ ├── building.md
│ │ │ ├── install-prose.md
│ │ │ ├── intro.md
│ │ │ ├── publishing.md
│ │ │ ├── setup-cloud-server.md
│ │ │ ├── setup-discourse.md
│ │ │ ├── setup-mobile.md
│ │ │ ├── setup.md
│ │ │ ├── updating.md
│ │ │ └── white-label.md
│ │ └── white-labeling.md
│ ├── version-2.1.0
│ │ ├── app-store.md
│ │ ├── assets.md
│ │ ├── commercial-support.md
│ │ ├── concepts.md
│ │ ├── contributing.md
│ │ ├── customize.md
│ │ ├── dedicated.md
│ │ ├── deployment.md
│ │ ├── discourse-features.md
│ │ ├── discourse-plugin-enable.md
│ │ ├── discourse-plugin-installation.md
│ │ ├── discourse-plugin.md
│ │ ├── email-deep-linking
│ │ │ ├── intro.md
│ │ │ └── setup
│ │ │ │ ├── enable-email-deep-linking.md
│ │ │ │ └── verify-email-deep-linking.md
│ │ ├── env-mobile.md
│ │ ├── env-prose.md
│ │ ├── intro.md
│ │ ├── lexicon-updates.md
│ │ ├── optimal.md
│ │ ├── play-store.md
│ │ ├── publish-app.md
│ │ ├── push-notifications
│ │ │ ├── introduction.md
│ │ │ ├── plugin-interaction.md
│ │ │ └── setup
│ │ │ │ ├── enable-push-notifications.md
│ │ │ │ └── verify-push-notifications.md
│ │ ├── quick-start.md
│ │ ├── rationale.md
│ │ ├── setup.md
│ │ ├── supported-devices.md
│ │ ├── technologies.md
│ │ ├── theming.md
│ │ ├── troubleshooting-build.md
│ │ ├── tutorial
│ │ │ ├── building.md
│ │ │ ├── install-prose.md
│ │ │ ├── intro.md
│ │ │ ├── publishing.md
│ │ │ ├── setup-cloud-server.md
│ │ │ ├── setup-discourse.md
│ │ │ ├── setup-mobile.md
│ │ │ ├── setup.md
│ │ │ ├── updating.md
│ │ │ └── white-label.md
│ │ └── white-labeling.md
│ ├── version-2.2.0
│ │ ├── activation-with-link
│ │ │ ├── intro.md
│ │ │ └── setup
│ │ │ │ ├── enable-activate-with-link.md
│ │ │ │ └── verify-activate-with-link.md
│ │ ├── app-store.md
│ │ ├── assets.md
│ │ ├── commercial-support.md
│ │ ├── concepts.md
│ │ ├── contributing.md
│ │ ├── customize.md
│ │ ├── dedicated.md
│ │ ├── deployment.md
│ │ ├── discourse-features.md
│ │ ├── discourse-plugin-enable.md
│ │ ├── discourse-plugin-installation.md
│ │ ├── discourse-plugin.md
│ │ ├── email-deep-linking
│ │ │ ├── intro.md
│ │ │ └── setup
│ │ │ │ ├── enable-email-deep-linking.md
│ │ │ │ └── verify-email-deep-linking.md
│ │ ├── env-mobile.md
│ │ ├── env-prose.md
│ │ ├── intro.md
│ │ ├── lexicon-updates.md
│ │ ├── login-with-apple
│ │ │ ├── intro.md
│ │ │ └── setup
│ │ │ │ ├── enable-login-with-apple.md
│ │ │ │ └── verify-login-with-apple.md
│ │ ├── login-with-link
│ │ │ ├── intro.md
│ │ │ └── setup
│ │ │ │ ├── enable-login-with-link.md
│ │ │ │ └── verify-login-with-link.md
│ │ ├── optimal.md
│ │ ├── play-store.md
│ │ ├── publish-app.md
│ │ ├── push-notifications
│ │ │ ├── introduction.md
│ │ │ ├── plugin-interaction.md
│ │ │ └── setup
│ │ │ │ ├── enable-push-notifications.md
│ │ │ │ └── verify-push-notifications.md
│ │ ├── quick-start.md
│ │ ├── rationale.md
│ │ ├── setup.md
│ │ ├── supported-devices.md
│ │ ├── technologies.md
│ │ ├── theming.md
│ │ ├── troubleshooting-build.md
│ │ ├── tutorial
│ │ │ ├── building.md
│ │ │ ├── install-prose.md
│ │ │ ├── intro.md
│ │ │ ├── publishing.md
│ │ │ ├── setup-cloud-server.md
│ │ │ ├── setup-discourse.md
│ │ │ ├── setup-mobile.md
│ │ │ ├── setup.md
│ │ │ ├── updating.md
│ │ │ └── white-label.md
│ │ └── white-labeling.md
│ ├── version-3.0.0
│ │ ├── app-store.md
│ │ ├── assets.md
│ │ ├── commercial-support.md
│ │ ├── concepts.md
│ │ ├── contributing.md
│ │ ├── customize.md
│ │ ├── discourse-features.md
│ │ ├── discourse-login-setup.md
│ │ ├── discourse-plugin-enable.md
│ │ ├── discourse-plugin-installation.md
│ │ ├── discourse-plugin.md
│ │ ├── email-deep-linking
│ │ │ ├── intro.md
│ │ │ └── setup
│ │ │ │ ├── enable-email-deep-linking.md
│ │ │ │ └── verify-email-deep-linking.md
│ │ ├── env-mobile.md
│ │ ├── intro.md
│ │ ├── lexicon-updates.md
│ │ ├── optimal.md
│ │ ├── play-store.md
│ │ ├── publish-app.md
│ │ ├── push-notifications
│ │ │ ├── introduction.md
│ │ │ ├── plugin-interaction.md
│ │ │ └── setup
│ │ │ │ ├── enable-push-notifications.md
│ │ │ │ └── verify-push-notifications.md
│ │ ├── quick-start.md
│ │ ├── rationale.md
│ │ ├── setup.md
│ │ ├── supported-devices.md
│ │ ├── technologies.md
│ │ ├── theming.md
│ │ ├── troubleshooting-build.md
│ │ ├── tutorial
│ │ │ ├── building.md
│ │ │ ├── intro.md
│ │ │ ├── publishing.md
│ │ │ ├── setup-cloud-server.md
│ │ │ ├── setup-discourse.md
│ │ │ ├── setup-mobile.md
│ │ │ ├── setup.md
│ │ │ ├── updating.md
│ │ │ └── white-label.md
│ │ └── white-labeling.md
│ ├── version-3.1.0
│ │ ├── app-store.md
│ │ ├── assets.md
│ │ ├── commercial-support.md
│ │ ├── concepts.md
│ │ ├── contributing.md
│ │ ├── customize.md
│ │ ├── discourse-features.md
│ │ ├── discourse-login-setup.md
│ │ ├── discourse-plugin-enable.md
│ │ ├── discourse-plugin-installation.md
│ │ ├── discourse-plugin.md
│ │ ├── email-deep-linking
│ │ │ ├── intro.md
│ │ │ └── setup
│ │ │ │ ├── enable-email-deep-linking.md
│ │ │ │ └── verify-email-deep-linking.md
│ │ ├── env-mobile.md
│ │ ├── intro.md
│ │ ├── lexicon-updates.md
│ │ ├── optimal.md
│ │ ├── play-store.md
│ │ ├── publish-app.md
│ │ ├── push-notifications
│ │ │ ├── introduction.md
│ │ │ ├── plugin-interaction.md
│ │ │ └── setup
│ │ │ │ ├── enable-push-notifications.md
│ │ │ │ └── verify-push-notifications.md
│ │ ├── quick-start.md
│ │ ├── rationale.md
│ │ ├── setup.md
│ │ ├── supported-devices.md
│ │ ├── technologies.md
│ │ ├── theming.md
│ │ ├── troubleshooting-build.md
│ │ ├── tutorial
│ │ │ ├── building.md
│ │ │ ├── intro.md
│ │ │ ├── publishing.md
│ │ │ ├── setup-cloud-server.md
│ │ │ ├── setup-discourse.md
│ │ │ ├── setup-mobile.md
│ │ │ ├── setup.md
│ │ │ ├── updating.md
│ │ │ └── white-label.md
│ │ └── white-labeling.md
│ └── version-3.2.0
│ │ ├── app-store.md
│ │ ├── assets.md
│ │ ├── commercial-support.md
│ │ ├── concepts.md
│ │ ├── contributing.md
│ │ ├── customize.md
│ │ ├── discourse-features.md
│ │ ├── discourse-login-setup.md
│ │ ├── discourse-plugin-enable.md
│ │ ├── discourse-plugin-installation.md
│ │ ├── discourse-plugin.md
│ │ ├── email-deep-linking
│ │ ├── intro.md
│ │ └── setup
│ │ │ ├── enable-email-deep-linking.md
│ │ │ └── verify-email-deep-linking.md
│ │ ├── env-mobile.md
│ │ ├── intro.md
│ │ ├── lexicon-updates.md
│ │ ├── optimal.md
│ │ ├── play-store.md
│ │ ├── publish-app.md
│ │ ├── push-notifications
│ │ ├── introduction.md
│ │ ├── plugin-interaction.md
│ │ └── setup
│ │ │ ├── enable-push-notifications.md
│ │ │ └── verify-push-notifications.md
│ │ ├── quick-start.md
│ │ ├── rationale.md
│ │ ├── setup.md
│ │ ├── supported-devices.md
│ │ ├── technologies.md
│ │ ├── theming.md
│ │ ├── troubleshooting-build.md
│ │ ├── tutorial
│ │ ├── building.md
│ │ ├── intro.md
│ │ ├── publishing.md
│ │ ├── setup-cloud-server.md
│ │ ├── setup-discourse.md
│ │ ├── setup-mobile.md
│ │ ├── setup.md
│ │ ├── updating.md
│ │ └── white-label.md
│ │ └── white-labeling.md
├── versioned_sidebars
│ ├── version-1.0.0-sidebars.json
│ ├── version-2.0.0-sidebars.json
│ ├── version-2.1.0-sidebars.json
│ ├── version-2.2.0-sidebars.json
│ ├── version-3.0.0-sidebars.json
│ ├── version-3.1.0-sidebars.json
│ └── version-3.2.0-sidebars.json
├── versions.json
└── yarn.lock
├── frontend
├── .detoxrc.js
├── .eslintignore
├── .gitignore
├── App.ts
├── Config.mock.ts
├── Config.ts
├── apollo.config.js
├── app.json
├── assets
│ ├── favicon.png
│ ├── icon.png
│ ├── icons
│ │ ├── Add.svg
│ │ ├── AddCircle.svg
│ │ ├── AdminPanel.svg
│ │ ├── AlternateEmail.svg
│ │ ├── ArrowUpward.svg
│ │ ├── Ballot.svg
│ │ ├── BoldText.svg
│ │ ├── BulletList.svg
│ │ ├── Cancel.svg
│ │ ├── Chart.svg
│ │ ├── CheckCircle.svg
│ │ ├── ChevronDown.svg
│ │ ├── ChevronRight.svg
│ │ ├── ChevronUp.svg
│ │ ├── Clock.svg
│ │ ├── Close.svg
│ │ ├── Collapsible.svg
│ │ ├── Dark.svg
│ │ ├── Delete.svg
│ │ ├── Done.svg
│ │ ├── Draft.svg
│ │ ├── DropdownArrow.svg
│ │ ├── Edit.svg
│ │ ├── Event.svg
│ │ ├── ExpandLess.svg
│ │ ├── ExpandMore.svg
│ │ ├── Folder.svg
│ │ ├── Home.svg
│ │ ├── Information.svg
│ │ ├── ItalicText.svg
│ │ ├── KeyboardHide.svg
│ │ ├── Lexicon.svg
│ │ ├── Likes.svg
│ │ ├── Link.svg
│ │ ├── Lock.svg
│ │ ├── Mail.svg
│ │ ├── More.svg
│ │ ├── MoreVert.svg
│ │ ├── NoConnection.svg
│ │ ├── NotificationActive.svg
│ │ ├── Notifications.svg
│ │ ├── NumberList.svg
│ │ ├── Online.svg
│ │ ├── Person.svg
│ │ ├── Photo.svg
│ │ ├── Pin.svg
│ │ ├── PoliceBadge.svg
│ │ ├── Power.svg
│ │ ├── QuoteText.svg
│ │ ├── RemoveCircle.svg
│ │ ├── Replies.svg
│ │ ├── Search.svg
│ │ ├── Send.svg
│ │ ├── Settings.svg
│ │ ├── SideBar.svg
│ │ ├── SideBarAndroid.svg
│ │ ├── Triangle.svg
│ │ ├── Unreachable.svg
│ │ ├── Views.svg
│ │ ├── WarningCircle.svg
│ │ └── chatBubble.svg
│ └── images
│ │ ├── imagePlaceholder.png
│ │ ├── index.ts
│ │ ├── logo.png
│ │ ├── logoDark.png
│ │ ├── splash.png
│ │ └── splashDark.png
├── babel.config.js
├── codegen.ts
├── docs
│ ├── DeepLinkRedirect.md
│ ├── authentication.md
│ ├── expoExperienceId.md
│ └── testing
│ │ ├── detox.md
│ │ └── example-Ios-CI.md
├── e2e
│ ├── global
│ │ ├── constant.ts
│ │ └── index.ts
│ ├── helpers
│ │ ├── deepLink.ts
│ │ ├── index.ts
│ │ ├── link.ts
│ │ ├── logout.ts
│ │ ├── poll.ts
│ │ ├── post.ts
│ │ ├── privateMessage.ts
│ │ ├── tab.ts
│ │ └── tooltip.ts
│ ├── init.ts
│ ├── jest.config.js
│ ├── rest-mock
│ │ ├── assets
│ │ │ ├── grinning.png
│ │ │ ├── heart_eyes.png
│ │ │ └── smile.png
│ │ ├── data
│ │ │ ├── categories.ts
│ │ │ ├── chat.ts
│ │ │ ├── index.ts
│ │ │ ├── messageDetails.ts
│ │ │ ├── messages.ts
│ │ │ ├── postDrafts.ts
│ │ │ ├── posts.ts
│ │ │ ├── site.ts
│ │ │ ├── token.ts
│ │ │ ├── topicDetails.ts
│ │ │ ├── topics.ts
│ │ │ └── users.ts
│ │ ├── mswServer.ts
│ │ ├── rest
│ │ │ ├── aboutHandler.ts
│ │ │ ├── categoriesHandler.ts
│ │ │ ├── chatHandler.ts
│ │ │ ├── draftHandler.ts
│ │ │ ├── helper
│ │ │ │ ├── index.ts
│ │ │ │ └── parseNumber.ts
│ │ │ ├── index.ts
│ │ │ ├── messagesHandler.ts
│ │ │ ├── notificationsHandler.ts
│ │ │ ├── pollHandler.ts
│ │ │ ├── profileHandler.ts
│ │ │ ├── siteHandler.ts
│ │ │ ├── timingsHandler.ts
│ │ │ ├── topicsHandler.ts
│ │ │ ├── userActivityHandler.ts
│ │ │ ├── userHandler.ts
│ │ │ └── userStatusHandler.ts
│ │ └── utils
│ │ │ ├── index.ts
│ │ │ └── transformSnakeCase.ts
│ └── tests
│ │ ├── activity.e2e.ts
│ │ ├── chat.e2e.ts
│ │ ├── collapsible.e2e.ts
│ │ ├── deepLink.e2e.ts
│ │ ├── draft.e2e.ts
│ │ ├── messages.e2e.ts
│ │ ├── polls.e2e.ts
│ │ ├── profile.e2e.ts
│ │ ├── topics.e2e.ts
│ │ └── userStatus.e2e.ts
├── eas.json
├── index.js
├── metro.config.js
├── package.json
├── patches
│ ├── @react-navigation+stack+6.2.2.patch
│ ├── react-native-animated-progress+1.0.2.patch
│ ├── react-native-modal-datetime-picker+17.1.0.patch
│ ├── react-native-reanimated+3.10.1.patch
│ ├── react-native-skeleton-placeholder+5.2.4.patch
│ ├── reactotron-core-client+2.9.6.patch
│ └── reactotron-react-native+5.1.10.patch
├── reactotronConfig.js
├── scripts
│ ├── android-E2E.sh
│ └── rename-generated-files.js
├── src
│ ├── App.tsx
│ ├── __mocks__
│ │ ├── mockData.ts
│ │ ├── mockQuery.ts
│ │ ├── setup.js
│ │ ├── setupLinking.ts
│ │ └── svgMock.js
│ ├── api
│ │ ├── bodyBuilder
│ │ │ ├── index.ts
│ │ │ ├── newMessage.ts
│ │ │ ├── timings.ts
│ │ │ └── votePoll.ts
│ │ ├── bodySerializers
│ │ │ ├── fileEncode.ts
│ │ │ └── index.ts
│ │ ├── client.ts
│ │ ├── dataLoader
│ │ │ ├── index.ts
│ │ │ └── searchUsersDataLoader.ts
│ │ ├── discourse-apollo-rest
│ │ │ ├── about.ts
│ │ │ ├── addEmail.ts
│ │ │ ├── auth.ts
│ │ │ ├── changePassword.ts
│ │ │ ├── channels.ts
│ │ │ ├── chatChannel.ts
│ │ │ ├── deleteEmail.ts
│ │ │ ├── editProfile.ts
│ │ │ ├── flagPost.ts
│ │ │ ├── getTopicDetail.ts
│ │ │ ├── likePost.ts
│ │ │ ├── lookupUrls.ts
│ │ │ ├── messages.ts
│ │ │ ├── newTopic.ts
│ │ │ ├── notification.ts
│ │ │ ├── poll.ts
│ │ │ ├── postDraft.ts
│ │ │ ├── postRaw.ts
│ │ │ ├── profile.ts
│ │ │ ├── readChat.ts
│ │ │ ├── reply.ts
│ │ │ ├── replyChat.ts
│ │ │ ├── schema.graphql
│ │ │ ├── search.ts
│ │ │ ├── setPrimaryEmail.ts
│ │ │ ├── site.ts
│ │ │ ├── thread.ts
│ │ │ ├── timings.ts
│ │ │ ├── topics.ts
│ │ │ ├── upload.ts
│ │ │ ├── userActivity.ts
│ │ │ ├── userStatus.ts
│ │ │ └── utils.ts
│ │ ├── pathBuilder
│ │ │ ├── chatChannelMessages.ts
│ │ │ ├── deleteEmail.ts
│ │ │ ├── deletePostDraft.ts
│ │ │ ├── getChatChannels.ts
│ │ │ ├── helper
│ │ │ │ ├── __tests__
│ │ │ │ │ └── parseTopicUrl.test.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── parseTopicUrl.ts
│ │ │ ├── index.ts
│ │ │ ├── likeTopicOrPost.ts
│ │ │ ├── message.ts
│ │ │ ├── notifications.ts
│ │ │ ├── postDraft.ts
│ │ │ ├── searchPost.ts
│ │ │ ├── searchTag.ts
│ │ │ ├── threadMessages.ts
│ │ │ ├── topicDetail.ts
│ │ │ └── topics.ts
│ │ ├── resolver
│ │ │ ├── helper
│ │ │ │ ├── getUpdatedLikedTopic.ts
│ │ │ │ └── likeErrorHandler.ts
│ │ │ ├── index.ts
│ │ │ ├── locale
│ │ │ │ └── index.ts
│ │ │ ├── mutation
│ │ │ │ ├── createAndUpdatePostDraft.ts
│ │ │ │ ├── editProfile.ts
│ │ │ │ ├── likeTopicOrPost.ts
│ │ │ │ └── logout.ts
│ │ │ └── query
│ │ │ │ └── privateMessage.ts
│ │ ├── responseTransformer
│ │ │ ├── CheckPostDraftResult.ts
│ │ │ ├── JoinLeaveChannelOutput.ts
│ │ │ ├── ListPostDraftsResult.ts
│ │ │ ├── Post.ts
│ │ │ ├── ReplyingToOutput.ts
│ │ │ ├── changePasswordOutput.ts
│ │ │ ├── helper
│ │ │ │ ├── __tests__
│ │ │ │ │ └── transformDraftData.test.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── transformDraftData.ts
│ │ │ ├── index.ts
│ │ │ ├── searchTagOutput.ts
│ │ │ ├── stringOutput.ts
│ │ │ └── userActions.ts
│ │ └── typePatcher
│ │ │ ├── about.ts
│ │ │ ├── categoryList.ts
│ │ │ ├── changeProfileOutput.ts
│ │ │ ├── chatChannelMessages.ts
│ │ │ ├── getChatChannelsOutput.ts
│ │ │ ├── getThreadDetailOutput.ts
│ │ │ ├── getThreadMessagesOutput.ts
│ │ │ ├── helper
│ │ │ ├── Post.ts
│ │ │ ├── index.ts
│ │ │ └── poll.ts
│ │ │ ├── index.ts
│ │ │ ├── lookupUrls.ts
│ │ │ ├── pollActionOutput.ts
│ │ │ ├── post.ts
│ │ │ ├── postRaw.ts
│ │ │ ├── privateMessageDetailOutput.ts
│ │ │ ├── privateMessageOutput.ts
│ │ │ ├── profileOutput.ts
│ │ │ ├── searchOutput.ts
│ │ │ ├── searchUserOutput.ts
│ │ │ ├── siteOutput.ts
│ │ │ ├── topicDetailOutput.ts
│ │ │ ├── topicsOutput.ts
│ │ │ ├── types
│ │ │ └── userActions.ts
│ │ │ ├── uploadOutput.ts
│ │ │ ├── userActions.ts
│ │ │ ├── userUnreadNotifications.ts
│ │ │ └── votePollOutput.ts
│ ├── components
│ │ ├── ActionSheet
│ │ │ ├── ActionSheet.tsx
│ │ │ └── ActionSheetItem.tsx
│ │ ├── AlertBanner.tsx
│ │ ├── Author.tsx
│ │ ├── AvatarRow.tsx
│ │ ├── BottomMenu.tsx
│ │ ├── CustomFlatList
│ │ │ ├── CustomFlatList.tsx
│ │ │ └── index.ts
│ │ ├── DateTimePicker.tsx
│ │ ├── Dropdown.tsx
│ │ ├── ErrorBoundary.tsx
│ │ ├── FooterLoadingIndicator.tsx
│ │ ├── Header
│ │ │ ├── CustomHeader.tsx
│ │ │ ├── HeaderItem.tsx
│ │ │ ├── ModalHeader.tsx
│ │ │ └── index.tsx
│ │ ├── KeyboardTextAreaScrollView.tsx
│ │ ├── LoadingOrError.tsx
│ │ ├── LoadingOverlay.tsx
│ │ ├── Markdown.tsx
│ │ ├── MarkdownContent.tsx
│ │ ├── MentionList.tsx
│ │ ├── Metrics
│ │ │ ├── MetricItem.tsx
│ │ │ └── Metrics.tsx
│ │ ├── NestedComment.tsx
│ │ ├── Poll
│ │ │ ├── ListCreatePoll.tsx
│ │ │ ├── PollChoiceCard.tsx
│ │ │ ├── PollOptionItem.tsx
│ │ │ ├── PollPostPreview.tsx
│ │ │ ├── PollPreview.tsx
│ │ │ └── index.ts
│ │ ├── PostItem
│ │ │ ├── HomePostItem.tsx
│ │ │ ├── PostDetailHeaderItem.tsx
│ │ │ ├── PostGroupings.tsx
│ │ │ ├── PostHidden.tsx
│ │ │ ├── PostItem.tsx
│ │ │ ├── PostItemFooter.tsx
│ │ │ ├── SearchPostItem.tsx
│ │ │ ├── UserInformationPostItem.tsx
│ │ │ └── index.ts
│ │ ├── PostList.tsx
│ │ ├── RepliedPost.tsx
│ │ ├── RequestError.tsx
│ │ ├── SearchBar.tsx
│ │ ├── SegmentedControl.tsx
│ │ ├── SelectImagePreviewEdit.tsx
│ │ ├── ShowImageModal.tsx
│ │ ├── StackedAvatars.tsx
│ │ ├── TextArea.tsx
│ │ ├── Toast.tsx
│ │ ├── UserStatus.tsx
│ │ └── index.ts
│ ├── constants
│ │ ├── __tests__
│ │ │ └── getDiscourseEndpoint.test.ts
│ │ ├── alert.ts
│ │ ├── api.ts
│ │ ├── app.ts
│ │ ├── defaultValues.ts
│ │ ├── draftSaveState.ts
│ │ ├── emoji.ts
│ │ ├── errorTypes.ts
│ │ ├── index.ts
│ │ ├── links.ts
│ │ ├── postDraft.ts
│ │ ├── refetch.ts
│ │ ├── regex.ts
│ │ ├── route.ts
│ │ ├── theme
│ │ │ ├── animations.ts
│ │ │ ├── colors.ts
│ │ │ ├── fonts.ts
│ │ │ ├── graphs.ts
│ │ │ ├── icons.ts
│ │ │ ├── images.ts
│ │ │ ├── index.ts
│ │ │ └── spacing.ts
│ │ └── wordings.ts
│ ├── core-ui
│ │ ├── ActivityIndicator.tsx
│ │ ├── AppleSignInButton.tsx
│ │ ├── Avatar
│ │ │ ├── LetterAvatar.tsx
│ │ │ └── index.tsx
│ │ ├── Button.tsx
│ │ ├── CachedImage.tsx
│ │ ├── ChatBubble.tsx
│ │ ├── Chip.tsx
│ │ ├── ChipRow.tsx
│ │ ├── CustomImage.tsx
│ │ ├── Divider.tsx
│ │ ├── Dot.tsx
│ │ ├── Emoji.tsx
│ │ ├── FloatingButton.tsx
│ │ ├── Icon.tsx
│ │ ├── IconWithLabel.tsx
│ │ ├── Link.tsx
│ │ ├── MentionedText.tsx
│ │ ├── RadioButton.tsx
│ │ ├── Text.tsx
│ │ ├── TextInput.tsx
│ │ └── index.ts
│ ├── helpers
│ │ ├── PrivateTopicAlert.ts
│ │ ├── PushNotificationsSetupFailAlert.ts
│ │ ├── __tests__
│ │ │ ├── addHour.test.ts
│ │ │ ├── automaticFontColor.test.ts
│ │ │ ├── capitalizeFirstLetter.test.ts
│ │ │ ├── chatMessageSceneHelper.test.ts
│ │ │ ├── checkImageFile.test.ts
│ │ │ ├── collapsible.test.ts
│ │ │ ├── colorScheme.test.ts
│ │ │ ├── compareTime.test.ts
│ │ │ ├── convertPollMarkdown.test.ts
│ │ │ ├── convertUrl.ts
│ │ │ ├── deleteQuoteBbCode.test.ts
│ │ │ ├── emojiHandler.test.ts
│ │ │ ├── existingPostIsValid.test.ts
│ │ │ ├── experienceId.test.ts
│ │ │ ├── extractAttributes.test.ts
│ │ │ ├── fontFormatting.test.ts
│ │ │ ├── formatCount.test.ts
│ │ │ ├── formatDateTime.test.ts
│ │ │ ├── formatExtensions.test.ts
│ │ │ ├── formatRelativeTime.test.ts
│ │ │ ├── formatTag.test.ts
│ │ │ ├── generatePollFormFromMarkdown.test.ts
│ │ │ ├── generatePollMarkdown.test.ts
│ │ │ ├── generateSlug.test.ts
│ │ │ ├── getDistanceToNow.test.ts
│ │ │ ├── getFormat.test.ts
│ │ │ ├── getHyperlink.test.ts
│ │ │ ├── getMimeFromImagePicker.test.ts
│ │ │ ├── getUserImage.test.ts
│ │ │ ├── linking.test.ts
│ │ │ ├── listNumberStep.test.ts
│ │ │ ├── newPostIsValid.test.ts
│ │ │ ├── paginationHandler.test.ts
│ │ │ ├── pollUtility.test.ts
│ │ │ ├── postDetailContentHandler.test.ts
│ │ │ ├── privateMessageRecipientsDraft.test.ts
│ │ │ ├── privateMessageReplyImageHandler.test.ts
│ │ │ ├── processRawContent.test.ts
│ │ │ ├── relatedUsers.test.ts
│ │ │ ├── replaceQuotesWithMarkdown.test.ts
│ │ │ ├── stripHTML.test.ts
│ │ │ ├── unescapeHTML.test.ts
│ │ │ └── updateLike.test.ts
│ │ ├── addHour.ts
│ │ ├── api
│ │ │ ├── __test__
│ │ │ │ ├── getPosterTypeDetails.test.ts
│ │ │ │ ├── poll.test.ts
│ │ │ │ └── processRawContent.test.ts
│ │ │ ├── getPosterTypeDetails.ts
│ │ │ ├── getTopicAuthor.ts
│ │ │ ├── index.ts
│ │ │ ├── poll.ts
│ │ │ ├── privateMessagesMerger.ts
│ │ │ └── processRawContent.ts
│ │ ├── automaticFontColor.ts
│ │ ├── bottomMenu.ts
│ │ ├── capitalizeFirstLetter.ts
│ │ ├── chatMessageSceneHelper.ts
│ │ ├── checkImageFile.ts
│ │ ├── checkPostDraftAlert.ts
│ │ ├── clampWorklet.ts
│ │ ├── collapsible.ts
│ │ ├── colorScheme.ts
│ │ ├── compareTime.ts
│ │ ├── convertPollMarkdown.ts
│ │ ├── convertUrl.ts
│ │ ├── createCachedStorage.mock.tsx
│ │ ├── createCachedStorage.tsx
│ │ ├── createReactNativeFile.ts
│ │ ├── deleteQuoteBbCode.ts
│ │ ├── emojiHandler.ts
│ │ ├── errorHandler.ts
│ │ ├── errorMessage.ts
│ │ ├── existingPostIsValid.ts
│ │ ├── experienceId.ts
│ │ ├── extractAttributes.ts
│ │ ├── findChannelByCategoryId.ts
│ │ ├── fontFormatting.ts
│ │ ├── formatCount.ts
│ │ ├── formatDateTime.ts
│ │ ├── formatExtensions.ts
│ │ ├── formatRelativeTime.ts
│ │ ├── formatTag.ts
│ │ ├── generatePollFormFromMarkdown.ts
│ │ ├── generatePollMarkdown.ts
│ │ ├── generateSlug.ts
│ │ ├── getDistanceToNow.ts
│ │ ├── getExpoPushTokenHandler.mock.ts
│ │ ├── getExpoPushTokenHandler.ts
│ │ ├── getFetchMorePostIds.ts
│ │ ├── getFormat.ts
│ │ ├── getHyperlink.ts
│ │ ├── getMimeFromImagePicker.ts
│ │ ├── getTextInputRules.ts
│ │ ├── getTopicDetailOutputCacheBehavior.ts
│ │ ├── getUserImage.ts
│ │ ├── goBackWithoutSaveDraftAlert.ts
│ │ ├── handleDuplicates.ts
│ │ ├── imagePickerHandler.ts
│ │ ├── imageUploadHandler.ts
│ │ ├── index.ts
│ │ ├── insertHyperlink.ts
│ │ ├── linking.ts
│ │ ├── listNumberStep.ts
│ │ ├── localStorage.tsx
│ │ ├── mentionHelper.ts
│ │ ├── messageDetailHandler.ts
│ │ ├── navigateInProfile.ts
│ │ ├── newPostIsValid.ts
│ │ ├── notificationHandler.ts
│ │ ├── paginationHandler.ts
│ │ ├── parser.ts
│ │ ├── pickImage.ts
│ │ ├── pollUtility.ts
│ │ ├── postDetailContentHandler.ts
│ │ ├── postDraftContentHandler.ts
│ │ ├── privateMessageRecipientsDraft.ts
│ │ ├── privateMessageReplyImageHandler.ts
│ │ ├── processRawContent.ts
│ │ ├── relatedUsers.ts
│ │ ├── replaceQuotesWithMarkdown.ts
│ │ ├── saveAndDiscardPostDraftAlert.ts
│ │ ├── secureStore.ts
│ │ ├── showErrorFormAlert.ts
│ │ ├── showLogoutAlert.ts
│ │ ├── storage.mock.ts
│ │ ├── storage.ts
│ │ ├── stripHTML.ts
│ │ ├── textArea.ts
│ │ ├── token.ts
│ │ ├── transformTopicToPost.ts
│ │ ├── unescapeHTML.ts
│ │ └── updateLike.ts
│ ├── hooks
│ │ ├── index.ts
│ │ ├── rest
│ │ │ ├── auth
│ │ │ │ ├── useLogout.ts
│ │ │ │ └── usePushNotifications.ts
│ │ │ ├── chat
│ │ │ │ ├── useChatChannelDetail.ts
│ │ │ │ ├── useChatChannelMessages.ts
│ │ │ │ ├── useCreateThread.ts
│ │ │ │ ├── useGetChatChannels.ts
│ │ │ │ ├── useGetThreadDetail.ts
│ │ │ │ ├── useGetThreadMessages.ts
│ │ │ │ ├── useJoinChannel.ts
│ │ │ │ ├── useLeaveChannel.ts
│ │ │ │ ├── useMarkReadChat.ts
│ │ │ │ └── useReplyChat.ts
│ │ │ ├── draft
│ │ │ │ ├── useCheckPostDraft.ts
│ │ │ │ ├── useCreateAndUpdatePostDraft.ts
│ │ │ │ ├── useDeletePostDraft.ts
│ │ │ │ └── useListPostDrafts.ts
│ │ │ ├── poll
│ │ │ │ ├── useTogglePollStatus.ts
│ │ │ │ ├── useUndoVotePoll.ts
│ │ │ │ └── useVotePoll.ts
│ │ │ ├── post
│ │ │ │ ├── useActivity.ts
│ │ │ │ ├── useEditPost.ts
│ │ │ │ ├── useEditTopic.ts
│ │ │ │ ├── useFlagPost.ts
│ │ │ │ ├── useLeaveMessage.ts
│ │ │ │ ├── useLikeTopicOrPost.ts
│ │ │ │ ├── useLookupUrls.ts
│ │ │ │ ├── useMention.ts
│ │ │ │ ├── useMessageDetail.ts
│ │ │ │ ├── useMessageList.ts
│ │ │ │ ├── useNewMessage.ts
│ │ │ │ ├── useNewTopic.ts
│ │ │ │ ├── usePost.ts
│ │ │ │ ├── usePostRaw.ts
│ │ │ │ ├── useReplyTopic.ts
│ │ │ │ ├── useReplyingTo.ts
│ │ │ │ ├── useSearchPost.ts
│ │ │ │ ├── useTags.ts
│ │ │ │ ├── useTiming.ts
│ │ │ │ ├── useTopicDetail.ts
│ │ │ │ └── useTopicList.ts
│ │ │ ├── profile
│ │ │ │ ├── useChangePassword.ts
│ │ │ │ ├── useEmail.ts
│ │ │ │ ├── useNotification.ts
│ │ │ │ └── useProfile.ts
│ │ │ ├── site
│ │ │ │ ├── useAbout.ts
│ │ │ │ ├── useChannels.ts
│ │ │ │ └── useSiteSettings.ts
│ │ │ ├── useUpload.ts
│ │ │ └── user
│ │ │ │ ├── useDeleteUserStatus.ts
│ │ │ │ ├── useEditUserStatus.ts
│ │ │ │ └── useSearchUsers.ts
│ │ ├── useAutoSaveManager.ts
│ │ ├── useAutoSavePostDraft.ts
│ │ ├── useGetUrlEmoji.ts
│ │ ├── useInitialLoad.tsx
│ │ ├── useKASVWorkaround.ts
│ │ ├── useKeyboardListener.ts
│ │ ├── useLoadFonts.ts
│ │ ├── useLoadMorePost.ts
│ │ ├── usePolling.ts
│ │ └── useUpdateApp.ts
│ ├── i18n
│ │ └── translate.ts
│ ├── icons.ts
│ ├── navigation
│ │ ├── AppNavigator.tsx
│ │ ├── NavigationService.tsx
│ │ ├── ProfileDrawerNavigator.tsx
│ │ ├── ProfileStackNavigator.tsx
│ │ ├── RootStackNavigator.tsx
│ │ └── TabNavigator.tsx
│ ├── polyfills.ts
│ ├── reactiveVars
│ │ ├── index.tsx
│ │ ├── tokenReactive.mock.tsx
│ │ └── tokenReactive.tsx
│ ├── screens
│ │ ├── Activity.tsx
│ │ ├── AddEmail.tsx
│ │ ├── AuthenticationWebView.tsx
│ │ ├── ChangePassword.tsx
│ │ ├── Channels
│ │ │ ├── ChannelSideBarContent.tsx
│ │ │ ├── ChannelSideBarDrawer.tsx
│ │ │ ├── Channels.tsx
│ │ │ ├── Components
│ │ │ │ └── ChannelItem.tsx
│ │ │ └── index.ts
│ │ ├── Chat
│ │ │ ├── ChannelChat.tsx
│ │ │ ├── ChatChannelDetail.tsx
│ │ │ ├── ThreadDetails.tsx
│ │ │ └── components
│ │ │ │ ├── ChannelItem.tsx
│ │ │ │ ├── ChannelList.tsx
│ │ │ │ ├── ChatList.tsx
│ │ │ │ ├── ChatMessageItem.tsx
│ │ │ │ ├── FooterReplyChat.tsx
│ │ │ │ ├── Search.tsx
│ │ │ │ ├── ThreadDetailsHeader.tsx
│ │ │ │ └── index.ts
│ │ ├── EditProfile.tsx
│ │ ├── EditUserStatus
│ │ │ ├── EditUserStatus.tsx
│ │ │ └── components
│ │ │ │ └── DateTimeButton.tsx
│ │ ├── EmailAddress
│ │ │ ├── EmailAddress.tsx
│ │ │ └── components
│ │ │ │ └── EmailAddressItem.tsx
│ │ ├── EmojiPicker.tsx
│ │ ├── FlagPost.tsx
│ │ ├── Home
│ │ │ ├── Home.tsx
│ │ │ └── components
│ │ │ │ ├── HomeNavBar.tsx
│ │ │ │ ├── HomeTabletNavBar.tsx
│ │ │ │ ├── SearchBar.tsx
│ │ │ │ └── index.ts
│ │ ├── Hyperlink.tsx
│ │ ├── MessageDetail
│ │ │ ├── EditPollsList.tsx
│ │ │ ├── ImagePreview.tsx
│ │ │ ├── MessageDetail.tsx
│ │ │ ├── Poll.tsx
│ │ │ └── components
│ │ │ │ ├── ListImageSelected.tsx
│ │ │ │ ├── MessageItem.tsx
│ │ │ │ ├── PollChatBubble.tsx
│ │ │ │ ├── ReplyInputField.tsx
│ │ │ │ ├── ToolTip.tsx
│ │ │ │ └── index.ts
│ │ ├── Messages
│ │ │ ├── Components
│ │ │ │ ├── MessageAvatar.tsx
│ │ │ │ ├── MessageCard.tsx
│ │ │ │ ├── MessageContent.tsx
│ │ │ │ ├── MessageNotification.tsx
│ │ │ │ └── index.ts
│ │ │ └── Messages.tsx
│ │ ├── NewMessage.tsx
│ │ ├── NewPoll.tsx
│ │ ├── NewPost.tsx
│ │ ├── Notifications
│ │ │ ├── Notifications.tsx
│ │ │ └── components
│ │ │ │ └── NotificationItem.tsx
│ │ ├── PostDetail
│ │ │ ├── PostDetail.tsx
│ │ │ ├── PostDetailSkeletonLoading.tsx
│ │ │ ├── hooks
│ │ │ │ ├── index.tsx
│ │ │ │ └── useNotificationScroll.tsx
│ │ │ └── index.tsx
│ │ ├── PostDraft.tsx
│ │ ├── PostImagePreview.tsx
│ │ ├── PostPreview.tsx
│ │ ├── PostReply.tsx
│ │ ├── Preferences
│ │ │ ├── DarkMode.tsx
│ │ │ ├── Preferences.tsx
│ │ │ ├── PushNotifications.tsx
│ │ │ └── components
│ │ │ │ ├── SettingsItem.tsx
│ │ │ │ └── SettingsSwitch.tsx
│ │ ├── Profile
│ │ │ ├── Profile.tsx
│ │ │ └── components
│ │ │ │ └── MenuItem.tsx
│ │ ├── Search.tsx
│ │ ├── SelectUser
│ │ │ ├── SelectUser.tsx
│ │ │ └── components
│ │ │ │ └── UserItem.tsx
│ │ ├── StackAvatarModal.tsx
│ │ ├── Tags
│ │ │ ├── Tags.tsx
│ │ │ └── components
│ │ │ │ ├── AvailableTags.tsx
│ │ │ │ ├── SelectedTags.tsx
│ │ │ │ ├── TagItem.tsx
│ │ │ │ └── index.ts
│ │ ├── Troubleshoot.tsx
│ │ ├── UserInformation.tsx
│ │ ├── Welcome.tsx
│ │ └── index.ts
│ ├── theme
│ │ ├── AppearanceProvider.tsx
│ │ ├── ThemeProvider.tsx
│ │ ├── index.ts
│ │ ├── makeStyles.ts
│ │ └── theme.ts
│ ├── types
│ │ ├── ContentState.ts
│ │ ├── DiscourseNotification.ts
│ │ ├── ErrorSchema.ts
│ │ ├── Form.ts
│ │ ├── Hook.ts
│ │ ├── Navigation.ts
│ │ ├── NumericString.ts
│ │ ├── Post.ts
│ │ ├── Theme.ts
│ │ ├── Types.ts
│ │ ├── __tests__
│ │ │ └── NumericString.test.ts
│ │ ├── api
│ │ │ ├── chat.ts
│ │ │ ├── group.ts
│ │ │ ├── index.ts
│ │ │ ├── message.ts
│ │ │ ├── poll.ts
│ │ │ ├── post.ts
│ │ │ ├── postDraft.ts
│ │ │ ├── topic.ts
│ │ │ └── user.ts
│ │ ├── chat.ts
│ │ ├── global.d.ts
│ │ ├── imports.d.ts
│ │ ├── index.ts
│ │ ├── markdown-it-flowdock.d.ts
│ │ ├── pagination.ts
│ │ └── react-native-animated-progress.d.ts
│ └── utils
│ │ ├── AuthProvider.tsx
│ │ ├── DeviceProvider.tsx
│ │ ├── ModalProvider.tsx
│ │ ├── OngoingLikedTopicProvider.tsx
│ │ ├── PushNotificationsProvider.tsx
│ │ ├── RedirectProvider.tsx
│ │ ├── index.ts
│ │ ├── useLazyQuery.ts
│ │ ├── useMutation.ts
│ │ └── useQuery.ts
├── tsconfig.json
└── yarn.lock
├── package.json
└── yarn.lock
/.easignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | npm-debug.*
3 |
4 | .expo/*
5 | */coverage
6 | */.env
7 | */playstore_secret.json
8 |
9 | frontend/web-build/
10 | frontend/dist
11 |
12 | api/lib
13 |
14 | # macOS
15 | .DS_Store
16 |
17 | # Ignoring other directories
18 | /documentation
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/new_feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: New Feature
3 | about: A new Lexicon feature
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Description
11 |
12 | ## Acceptance Criteria
13 | - [ ]
14 |
15 | ## Guidance
16 |
--------------------------------------------------------------------------------
/.github/workflows/delete-build-jet-cache.yml:
--------------------------------------------------------------------------------
1 | name: Manually Delete BuildJet Cache
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | cache_key:
7 | description: 'BuildJet Cache Key to Delete'
8 | required: true
9 | type: string
10 | jobs:
11 | manually-delete-buildjet-cache:
12 | runs-on: buildjet-2vcpu-ubuntu-2204
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v3
16 | - uses: buildjet/cache-delete@v1
17 | with:
18 | cache_key: ${{ inputs.cache_key }}
19 |
20 | - name: Stop Workflow
21 | if: ${{ success() }}
22 | run: exit 0
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | node_modules/
3 | .DS_Store
4 | sync.sh
5 | .expo/
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | nodejs 16.20.0
--------------------------------------------------------------------------------
/documentation/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | /node_modules
3 |
4 | # Production
5 | /build
6 |
7 | # Generated files
8 | .docusaurus
9 | .cache-loader
10 |
11 | # Misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
--------------------------------------------------------------------------------
/documentation/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3 | };
4 |
--------------------------------------------------------------------------------
/documentation/check-node-version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | node_required=18
4 |
5 | # Get the major version of Node
6 | node_current=$(node -v | awk -F \. {'print substr($1,2)'})
7 |
8 | if [[ "$node_current" -lt "$node_required" ]]; then
9 | echo "NOTE: the Lexicon documentation requires Node >= 18.0."
10 | echo "Your current Node version is $(node -v)"
11 | echo "\n"
12 | echo "This is a requirement of our tooling, Docusaurus (>= 3.0)."
13 | echo "To help make this less of a pain, we recommend you install a tool like nvm (Node Version Manager)."
14 | echo "This allows you to easily switch between Node versions for situations like this."
15 | echo "\n"
16 | echo "https://github.com/nvm-sh/nvm"
17 | echo "\n"
18 |
19 | exit 1;
20 | fi;
21 |
--------------------------------------------------------------------------------
/documentation/docs/commercial-support.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Commercial Support
3 | ---
4 |
5 | With official support, you get expert help straight from the core team. We provide app customization, dedicated support, prioritize feature requests, deployment strategies, advice on best practices, design decisions, and team augmentation.
6 |
7 | Additionally, we are open to engagements for non-technical site owners looking to customize, deploy, and launch a mobile app for their Discourse users.
8 |
9 | Reach out to us for consulting at support@kodefox.com.
10 |
--------------------------------------------------------------------------------
/documentation/docs/email-deep-linking/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: intro
3 | title: Introduction
4 | slug: discourse-plugin/email-deep-linking
5 | ---
6 |
7 | The Lexicon Discourse plugin provides support for integrating Discourse's email notifications with your Lexicon-powered mobile app. Our plugin modifies links in specific Discourse emails so that when a relevant link is tapped and the user has your Lexicon-powered mobile app installed, it will open the app to the relevant topic or post. Otherwise, it will fall back to opening the topic in the device's web browser as it normally would.
8 |
9 | This section of the documentation offers step-by-step instructions to integrate email deep linking into your Discourse site so that your users have a more seamless experience with your Lexicon-powered mobile app.
10 |
--------------------------------------------------------------------------------
/documentation/docs/publish-app.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Publishing your App
3 | ---
4 |
5 | :::danger Progress
6 | This page has not been started yet or needs a lot more work.
7 | :::
8 |
9 | Expo workflow, benefits of, etc.
10 |
11 | Over the air updates?
12 |
--------------------------------------------------------------------------------
/documentation/docs/push-notifications/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | slug: /push-notifications
4 | ---
5 |
6 | The Lexicon Discourse plugin provides support for native push notifications for your Lexicon-powered mobile app. This works for both Android and iOS, and is handled by Expo's [push notifications service](https://docs.expo.dev/push-notifications/overview/).
7 |
8 | This documentation offers step-by-step instructions to seamlessly integrate push notifications into your Discourse site so that your users receive them in your Lexicon-powered mobile app. By following this guide, you will be able to enhance the UX of your users by ensuring they receive timely and engaging notifications about activity on your Discourse site.
9 |
--------------------------------------------------------------------------------
/documentation/docs/white-labeling.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Overview
3 | ---
4 |
5 | The Lexicon Mobile App allows you to customize its appearance through a process known as **White Labeling**.
6 |
7 | If you're unfamiliar with this term, it's essentially the process of branding an existing application specifically for your users.
8 |
9 | White Labeling allows you to configure the app with your own logo, app icon, color theme, fonts, and so on.
10 |
11 | The idea is that your users won't know that the Lexicon team built this app. Its appearance will be completely customized to your brand.
12 |
13 | To learn more about White Labeling the Lexicon Mobile App, continue to the next section.
14 |
--------------------------------------------------------------------------------
/documentation/src/css/image.css:
--------------------------------------------------------------------------------
1 | .image-container-center-multiple {
2 | display: flex;
3 | align-items: center;
4 | justify-content: space-evenly;
5 | }
6 |
7 | .image-container-center {
8 | text-align: center;
9 | }
10 |
11 | .image-container-resize {
12 | max-width: 50%;
13 | }
14 |
--------------------------------------------------------------------------------
/documentation/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Redirect } from 'react-router-dom';
3 |
4 | export default function Home() {
5 | return ;
6 | }
7 |
--------------------------------------------------------------------------------
/documentation/src/pages/playground.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default () => {
4 | return (
5 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/documentation/src/pages/styles.module.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable docusaurus/copyright-header */
2 |
3 | /**
4 | * CSS files with the .module.css suffix will be treated as CSS modules
5 | * and scoped locally.
6 | */
7 |
8 | .heroBanner {
9 | padding: 4rem 0;
10 | text-align: center;
11 | position: relative;
12 | overflow: hidden;
13 | }
14 |
15 | @media screen and (max-width: 966px) {
16 | .heroBanner {
17 | padding: 2rem;
18 | }
19 | }
20 |
21 | .buttons {
22 | display: flex;
23 | align-items: center;
24 | justify-content: center;
25 | }
26 |
27 | .features {
28 | display: flex;
29 | align-items: center;
30 | padding: 2rem 0;
31 | width: 100%;
32 | }
33 |
34 | .featureImage {
35 | height: 200px;
36 | width: 200px;
37 | }
38 |
--------------------------------------------------------------------------------
/documentation/static/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/.nojekyll
--------------------------------------------------------------------------------
/documentation/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/favicon.ico
--------------------------------------------------------------------------------
/documentation/static/img/guides/playStore/build-artifact.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/guides/playStore/build-artifact.png
--------------------------------------------------------------------------------
/documentation/static/img/guides/playStore/builds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/guides/playStore/builds.png
--------------------------------------------------------------------------------
/documentation/static/img/guides/testFlight/add-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/guides/testFlight/add-app.png
--------------------------------------------------------------------------------
/documentation/static/img/guides/testFlight/app-connect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/guides/testFlight/app-connect.png
--------------------------------------------------------------------------------
/documentation/static/img/guides/testFlight/build-artifact.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/guides/testFlight/build-artifact.png
--------------------------------------------------------------------------------
/documentation/static/img/guides/testFlight/builds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/guides/testFlight/builds.png
--------------------------------------------------------------------------------
/documentation/static/img/guides/testFlight/new-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/guides/testFlight/new-app.png
--------------------------------------------------------------------------------
/documentation/static/img/guides/testFlight/register-app-id.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/guides/testFlight/register-app-id.png
--------------------------------------------------------------------------------
/documentation/static/img/guides/testFlight/review-contact.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/guides/testFlight/review-contact.png
--------------------------------------------------------------------------------
/documentation/static/img/guides/testFlight/review-information.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/guides/testFlight/review-information.png
--------------------------------------------------------------------------------
/documentation/static/img/guides/testFlight/review-signin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/guides/testFlight/review-signin.png
--------------------------------------------------------------------------------
/documentation/static/img/lexicon-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/lexicon-architecture.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_Comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_Comment.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_DarkMode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_DarkMode.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_Home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_Home.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_Login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_Login.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_Message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_Message.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_MessageReply.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_MessageReply.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_NewMessage.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_NewMessage.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_NewPost.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_NewPost.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_NewPost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_NewPost.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_Notifications.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_Notifications.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_PostDetail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_PostDetail.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_PostReply.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_PostReply.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_Profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_Profile.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Android_SignUp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Android_SignUp.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_Comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_Comment.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_DarkMode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_DarkMode.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_Home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_Home.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_Login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_Login.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_Message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_Message.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_MessageReply.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_MessageReply.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_NewMessage.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_NewMessage.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_NewPost.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_NewPost.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_NewPost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_NewPost.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_Notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_Notification.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_PostDetail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_PostDetail.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_PostReply.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_PostReply.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_Profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_Profile.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/IOS_SignUp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/IOS_SignUp.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Mobile-LoginWithApple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Mobile-LoginWithApple.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Please_connect_network_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Please_connect_network_error.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/Website_SignUp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/Website_SignUp.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/add-ssh-key.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/add-ssh-key.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/authenticating-droplet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/authenticating-droplet.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/choose-a-plan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/choose-a-plan.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/choose-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/choose-image.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/control-panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/control-panel.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/droplet-ip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/droplet-ip.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/droplets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/droplets.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/finalizing-creating-droplet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/finalizing-creating-droplet.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/playground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/playground.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/Discourse-Plugin-Email-notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/Discourse-Plugin-Email-notification.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/Discourse-Plugin-EmailDeepLinking-Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/Discourse-Plugin-EmailDeepLinking-Settings.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/Discourse-Plugin-Enable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/Discourse-Plugin-Enable.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/Discourse-Plugin-PushNotif-Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/Discourse-Plugin-PushNotif-Settings.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/Discourse-Plugin-Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/Discourse-Plugin-Settings.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/Mobile-PushNotification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/Mobile-PushNotification.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-ActivationWithLink-Email.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-ActivationWithLink-Email.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-EmailDeepLinking-Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-EmailDeepLinking-Settings.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-Enable-ActivationWithLink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-Enable-ActivationWithLink.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-Enable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-Enable.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-Login-With-Apple-App-ID.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-Login-With-Apple-App-ID.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-Login-With-Apple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-Login-With-Apple.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-Login-With-Link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-Login-With-Link.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-LoginWithLink-Email.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-LoginWithLink-Email.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-PushNotif-Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-PushNotif-Settings.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-2.2.0/Discourse-Plugin-Settings.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-2.2.0/Mobile-ActivationWithLink-Redirect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-2.2.0/Mobile-ActivationWithLink-Redirect.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-2.2.0/Mobile-LoginWithLink-Redirect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-2.2.0/Mobile-LoginWithLink-Redirect.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-2.2.0/Mobile-LoginWithLink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-2.2.0/Mobile-LoginWithLink.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-3.0.0/Discourse-Plugin-EmailDeepLinking-Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-3.0.0/Discourse-Plugin-EmailDeepLinking-Settings.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-3.0.0/Discourse-Plugin-PushNotif-Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-3.0.0/Discourse-Plugin-PushNotif-Settings.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/plugins/version-3.0.0/Discourse-Plugin-Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/plugins/version-3.0.0/Discourse-Plugin-Settings.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/region.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/region.jpeg
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_Android_Landscape_Home.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_Android_Landscape_Home.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_Android_Landscape_NewPost.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_Android_Landscape_NewPost.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_Android_Landscape_Profile.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_Android_Landscape_Profile.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_Android_Landscape_ReplyMessage.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_Android_Landscape_ReplyMessage.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_Android_Portrait_Home.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_Android_Portrait_Home.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_Android_Portrait_NewPost.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_Android_Portrait_NewPost.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_Android_Portrait_Profile.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_Android_Portrait_Profile.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_Android_Portrait_ReplyMessage.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_Android_Portrait_ReplyMessage.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_IOS_Landscape_Home.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_IOS_Landscape_Home.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_IOS_Landscape_NewPost.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_IOS_Landscape_NewPost.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_IOS_Landscape_Profile.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_IOS_Landscape_Profile.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_IOS_Landscape_ReplyMessage.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_IOS_Landscape_ReplyMessage.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_IOS_Portrait_Home.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_IOS_Portrait_Home.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_IOS_Portrait_NewPost.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_IOS_Portrait_NewPost.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_IOS_Portrait_Profile.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_IOS_Portrait_Profile.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/tablet/Tablet_IOS_Portrait_ReplyMessage.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/tablet/Tablet_IOS_Portrait_ReplyMessage.gif
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/terminal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/terminal.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/user-api-keys/Discourse-App-Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/user-api-keys/Discourse-App-Settings.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/user-api-keys/Discourse-Profile-Security-Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/user-api-keys/Discourse-Profile-Security-Settings.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/user-api-keys/Discourse-Settings-Auth-Redirect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/user-api-keys/Discourse-Settings-Auth-Redirect.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/user-api-keys/Discourse-Settings-New-Auth-Redirect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/user-api-keys/Discourse-Settings-New-Auth-Redirect.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/user-api-keys/Mobile-Authorize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/user-api-keys/Mobile-Authorize.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/user-api-keys/Mobile-Discourse-Login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/user-api-keys/Mobile-Discourse-Login.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/user-api-keys/Mobile-Welcome-Page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/user-api-keys/Mobile-Welcome-Page.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/vscode-env.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/vscode-env.png
--------------------------------------------------------------------------------
/documentation/static/img/screenshot/vscode-file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/screenshot/vscode-file.png
--------------------------------------------------------------------------------
/documentation/static/img/version-3.0.0/new-lexicon-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/documentation/static/img/version-3.0.0/new-lexicon-architecture.png
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-1.0.0/commercial-support.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Commercial Support
3 | ---
4 |
5 | With official support, you get expert help straight from the core team. We provide app customization, dedicated support, prioritize feature requests, deployment strategies, advice on best practices, design decisions, and team augmentation.
6 |
7 | Additionally, we are open to engagements for non-technical site owners looking to customize, deploy, and launch a mobile app for their Discourse users.
8 |
9 | Reach out to us for consulting at support@kodefox.com.
10 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-1.0.0/publish-app.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Publishing your App
3 | ---
4 |
5 | :::danger Progress
6 | This page has not been started yet or needs a lot more work.
7 | :::
8 |
9 | Expo workflow, benefits of, etc.
10 |
11 | Over the air updates?
12 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-1.0.0/white-labeling.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Overview
3 | ---
4 |
5 | The Lexicon Mobile App allows you to customize its appearance through a process known as **White Labeling**.
6 |
7 | If you're unfamiliar with this term, it's essentially the process of branding an existing application specifically for your users.
8 |
9 | White Labeling allows you to configure the app with your own logo, app icon, color theme, fonts, and so on.
10 |
11 | The idea is that your users won't know that the Lexicon team built this app. Its appearance will be completely customized to your brand.
12 |
13 | To learn more about White Labeling the Lexicon Mobile App, continue to the next section.
14 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.0.0/commercial-support.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Commercial Support
3 | ---
4 |
5 | With official support, you get expert help straight from the core team. We provide app customization, dedicated support, prioritize feature requests, deployment strategies, advice on best practices, design decisions, and team augmentation.
6 |
7 | Additionally, we are open to engagements for non-technical site owners looking to customize, deploy, and launch a mobile app for their Discourse users.
8 |
9 | Reach out to us for consulting at support@kodefox.com.
10 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.0.0/email-deep-linking/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: intro
3 | title: Introduction
4 | slug: discourse-plugin/email-deep-linking
5 | ---
6 |
7 | The Lexicon Discourse plugin provides support for integrating Discourse's email notifications with your Lexicon-powered mobile app. Our plugin modifies links in specific Discourse emails so that when a relevant link is tapped and the user has your Lexicon-powered mobile app installed, it will open the app to the relevant topic or post. Otherwise, it will fall back to opening the topic in the device's web browser as it normally would.
8 |
9 | This section of the documentation offers step-by-step instructions to integrate email deep linking into your Discourse site so that your users have a more seamless experience with your Lexicon-powered mobile app.
10 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.0.0/publish-app.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Publishing your App
3 | ---
4 |
5 | :::danger Progress
6 | This page has not been started yet or needs a lot more work.
7 | :::
8 |
9 | Expo workflow, benefits of, etc.
10 |
11 | Over the air updates?
12 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.0.0/push-notifications/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | slug: /push-notifications
4 | ---
5 |
6 | The Lexicon Discourse plugin provides support for native push notifications for your Lexicon-powered mobile app. This works for both Android and iOS, and is handled by Expo's [push notifications service](https://docs.expo.dev/push-notifications/overview/).
7 |
8 | This documentation offers step-by-step instructions to seamlessly integrate push notifications into your Discourse site so that your users receive them in your Lexicon-powered mobile app. By following this guide, you will be able to enhance the UX of your users by ensuring they receive timely and engaging notifications about activity on your Discourse site.
9 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.0.0/white-labeling.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Overview
3 | ---
4 |
5 | The Lexicon Mobile App allows you to customize its appearance through a process known as **White Labeling**.
6 |
7 | If you're unfamiliar with this term, it's essentially the process of branding an existing application specifically for your users.
8 |
9 | White Labeling allows you to configure the app with your own logo, app icon, color theme, fonts, and so on.
10 |
11 | The idea is that your users won't know that the Lexicon team built this app. Its appearance will be completely customized to your brand.
12 |
13 | To learn more about White Labeling the Lexicon Mobile App, continue to the next section.
14 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.1.0/commercial-support.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Commercial Support
3 | ---
4 |
5 | With official support, you get expert help straight from the core team. We provide app customization, dedicated support, prioritize feature requests, deployment strategies, advice on best practices, design decisions, and team augmentation.
6 |
7 | Additionally, we are open to engagements for non-technical site owners looking to customize, deploy, and launch a mobile app for their Discourse users.
8 |
9 | Reach out to us for consulting at support@kodefox.com.
10 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.1.0/email-deep-linking/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: intro
3 | title: Introduction
4 | slug: discourse-plugin/email-deep-linking
5 | ---
6 |
7 | The Lexicon Discourse plugin provides support for integrating Discourse's email notifications with your Lexicon-powered mobile app. Our plugin modifies links in specific Discourse emails so that when a relevant link is tapped and the user has your Lexicon-powered mobile app installed, it will open the app to the relevant topic or post. Otherwise, it will fall back to opening the topic in the device's web browser as it normally would.
8 |
9 | This section of the documentation offers step-by-step instructions to integrate email deep linking into your Discourse site so that your users have a more seamless experience with your Lexicon-powered mobile app.
10 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.1.0/publish-app.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Publishing your App
3 | ---
4 |
5 | :::danger Progress
6 | This page has not been started yet or needs a lot more work.
7 | :::
8 |
9 | Expo workflow, benefits of, etc.
10 |
11 | Over the air updates?
12 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.1.0/push-notifications/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | slug: /push-notifications
4 | ---
5 |
6 | The Lexicon Discourse plugin provides support for native push notifications for your Lexicon-powered mobile app. This works for both Android and iOS, and is handled by Expo's [push notifications service](https://docs.expo.dev/push-notifications/overview/).
7 |
8 | This documentation offers step-by-step instructions to seamlessly integrate push notifications into your Discourse site so that your users receive them in your Lexicon-powered mobile app. By following this guide, you will be able to enhance the UX of your users by ensuring they receive timely and engaging notifications about activity on your Discourse site.
9 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.1.0/white-labeling.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Overview
3 | ---
4 |
5 | The Lexicon Mobile App allows you to customize its appearance through a process known as **White Labeling**.
6 |
7 | If you're unfamiliar with this term, it's essentially the process of branding an existing application specifically for your users.
8 |
9 | White Labeling allows you to configure the app with your own logo, app icon, color theme, fonts, and so on.
10 |
11 | The idea is that your users won't know that the Lexicon team built this app. Its appearance will be completely customized to your brand.
12 |
13 | To learn more about White Labeling the Lexicon Mobile App, continue to the next section.
14 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.2.0/activation-with-link/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: intro
3 | title: Introduction
4 | slug: discourse-plugin/activation-with-link
5 | ---
6 |
7 | The Lexicon Discourse plugin provides support for integrating Discourse's email activation with your Lexicon-powered mobile app. Our plugin modifies links in specific Discourse activation emails account so that when a relevant link is tapped and the user has your Lexicon-powered mobile app installed, it will open the app and automatically activate account and log the user in.
8 |
9 | This section of the documentation offers step-by-step instructions to integrate activation with link into your Discourse site so that your users have a more seamless experience with your Lexicon-powered mobile app.
10 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.2.0/commercial-support.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Commercial Support
3 | ---
4 |
5 | With official support, you get expert help straight from the core team. We provide app customization, dedicated support, prioritize feature requests, deployment strategies, advice on best practices, design decisions, and team augmentation.
6 |
7 | Additionally, we are open to engagements for non-technical site owners looking to customize, deploy, and launch a mobile app for their Discourse users.
8 |
9 | Reach out to us for consulting at support@kodefox.com.
10 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.2.0/email-deep-linking/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: intro
3 | title: Introduction
4 | slug: discourse-plugin/email-deep-linking
5 | ---
6 |
7 | The Lexicon Discourse plugin provides support for integrating Discourse's email notifications with your Lexicon-powered mobile app. Our plugin modifies links in specific Discourse emails so that when a relevant link is tapped and the user has your Lexicon-powered mobile app installed, it will open the app to the relevant topic or post. Otherwise, it will fall back to opening the topic in the device's web browser as it normally would.
8 |
9 | This section of the documentation offers step-by-step instructions to integrate email deep linking into your Discourse site so that your users have a more seamless experience with your Lexicon-powered mobile app.
10 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.2.0/login-with-apple/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: intro
3 | title: Introduction
4 | slug: discourse-plugin/login-with-apple
5 | ---
6 |
7 | The Lexicon Discourse plugin provides support for integrating Apple's authentication with your Lexicon-powered mobile app. Our plugin enables signing into your Discourse site using Apple authentication.
8 |
9 | This section of the documentation offers step-by-step instructions to integrate this login functionality into your Discourse site, providing your users with a more seamless experience with your Lexicon-powered mobile app.
10 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.2.0/login-with-link/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | id: intro
3 | title: Introduction
4 | slug: discourse-plugin/login-with-link
5 | ---
6 |
7 | The Lexicon Discourse plugin provides support for integrating Discourse's email login with your Lexicon-powered mobile app. Our plugin modifies links in Discourse login emails so that when a relevant link is tapped, and the user has your Lexicon-powered mobile app installed, it will open the app and automatically log the user in.
8 |
9 | This section of the documentation offers step-by-step instructions to integrate this login functionality into your Discourse site, providing your users with a more seamless experience with your Lexicon-powered mobile app.
10 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.2.0/publish-app.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Publishing your App
3 | ---
4 |
5 | :::danger Progress
6 | This page has not been started yet or needs a lot more work.
7 | :::
8 |
9 | Expo workflow, benefits of, etc.
10 |
11 | Over the air updates?
12 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.2.0/push-notifications/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | slug: /push-notifications
4 | ---
5 |
6 | The Lexicon Discourse plugin provides support for native push notifications for your Lexicon-powered mobile app. This works for both Android and iOS, and is handled by Expo's [push notifications service](https://docs.expo.dev/push-notifications/overview/).
7 |
8 | This documentation offers step-by-step instructions to seamlessly integrate push notifications into your Discourse site so that your users receive them in your Lexicon-powered mobile app. By following this guide, you will be able to enhance the UX of your users by ensuring they receive timely and engaging notifications about activity on your Discourse site.
9 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-2.2.0/white-labeling.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Overview
3 | ---
4 |
5 | The Lexicon Mobile App allows you to customize its appearance through a process known as **White Labeling**.
6 |
7 | If you're unfamiliar with this term, it's essentially the process of branding an existing application specifically for your users.
8 |
9 | White Labeling allows you to configure the app with your own logo, app icon, color theme, fonts, and so on.
10 |
11 | The idea is that your users won't know that the Lexicon team built this app. Its appearance will be completely customized to your brand.
12 |
13 | To learn more about White Labeling the Lexicon Mobile App, continue to the next section.
14 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-3.0.0/commercial-support.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Commercial Support
3 | ---
4 |
5 | With official support, you get expert help straight from the core team. We provide app customization, dedicated support, prioritize feature requests, deployment strategies, advice on best practices, design decisions, and team augmentation.
6 |
7 | Additionally, we are open to engagements for non-technical site owners looking to customize, deploy, and launch a mobile app for their Discourse users.
8 |
9 | Reach out to us for consulting at support@kodefox.com.
10 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-3.0.0/publish-app.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Publishing your App
3 | ---
4 |
5 | :::danger Progress
6 | This page has not been started yet or needs a lot more work.
7 | :::
8 |
9 | Expo workflow, benefits of, etc.
10 |
11 | Over the air updates?
12 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-3.0.0/push-notifications/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | slug: /push-notifications
4 | ---
5 |
6 | The Lexicon Discourse plugin provides support for native push notifications for your Lexicon-powered mobile app. This works for both Android and iOS, and is handled by Expo's [push notifications service](https://docs.expo.dev/push-notifications/overview/).
7 |
8 | This documentation offers step-by-step instructions to seamlessly integrate push notifications into your Discourse site so that your users receive them in your Lexicon-powered mobile app. By following this guide, you will be able to enhance the UX of your users by ensuring they receive timely and engaging notifications about activity on your Discourse site.
9 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-3.0.0/white-labeling.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Overview
3 | ---
4 |
5 | The Lexicon Mobile App allows you to customize its appearance through a process known as **White Labeling**.
6 |
7 | If you're unfamiliar with this term, it's essentially the process of branding an existing application specifically for your users.
8 |
9 | White Labeling allows you to configure the app with your own logo, app icon, color theme, fonts, and so on.
10 |
11 | The idea is that your users won't know that the Lexicon team built this app. Its appearance will be completely customized to your brand.
12 |
13 | To learn more about White Labeling the Lexicon Mobile App, continue to the next section.
14 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-3.1.0/commercial-support.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Commercial Support
3 | ---
4 |
5 | With official support, you get expert help straight from the core team. We provide app customization, dedicated support, prioritize feature requests, deployment strategies, advice on best practices, design decisions, and team augmentation.
6 |
7 | Additionally, we are open to engagements for non-technical site owners looking to customize, deploy, and launch a mobile app for their Discourse users.
8 |
9 | Reach out to us for consulting at support@kodefox.com.
10 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-3.1.0/publish-app.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Publishing your App
3 | ---
4 |
5 | :::danger Progress
6 | This page has not been started yet or needs a lot more work.
7 | :::
8 |
9 | Expo workflow, benefits of, etc.
10 |
11 | Over the air updates?
12 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-3.1.0/push-notifications/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | slug: /push-notifications
4 | ---
5 |
6 | The Lexicon Discourse plugin provides support for native push notifications for your Lexicon-powered mobile app. This works for both Android and iOS, and is handled by Expo's [push notifications service](https://docs.expo.dev/push-notifications/overview/).
7 |
8 | This documentation offers step-by-step instructions to seamlessly integrate push notifications into your Discourse site so that your users receive them in your Lexicon-powered mobile app. By following this guide, you will be able to enhance the UX of your users by ensuring they receive timely and engaging notifications about activity on your Discourse site.
9 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-3.1.0/white-labeling.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Overview
3 | ---
4 |
5 | The Lexicon Mobile App allows you to customize its appearance through a process known as **White Labeling**.
6 |
7 | If you're unfamiliar with this term, it's essentially the process of branding an existing application specifically for your users.
8 |
9 | White Labeling allows you to configure the app with your own logo, app icon, color theme, fonts, and so on.
10 |
11 | The idea is that your users won't know that the Lexicon team built this app. Its appearance will be completely customized to your brand.
12 |
13 | To learn more about White Labeling the Lexicon Mobile App, continue to the next section.
14 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-3.2.0/commercial-support.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Commercial Support
3 | ---
4 |
5 | With official support, you get expert help straight from the core team. We provide app customization, dedicated support, prioritize feature requests, deployment strategies, advice on best practices, design decisions, and team augmentation.
6 |
7 | Additionally, we are open to engagements for non-technical site owners looking to customize, deploy, and launch a mobile app for their Discourse users.
8 |
9 | Reach out to us for consulting at support@kodefox.com.
10 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-3.2.0/publish-app.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Publishing your App
3 | ---
4 |
5 | :::danger Progress
6 | This page has not been started yet or needs a lot more work.
7 | :::
8 |
9 | Expo workflow, benefits of, etc.
10 |
11 | Over the air updates?
12 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-3.2.0/push-notifications/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | slug: /push-notifications
4 | ---
5 |
6 | The Lexicon Discourse plugin provides support for native push notifications for your Lexicon-powered mobile app. This works for both Android and iOS, and is handled by Expo's [push notifications service](https://docs.expo.dev/push-notifications/overview/).
7 |
8 | This documentation offers step-by-step instructions to seamlessly integrate push notifications into your Discourse site so that your users receive them in your Lexicon-powered mobile app. By following this guide, you will be able to enhance the UX of your users by ensuring they receive timely and engaging notifications about activity on your Discourse site.
9 |
--------------------------------------------------------------------------------
/documentation/versioned_docs/version-3.2.0/white-labeling.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Overview
3 | ---
4 |
5 | The Lexicon Mobile App allows you to customize its appearance through a process known as **White Labeling**.
6 |
7 | If you're unfamiliar with this term, it's essentially the process of branding an existing application specifically for your users.
8 |
9 | White Labeling allows you to configure the app with your own logo, app icon, color theme, fonts, and so on.
10 |
11 | The idea is that your users won't know that the Lexicon team built this app. Its appearance will be completely customized to your brand.
12 |
13 | To learn more about White Labeling the Lexicon Mobile App, continue to the next section.
14 |
--------------------------------------------------------------------------------
/documentation/versions.json:
--------------------------------------------------------------------------------
1 | [
2 | "3.2.0",
3 | "3.1.0",
4 | "3.0.0",
5 | "2.2.0",
6 | "2.1.0",
7 | "2.0.0",
8 | "1.0.0"
9 | ]
10 |
--------------------------------------------------------------------------------
/frontend/.eslintignore:
--------------------------------------------------------------------------------
1 | src/generated/**/*.ts
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 | *.jks
5 | *.p8
6 | *.p12
7 | *.key
8 | *.mobileprovision
9 | *.orig.*
10 | web-build/
11 | src/generatedAPI/**/*.ts
12 | src/graphql/schema.json
13 | .env
14 | playstore_secret.json
15 | dist/
16 | bin/
17 | artifacts/
18 |
19 | # macOS
20 | .DS_Store
21 | /coverage
22 |
23 | #firebase
24 | google-services.json
25 | encoded-google-services*
26 |
27 | android/
28 |
29 | ios/
30 |
--------------------------------------------------------------------------------
/frontend/App.ts:
--------------------------------------------------------------------------------
1 | import './src/polyfills';
2 | export { default } from './src/App';
3 |
--------------------------------------------------------------------------------
/frontend/apollo.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | client: {
3 | service: {
4 | name: 'prose-api-schema',
5 | localSchemaFile: '../api/src/generated/schema.graphql',
6 | },
7 | excludes: ['**/generated/**/*'],
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/frontend/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/frontend/assets/favicon.png
--------------------------------------------------------------------------------
/frontend/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/frontend/assets/icon.png
--------------------------------------------------------------------------------
/frontend/assets/icons/Add.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/AddCircle.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/AlternateEmail.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/ArrowUpward.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Ballot.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/assets/icons/BoldText.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/frontend/assets/icons/BulletList.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Cancel.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Chart.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/CheckCircle.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/ChevronDown.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/assets/icons/ChevronRight.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/ChevronUp.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Clock.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Close.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Collapsible.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Dark.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Delete.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Done.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/DropdownArrow.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Edit.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Event.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/ExpandLess.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/assets/icons/ExpandMore.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Folder.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Home.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/ItalicText.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/frontend/assets/icons/KeyboardHide.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Likes.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Link.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Lock.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Mail.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/More.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/MoreVert.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Notifications.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/NumberList.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Online.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Person.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Photo.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Pin.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/PoliceBadge.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Power.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/QuoteText.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/frontend/assets/icons/RemoveCircle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Replies.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Search.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Send.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/SideBarAndroid.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Triangle.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/Views.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/assets/icons/WarningCircle.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/frontend/assets/images/imagePlaceholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/frontend/assets/images/imagePlaceholder.png
--------------------------------------------------------------------------------
/frontend/assets/images/index.ts:
--------------------------------------------------------------------------------
1 | export { default as LightSplash } from './splash.png';
2 | export { default as DarkSplash } from './splashDark.png';
3 | export { default as LightLogo } from './logo.png';
4 | export { default as DarkLogo } from './logoDark.png';
5 | export { default as DEFAULT_IMAGE } from './imagePlaceholder.png';
6 |
--------------------------------------------------------------------------------
/frontend/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/frontend/assets/images/logo.png
--------------------------------------------------------------------------------
/frontend/assets/images/logoDark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/frontend/assets/images/logoDark.png
--------------------------------------------------------------------------------
/frontend/assets/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/frontend/assets/images/splash.png
--------------------------------------------------------------------------------
/frontend/assets/images/splashDark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/frontend/assets/images/splashDark.png
--------------------------------------------------------------------------------
/frontend/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | plugins: [
6 | ['module:react-native-dotenv', { allowUndefined: true }],
7 | /**
8 | * Reanimated plugin has to be listed last
9 | * Added to avoid this error
10 | * `Export namespace should be first transformed by @babel/plugin-proposal-export-namespace-from`
11 | * and as suggested in the docs
12 | * https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation/#babel-plugin
13 | */
14 | 'react-native-reanimated/plugin',
15 | ],
16 | };
17 | };
18 |
--------------------------------------------------------------------------------
/frontend/e2e/global/constant.ts:
--------------------------------------------------------------------------------
1 | export const MOCK_SERVER_PORT = '8929';
2 |
--------------------------------------------------------------------------------
/frontend/e2e/global/index.ts:
--------------------------------------------------------------------------------
1 | export * from './constant';
2 |
--------------------------------------------------------------------------------
/frontend/e2e/helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from './logout';
2 | export * from './tab';
3 | export * from './post';
4 | export * from './link';
5 | export * from './deepLink';
6 | export * from './privateMessage';
7 | export * from './tooltip';
8 | export * from './poll';
9 |
--------------------------------------------------------------------------------
/frontend/e2e/helpers/link.ts:
--------------------------------------------------------------------------------
1 | import { by, expect, element } from 'detox';
2 |
3 | export async function hyperLinkScene() {
4 | await expect(element(by.text('Insert Hyperlink')).atIndex(0)).toBeVisible();
5 |
6 | const textInputUrl = element(by.id('Hyperlink:TextInput:URL'));
7 | await expect(textInputUrl).toBeVisible();
8 | await textInputUrl.replaceText('www.google.com');
9 |
10 | const textInputTitle = element(by.id('Hyperlink:TextInput:Title'));
11 | await textInputTitle.replaceText('test url');
12 |
13 | const textDone = device.getPlatform() === 'android' ? 'Add' : 'Done';
14 | await expect(element(by.text(textDone)).atIndex(0)).toBeVisible();
15 | await element(by.text(textDone)).atIndex(0).tap();
16 | }
17 |
--------------------------------------------------------------------------------
/frontend/e2e/helpers/logout.ts:
--------------------------------------------------------------------------------
1 | import { by, element } from 'detox';
2 |
3 | import { waitTabProfile } from './tab';
4 |
5 | export async function logout() {
6 | await waitTabProfile();
7 |
8 | await element(by.id('Tab:Profile')).tap();
9 |
10 | await element(by.id('Profile:ScrollView')).scrollTo('bottom');
11 | await element(by.id('Profile:MenuItem:Logout')).tap();
12 | }
13 |
--------------------------------------------------------------------------------
/frontend/e2e/helpers/poll.ts:
--------------------------------------------------------------------------------
1 | import { by, element } from 'detox';
2 |
3 | export async function createPollOptions() {
4 | await element(by.id('NewPoll:TextInput:Options'))
5 | .atIndex(0)
6 | .replaceText('Apple');
7 | await element(by.id('NewPoll:Button:AddOption')).multiTap(2);
8 | await element(by.id('NewPoll:TextInput:Options'))
9 | .atIndex(1)
10 | .replaceText('Banana');
11 | await element(by.id('NewPoll:Button:AddOption')).multiTap(2);
12 | await element(by.id('NewPoll:TextInput:Options'))
13 | .atIndex(2)
14 | .typeText('Mango\n');
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/e2e/helpers/privateMessage.ts:
--------------------------------------------------------------------------------
1 | import { by, expect, element, device } from 'detox';
2 |
3 | export async function redirectNewPrivateMessage() {
4 | if (device.getPlatform() === 'android') {
5 | await expect(element(by.id('FloatingButton'))).toBeVisible();
6 | await element(by.id('FloatingButton')).tap();
7 | } else {
8 | await expect(element(by.id('HeaderItem:IconOnly'))).toBeVisible();
9 | await element(by.id('HeaderItem:IconOnly')).tap();
10 | }
11 | }
12 |
13 | export const redirectToMessageDetail = async () => {
14 | const message = element(by.text('Testing new message')).atIndex(0);
15 | await message.tap();
16 | };
17 |
--------------------------------------------------------------------------------
/frontend/e2e/helpers/tab.ts:
--------------------------------------------------------------------------------
1 | import { by, element, waitFor } from 'detox';
2 |
3 | export async function waitTabProfile() {
4 | await waitFor(element(by.id('Tab:Profile')))
5 | .toBeVisible()
6 | .withTimeout(7000);
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/e2e/helpers/tooltip.ts:
--------------------------------------------------------------------------------
1 | import { by, element, expect } from 'detox';
2 |
3 | export async function openToolTipMessageReply() {
4 | const buttonToolTip = element(by.id('ToolTip:AddIcon'));
5 | await expect(buttonToolTip).toBeVisible();
6 | await buttonToolTip.tap();
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/e2e/init.ts:
--------------------------------------------------------------------------------
1 | import { device } from 'detox';
2 | import { config } from 'detox/internals';
3 |
4 | import { MockServerContext, startMockServer } from './rest-mock/mswServer';
5 |
6 | let mockServer: MockServerContext;
7 | // Set the default timeout
8 | const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
9 | jest.setTimeout(DEFAULT_TIMEOUT_MS);
10 |
11 | beforeAll(async () => {
12 | mockServer = await startMockServer();
13 | if (config.behavior.init?.reinstallApp === false) {
14 | await device.installApp();
15 | }
16 |
17 | return device.launchApp();
18 | });
19 |
20 | afterAll(() => {
21 | mockServer.stop();
22 | });
23 |
--------------------------------------------------------------------------------
/frontend/e2e/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@jest/types').Config.InitialOptions} */
2 | module.exports = {
3 | rootDir: '..',
4 | testRegex: '\\.e2e\\.ts$',
5 | testTimeout: 120000,
6 | maxWorkers: 1,
7 | globalSetup: 'detox/runners/jest/globalSetup',
8 | globalTeardown: 'detox/runners/jest/globalTeardown',
9 | reporters: ['detox/runners/jest/reporter'],
10 | testEnvironment: 'detox/runners/jest/testEnvironment',
11 | setupFilesAfterEnv: ['/e2e/init.ts'],
12 | verbose: true,
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/assets/grinning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/frontend/e2e/rest-mock/assets/grinning.png
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/assets/heart_eyes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/frontend/e2e/rest-mock/assets/heart_eyes.png
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/assets/smile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lexiconhq/lexicon/f484982ffd216e83d2533a090774c6b99ef9b8bc/frontend/e2e/rest-mock/assets/smile.png
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/data/categories.ts:
--------------------------------------------------------------------------------
1 | export const mockCategories = [
2 | {
3 | id: 2,
4 | color: '25AAE2',
5 | name: 'General',
6 | descriptionText:
7 | 'Create topics here that don’t fit into any other existing category.',
8 | },
9 | {
10 | id: 3,
11 | color: '231F20',
12 | name: 'Lexicon UAT',
13 | descriptionText:
14 | 'Use this category for all testing purposes related to Lexicon. Whether you’re testing certain features like push notifications & email deep linking—or just testing that posts can still be created correctly—please make sure you assign to this category.',
15 | },
16 | ];
17 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/data/index.ts:
--------------------------------------------------------------------------------
1 | export * from './categories';
2 | export * from './chat';
3 | export * from './messageDetails';
4 | export * from './messages';
5 | export * from './postDrafts';
6 | export * from './posts';
7 | export * from './site';
8 | export * from './token';
9 | export * from './topicDetails';
10 | export * from './topics';
11 | export * from './users';
12 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/data/messageDetails.ts:
--------------------------------------------------------------------------------
1 | import { mockMessages } from './messages';
2 | import { mockMessageReplies } from './posts';
3 | import { mockUsers } from './users';
4 |
5 | export let mockMessageDetails = [
6 | {
7 | id: mockMessages[0].id,
8 | title: 'Testing new message',
9 | postStream: {
10 | posts: [mockMessageReplies[0]],
11 | stream: [mockMessageReplies[0].id],
12 | firstPost: null,
13 | },
14 | details: {
15 | allowedUsers: mockUsers,
16 | participants: [mockUsers[1]],
17 | createdBy: {
18 | id: mockUsers[1].id,
19 | username: mockUsers[1].username,
20 | },
21 | },
22 | postsCount: mockMessages[0].postsCount,
23 | },
24 | ];
25 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/data/token.ts:
--------------------------------------------------------------------------------
1 | export const mockToken = '1234567890';
2 | export const emailToken = 'abcdef123456';
3 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/rest/aboutHandler.ts:
--------------------------------------------------------------------------------
1 | import { http, HttpResponse } from 'msw';
2 |
3 | import { mockTopicsRest } from '../data';
4 |
5 | export const aboutHandler = [
6 | http.get('/about.json', () => {
7 | return HttpResponse.json({
8 | about: {
9 | stats: {
10 | topicCount: mockTopicsRest.length,
11 | },
12 | },
13 | });
14 | }),
15 | ];
16 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/rest/categoriesHandler.ts:
--------------------------------------------------------------------------------
1 | import { http, HttpResponse } from 'msw';
2 |
3 | import { mockCategories } from '../data';
4 |
5 | export const categoriesHandler = [
6 | http.get('/categories.json', () => {
7 | return HttpResponse.json({
8 | categoryList: {
9 | canCreateCategory: false,
10 | canCreateTopic: true,
11 | categories: mockCategories,
12 | },
13 | });
14 | }),
15 | ];
16 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/rest/helper/index.ts:
--------------------------------------------------------------------------------
1 | export * from './parseNumber';
2 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/rest/helper/parseNumber.ts:
--------------------------------------------------------------------------------
1 | export function parseStringIntoNumber(data: string): number | null {
2 | const parsed = Number.parseInt(data.trim(), 10);
3 | return isNaN(parsed) ? null : parsed;
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/rest/index.ts:
--------------------------------------------------------------------------------
1 | export * from './aboutHandler';
2 | export * from './categoriesHandler';
3 | export * from './chatHandler';
4 | export * from './draftHandler';
5 | export * from './messagesHandler';
6 | export * from './notificationsHandler';
7 | export * from './pollHandler';
8 | export * from './profileHandler';
9 | export * from './siteHandler';
10 | export * from './timingsHandler';
11 | export * from './topicsHandler';
12 | export * from './userActivityHandler';
13 | export * from './userHandler';
14 | export * from './userStatusHandler';
15 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/rest/notificationsHandler.ts:
--------------------------------------------------------------------------------
1 | import { http, HttpResponse } from 'msw';
2 |
3 | export const notificationsHandler = [
4 | http.get('/notifications.json', (req) => {
5 | let url = new URL(req.request.url);
6 | /**
7 | * unread condition used for profile
8 | */
9 | if (url.searchParams.get('filter') === 'unread') {
10 | return HttpResponse.json({
11 | notifications: [],
12 | });
13 | }
14 | return HttpResponse.json({
15 | notifications: [],
16 | });
17 | }),
18 | ];
19 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/rest/siteHandler.ts:
--------------------------------------------------------------------------------
1 | import { http, HttpResponse } from 'msw';
2 |
3 | import { SITE, SITE_SETTINGS } from '../data';
4 |
5 | export const siteHandler = [
6 | http.get('/site.json', (req) => {
7 | const token = req.request.headers.get('Authorization');
8 | if (!token) {
9 | throw new Error('Authorization Failed');
10 | }
11 | return HttpResponse.json({
12 | ...SITE,
13 | });
14 | }),
15 | http.get('/site/settings.json', (req) => {
16 | const token = req.request.headers.get('Authorization');
17 | if (!token) {
18 | throw new Error('Authorization Failed');
19 | }
20 | return HttpResponse.json({
21 | ...SITE_SETTINGS,
22 | });
23 | }),
24 | ];
25 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/rest/timingsHandler.ts:
--------------------------------------------------------------------------------
1 | import { http, HttpResponse } from 'msw';
2 |
3 | export const timingsHandler = [
4 | http.post('/topics/timings.json', () => {
5 | return HttpResponse.json({ message: 'success' });
6 | }),
7 | ];
8 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/rest/userHandler.ts:
--------------------------------------------------------------------------------
1 | import { http, HttpResponse } from 'msw';
2 |
3 | import { mockUsers } from '../data';
4 |
5 | export const userHandler = [
6 | http.get('/u/search/users.json', () => {
7 | return HttpResponse.json({
8 | groups: [],
9 | users: mockUsers,
10 | });
11 | }),
12 | ];
13 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './transformSnakeCase';
2 |
--------------------------------------------------------------------------------
/frontend/e2e/rest-mock/utils/transformSnakeCase.ts:
--------------------------------------------------------------------------------
1 | // This implementation based on https://gist.github.com/kuroski/9a7ae8e5e5c9e22985364d1ddbf3389d
2 |
3 | // Utility type to transform camelCase strings to snake_case
4 | type CamelToSnake = T extends `${infer First}${infer Rest}`
5 | ? `${First extends Capitalize
6 | ? '_'
7 | : ''}${Lowercase}${CamelToSnake}`
8 | : '';
9 |
10 | // Transform keys of an object type from camelCase to snake_case
11 | export type KeysToSnakeCase = {
12 | [K in keyof T as CamelToSnake]: T[K];
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/e2e/tests/activity.e2e.ts:
--------------------------------------------------------------------------------
1 | import { by, expect, element } from 'detox';
2 |
3 | import { waitTabProfile } from '../helpers';
4 |
5 | describe('Activity', () => {
6 | it("should correctly show the user's activity", async () => {
7 | await waitTabProfile();
8 | await expect(element(by.id('Home:PostList'))).toBeVisible();
9 |
10 | await expect(element(by.id('Author:Avatar')).atIndex(0)).toBeVisible();
11 | await element(by.id('Author:Avatar')).atIndex(0).tap();
12 |
13 | await expect(element(by.id('UserInformation:PostList'))).toBeVisible();
14 | await element(by.text('Detox Test')).atIndex(0).tap();
15 | await expect(element(by.id('PostDetail:List'))).toBeVisible();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/frontend/index.js:
--------------------------------------------------------------------------------
1 | import { registerRootComponent } from 'expo';
2 |
3 | import App from './App';
4 |
5 | registerRootComponent(App);
6 |
--------------------------------------------------------------------------------
/frontend/patches/reactotron-core-client+2.9.6.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/reactotron-core-client/package.json b/node_modules/reactotron-core-client/package.json
2 | index 41b32e5..9697d6b 100644
3 | --- a/node_modules/reactotron-core-client/package.json
4 | +++ b/node_modules/reactotron-core-client/package.json
5 | @@ -16,10 +16,10 @@
6 | "main": "dist/index.js",
7 | "module": "dist/index.esm.js",
8 | "types": "dist/types/src/index.d.ts",
9 | - "react-native": "src/index.ts",
10 | + "react-native": "./dist/index.js",
11 | "exports": {
12 | "import": "./dist/index.esm.js",
13 | - "react-native": "./src/index.ts",
14 | + "react-native": "./dist/index.js",
15 | "types": "./dist/types/src/index.d.ts",
16 | "default": "./dist/index.js"
17 | },
18 |
--------------------------------------------------------------------------------
/frontend/patches/reactotron-react-native+5.1.10.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/reactotron-react-native/package.json b/node_modules/reactotron-react-native/package.json
2 | index db273fb..64409cc 100644
3 | --- a/node_modules/reactotron-react-native/package.json
4 | +++ b/node_modules/reactotron-react-native/package.json
5 | @@ -16,11 +16,11 @@
6 | "main": "dist/index.js",
7 | "module": "dist/index.esm.js",
8 | "types": "dist/types/src/index.d.ts",
9 | - "react-native": "src/index.ts",
10 | + "react-native": "./dist/index.js",
11 | "exports": {
12 | "import": "./dist/index.esm.js",
13 | "types": "./dist/types/src/index.d.ts",
14 | - "react-native": "./src/index.ts",
15 | + "react-native": "./dist/index.js",
16 | "default": "./dist/index.js"
17 | },
18 | "scripts": {
19 |
--------------------------------------------------------------------------------
/frontend/reactotronConfig.js:
--------------------------------------------------------------------------------
1 | import AsyncStorage from '@react-native-async-storage/async-storage';
2 | import Reactotron from 'reactotron-react-native';
3 |
4 | // eslint-disable-next-line no-console
5 | console.log('reactotron config');
6 |
7 | let reactotron = Reactotron.configure({}) // controls connection & communication settings
8 | .useReactNative();
9 |
10 | reactotron.setAsyncStorageHandler &&
11 | reactotron.setAsyncStorageHandler(AsyncStorage);
12 |
13 | reactotron.connect();
14 |
--------------------------------------------------------------------------------
/frontend/scripts/android-E2E.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Run expo client
4 | cd frontend
5 | yarn start:android:test &>/dev/null &
6 | EXPO_PID=$!
7 |
8 | # Run detox
9 | yarn tests:android:test
10 | DETOX_EXIT_CODE=$?
11 |
12 | kill $EXPO_PID
13 |
14 | exit $DETOX_EXIT_CODE
15 |
--------------------------------------------------------------------------------
/frontend/src/__mocks__/setup.js:
--------------------------------------------------------------------------------
1 | import '../polyfills';
2 |
--------------------------------------------------------------------------------
/frontend/src/__mocks__/setupLinking.ts:
--------------------------------------------------------------------------------
1 | jest.mock('expo-linking', () => {
2 | const module: typeof import('expo-linking') = {
3 | ...jest.requireActual('expo-linking'),
4 | createURL: jest.fn(),
5 | };
6 |
7 | return module;
8 | });
9 |
--------------------------------------------------------------------------------
/frontend/src/__mocks__/svgMock.js:
--------------------------------------------------------------------------------
1 | function SvgMock() {
2 | return null;
3 | }
4 |
5 | module.exports = SvgMock;
6 | module.exports.ReactComponent = SvgMock;
7 |
--------------------------------------------------------------------------------
/frontend/src/api/bodyBuilder/index.ts:
--------------------------------------------------------------------------------
1 | export * from './newMessage';
2 | export * from './timings';
3 | export * from './votePoll';
4 |
--------------------------------------------------------------------------------
/frontend/src/api/bodyBuilder/newMessage.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | /**
4 | * For new private message we need change data targetRecipients from array of string into string
5 | * input: ["adam", "world"]
6 | * expect: "adam,world"
7 | *
8 | * beside that we need add default value archetype: 'private_message' every time time create new private message.
9 | */
10 | export function newPrivateMessageBodyBuilder({
11 | args,
12 | }: RestLink.RestLinkHelperProps) {
13 | const { newPrivateMessageInput } = args;
14 |
15 | return {
16 | ...newPrivateMessageInput,
17 | targetRecipients: newPrivateMessageInput.targetRecipients.join(','),
18 | archetype: 'private_message',
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/api/bodyBuilder/timings.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | function getTopicTimings(postTimings: Array, topicId: number) {
4 | let timings: Record = {};
5 | timings = postTimings.reduce((prev, curr) => {
6 | prev[curr] = 1000;
7 | return prev;
8 | }, timings);
9 | return {
10 | timings,
11 | topic_id: topicId,
12 | topic_time: 1000,
13 | };
14 | }
15 |
16 | export function timingsBodyBuilder({ args }: RestLink.RestLinkHelperProps) {
17 | const { postNumbers, topicId } = args;
18 |
19 | return getTopicTimings(postNumbers, topicId);
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/api/bodyBuilder/votePoll.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | import { extractPollOptionIds } from '../../helpers/api';
4 |
5 | export function votePollBodyBuilder({ args }: RestLink.RestLinkHelperProps) {
6 | const { pollName, postId, options } = args;
7 |
8 | let strippedOptions = extractPollOptionIds(options);
9 |
10 | return {
11 | post_id: postId,
12 | poll_name: pollName,
13 | options: strippedOptions,
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/api/bodySerializers/index.ts:
--------------------------------------------------------------------------------
1 | import { fileEncodeBodySerializers } from './fileEncode';
2 |
3 | export const bodySerializers = {
4 | fileEncode: fileEncodeBodySerializers,
5 | };
6 |
--------------------------------------------------------------------------------
/frontend/src/api/dataLoader/index.ts:
--------------------------------------------------------------------------------
1 | export * from './searchUsersDataLoader';
2 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/about.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const ABOUT = gql`
4 | query About {
5 | about @rest(type: "About", path: "/about.json") {
6 | topicCount
7 | postCount
8 | }
9 | }
10 | `;
11 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/addEmail.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const ADD_EMAIL_ADDRESS = gql`
4 | mutation AddEmailAddress($addEmailInput: AddEmailInput!, $username: String!) {
5 | addEmail(addEmailInput: $addEmailInput, username: $username)
6 | @rest(
7 | type: "String"
8 | path: "/u/{args.username}/preferences/email.json"
9 | method: "POST"
10 | bodyKey: "addEmailInput"
11 | )
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/changePassword.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const CHANGE_PASSWORD = gql`
4 | mutation ChangeNewPassword($changePasswordInput: ChangePasswordInput!) {
5 | changePassword(changePasswordInput: $changePasswordInput)
6 | @rest(
7 | type: "ChangePasswordOutput"
8 | path: "/session/forgot_password.json"
9 | method: "POST"
10 | bodyKey: "changePasswordInput"
11 | )
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/channels.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const GET_CHANNELS = gql`
4 | query GetChannels {
5 | category @rest(type: "CategoryList", path: "/categories.json") {
6 | categories @type(name: "Categories") {
7 | id
8 | color
9 | name
10 | descriptionText
11 | }
12 | }
13 | }
14 | `;
15 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/deleteEmail.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const DELETE_EMAIL = gql`
4 | mutation DeleteEmail(
5 | $email: String!
6 | $username: String!
7 | $deleteEmailPath: PathBuilder
8 | ) {
9 | deleteEmail(email: $email, username: $username)
10 | @rest(
11 | type: "DeleteEmailOutput"
12 | method: "DELETE"
13 | path: ""
14 | pathBuilder: $deleteEmailPath
15 | )
16 | }
17 | `;
18 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/lookupUrls.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const LOOKUP_URLS = gql`
4 | query LookupUrls($lookupUrlInput: LookupUrlInput!) {
5 | lookupUrls(lookupUrlInput: $lookupUrlInput)
6 | @rest(
7 | type: "LookupUrl"
8 | path: "/uploads/lookup-urls.json"
9 | method: "POST"
10 | bodyKey: "lookupUrlInput"
11 | ) {
12 | shortUrl
13 | url
14 | }
15 | }
16 | `;
17 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/newTopic.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | /**
4 | * Adds a new `archetype` field to `NewTopicInput`, which will always have the value 'regular' for `newTopic`.
5 | * In the previous implementation, the `archetype` was added to the payload body in prose.
6 | */
7 | export const NEW_TOPIC = gql`
8 | mutation NewTopic($newTopicInput: NewTopicInput!) {
9 | newTopic(newTopicInput: $newTopicInput)
10 | @rest(
11 | type: "Post"
12 | path: "/posts.json"
13 | method: "POST"
14 | bodyKey: "newTopicInput"
15 | ) {
16 | topicId
17 | }
18 | }
19 | `;
20 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/postRaw.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const POST_RAW = gql`
4 | query PostRaw($postId: Int!) {
5 | postRaw(postId: $postId)
6 | @rest(type: "PostRaw", path: "/posts/{args.postId}/raw.json") {
7 | raw
8 | cooked(postId: $postId)
9 | @rest(type: "PostCooked", path: "/posts/{args.postId}/cooked.json") {
10 | markdownContent
11 | mentions
12 | }
13 | }
14 | }
15 | `;
16 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/readChat.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const MARK_READ_CHAT = gql`
4 | mutation MarkReadChat(
5 | $channelId: Int!
6 | $markReadChatInput: MarkReadChatInput!
7 | ) {
8 | markReadChat(channelId: $channelId, markReadChatInput: $markReadChatInput)
9 | @rest(
10 | type: "MarkReadChatOutput"
11 | path: "/chat/api/channels/{args.channelId}/read.json"
12 | method: "PUT"
13 | bodyKey: "markReadChatInput"
14 | )
15 | }
16 | `;
17 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/reply.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const REPLY_TOPIC = gql`
4 | mutation ReplyTopic($replyInput: ReplyInput!) {
5 | replyPost(replyInput: $replyInput)
6 | @rest(
7 | type: "Post"
8 | path: "/posts.json"
9 | method: "POST"
10 | bodyKey: "replyInput"
11 | ) {
12 | id
13 | postNumber
14 | }
15 | }
16 | `;
17 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/replyChat.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const REPLY_Chat = gql`
4 | mutation ReplyChat($channelId: Int!, $replyChatInput: ReplyChatInput!) {
5 | replyChat(channelId: $channelId, replyChatInput: $replyChatInput)
6 | @rest(
7 | type: "ReplyChatOutput"
8 | path: "/chat/{args.channelId}.json"
9 | method: "POST"
10 | bodyKey: "replyChatInput"
11 | ) {
12 | messageId
13 | }
14 | }
15 | `;
16 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/setPrimaryEmail.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const SET_PRIMARY_EMAIL = gql`
4 | mutation SetPrimaryEmail($input: SetPrimaryEmailInput!, $username: String!) {
5 | setPrimaryEmail(input: $input, username: $username)
6 | @rest(
7 | type: "SetPrimaryEmailOutput"
8 | path: "/users/{args.username}/preferences/primary-email.json"
9 | method: "PUT"
10 | bodyKey: "input"
11 | )
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/timings.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const TIMINGS = gql`
4 | mutation Timings(
5 | $postNumbers: [Int!]!
6 | $topicId: Int!
7 | $timingsBodyBuilder: BodyBuilder
8 | ) {
9 | timings(postNumbers: $postNumbers, topicId: $topicId)
10 | @rest(
11 | type: "String"
12 | path: "/topics/timings.json"
13 | method: "POST"
14 | bodyBuilder: $timingsBodyBuilder
15 | )
16 | }
17 | `;
18 |
--------------------------------------------------------------------------------
/frontend/src/api/discourse-apollo-rest/upload.ts:
--------------------------------------------------------------------------------
1 | import { gql } from '@apollo/client';
2 |
3 | export const UPLOAD = gql`
4 | mutation Upload($input: UploadInput!) {
5 | upload(input: $input)
6 | @rest(
7 | type: "UploadOutput"
8 | path: "/uploads.json"
9 | method: "POST"
10 | bodySerializer: "fileEncode"
11 | ) {
12 | id
13 | url
14 | filesize
15 | width
16 | height
17 | thumbnailWidth
18 | thumbnailHeight
19 | extension
20 | shortUrl
21 | shortPath
22 | humanFilesize
23 | originalFilename
24 | token
25 | }
26 | }
27 | `;
28 |
--------------------------------------------------------------------------------
/frontend/src/api/pathBuilder/chatChannelMessages.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | import { CHAT_CHANNEL_DETAIL_PAGE_SIZE } from '../../constants';
4 |
5 | export const chatChannelMessagesPathBuilder = ({
6 | args,
7 | }: RestLink.PathBuilderProps) => {
8 | const queryParams = new URLSearchParams();
9 | queryParams.set('fetch_from_last_read', args.targetMessageId ? '' : 'true');
10 | queryParams.set('page_size', args.pageSize || CHAT_CHANNEL_DETAIL_PAGE_SIZE);
11 | queryParams.set('target_message_id', args.targetMessageId || '');
12 |
13 | if (args.direction) {
14 | queryParams.set('direction', args.direction);
15 | }
16 |
17 | return `/chat/api/channels/${
18 | args.channelId
19 | }/messages.json?${queryParams.toString()}`;
20 | };
21 |
--------------------------------------------------------------------------------
/frontend/src/api/pathBuilder/deleteEmail.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | export const deleteEmailPathBuilder = ({ args }: RestLink.PathBuilderProps) => {
4 | const queryParams = new URLSearchParams();
5 | queryParams.set('email', args.email);
6 |
7 | return `/users/${
8 | args.username
9 | }/preferences/email.json?${queryParams.toString()}`;
10 | };
11 |
--------------------------------------------------------------------------------
/frontend/src/api/pathBuilder/deletePostDraft.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | export function deletePostDraftPathBuilder({
4 | args,
5 | }: RestLink.RestLinkHelperProps) {
6 | const queryParams = new URLSearchParams();
7 | queryParams.set('draft_key', args.draftKey);
8 | queryParams.set('sequence', args.sequence);
9 |
10 | return `/drafts/${args.draftKey}.json?${queryParams.toString()}`;
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/api/pathBuilder/helper/index.ts:
--------------------------------------------------------------------------------
1 | export * from './parseTopicUrl';
2 |
--------------------------------------------------------------------------------
/frontend/src/api/pathBuilder/helper/parseTopicUrl.ts:
--------------------------------------------------------------------------------
1 | import { FilterInput } from '../topics';
2 |
3 | export function parseTopicUrl({
4 | sort,
5 | topPeriod,
6 | tag,
7 | categoryId,
8 | }: FilterInput) {
9 | let sortBy = sort.toLowerCase();
10 | if (sort === 'TOP' && topPeriod && !tag) {
11 | sortBy = `${sortBy}/${topPeriod.toLowerCase()}`;
12 | }
13 | if (categoryId) {
14 | if (tag) {
15 | sortBy = `tags/c/${categoryId}/${tag}/l/${sortBy}`;
16 | } else {
17 | sortBy = `c/${categoryId}/l/${sortBy}`;
18 | }
19 | } else {
20 | if (tag) {
21 | sortBy = `tag/${tag}/l/${sortBy}`;
22 | }
23 | }
24 | return sortBy;
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/src/api/pathBuilder/index.ts:
--------------------------------------------------------------------------------
1 | export * from './chatChannelMessages';
2 | export * from './deleteEmail';
3 | export * from './deletePostDraft';
4 | export * from './getChatChannels';
5 | export * from './likeTopicOrPost';
6 | export * from './message';
7 | export * from './notifications';
8 | export * from './postDraft';
9 | export * from './searchPost';
10 | export * from './searchTag';
11 | export * from './threadMessages';
12 | export * from './topicDetail';
13 | export * from './topics';
14 |
--------------------------------------------------------------------------------
/frontend/src/api/pathBuilder/likeTopicOrPost.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | import { LIKE_ACTION_ID } from '../../constants';
4 |
5 | export const unlikeTopicOrPostPathBuilder = ({
6 | args,
7 | }: RestLink.PathBuilderProps) => {
8 | return `/post_actions/${args.postId}.json?post_action_type_id=${LIKE_ACTION_ID}`;
9 | };
10 |
--------------------------------------------------------------------------------
/frontend/src/api/pathBuilder/message.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | export function messagePathBuilder({ args }: RestLink.PathBuilderProps) {
4 | const params = args.page ? `?page=${args.page}` : '';
5 | const type = args.messageType === 'sent' ? '-sent' : '';
6 |
7 | return `/topics/private-messages${type}/${args.username}.json${params}`;
8 | }
9 |
--------------------------------------------------------------------------------
/frontend/src/api/pathBuilder/notifications.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | /**
4 | * Builds the API endpoint for fetching notifications with pagination.
5 | * ex Url: /notifications.json?filter=all&offset=1
6 | *
7 | * @returns {string} The constructed API endpoint URL with query parameters.
8 | */
9 | export const notificationsPathBuilder = ({
10 | args,
11 | }: RestLink.PathBuilderProps) => {
12 | const { page } = args;
13 |
14 | const queryParams = new URLSearchParams({
15 | filter: 'all',
16 | offset: ((page - 1) * 60).toString(),
17 | });
18 |
19 | return `/notifications.json?${queryParams}`;
20 | };
21 |
--------------------------------------------------------------------------------
/frontend/src/api/pathBuilder/postDraft.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | import { defaultArgsListPostDraft } from '../../constants/postDraft';
4 |
5 | /**
6 | * Builds the API endpoint for fetching post draft with pagination.
7 | * ex Url: /drafts.json?limit=50&offset=0
8 | *
9 | * @returns {string} The constructed API endpoint URL with query parameters.
10 | */
11 | export const postDraftPathBuilder = ({ args }: RestLink.PathBuilderProps) => {
12 | const { page } = args;
13 |
14 | const queryParams = new URLSearchParams({
15 | limit: defaultArgsListPostDraft.limit.toString(),
16 | offset: ((page - 1) * defaultArgsListPostDraft.limit).toString(),
17 | });
18 | return `/drafts.json?${queryParams}`;
19 | };
20 |
--------------------------------------------------------------------------------
/frontend/src/api/resolver/helper/getUpdatedLikedTopic.ts:
--------------------------------------------------------------------------------
1 | import { LikedTopic } from '../../../types/api';
2 |
3 | type Params = {
4 | currentLikedTopicResponse: LikedTopic;
5 | isLiked: boolean;
6 | };
7 |
8 | export function getUpdatedLikedTopic(params: Params) {
9 | const { currentLikedTopicResponse, isLiked: liked } = params;
10 |
11 | let { likeCount } = currentLikedTopicResponse;
12 | const prevLikeCount = likeCount ?? 0;
13 | likeCount = liked ? prevLikeCount + 1 : prevLikeCount - 1;
14 | return {
15 | ...currentLikedTopicResponse,
16 | likeCount,
17 | liked,
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/api/resolver/index.ts:
--------------------------------------------------------------------------------
1 | export * from './mutation/createAndUpdatePostDraft';
2 | export * from './mutation/editProfile';
3 | export * from './mutation/likeTopicOrPost';
4 | export * from './mutation/logout';
5 | export * from './query/privateMessage';
6 |
--------------------------------------------------------------------------------
/frontend/src/api/responseTransformer/Post.ts:
--------------------------------------------------------------------------------
1 | export const postRawResponseTransform = (data: string) => {
2 | return { raw: data };
3 | };
4 |
5 | export const postCookedResponseTransform = (data: string) => {
6 | return { cooked: data };
7 | };
8 |
--------------------------------------------------------------------------------
/frontend/src/api/responseTransformer/ReplyingToOutput.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Transforms the response data for a reply post by extracting the first reply
3 | * from an array and returning it as an object.
4 | *
5 | */
6 |
7 | export const replyingToOutputResponseTransform = (
8 | data: Array<{ id: string }>,
9 | ) => {
10 | return data[0];
11 | };
12 |
--------------------------------------------------------------------------------
/frontend/src/api/responseTransformer/changePasswordOutput.ts:
--------------------------------------------------------------------------------
1 | export const changePasswordOutputResponseTransformer = (data: {
2 | success: string;
3 | }) => {
4 | if (!data.success) {
5 | throw new Error(`No account found`);
6 | }
7 | return 'success';
8 | };
9 |
--------------------------------------------------------------------------------
/frontend/src/api/responseTransformer/helper/index.ts:
--------------------------------------------------------------------------------
1 | export * from './transformDraftData';
2 |
--------------------------------------------------------------------------------
/frontend/src/api/responseTransformer/searchTagOutput.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Transforms the response data for the search tag API from an object into an array.
3 | *
4 | * When the Discourse API for search tags is called, it returns a response in the following format:
5 | *
6 | * {
7 | * results: [
8 | * {
9 | * count: 0,
10 | * id: 1,
11 | * text: 'test',
12 | * },
13 | * ],
14 | * }
15 | *
16 | * This function converts it into a simple array format:
17 | *
18 | * [
19 | * {
20 | * count: 0,
21 | * id: 1,
22 | * text: 'test',
23 | * },
24 | * ];
25 | */
26 |
27 | import { Tag } from '../../generatedAPI/server';
28 |
29 | export const searchTagOutputResponseTransform = (data: {
30 | results: Array;
31 | }) => {
32 | return data.results;
33 | };
34 |
--------------------------------------------------------------------------------
/frontend/src/api/responseTransformer/stringOutput.ts:
--------------------------------------------------------------------------------
1 | export const successResponseTransform = (data: { success: string }) => {
2 | return data.success === 'OK' ? 'success' : data.success;
3 | };
4 |
5 | export const stringResponseTransform = () => {
6 | return 'success';
7 | };
8 |
--------------------------------------------------------------------------------
/frontend/src/api/responseTransformer/userActions.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Transforms the response data for user activity by extracting the `user_actions` array.
3 | *
4 | * The input data is in the following format:
5 | * {
6 | * user_actions: [ // array of user actions data ]
7 | * }
8 | *
9 | * This function converts it into:
10 | * [ // array of user actions data ]
11 | *
12 | * @param {Object} data - The response data containing the `user_actions` field.
13 | * @returns {Array} - The transformed array of user actions.
14 | */
15 |
16 | import { UserActions } from '../../generatedAPI/server';
17 |
18 | export const userActionsResponseTransform = (data: {
19 | user_actions: Array;
20 | }) => {
21 | return data.user_actions;
22 | };
23 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/about.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | export const aboutPatcher: RestLink.FunctionalTypePatcher = (data) => {
4 | return {
5 | __typename: 'About',
6 | topicCount:
7 | data.about.stats.topicCount || data.about.stats.topicsCount || 0,
8 | postCount: data.about.stats.postCount || data.about.stats.postsCount || 0,
9 | };
10 | };
11 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/categoryList.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | export const categoryListPatcher: RestLink.FunctionalTypePatcher = (data) => {
4 | return {
5 | __typename: 'CategoryList',
6 | canCreateCategory: data.categoryList.canCreateCategory,
7 | canCreateTopic: data.categoryList.canCreateTopic,
8 | categories: data.categoryList.categories,
9 | };
10 | };
11 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/changeProfileOutput.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | import { getNormalizedUrlTemplate } from '../discourse-apollo-rest/utils';
4 |
5 | export const changeProfileOutputPatcher: RestLink.FunctionalTypePatcher = (
6 | data,
7 | ) => {
8 | data.user = {
9 | __typename: 'UserDetail',
10 | ...data.user,
11 | avatarTemplate: getNormalizedUrlTemplate({ instance: data.user }),
12 | };
13 |
14 | return data;
15 | };
16 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/helper/index.ts:
--------------------------------------------------------------------------------
1 | export * from './poll';
2 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/lookupUrls.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | import { getNormalizedUrlTemplate } from '../discourse-apollo-rest/utils';
4 |
5 | export const lookupUrlOutputPatcher: RestLink.FunctionalTypePatcher = (
6 | data,
7 | ) => {
8 | const url = getNormalizedUrlTemplate({ instance: data, variant: 'url' });
9 |
10 | return {
11 | ...data,
12 | url,
13 | __typename: 'LookupUrl',
14 | };
15 | };
16 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/post.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | import { generatePostPatcher } from './helper/Post';
4 |
5 | export const PostPatcher: RestLink.FunctionalTypePatcher = (post) => {
6 | return generatePostPatcher(post);
7 | };
8 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/postRaw.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | import { generateMarkdownContent, getMention } from '../../helpers/api';
4 |
5 | export const postRawPatcher: RestLink.FunctionalTypePatcher = (data) => {
6 | return {
7 | __typename: 'PostRaw',
8 | raw: data.raw,
9 | };
10 | };
11 |
12 | export const postCookedPatcher: RestLink.FunctionalTypePatcher = (
13 | data,
14 | _,
15 | __,
16 | context,
17 | ) => {
18 | const raw = context.resolverParams.root?.raw || '';
19 | const markdownContent = generateMarkdownContent(raw, data.cooked);
20 | const mentions = getMention(data.cooked) ?? [];
21 |
22 | return {
23 | __typename: 'PostCooked',
24 | markdownContent,
25 | mentions,
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/profileOutput.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | import { getNormalizedUrlTemplate } from '../discourse-apollo-rest/utils';
4 |
5 | export const profileOutputPatcher: RestLink.FunctionalTypePatcher = (data) => {
6 | data.user = {
7 | __typename: 'UserDetail',
8 | ...data.user,
9 | avatarTemplate: getNormalizedUrlTemplate({ instance: data.user }),
10 | };
11 |
12 | return data;
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/searchOutput.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | import { SearchPost } from '../../generatedAPI/server';
4 | import { getNormalizedUrlTemplate } from '../discourse-apollo-rest/utils';
5 |
6 | export const searchOutputPatcher: RestLink.FunctionalTypePatcher = (data) => {
7 | if (!data.topics) {
8 | data.topics = [];
9 | }
10 | if (!data.posts) {
11 | data.posts = [];
12 | }
13 | data.posts = data.posts.map((post: SearchPost) => {
14 | return {
15 | ...post,
16 | __typename: 'SearchPost',
17 | avatarTemplate: getNormalizedUrlTemplate({ instance: post }),
18 | };
19 | });
20 | return data;
21 | };
22 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/searchUserOutput.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | import { SearchUser } from '../../generatedAPI/server';
4 | import { getNormalizedUrlTemplate } from '../discourse-apollo-rest/utils';
5 |
6 | export const searchUserOutputPatcher: RestLink.FunctionalTypePatcher = (
7 | data,
8 | ) => {
9 | return {
10 | __typename: 'SearchUserOutput',
11 | ...data,
12 | users:
13 | data.users.length > 0
14 | ? data.users.map((user: SearchUser) => {
15 | return {
16 | ...user,
17 | avatarTemplate: getNormalizedUrlTemplate({ instance: user }),
18 | };
19 | })
20 | : [],
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/types/userActions.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | const UserActionSchema = z.object({
4 | actionType: z.number().int(),
5 | avatarTemplate: z.string(),
6 | categoryId: z.number().int().nullable(),
7 | createdAt: z.string(),
8 | excerpt: z.string(),
9 | hidden: z.boolean().nullable(),
10 | markdownContent: z.string().nullable(),
11 | postId: z.number().int().nullable(),
12 | postNumber: z.number().int(),
13 | title: z.string(),
14 | topicId: z.number().int(),
15 | username: z.string(),
16 | });
17 |
18 | export type UserAction = z.infer;
19 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/uploadOutput.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | export const uploadOutputPatcher: RestLink.FunctionalTypePatcher = (
4 | data,
5 | _,
6 | __,
7 | ctx,
8 | ) => {
9 | const { token } = ctx.resolverParams.args.input;
10 |
11 | return {
12 | __typename: 'UploadOutput',
13 | ...data,
14 | token: token ?? null,
15 | };
16 | };
17 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/userActions.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | import { userActivityMarkdownContent } from '../../helpers/api';
4 | import { getNormalizedUrlTemplate } from '../discourse-apollo-rest/utils';
5 |
6 | export const userActionsPatcher: RestLink.FunctionalTypePatcher = (data) => {
7 | let userActions = data;
8 | if (!userActions) {
9 | return data;
10 | }
11 |
12 | data = {
13 | ...userActions,
14 | __typename: 'UserActions',
15 | avatarTemplate: getNormalizedUrlTemplate({ instance: userActions }),
16 | markdownContent: userActivityMarkdownContent(userActions?.excerpt),
17 | };
18 | return data;
19 | };
20 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/userUnreadNotifications.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | export const userUnreadNotificationsPatcher: RestLink.FunctionalTypePatcher = (
4 | data,
5 | ) => {
6 | return {
7 | __typename: 'UserUnreadNotifications',
8 | isThereUnreadNotifications: !!data.notifications?.length,
9 | };
10 | };
11 |
--------------------------------------------------------------------------------
/frontend/src/api/typePatcher/votePollOutput.ts:
--------------------------------------------------------------------------------
1 | import { RestLink } from 'apollo-link-rest';
2 |
3 | import { formatPollOptionId } from '../../helpers/api';
4 |
5 | import { generatePollPatcher } from './helper';
6 |
7 | export const PollVoteOutputPatcher: RestLink.FunctionalTypePatcher = (
8 | data,
9 | _,
10 | __,
11 | ctx,
12 | ) => {
13 | const { postId, pollName } = ctx.resolverParams.args;
14 | const votePollData = data.poll;
15 |
16 | let formattedPoll = generatePollPatcher({
17 | pollName,
18 | postId,
19 | poll: votePollData,
20 | });
21 |
22 | data.poll = formattedPoll;
23 | data.vote = data.vote.map((id: string) =>
24 | formatPollOptionId(postId, pollName, id),
25 | );
26 |
27 | return {
28 | __typename: 'PollVoteOutput',
29 | ...data,
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/frontend/src/components/CustomFlatList/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CustomFlatList';
2 |
--------------------------------------------------------------------------------
/frontend/src/components/FooterLoadingIndicator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { View } from 'react-native';
3 |
4 | import { ActivityIndicator } from '../core-ui';
5 | import { makeStyles } from '../theme';
6 |
7 | type Props = {
8 | isHidden?: boolean;
9 | };
10 |
11 | export function FooterLoadingIndicator(props: Props) {
12 | const styles = useStyles();
13 |
14 | const { isHidden = false } = props;
15 |
16 | return isHidden ? null : (
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | const useStyles = makeStyles(({ spacing }) => ({
24 | container: {
25 | width: '100%',
26 | paddingVertical: spacing.xxl,
27 | justifyContent: 'center',
28 | },
29 | }));
30 |
--------------------------------------------------------------------------------
/frontend/src/components/Header/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './CustomHeader';
2 | export * from './HeaderItem';
3 | export * from './ModalHeader';
4 |
--------------------------------------------------------------------------------
/frontend/src/components/Poll/index.ts:
--------------------------------------------------------------------------------
1 | export * from './PollOptionItem';
2 | export * from './PollPreview';
3 | export * from './PollChoiceCard';
4 | export * from './PollPostPreview';
5 | export * from './ListCreatePoll';
6 |
--------------------------------------------------------------------------------
/frontend/src/components/PostItem/index.ts:
--------------------------------------------------------------------------------
1 | export * from './HomePostItem';
2 | export * from './PostGroupings';
3 | export * from './PostHidden';
4 | export * from './PostItem';
5 | export * from './PostItemFooter';
6 | export * from './PostDetailHeaderItem';
7 | export * from './SearchPostItem';
8 | export * from './UserInformationPostItem';
9 |
--------------------------------------------------------------------------------
/frontend/src/components/ShowImageModal.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import CachedImage from '../core-ui/CachedImage';
4 | import { makeStyles } from '../theme';
5 |
6 | type Props = {
7 | show: boolean;
8 | userImage: { uri: string };
9 | onPressCancel: () => void;
10 | };
11 |
12 | export function ShowImageModal(props: Props) {
13 | const styles = useStyles();
14 |
15 | const { show, userImage, onPressCancel } = props;
16 |
17 | return (
18 |
25 | );
26 | }
27 |
28 | const useStyles = makeStyles(() => ({
29 | imageDetail: {
30 | flexGrow: 1,
31 | },
32 | }));
33 |
--------------------------------------------------------------------------------
/frontend/src/constants/alert.ts:
--------------------------------------------------------------------------------
1 | // NOTE: In the future we would want to move other alert messages here
2 |
3 | export const LOGIN_LINK_SUCCESS_ALERT = t(
4 | 'We found an account that matches, you should receive an email with a login link shortly',
5 | );
6 |
--------------------------------------------------------------------------------
/frontend/src/constants/api.ts:
--------------------------------------------------------------------------------
1 | export const UNCATEGORIZED_CATEGORY_ID = 1;
2 |
3 | export const LIKE_ACTION_ID = 2;
4 |
5 | export const CHAT_CHANNEL_DETAIL_PAGE_SIZE = 50;
6 |
--------------------------------------------------------------------------------
/frontend/src/constants/index.ts:
--------------------------------------------------------------------------------
1 | export * from './alert';
2 | export * from './api';
3 | export * from './app';
4 | export * from './defaultValues';
5 | export * from './draftSaveState';
6 | export * from './errorTypes';
7 | export * from './links';
8 | export * from './refetch';
9 | export * from './regex';
10 | export * from './route';
11 | export * from './theme';
12 | export * from './wordings';
13 |
--------------------------------------------------------------------------------
/frontend/src/constants/postDraft.ts:
--------------------------------------------------------------------------------
1 | // move these constant from api to this file to solve problem Require cycle warning
2 |
3 | export const defaultArgsListPostDraft = {
4 | limit: 50,
5 | offset: 0,
6 | };
7 |
--------------------------------------------------------------------------------
/frontend/src/constants/refetch.ts:
--------------------------------------------------------------------------------
1 | import { postDraftPathBuilder } from '../api/pathBuilder/postDraft';
2 | import { ListPostDraftsDocument } from '../generatedAPI/server';
3 |
4 | import { defaultArgsListPostDraft } from './postDraft';
5 |
6 | export const refetchQueriesPostDraft = [
7 | {
8 | query: ListPostDraftsDocument,
9 | variables: {
10 | ...defaultArgsListPostDraft,
11 | postDraftPath: postDraftPathBuilder,
12 | },
13 | },
14 | ];
15 |
--------------------------------------------------------------------------------
/frontend/src/constants/regex.ts:
--------------------------------------------------------------------------------
1 | // Capture group 1: capture the attribute of the quote bb code
2 | // Capture group 2: capture the content of the quote
3 | export const QUOTE_REGEX =
4 | /\[quote(?:="([^"]*)")?\]([\s\S]*?)\[\/quote\](?:\n|$)/g;
5 | export const QUOTE_OPEN_REGEX = /\[quote(?:="([^"]*)")?\]/g;
6 | export const QUOTE_CLOSE_REGEX = /(\[\/quote\])(?!.*\1)/g;
7 |
--------------------------------------------------------------------------------
/frontend/src/constants/theme/animations.ts:
--------------------------------------------------------------------------------
1 | export const ANIMATION_DURATION = {
2 | s: 350,
3 | };
4 |
--------------------------------------------------------------------------------
/frontend/src/constants/theme/fonts.ts:
--------------------------------------------------------------------------------
1 | import { TextStyle } from 'react-native';
2 |
3 | type FontVariants = Record;
4 |
5 | export const FONT_VARIANTS: FontVariants = {
6 | normal: { fontWeight: '400' },
7 | semiBold: { fontWeight: '600' },
8 | bold: { fontWeight: '700' },
9 | };
10 |
11 | export const FONT_SIZES = {
12 | xxxl: 64,
13 | xxl: 40,
14 | xl: 24,
15 | l: 18,
16 | m: 16,
17 | s: 14,
18 | xs: 12,
19 | };
20 |
21 | export const HEADING_FONT_SIZES = {
22 | h1: 32,
23 | h2: 24,
24 | h3: 22,
25 | h4: 20,
26 | h5: 18,
27 | h6: 17,
28 | };
29 |
--------------------------------------------------------------------------------
/frontend/src/constants/theme/graphs.ts:
--------------------------------------------------------------------------------
1 | export const GRAPH_SIZES = {
2 | l: 172,
3 | m: 152,
4 | s: 132,
5 | xs: 112,
6 | };
7 |
--------------------------------------------------------------------------------
/frontend/src/constants/theme/icons.ts:
--------------------------------------------------------------------------------
1 | export const ICON_SIZES = {
2 | xxxl: 120,
3 | xxl: 64,
4 | xl: 28,
5 | l: 24,
6 | m: 20,
7 | s: 18,
8 | xs: 16,
9 | xxs: 12,
10 | };
11 |
--------------------------------------------------------------------------------
/frontend/src/constants/theme/images.ts:
--------------------------------------------------------------------------------
1 | export const AVATAR_ICON_SIZES = {
2 | l: 96,
3 | m: 52,
4 | s: 40,
5 | xs: 36,
6 | xxs: 28,
7 | };
8 |
9 | export const AVATAR_IMAGE_SIZES = {
10 | xl: 450,
11 | l: 150,
12 | m: 100,
13 | s: 50,
14 | };
15 |
16 | export const AVATAR_LETTER_SIZES = {
17 | l: 72,
18 | m: 36,
19 | s: 28,
20 | xs: 24,
21 | xxs: 16,
22 | };
23 |
24 | export const EMOJI_SIZES = {
25 | l: 64,
26 | m: 42,
27 | s: 24,
28 | xs: 16,
29 | };
30 |
31 | export type AVATAR_ICON_SIZE_VARIANTS = keyof typeof AVATAR_ICON_SIZES;
32 | export type AVATAR_IMAGE_VARIANTS = keyof typeof AVATAR_IMAGE_SIZES;
33 | export type EMOJI_SIZES_VARIANTS = keyof typeof EMOJI_SIZES;
34 |
--------------------------------------------------------------------------------
/frontend/src/constants/theme/index.ts:
--------------------------------------------------------------------------------
1 | export * from './colors';
2 | export * from './fonts';
3 | export * from './icons';
4 | export * from './images';
5 | export * from './spacing';
6 | export * from './graphs';
7 |
--------------------------------------------------------------------------------
/frontend/src/constants/theme/spacing.ts:
--------------------------------------------------------------------------------
1 | export const SPACING = {
2 | xxxxxl: 60,
3 | xxxxl: 48,
4 | xxxl: 36,
5 | xxl: 24,
6 | xl: 16,
7 | l: 12,
8 | m: 8,
9 | s: 4,
10 | xs: 2,
11 | };
12 |
--------------------------------------------------------------------------------
/frontend/src/constants/wordings.ts:
--------------------------------------------------------------------------------
1 | export const NO_EXCERPT_WORDING = t('Tap to View Post');
2 | export const NO_USERNAME_ALERT = t("This User doesn't exist");
3 | export const NO_USERNAME_SUB_ALERT = t(
4 | 'We were unable to find a user with this username.',
5 | );
6 |
--------------------------------------------------------------------------------
/frontend/src/core-ui/ActivityIndicator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | ActivityIndicator as BaseActivityIndicator,
4 | ActivityIndicatorProps,
5 | } from 'react-native';
6 |
7 | import { Color, useTheme } from '../theme';
8 |
9 | type Props = ActivityIndicatorProps & {
10 | color?: Color;
11 | };
12 |
13 | export function ActivityIndicator(props: Props) {
14 | const { colors } = useTheme();
15 |
16 | const { color = 'loading', ...otherProps } = props;
17 | const indicatorColor = colors[color];
18 |
19 | return ;
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/core-ui/Link.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import * as Linking from 'expo-linking';
3 |
4 | import { makeStyles } from '../theme';
5 |
6 | import { Text } from './Text';
7 |
8 | type Props = {
9 | url: string;
10 | };
11 |
12 | export let Link = ({ url }: Props) => {
13 | let styles = useStyles();
14 | return (
15 | {
17 | Linking.openURL(url);
18 | }}
19 | style={styles.link}
20 | >
21 | {url}
22 |
23 | );
24 | };
25 |
26 | let useStyles = makeStyles(({ colors }) => ({
27 | link: {
28 | color: colors.activeTab,
29 | },
30 | }));
31 |
--------------------------------------------------------------------------------
/frontend/src/core-ui/MentionedText.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ParsedText from 'react-native-parsed-text';
3 |
4 | import { makeStyles } from '../theme';
5 |
6 | type Props = {
7 | textValue: string;
8 | };
9 |
10 | export function MentionedText(props: Props) {
11 | const styles = useStyles();
12 |
13 | const { textValue } = props;
14 |
15 | return (
16 |
24 | {textValue}
25 |
26 | );
27 | }
28 |
29 | const useStyles = makeStyles(({ colors, fontVariants }) => ({
30 | parsedText: {
31 | color: colors.primary,
32 | ...fontVariants.bold,
33 | },
34 | }));
35 |
--------------------------------------------------------------------------------
/frontend/src/core-ui/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ActivityIndicator';
2 | export * from './Avatar';
3 | export * from './Button';
4 | export * from './CachedImage';
5 | export * from './ChatBubble';
6 | export * from './Chip';
7 | export * from './ChipRow';
8 | export * from './CustomImage';
9 | export * from './Divider';
10 | export * from './Dot';
11 | export * from './FloatingButton';
12 | export * from './Icon';
13 | export * from './IconWithLabel';
14 | export * from './Link';
15 | export * from './MentionedText';
16 | export * from './RadioButton';
17 | export * from './Text';
18 | export * from './TextInput';
19 | export * from './Emoji';
20 | export * from './AppleSignInButton';
21 |
--------------------------------------------------------------------------------
/frontend/src/helpers/PrivateTopicAlert.ts:
--------------------------------------------------------------------------------
1 | import { Alert } from 'react-native';
2 |
3 | import { ERROR_PRIVATE_POST, ERROR_MESSAGE_INVALID_ACCESS } from '../constants';
4 |
5 | export function privateTopicAlert() {
6 | Alert.alert(
7 | ERROR_PRIVATE_POST.title,
8 | ERROR_PRIVATE_POST.content,
9 | [{ text: t('Got It') }],
10 | { cancelable: false },
11 | );
12 | }
13 |
14 | export function messageInvalidAccessAlert() {
15 | Alert.alert(
16 | ERROR_MESSAGE_INVALID_ACCESS.title,
17 | ERROR_MESSAGE_INVALID_ACCESS.content,
18 | [{ text: t('Got It') }],
19 | { cancelable: false },
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/helpers/PushNotificationsSetupFailAlert.ts:
--------------------------------------------------------------------------------
1 | import { Alert } from 'react-native';
2 |
3 | import { ERROR_SETUP_PUSH_NOTIFICATIONS } from '../constants';
4 |
5 | export function pushNotificationsSetupFailAlert() {
6 | Alert.alert(
7 | ERROR_SETUP_PUSH_NOTIFICATIONS.title,
8 | ERROR_SETUP_PUSH_NOTIFICATIONS.content,
9 | [{ text: t('Got It') }],
10 | { cancelable: false },
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/frontend/src/helpers/__tests__/addHour.test.ts:
--------------------------------------------------------------------------------
1 | import { addHour } from '../addHour';
2 |
3 | describe('addHour', () => {
4 | it('should add hours to a valid date', () => {
5 | const inputDate = '2023-09-23T03:07:01.000Z';
6 | const result = addHour({ dateString: inputDate, hour: 2 });
7 |
8 | const expectedDate = new Date('2023-09-23T05:07:01.000Z');
9 |
10 | expect(result).toEqual(expectedDate);
11 | });
12 |
13 | it('should return an empty string for an invalid date', () => {
14 | const inputDate = 'invalid-date-string';
15 | const result = addHour({ dateString: inputDate, hour: 2 });
16 |
17 | expect(result).toBe('');
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/frontend/src/helpers/__tests__/capitalizeFirstLetter.test.ts:
--------------------------------------------------------------------------------
1 | import { capitalizeAllWords } from '../capitalizeFirstLetter';
2 |
3 | it('should return new format text', () => {
4 | expect(capitalizeAllWords('hi john')).toEqual('Hi John');
5 |
6 | expect(capitalizeAllWords('this is the end')).toEqual('This Is The End');
7 | });
8 |
--------------------------------------------------------------------------------
/frontend/src/helpers/__tests__/checkImageFile.test.ts:
--------------------------------------------------------------------------------
1 | import { isImageValidUrl } from '../checkImageFile';
2 |
3 | it('should check is image uri or not', () => {
4 | const url1 = 'https://google.com';
5 | const url2 = 'https://upload/x/imahe.png';
6 | const url3 = 'video.webm';
7 |
8 | expect(isImageValidUrl(url1)).toBeFalsy();
9 |
10 | expect(isImageValidUrl(url2)).toBeTruthy();
11 |
12 | expect(isImageValidUrl(url3)).toBeFalsy();
13 | });
14 |
--------------------------------------------------------------------------------
/frontend/src/helpers/__tests__/colorScheme.test.ts:
--------------------------------------------------------------------------------
1 | import { filterColorScheme } from '../colorScheme';
2 |
3 | it('should return undefined color', () => {
4 | expect(filterColorScheme(null)).toEqual(undefined);
5 | });
6 |
7 | it('should return light color', () => {
8 | expect(filterColorScheme('light')).toEqual('light');
9 | });
10 |
--------------------------------------------------------------------------------
/frontend/src/helpers/__tests__/formatExtensions.test.ts:
--------------------------------------------------------------------------------
1 | import { formatExtensions } from '../formatExtensions';
2 |
3 | it('should return empty array', () => {
4 | expect(formatExtensions()).toEqual([]);
5 | expect(formatExtensions([])).toEqual([]);
6 | });
7 |
8 | it('should return original extensions', () => {
9 | expect(formatExtensions(['jpeg', 'jpg', 'png'])).toEqual([
10 | 'jpeg',
11 | 'jpg',
12 | 'png',
13 | ]);
14 | });
15 |
16 | it('should return processed extensions', () => {
17 | expect(formatExtensions(['.jpeg', '.jpg', '.png'])).toEqual([
18 | 'jpeg',
19 | 'jpg',
20 | 'png',
21 | ]);
22 | });
23 |
--------------------------------------------------------------------------------
/frontend/src/helpers/__tests__/generateSlug.test.ts:
--------------------------------------------------------------------------------
1 | import { generateSlug } from '../generateSlug';
2 |
3 | it('should return correct slug', () => {
4 | const text1 = 'beard man';
5 | const text2 = 'smiley face 1 ';
6 | const text3 = 'Warning !!';
7 | expect(generateSlug(text1)).toEqual('beard_man');
8 | expect(generateSlug(text2)).toEqual('smiley_face_1');
9 | expect(generateSlug(text3)).toEqual('warning_!!');
10 | });
11 |
--------------------------------------------------------------------------------
/frontend/src/helpers/__tests__/getFormat.test.ts:
--------------------------------------------------------------------------------
1 | import { getFormat } from '../getFormat';
2 |
3 | it('should return format for image', () => {
4 | expect(
5 | getFormat(
6 | 'https://png.pngtree.com/element_our/png/20180928/beautiful-hologram-water-color-frame-png_119551.jpg',
7 | ),
8 | ).toEqual('jpg');
9 | });
10 |
11 | it('should not return format for image', () => {
12 | expect(
13 | getFormat(
14 | 'https://png.pngtree.com/element_our/png/20180928/beautiful-hologram-water-color-frame-png_119551',
15 | ),
16 | ).toEqual('');
17 | });
18 |
--------------------------------------------------------------------------------
/frontend/src/helpers/__tests__/getMimeFromImagePicker.test.ts:
--------------------------------------------------------------------------------
1 | import getMimeFromImagePicker from '../getMimeFromImagePicker';
2 |
3 | it('should return format for image', () => {
4 | expect(
5 | getMimeFromImagePicker(
6 | 'https://png.pngtree.com/element_our/png/20180928/beautiful-hologram-water-color-frame-png_119551.jpg',
7 | ),
8 | ).toEqual('image/jpg');
9 | });
10 |
11 | it('should return format for image', () => {
12 | expect(
13 | getMimeFromImagePicker('/Users/Me/Desktop/logo-lexicon-512.png'),
14 | ).toEqual('image/png');
15 | });
16 |
--------------------------------------------------------------------------------
/frontend/src/helpers/__tests__/getUserImage.test.ts:
--------------------------------------------------------------------------------
1 | import { getImage } from '../getUserImage';
2 |
3 | jest.mock('expo-linking');
4 |
5 | it('should replace the size', () => {
6 | const url = `www.example.com/user_avatar/wiki.kfox.io/miichael/{size}/15_2.png`;
7 |
8 | expect(getImage(url, 'l')).toEqual(
9 | `www.example.com/user_avatar/wiki.kfox.io/miichael/150/15_2.png`,
10 | );
11 | });
12 |
--------------------------------------------------------------------------------
/frontend/src/helpers/__tests__/stripHTML.test.ts:
--------------------------------------------------------------------------------
1 | import { stripHTML } from '../stripHTML';
2 |
3 | it('should strip HTML tags', () => {
4 | expect(
5 | stripHTML(
6 | `Hello World
, did you know 5 > 3? But -5 < 3.`,
7 | ),
8 | ).toEqual('Hello World, did you know 5 > 3? But -5 < 3.');
9 | expect(stripHTML('a < b and c > d')).toEqual('a < b and c > d');
10 | expect(stripHTML('H')).toEqual('H');
11 | expect(stripHTML('H< there are error in here ===>')).toEqual(
12 | 'H< there are error in here ===>',
13 | );
14 | });
15 |
--------------------------------------------------------------------------------
/frontend/src/helpers/__tests__/unescapeHTML.test.ts:
--------------------------------------------------------------------------------
1 | import { unescapeHTML } from '../unescapeHTML';
2 |
3 | it('should unescape HTML characters', () => {
4 | expect(
5 | unescapeHTML(
6 | `<test>'hello' & "world"…</test>`,
7 | ),
8 | ).toEqual(`'hello' & "world"...`);
9 | });
10 |
--------------------------------------------------------------------------------
/frontend/src/helpers/addHour.ts:
--------------------------------------------------------------------------------
1 | export function addHour({
2 | dateString,
3 | hour,
4 | }: {
5 | dateString: string | number;
6 | hour: number;
7 | }) {
8 | let date = new Date(dateString);
9 | if (isNaN(date.getDate())) {
10 | return '';
11 | }
12 | const newTimestamp = date.getTime() + hour * 60 * 60 * 1000;
13 | const newDate = new Date(newTimestamp);
14 | return newDate;
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/helpers/api/getTopicAuthor.ts:
--------------------------------------------------------------------------------
1 | import { TopicPoster } from '../../generatedAPI/server';
2 |
3 | import { getPosterTypeDetails } from './getPosterTypeDetails';
4 |
5 | export function getTopicAuthor(
6 | posters: Readonly>,
7 | ): TopicPoster | undefined {
8 | return posters.find((poster) => {
9 | const { isAuthor } = getPosterTypeDetails(poster.description);
10 | return isAuthor;
11 | });
12 | }
13 |
14 | export function getTopicAuthorUserId(
15 | posters: Readonly>,
16 | ): number | undefined {
17 | const author = getTopicAuthor(posters);
18 |
19 | if (author) {
20 | if ('userId' in author) {
21 | return author.userId || undefined;
22 | } else if ('user' in author) {
23 | return author.user?.id;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/helpers/api/index.ts:
--------------------------------------------------------------------------------
1 | export * from './getTopicAuthor';
2 | export * from './poll';
3 | export * from './processRawContent';
4 | export * from './privateMessagesMerger';
5 |
--------------------------------------------------------------------------------
/frontend/src/helpers/capitalizeFirstLetter.ts:
--------------------------------------------------------------------------------
1 | export function capitalizeAllWords(sentence: string) {
2 | return sentence
3 | .split(' ')
4 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
5 | .join(' ');
6 | }
7 |
--------------------------------------------------------------------------------
/frontend/src/helpers/checkImageFile.ts:
--------------------------------------------------------------------------------
1 | export function isImageValidUrl(imageUri: string) {
2 | let imageRegex = /([^\s]+(\.(jpe?g|png|gif|heic|heif))$)/g;
3 | return imageRegex.test(imageUri);
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/src/helpers/clampWorklet.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This is made for reanimated2 hence the 'worklet' keyword
3 | * It will run on UI thread instead of JS thread
4 | * more info on https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/worklets/
5 | */
6 | export const clamp = (
7 | value: number,
8 | lowerBound: number,
9 | upperBound: number,
10 | ) => {
11 | 'worklet';
12 | return Math.min(Math.max(lowerBound, value), upperBound);
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/src/helpers/colorScheme.ts:
--------------------------------------------------------------------------------
1 | import { Appearance } from 'react-native';
2 |
3 | export type ColorScheme = 'light' | 'dark' | 'no-preference';
4 |
5 | export function filterColorScheme(colorScheme: ColorScheme | null | undefined) {
6 | if (colorScheme && colorScheme !== 'no-preference') {
7 | return colorScheme;
8 | }
9 | }
10 |
11 | export function getSystemColorScheme(): ColorScheme {
12 | return Appearance.getColorScheme() === 'dark' ? 'dark' : 'light';
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/src/helpers/convertUrl.ts:
--------------------------------------------------------------------------------
1 | export function isProtocolRelativeUrl(url: string) {
2 | const regex = /^\/\/[^/].*$/;
3 | return regex.test(url);
4 | }
5 |
6 | /**
7 | * This function is used to convert protocolRelativeUrl into absolute url
8 | * ex: //example.com
9 | * into: https://example.com
10 | *
11 | * @param url string
12 | * @returns string of url
13 | */
14 |
15 | export function convertUrl(url: string) {
16 | return isProtocolRelativeUrl(url) ? `https:${url}` : url;
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/src/helpers/createReactNativeFile.ts:
--------------------------------------------------------------------------------
1 | import getMimeFromImagePicker from './getMimeFromImagePicker';
2 |
3 | export const createReactNativeFile = (
4 | filePath: string,
5 | customPrefix?: string,
6 | ) => {
7 | if (!filePath) {
8 | return null;
9 | }
10 |
11 | const prefix = customPrefix ? `${customPrefix}-` : '';
12 | const name = `${prefix}${new Date().getTime()}`;
13 |
14 | return {
15 | uri: filePath,
16 | type: getMimeFromImagePicker(filePath),
17 | name,
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/frontend/src/helpers/emojiHandler.ts:
--------------------------------------------------------------------------------
1 | const emojiImageNameRegex = /^emoji-:[^:]+(?::t\d+)?:$/;
2 |
3 | export function isEmojiImage(nameContent: string) {
4 | return emojiImageNameRegex.test(nameContent);
5 | }
6 |
--------------------------------------------------------------------------------
/frontend/src/helpers/errorMessage.ts:
--------------------------------------------------------------------------------
1 | export let EditPostError =
2 | 'That post was created too long ago. It can no longer be edited or deleted.';
3 | export let ChangeUsernameError = 'This username is already taken';
4 | export let UsedTitleError = 'Title has already been used';
5 | export let LoginError = 'You need to be logged in to do that.';
6 | export let RepliedPostLoadFail = 'Failed to load replied post';
7 | export let LeaveMessageError = 'Failed to leave message';
8 | export let PollVoteFail = 'Failed to vote';
9 | export let PollValueRequired = 'Value is required';
10 | export let PollValueOutOfRange = 'Value out of range';
11 | export let DuplicatePollOptionsError = 'Poll must have different options';
12 |
--------------------------------------------------------------------------------
/frontend/src/helpers/extractAttributes.ts:
--------------------------------------------------------------------------------
1 | export function extractAttributes(attributesCSV: string) {
2 | const split = attributesCSV.split(',');
3 | const attributeStrings = split.filter((item) => item.includes(':'));
4 |
5 | return attributeStrings.reduce>(
6 | (accumulator, current) => {
7 | const trimmed = current.trim();
8 | const [key, value] = trimmed.split(':');
9 | return {
10 | ...accumulator,
11 | [key]: value,
12 | };
13 | },
14 | {},
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/frontend/src/helpers/findChannelByCategoryId.ts:
--------------------------------------------------------------------------------
1 | import { DEFAULT_CHANNEL } from '../constants';
2 | import { Channel } from '../types';
3 |
4 | type findChannelByCategoryIdParams = {
5 | categoryId?: number | null;
6 | channels?: Array | null;
7 | };
8 | export function findChannelByCategoryId({
9 | categoryId,
10 | channels,
11 | }: findChannelByCategoryIdParams): Channel {
12 | return (
13 | channels?.find((channel) => channel.id === categoryId) ?? DEFAULT_CHANNEL
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/helpers/formatCount.ts:
--------------------------------------------------------------------------------
1 | type NumberScale = { scale: number; symbol: 'M' | 'k'; decimal: number };
2 |
3 | export function formatCount(plainNumber: number): string {
4 | let numberScales: Array = [
5 | { scale: 1000000, symbol: 'M', decimal: 2 },
6 | { scale: 1000, symbol: 'k', decimal: 1 },
7 | ];
8 |
9 | for (let numberScale of numberScales) {
10 | let result = plainNumber / numberScale.scale;
11 | if (result >= 1) {
12 | return (
13 | result.toFixed(numberScale.decimal).replace(/\.?0+$/, '') +
14 | numberScale.symbol
15 | );
16 | }
17 | }
18 |
19 | return plainNumber.toString();
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/helpers/formatExtensions.ts:
--------------------------------------------------------------------------------
1 | export function formatExtensions(extensions?: Array) {
2 | if (!extensions) {
3 | return [];
4 | }
5 |
6 | const normalizedExtensions = extensions.map((ext) =>
7 | ext.includes('.') ? ext.substring(1) : ext,
8 | );
9 |
10 | return normalizedExtensions;
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/helpers/formatTag.ts:
--------------------------------------------------------------------------------
1 | export function formatTag(text: string, maxLength?: number): string {
2 | let words = text.match(/[a-z0-9]+/gi);
3 |
4 | if (!words) {
5 | return '';
6 | }
7 |
8 | if (maxLength) {
9 | return words
10 | .map((word) => word.toLocaleLowerCase())
11 | .join('-')
12 | .slice(0, maxLength);
13 | }
14 |
15 | return words.map((word) => word.toLocaleLowerCase()).join('-');
16 | }
17 |
--------------------------------------------------------------------------------
/frontend/src/helpers/generateSlug.ts:
--------------------------------------------------------------------------------
1 | export function generateSlug(text: string): string {
2 | const lowercaseText = text.toLowerCase();
3 | const slug = lowercaseText.replace(/\s/g, '_');
4 | return slug.replace(/^_+|_+$/g, '');
5 | }
6 |
--------------------------------------------------------------------------------
/frontend/src/helpers/getExpoPushTokenHandler.mock.ts:
--------------------------------------------------------------------------------
1 | import * as Device from 'expo-device';
2 |
3 | import { mockToken } from '../../e2e/rest-mock/data';
4 |
5 | export async function getExpoPushTokenHandler() {
6 | if (!Device.isDevice) {
7 | return {
8 | success: false,
9 | message: 'PushNotificationsNotSupported: Must use physical device.',
10 | token: null,
11 | };
12 | }
13 |
14 | return {
15 | success: true,
16 | message: '',
17 | token: mockToken,
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/helpers/getFormat.ts:
--------------------------------------------------------------------------------
1 | export function getFormat(uri: string) {
2 | let format = uri.match(/\.\w+$/);
3 | const modifiedFormat = format ? format[0].substring(1) : '';
4 |
5 | return modifiedFormat;
6 | }
7 |
--------------------------------------------------------------------------------
/frontend/src/helpers/getHyperlink.ts:
--------------------------------------------------------------------------------
1 | export function getHyperlink(url: string, title: string) {
2 | const newTitle = title || url;
3 |
4 | let protocol = 'https://';
5 | let subDomain = 'www.';
6 |
7 | if (url.match(/^http:\/\//)) {
8 | protocol = 'http://';
9 | url = url.slice(protocol.length);
10 | } else if (url.match(/^https:\/\//)) {
11 | url = url.slice(protocol.length);
12 | }
13 |
14 | if (url.match(/^www\.\w/)) {
15 | url = url.slice(4);
16 | }
17 |
18 | const newUrl = protocol + subDomain + url;
19 |
20 | return { newUrl, newTitle };
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/helpers/getMimeFromImagePicker.ts:
--------------------------------------------------------------------------------
1 | import { getFormat } from './getFormat';
2 |
3 | export default (filePath: string) => {
4 | const fileExtension = getFormat(filePath);
5 |
6 | switch (fileExtension) {
7 | case 'jpg':
8 | return 'image/jpg';
9 | case 'jpeg':
10 | return 'image/jpeg';
11 | default:
12 | return `image/${fileExtension || ''}`;
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/frontend/src/helpers/getTopicDetailOutputCacheBehavior.ts:
--------------------------------------------------------------------------------
1 | import { replaceDataPagination } from './paginationHandler';
2 |
3 | export function getTopicDetailOutputCacheBehavior() {
4 | return {
5 | fields: {
6 | title: replaceDataPagination(),
7 | views: replaceDataPagination(),
8 | likeCount: replaceDataPagination(),
9 | postsCount: replaceDataPagination(),
10 | liked: replaceDataPagination(),
11 | categoryId: replaceDataPagination(),
12 | tags: replaceDataPagination(),
13 | createdAt: replaceDataPagination(),
14 | details: replaceDataPagination(),
15 | },
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/src/helpers/getUserImage.ts:
--------------------------------------------------------------------------------
1 | import { AVATAR_IMAGE_SIZES, AVATAR_IMAGE_VARIANTS } from '../constants';
2 |
3 | export function getImage(url: string, sizeOptions?: AVATAR_IMAGE_VARIANTS) {
4 | if (sizeOptions) {
5 | url = url.replace('{size}', `${AVATAR_IMAGE_SIZES[sizeOptions]}`);
6 | } else {
7 | url = url.replace('{size}', `${AVATAR_IMAGE_SIZES.s}`);
8 | }
9 |
10 | return url;
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/helpers/insertHyperlink.ts:
--------------------------------------------------------------------------------
1 | export function insertHyperlink(
2 | raw: string,
3 | hyperlinkTitle: string,
4 | hyperlinkUrl: string | undefined,
5 | ) {
6 | if (hyperlinkUrl) {
7 | raw = `${raw !== '' ? `${raw} ` : ''}[${hyperlinkTitle}](${hyperlinkUrl})`;
8 | }
9 |
10 | return raw;
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/helpers/listNumberStep.ts:
--------------------------------------------------------------------------------
1 | export function getListNumberStep({
2 | min,
3 | max,
4 | step,
5 | }: {
6 | min: number;
7 | max: number;
8 | step: number;
9 | }) {
10 | if (step <= 0) {
11 | return [];
12 | }
13 |
14 | return Array.from(
15 | { length: Math.floor((max - min) / step) + 1 },
16 | (_, index) => min + index * step,
17 | );
18 | }
19 |
20 | export function changeListNumberOption(listNumber: Array) {
21 | return listNumber.map((data) => {
22 | return { option: data.toString() };
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/helpers/navigateInProfile.ts:
--------------------------------------------------------------------------------
1 | import { currentScreenVar } from '../constants';
2 | import { navigate } from '../navigation/NavigationService';
3 | import { RootStackParamList, RootStackRouteName } from '../types';
4 |
5 | export function navigateInProfile(
6 | screen: RootStackRouteName,
7 | params: RootStackParamList[RootStackRouteName],
8 | ) {
9 | currentScreenVar({ screen, params });
10 | navigate([screen, params]);
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/helpers/newPostIsValid.ts:
--------------------------------------------------------------------------------
1 | import { PollFormContextValues } from '../types';
2 |
3 | export function newPostIsValid(
4 | title: string,
5 | content: string,
6 | uploadsInProgress: number,
7 | polls?: Array,
8 | ): boolean {
9 | const hasTitle = title.trim().length > 0;
10 | const hasContent = content.trim().length > 0;
11 | const hasPoll = (polls && polls.length > 0) || false;
12 | const noUploadsInProgress = uploadsInProgress < 1;
13 |
14 | return hasTitle && (hasContent || hasPoll) && noUploadsInProgress;
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/helpers/parser.ts:
--------------------------------------------------------------------------------
1 | export function parseInt(param: string) {
2 | const parsed = Number.parseInt(param, 10);
3 | return Number.isNaN(parsed) ? undefined : parsed;
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/src/helpers/secureStore.ts:
--------------------------------------------------------------------------------
1 | import * as Crypto from 'expo-crypto';
2 | import * as SecureStore from 'expo-secure-store';
3 |
4 | const DEVICE_ID_KEY = 'deviceId';
5 |
6 | export async function getDeviceId() {
7 | let deviceId = await SecureStore.getItemAsync(DEVICE_ID_KEY);
8 |
9 | if (!deviceId) {
10 | deviceId = Crypto.randomUUID();
11 | await SecureStore.setItemAsync(DEVICE_ID_KEY, deviceId);
12 | }
13 |
14 | return deviceId;
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/helpers/showLogoutAlert.ts:
--------------------------------------------------------------------------------
1 | import { Alert } from 'react-native';
2 |
3 | let alertShown = false;
4 |
5 | export function showLogoutAlert() {
6 | // This condition is used to make sure only one logout alert show at same time
7 | if (!alertShown) {
8 | Alert.alert(
9 | t('You have been logged out'),
10 | t('Please try logging in again.'),
11 | [{ text: t('Got It'), onPress: () => (alertShown = false) }],
12 | { cancelable: false },
13 | );
14 | alertShown = true;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/frontend/src/helpers/storage.mock.ts:
--------------------------------------------------------------------------------
1 | let token = '1234567890';
2 |
3 | export let setToken = (userToken: string) => {
4 | return (token = userToken);
5 | };
6 |
7 | export let getToken = async () => {
8 | return token;
9 | };
10 |
11 | export let removeToken = async () => {
12 | return (token = '');
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/src/helpers/storage.ts:
--------------------------------------------------------------------------------
1 | import AsyncStorage from '@react-native-async-storage/async-storage';
2 |
3 | const TOKEN_KEY = '@auth:sessionToken';
4 |
5 | export let setToken = (userToken: string) => {
6 | return AsyncStorage.setItem(TOKEN_KEY, userToken);
7 | };
8 |
9 | export let getToken = async () => {
10 | return await AsyncStorage.getItem(TOKEN_KEY);
11 | };
12 |
13 | export let removeToken = async () => {
14 | return await AsyncStorage.removeItem(TOKEN_KEY);
15 | };
16 |
--------------------------------------------------------------------------------
/frontend/src/helpers/stripHTML.ts:
--------------------------------------------------------------------------------
1 | export function stripHTML(str: string) {
2 | return str.replace(/<[^>\s]*>|<[^\s][^><]*>/g, '');
3 | }
4 |
--------------------------------------------------------------------------------
/frontend/src/helpers/token.ts:
--------------------------------------------------------------------------------
1 | import { Buffer } from 'buffer';
2 |
3 | export function decodeToken(token: string | null) {
4 | if (!token) {
5 | return '';
6 | }
7 | const base64TokenRegex =
8 | /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/;
9 | let isValidToken = base64TokenRegex.test(token);
10 | if (!isValidToken) {
11 | return '';
12 | }
13 |
14 | const buffer = Buffer.from(token, 'base64');
15 | const cookies = buffer.toString('utf8');
16 |
17 | return cookies;
18 | }
19 |
20 | export function encodeToBase64Token(key: string) {
21 | const buffer = Buffer.from(key);
22 | const token = buffer.toString('base64');
23 |
24 | return token;
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/src/helpers/unescapeHTML.ts:
--------------------------------------------------------------------------------
1 | const htmlEntities: { [key: string]: string } = {
2 | '<': '<',
3 | '>': '>',
4 | '"': '"',
5 | ''': "'",
6 | '&': '&',
7 | '…': '...',
8 | };
9 |
10 | export function unescapeHTML(str: string) {
11 | return str.replace(
12 | /<|>|"|'|&|…/g,
13 | (match: string) => htmlEntities[match],
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/chat/useCreateThread.ts:
--------------------------------------------------------------------------------
1 | import { MutationHookOptions } from '@apollo/client';
2 |
3 | import {
4 | CreateThreadDocument,
5 | CreateThreadMutation as CreateThreadType,
6 | CreateThreadMutationVariables as CreateThreadVariables,
7 | } from '../../../generatedAPI/server';
8 | import { useMutation } from '../../../utils';
9 |
10 | export function useCreateThread(
11 | options?: MutationHookOptions,
12 | ) {
13 | const [createThread, { loading }] = useMutation<
14 | CreateThreadType,
15 | CreateThreadVariables
16 | >(CreateThreadDocument, { ...options });
17 |
18 | return { createThread, loading };
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/chat/useGetThreadDetail.ts:
--------------------------------------------------------------------------------
1 | import { LazyQueryHookOptions, useLazyQuery } from '@apollo/client';
2 |
3 | import {
4 | GetThreadDetailDocument,
5 | GetThreadDetailQuery as GetThreadDetailType,
6 | GetThreadDetailQueryVariables as GetThreadDetailVariable,
7 | } from '../../../generatedAPI/server';
8 |
9 | export function useLazyGetThreadDetail(
10 | options?: LazyQueryHookOptions,
11 | ) {
12 | const [getThreadDetail, { ...other }] = useLazyQuery<
13 | GetThreadDetailType,
14 | GetThreadDetailVariable
15 | >(GetThreadDetailDocument, {
16 | context: { queryDeduplication: true },
17 | ...options,
18 | });
19 |
20 | return {
21 | getThreadDetail,
22 | ...other,
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/chat/useLeaveChannel.ts:
--------------------------------------------------------------------------------
1 | import { MutationHookOptions } from '@apollo/client';
2 |
3 | import {
4 | LeaveChannelDocument,
5 | LeaveChannelMutation as LeaveChannelType,
6 | LeaveChannelMutationVariables as LeaveChannelVariables,
7 | } from '../../../generatedAPI/server';
8 | import { useMutation } from '../../../utils';
9 |
10 | export function useLeaveChannel(
11 | options?: MutationHookOptions,
12 | ) {
13 | const [leaveChannel, { loading }] = useMutation<
14 | LeaveChannelType,
15 | LeaveChannelVariables
16 | >(LeaveChannelDocument, { ...options });
17 |
18 | return { leaveChannel, loading };
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/chat/useMarkReadChat.ts:
--------------------------------------------------------------------------------
1 | import { MutationHookOptions } from '@apollo/client';
2 |
3 | import {
4 | MarkReadChatDocument,
5 | MarkReadChatMutation as MarkReadChatType,
6 | MarkReadChatMutationVariables as MarkReadChatVariables,
7 | } from '../../../generatedAPI/server';
8 | import { useMutation } from '../../../utils';
9 |
10 | export function useMarkReadChat(
11 | options?: MutationHookOptions,
12 | ) {
13 | const [markReadChat, { loading }] = useMutation<
14 | MarkReadChatType,
15 | MarkReadChatVariables
16 | >(MarkReadChatDocument, { ...options });
17 |
18 | return { markReadChat, loading };
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/chat/useReplyChat.ts:
--------------------------------------------------------------------------------
1 | import { MutationHookOptions } from '@apollo/client';
2 |
3 | import {
4 | ReplyChatDocument,
5 | ReplyChatMutation as ReplyChatType,
6 | ReplyChatMutationVariables as ReplyChatVariables,
7 | } from '../../../generatedAPI/server';
8 | import { useMutation } from '../../../utils';
9 |
10 | export function useReplyChat(
11 | options?: MutationHookOptions,
12 | ) {
13 | const [replyChat, { loading }] = useMutation<
14 | ReplyChatType,
15 | ReplyChatVariables
16 | >(ReplyChatDocument, { ...options });
17 |
18 | return { replyChat, loading };
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/poll/useTogglePollStatus.ts:
--------------------------------------------------------------------------------
1 | import { MutationHookOptions } from '@apollo/client';
2 |
3 | import {
4 | TogglePollStatusDocument,
5 | TogglePollStatusMutationVariables,
6 | TogglePollStatusMutation as TogglePollStatusType,
7 | } from '../../../generatedAPI/server';
8 | import { useMutation } from '../../../utils';
9 |
10 | export function useTogglePollStatus(
11 | options?: MutationHookOptions<
12 | TogglePollStatusType,
13 | TogglePollStatusMutationVariables
14 | >,
15 | ) {
16 | const [togglePollStatus, { loading }] = useMutation<
17 | TogglePollStatusType,
18 | TogglePollStatusMutationVariables
19 | >(TogglePollStatusDocument, {
20 | ...options,
21 | });
22 |
23 | return { togglePollStatus, loading };
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/poll/useUndoVotePoll.ts:
--------------------------------------------------------------------------------
1 | import { MutationHookOptions } from '@apollo/client';
2 |
3 | import {
4 | UndoVotePollDocument,
5 | UndoVotePollMutationVariables,
6 | UndoVotePollMutation as UndoVotePollType,
7 | } from '../../../generatedAPI/server';
8 | import { useMutation } from '../../../utils';
9 |
10 | export function useUndoVotePoll(
11 | options?: MutationHookOptions<
12 | UndoVotePollType,
13 | UndoVotePollMutationVariables
14 | >,
15 | ) {
16 | const [undoVotePoll, { loading }] = useMutation<
17 | UndoVotePollType,
18 | UndoVotePollMutationVariables
19 | >(UndoVotePollDocument, {
20 | ...options,
21 | });
22 |
23 | return { undoVotePoll, loading };
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/post/useEditPost.ts:
--------------------------------------------------------------------------------
1 | import { MutationHookOptions } from '@apollo/client';
2 |
3 | import {
4 | EditPostDocument,
5 | EditPostMutationVariables,
6 | EditPostMutation as EditPostType,
7 | } from '../../../generatedAPI/server';
8 | import { useMutation } from '../../../utils';
9 |
10 | export function useEditPost(
11 | options?: MutationHookOptions,
12 | ) {
13 | const [editPost, { loading }] = useMutation<
14 | EditPostType,
15 | EditPostMutationVariables
16 | >(EditPostDocument, { ...options });
17 |
18 | return { editPost, loading };
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/post/useEditTopic.ts:
--------------------------------------------------------------------------------
1 | import { MutationHookOptions } from '@apollo/client';
2 |
3 | import {
4 | EditTopicDocument,
5 | EditTopicMutationVariables,
6 | EditTopicMutation as EditTopicType,
7 | } from '../../../generatedAPI/server';
8 | import { useMutation } from '../../../utils';
9 |
10 | export function useEditTopic(
11 | options?: MutationHookOptions,
12 | ) {
13 | const [editTopic, { loading }] = useMutation<
14 | EditTopicType,
15 | EditTopicMutationVariables
16 | >(EditTopicDocument, {
17 | ...options,
18 | });
19 |
20 | return { editTopic, loading };
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/post/useFlagPost.ts:
--------------------------------------------------------------------------------
1 | import { MutationHookOptions } from '@apollo/client';
2 |
3 | import {
4 | FlagPostDocument,
5 | FlagPostMutationVariables,
6 | FlagPostMutation as FlagPostType,
7 | } from '../../../generatedAPI/server';
8 | import { useMutation } from '../../../utils';
9 |
10 | export function useFlagPost(
11 | options?: MutationHookOptions,
12 | ) {
13 | const [flag, { loading }] = useMutation<
14 | FlagPostType,
15 | FlagPostMutationVariables
16 | >(FlagPostDocument, {
17 | ...options,
18 | });
19 |
20 | return { flag, loading };
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/post/useLeaveMessage.ts:
--------------------------------------------------------------------------------
1 | import { MutationHookOptions } from '@apollo/client';
2 |
3 | import {
4 | LeaveMessageDocument,
5 | LeaveMessageMutationVariables,
6 | LeaveMessageMutation as LeaveMessageType,
7 | } from '../../../generatedAPI/server';
8 | import { useMutation } from '../../../utils';
9 |
10 | export function useLeaveMessage(
11 | options?: MutationHookOptions<
12 | LeaveMessageType,
13 | LeaveMessageMutationVariables
14 | >,
15 | ) {
16 | const [leaveMessage, { loading }] = useMutation<
17 | LeaveMessageType,
18 | LeaveMessageMutationVariables
19 | >(LeaveMessageDocument, {
20 | ...options,
21 | });
22 |
23 | return { leaveMessage, loading };
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/post/useLookupUrls.ts:
--------------------------------------------------------------------------------
1 | import { LazyQueryHookOptions } from '@apollo/client';
2 |
3 | import {
4 | LookupUrlsDocument,
5 | LookupUrlsQuery,
6 | LookupUrlsQueryVariables,
7 | } from '../../../generatedAPI/server';
8 | import { useLazyQuery } from '../../../utils';
9 |
10 | export function useLookupUrls(
11 | options?: LazyQueryHookOptions,
12 | ) {
13 | const [getImageUrls, { loading }] = useLazyQuery<
14 | LookupUrlsQuery,
15 | LookupUrlsQueryVariables
16 | >(LookupUrlsDocument, { ...options });
17 |
18 | return { getImageUrls, loading };
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/post/usePost.ts:
--------------------------------------------------------------------------------
1 | import { QueryHookOptions } from '@apollo/client';
2 |
3 | import {
4 | PostQueryDocument,
5 | PostQueryVariables,
6 | PostQuery as PostType,
7 | } from '../../../generatedAPI/server';
8 | import { useLazyQuery } from '../../../utils';
9 |
10 | export function usePost(
11 | options?: QueryHookOptions,
12 | ) {
13 | const [getPost, { loading }] = useLazyQuery(
14 | PostQueryDocument,
15 | { ...options },
16 | );
17 |
18 | return { getPost, loading };
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/post/usePostRaw.ts:
--------------------------------------------------------------------------------
1 | import { LazyQueryHookOptions } from '@apollo/client';
2 |
3 | import {
4 | PostRawDocument,
5 | PostRawQueryVariables,
6 | PostRawQuery as PostRawType,
7 | } from '../../../generatedAPI/server';
8 | import { useLazyQuery } from '../../../utils';
9 |
10 | export function usePostRaw(
11 | options?: LazyQueryHookOptions,
12 | ) {
13 | const [postRaw, { loading }] = useLazyQuery<
14 | PostRawType,
15 | PostRawQueryVariables
16 | >(PostRawDocument, { ...options });
17 |
18 | return { postRaw, loading };
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/profile/useChangePassword.ts:
--------------------------------------------------------------------------------
1 | import { MutationHookOptions } from '@apollo/client';
2 |
3 | import {
4 | ChangeNewPasswordDocument,
5 | ChangeNewPasswordMutationVariables,
6 | ChangeNewPasswordMutation as ChangeNewPasswordType,
7 | } from '../../../generatedAPI/server';
8 | import { useMutation } from '../../../utils';
9 |
10 | export function useChangePassword(
11 | options?: MutationHookOptions<
12 | ChangeNewPasswordType,
13 | ChangeNewPasswordMutationVariables
14 | >,
15 | ) {
16 | const [changeNewPassword, { loading, error }] = useMutation<
17 | ChangeNewPasswordType,
18 | ChangeNewPasswordMutationVariables
19 | >(ChangeNewPasswordDocument, {
20 | ...options,
21 | });
22 |
23 | return { changeNewPassword, loading, error };
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/site/useAbout.ts:
--------------------------------------------------------------------------------
1 | import { LazyQueryHookOptions } from '@apollo/client';
2 |
3 | import {
4 | AboutDocument,
5 | AboutQuery as AboutType,
6 | } from '../../../generatedAPI/server';
7 | import { ErrorAlertOptionType } from '../../../types';
8 | import { useLazyQuery } from '../../../utils';
9 |
10 | export function useAbout(
11 | options?: LazyQueryHookOptions,
12 | errorAlert: ErrorAlertOptionType = 'SHOW_ALERT',
13 | ) {
14 | const [getAbout, { error }] = useLazyQuery(
15 | AboutDocument,
16 | {
17 | ...options,
18 | },
19 | errorAlert,
20 | );
21 |
22 | return { getAbout, error };
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/site/useChannels.ts:
--------------------------------------------------------------------------------
1 | import { QueryHookOptions } from '@apollo/client';
2 |
3 | import {
4 | GetChannelsQuery as ChannelsType,
5 | GetChannelsDocument,
6 | } from '../../../generatedAPI/server';
7 | import { ErrorAlertOptionType } from '../../../types';
8 | import { useQuery } from '../../../utils';
9 |
10 | export function useChannels(
11 | options?: QueryHookOptions,
12 | errorAlert: ErrorAlertOptionType = 'SHOW_ALERT',
13 | ) {
14 | const { data, loading, error, refetch } = useQuery(
15 | GetChannelsDocument,
16 | {
17 | ...options,
18 | },
19 | errorAlert,
20 | );
21 |
22 | return { data, loading, error, refetch };
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/user/useDeleteUserStatus.ts:
--------------------------------------------------------------------------------
1 | import { MutationHookOptions } from '@apollo/client';
2 |
3 | import {
4 | DeleteUserStatusDocument,
5 | DeleteUserStatusMutation,
6 | DeleteUserStatusMutationVariables,
7 | } from '../../../generatedAPI/server';
8 | import { useMutation } from '../../../utils';
9 |
10 | export function useDeleteUserStatus(
11 | options?: MutationHookOptions<
12 | DeleteUserStatusMutation,
13 | DeleteUserStatusMutationVariables
14 | >,
15 | ) {
16 | const [deleteUserStatus, { loading }] = useMutation<
17 | DeleteUserStatusMutation,
18 | DeleteUserStatusMutationVariables
19 | >(DeleteUserStatusDocument, {
20 | ...options,
21 | });
22 |
23 | return { deleteUserStatus, loading };
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/user/useEditUserStatus.ts:
--------------------------------------------------------------------------------
1 | import { MutationHookOptions } from '@apollo/client';
2 |
3 | import {
4 | EditUserStatusDocument,
5 | EditUserStatusMutation,
6 | EditUserStatusMutationVariables,
7 | } from '../../../generatedAPI/server';
8 | import { useMutation } from '../../../utils';
9 |
10 | export function useEditUserStatus(
11 | options?: MutationHookOptions<
12 | EditUserStatusMutation,
13 | EditUserStatusMutationVariables
14 | >,
15 | ) {
16 | const [editUserStatus, { loading }] = useMutation<
17 | EditUserStatusMutation,
18 | EditUserStatusMutationVariables
19 | >(EditUserStatusDocument, {
20 | ...options,
21 | });
22 |
23 | return { editUserStatus, loading };
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/hooks/rest/user/useSearchUsers.ts:
--------------------------------------------------------------------------------
1 | import { QueryHookOptions } from '@apollo/client';
2 |
3 | import {
4 | SearchUserDocument,
5 | SearchUserQueryVariables,
6 | SearchUserQuery as SearchUserType,
7 | } from '../../../generatedAPI/server';
8 | import { ErrorAlertOptionType } from '../../../types';
9 | import { useQuery } from '../../../utils';
10 |
11 | export function useSearchUsers(
12 | options?: QueryHookOptions,
13 | errorAlert: ErrorAlertOptionType = 'SHOW_ALERT',
14 | ) {
15 | const { data, loading, error } = useQuery<
16 | SearchUserType,
17 | SearchUserQueryVariables
18 | >(
19 | SearchUserDocument,
20 | {
21 | ...options,
22 | },
23 | errorAlert,
24 | );
25 |
26 | return { data, loading, error };
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useKeyboardListener.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Keyboard } from 'react-native';
3 |
4 | export function useKeyboardListener() {
5 | const [isKeyboardVisible, setKeyboardVisible] = useState(false);
6 |
7 | useEffect(() => {
8 | const keyboardDidShowListener = Keyboard.addListener(
9 | 'keyboardDidShow',
10 | () => {
11 | setKeyboardVisible(true);
12 | },
13 | );
14 | const keyboardDidHideListener = Keyboard.addListener(
15 | 'keyboardDidHide',
16 | () => {
17 | setKeyboardVisible(false);
18 | },
19 | );
20 |
21 | return () => {
22 | keyboardDidHideListener.remove();
23 | keyboardDidShowListener.remove();
24 | };
25 | }, []);
26 |
27 | return { isKeyboardVisible };
28 | }
29 |
--------------------------------------------------------------------------------
/frontend/src/i18n/translate.ts:
--------------------------------------------------------------------------------
1 | import * as Localization from 'expo-localization';
2 |
3 | export const LOCALE = Localization.locale;
4 |
5 | type ParamsObject = {
6 | [key: string]: unknown;
7 | };
8 |
9 | // This matches words inside curly braces.
10 | const PLACEHOLDER = /\{(\w+)\}/g;
11 |
12 | // This function doesn't currently do anything besides string interpolation,
13 | // since we're not yet implementing support for other locales.
14 | export function t(input: string, params?: ParamsObject): string {
15 | if (params) {
16 | return input.replace(PLACEHOLDER, (match: string, word: string) => {
17 | return params[word] != null ? String(params[word]) : match;
18 | });
19 | } else {
20 | return input;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/frontend/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | import { t } from './i18n/translate';
2 |
3 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
4 | // @ts-ignore
5 | global.t = t;
6 |
--------------------------------------------------------------------------------
/frontend/src/reactiveVars/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './tokenReactive';
2 |
--------------------------------------------------------------------------------
/frontend/src/screens/Channels/index.ts:
--------------------------------------------------------------------------------
1 | import Channels from './Channels';
2 | import ChannelSideBarContent from './ChannelSideBarContent';
3 | import ChannelSideBarDrawer from './ChannelSideBarDrawer';
4 |
5 | export { Channels, ChannelSideBarContent, ChannelSideBarDrawer };
6 |
--------------------------------------------------------------------------------
/frontend/src/screens/Chat/components/index.ts:
--------------------------------------------------------------------------------
1 | import ChannelList from './ChannelList';
2 | import Search from './Search';
3 |
4 | export { ChannelList, Search };
5 |
6 | export * from './ChatList';
7 | export * from './ChatMessageItem';
8 | export * from './FooterReplyChat';
9 | export * from './ThreadDetailsHeader';
10 |
--------------------------------------------------------------------------------
/frontend/src/screens/Home/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './HomeNavBar';
2 | export * from './SearchBar';
3 | export * from './HomeTabletNavBar';
4 |
--------------------------------------------------------------------------------
/frontend/src/screens/MessageDetail/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './MessageItem';
2 | export * from './ReplyInputField';
3 | export * from './ToolTip';
4 | export * from './ListImageSelected';
5 |
--------------------------------------------------------------------------------
/frontend/src/screens/Messages/Components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './MessageCard';
2 |
--------------------------------------------------------------------------------
/frontend/src/screens/PostDetail/hooks/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as useNotificationScroll } from './useNotificationScroll';
2 |
--------------------------------------------------------------------------------
/frontend/src/screens/PostDetail/index.tsx:
--------------------------------------------------------------------------------
1 | export { default } from './PostDetail';
2 |
--------------------------------------------------------------------------------
/frontend/src/screens/Tags/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AvailableTags';
2 | export * from './SelectedTags';
3 |
--------------------------------------------------------------------------------
/frontend/src/theme/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AppearanceProvider';
2 | export * from './makeStyles';
3 | export {
4 | Config,
5 | Theme,
6 | FontSize,
7 | FontVariant,
8 | IconSize,
9 | Spacing,
10 | Color,
11 | } from './theme';
12 | export * from './ThemeProvider';
13 |
--------------------------------------------------------------------------------
/frontend/src/theme/makeStyles.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import { ImageStyle, StyleSheet, TextStyle, ViewStyle } from 'react-native';
3 |
4 | import { Theme } from './theme';
5 | import { useTheme } from './ThemeProvider';
6 |
7 | type NamedStyles = { [P in keyof T]: ViewStyle | TextStyle | ImageStyle };
8 |
9 | type Creator = (theme: Theme) => T;
10 |
11 | export function makeStyles>(
12 | stylesOrCreator: T | Creator,
13 | ) {
14 | let creator: Creator =
15 | typeof stylesOrCreator === 'function'
16 | ? stylesOrCreator
17 | : (_: Theme) => stylesOrCreator;
18 |
19 | let useStyles = () => {
20 | let theme = useTheme();
21 | return useMemo(() => StyleSheet.create(creator(theme)), [theme]);
22 | };
23 |
24 | return useStyles;
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/src/types/ContentState.ts:
--------------------------------------------------------------------------------
1 | type Error = { status: 'ERROR'; error: string };
2 | type Loading = { status: 'LOADING'; loading: string };
3 | type Data = { status: 'LOADED'; data: Array };
4 |
5 | export type ContentState = Data | Loading | Error;
6 |
--------------------------------------------------------------------------------
/frontend/src/types/Hook.ts:
--------------------------------------------------------------------------------
1 | import { FetchResult, InternalRefetchQueriesInclude } from '@apollo/client';
2 |
3 | import {
4 | DeletePostDraftMutationVariables,
5 | DeletePostDraftMutation as DeletePostDraftType,
6 | } from '../generatedAPI/server';
7 |
8 | export type DeletePostDraftMutateFnInput = {
9 | variables: DeletePostDraftMutationVariables;
10 | refetchQueries?:
11 | | InternalRefetchQueriesInclude
12 | | ((
13 | result: FetchResult,
14 | ) => InternalRefetchQueriesInclude);
15 | };
16 |
--------------------------------------------------------------------------------
/frontend/src/types/NumericString.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | export const NumericString = z.string().transform((val, ctx) => {
4 | const parseNumber = parseInt(val, 10);
5 | if (isNaN(parseNumber)) {
6 | ctx.addIssue({
7 | code: z.ZodIssueCode.custom,
8 | message: 'Not a number',
9 | });
10 | return z.NEVER;
11 | }
12 | return parseNumber;
13 | });
14 |
--------------------------------------------------------------------------------
/frontend/src/types/Theme.ts:
--------------------------------------------------------------------------------
1 | import { TextStyle, ViewStyle } from 'react-native';
2 |
3 | type HeaderTitleAlign = 'center' | 'left' | undefined;
4 |
5 | export type NavHeader = {
6 | headerStyle: ViewStyle;
7 | headerTintColor: string;
8 | headerTitleStyle: TextStyle;
9 | headerTitleAlign?: HeaderTitleAlign;
10 | headerBackTitleStyle: TextStyle;
11 | headerBackTitle: string;
12 | };
13 |
--------------------------------------------------------------------------------
/frontend/src/types/api/group.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | const Group = z.object({
4 | id: z.number(),
5 | name: z.string(),
6 | });
7 |
8 | export type Group = z.infer;
9 |
--------------------------------------------------------------------------------
/frontend/src/types/api/index.ts:
--------------------------------------------------------------------------------
1 | export * from './chat';
2 | export * from './group';
3 | export * from './message';
4 | export * from './poll';
5 | export * from './post';
6 | export * from './postDraft';
7 | export * from './topic';
8 | export * from './user';
9 |
--------------------------------------------------------------------------------
/frontend/src/types/chat.ts:
--------------------------------------------------------------------------------
1 | import { ChannelList } from '../generatedAPI/server';
2 |
3 | export type ChannelListOutput = Array;
4 |
--------------------------------------------------------------------------------
/frontend/src/types/global.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | export { t } from '../helpers/translate';
3 | export const global: typeof globalThis;
4 | }
5 |
6 | export {};
7 |
--------------------------------------------------------------------------------
/frontend/src/types/imports.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.jpg';
2 | declare module '*.png';
3 |
4 | declare module '*.svg' {
5 | import { SvgProps } from 'react-native-svg';
6 | const Component: React.FC;
7 | export default Component;
8 | }
9 |
--------------------------------------------------------------------------------
/frontend/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './chat';
2 | import { ApolloClient, NormalizedCacheObject } from '@apollo/client';
3 |
4 | export * from './ContentState';
5 | export * from './DiscourseNotification';
6 | export * from './ErrorSchema';
7 | export * from './Form';
8 | export * from './Hook';
9 | export * from './Navigation';
10 | export * from './NumericString';
11 | export * from './pagination';
12 | export * from './Post';
13 | export * from './Theme';
14 | export * from './Types';
15 |
16 | export type ObjectValues = T[keyof T];
17 | export type Apollo = ApolloClient;
18 |
--------------------------------------------------------------------------------
/frontend/src/types/markdown-it-flowdock.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'markdown-it-flowdock' {
2 | function markdownItFlowdock(): void;
3 | export = markdownItFlowdock;
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/src/types/pagination.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | const Reference = z.object({
4 | __ref: z.string(),
5 | });
6 |
7 | export const SearchPost = z.object({
8 | 'posts@type({"name":"SearchPost"})': z.array(Reference),
9 | 'topics@type({"name":"SearchTopic"})': z.array(Reference),
10 | });
11 |
12 | export const MessageDetail = z.object({
13 | topicList: z.object({ topics: z.array(Reference) }),
14 | users: z.array(Reference),
15 | });
16 |
17 | export const Notifications = z.object({
18 | 'notifications@type({"name":"NotificationsData"})': z.array(Reference),
19 | });
20 |
--------------------------------------------------------------------------------
/frontend/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ModalProvider';
2 | export * from './OngoingLikedTopicProvider';
3 | export * from './PushNotificationsProvider';
4 | export * from './useLazyQuery';
5 | export * from './useMutation';
6 | export * from './useQuery';
7 | export * from './RedirectProvider';
8 | export * from './DeviceProvider';
9 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "jsx": "react-native",
5 | "lib": [
6 | "dom",
7 | "esnext"
8 | ],
9 | "moduleResolution": "node",
10 | "noEmit": true,
11 | "skipLibCheck": true,
12 | "resolveJsonModule": true,
13 | "downlevelIteration": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true
16 | },
17 | "extends": "expo/tsconfig.base"
18 | }
19 |
--------------------------------------------------------------------------------