├── .babelrc
├── .dockerignore
├── .eslintrc.yml
├── .github
├── dependabot.yml
└── workflows
│ ├── ci.yml
│ └── e2e.yml
├── .gitignore
├── .prettierrc
├── .tool-versions
├── LICENSE
├── README.md
├── app
├── API
│ ├── graphql_api.js
│ ├── graphql_queries.js
│ ├── http_api
│ │ ├── current_user.js
│ │ └── index.js
│ ├── index.js
│ ├── server_error.js
│ ├── socket_api.js
│ └── wikidata.js
├── App.jsx
├── assets
│ ├── achievements
│ │ ├── ambassador.jpg
│ │ ├── ambassador.mp4
│ │ ├── artist.jpg
│ │ ├── bug.jpg
│ │ ├── bug.mp4
│ │ ├── bulletproof.png
│ │ ├── default.png
│ │ ├── famous.jpg
│ │ ├── famous.mp4
│ │ ├── goodVibes.jpg
│ │ ├── help.jpg
│ │ ├── robot.jpg
│ │ ├── robot.mp4
│ │ ├── socialAddict.jpg
│ │ ├── socialAddict.mp4
│ │ ├── trump.png
│ │ ├── welcome.jpg
│ │ └── welcome.mp4
│ ├── browsers
│ │ ├── chrome.png
│ │ ├── firefox.png
│ │ └── internet_explorer.png
│ ├── demos
│ │ └── demo-extension.mp4
│ ├── example1.jpg
│ ├── examples
│ │ ├── image-1.jpg
│ │ ├── image-2.jpg
│ │ └── image-3.jpg
│ ├── landing-illustration.jpg
│ ├── landing-illustration.png
│ ├── logo-borderless.svg
│ ├── logo.svg
│ ├── media.jpg
│ ├── partners
│ │ ├── democratie-ouverte.jpg
│ │ ├── heureka.jpg
│ │ ├── imago.jpg
│ │ ├── larchipelle.jpg
│ │ ├── lecanardrefractaire.jpg
│ │ ├── maiamater.jpg
│ │ ├── systemed.jpg
│ │ ├── tedxnoumea.jpg
│ │ ├── thinkerview.jpg
│ │ ├── troncheenbiais.jpg
│ │ └── yeswehack.jpg
│ ├── sounds
│ │ ├── background_statement_confirm.mp3
│ │ ├── background_statement_neutral.mp3
│ │ └── background_statement_refute.mp3
│ └── team
│ │ ├── basile.jpg
│ │ ├── benjamin.png
│ │ ├── florence.jpg
│ │ ├── frederic.jpg
│ │ └── mathieu.jpg
├── components
│ ├── App
│ │ ├── BackgroundNotifier.jsx
│ │ ├── CrashReportPage.jsx
│ │ ├── LanguageSelector.jsx
│ │ ├── Layout.jsx
│ │ ├── Logo.jsx
│ │ ├── MenuToggleSwitch.jsx
│ │ ├── Navbar.jsx
│ │ └── Sidebar.jsx
│ ├── Comments
│ │ ├── CommentAction.jsx
│ │ ├── CommentActions.jsx
│ │ ├── CommentContent.jsx
│ │ ├── CommentDisplay.jsx
│ │ ├── CommentForm.jsx
│ │ ├── CommentHeader.jsx
│ │ ├── CommentsList.jsx
│ │ ├── CommentsListExpender.jsx
│ │ ├── CommentsListHeader.jsx
│ │ ├── FlagForm.jsx
│ │ ├── ModalDeleteComment.jsx
│ │ ├── ModalFlag.jsx
│ │ ├── OtherCommentActions.jsx
│ │ ├── OwnCommentActions.jsx
│ │ ├── Source.jsx
│ │ └── Vote.tsx
│ ├── FormUtils
│ │ ├── ControlInput.jsx
│ │ ├── ControlTextarea.jsx
│ │ ├── FieldWithButton.jsx
│ │ ├── FieldWithLabelAddon.jsx
│ │ ├── TextareaAutosize.jsx
│ │ └── TextareaLengthCounter.tsx
│ ├── Help
│ │ ├── Help.jsx
│ │ ├── HelpModal.jsx
│ │ └── HelpPageContent.jsx
│ ├── Home
│ │ ├── AllPartners.jsx
│ │ ├── AllTeam.jsx
│ │ ├── CFSocialProfiles.jsx
│ │ ├── Home.jsx
│ │ ├── LastVideos.jsx
│ │ └── OpenCollectiveContributors.jsx
│ ├── LoggedInUser
│ │ ├── LogoutPage.jsx
│ │ ├── NotificationBell.jsx
│ │ ├── Notifications.jsx
│ │ ├── NotificationsPage.jsx
│ │ ├── SubscriptionsPage.jsx
│ │ ├── UserLanguageSelector.jsx
│ │ └── UserProvider.tsx
│ ├── Modal
│ │ ├── MainModalContainer.jsx
│ │ ├── Modal.jsx
│ │ ├── ModalConfirm.jsx
│ │ ├── ModalConfirmDelete.tsx
│ │ └── ModalFormContainer.jsx
│ ├── Moderation
│ │ ├── FlagReasonSelect.jsx
│ │ ├── Moderation.jsx
│ │ └── ModerationForm.jsx
│ ├── Notifications
│ │ ├── NotificationDetails.jsx
│ │ ├── NotificationsPopupContent.jsx
│ │ └── SubscribeBtn.jsx
│ ├── Pages
│ │ ├── BrowserExtensionsPage.jsx
│ │ ├── NotFound.jsx
│ │ └── index.js
│ ├── Search
│ │ ├── IndexSearchEntriesCount.js
│ │ ├── SearchBox.js
│ │ ├── SearchPage.js
│ │ ├── SpeakerHit.js
│ │ ├── StatementHit.js
│ │ └── VideoHit.js
│ ├── Speakers
│ │ ├── AddSpeakerForm.jsx
│ │ ├── EditSpeakerFormModal.jsx
│ │ ├── ModalRemoveSpeaker.jsx
│ │ ├── SpeakerDropdownMenu.tsx
│ │ ├── SpeakerPage.jsx
│ │ ├── SpeakerPreview.jsx
│ │ └── SpeakersSelect.jsx
│ ├── Statements
│ │ ├── SpeakerComments.jsx
│ │ ├── Statement.jsx
│ │ ├── StatementComments.jsx
│ │ ├── StatementContainer.jsx
│ │ ├── StatementDropdownMenu.tsx
│ │ ├── StatementForm.jsx
│ │ ├── StatementHeader.jsx
│ │ └── StatementsList.jsx
│ ├── StyledUtils
│ │ ├── Container.jsx
│ │ ├── Keyframes.jsx
│ │ ├── StyledLink.jsx
│ │ ├── Text.jsx
│ │ └── Title.jsx
│ ├── SupportUs
│ │ └── index.jsx
│ ├── Users
│ │ ├── Achievement.jsx
│ │ ├── ActivityLog.jsx
│ │ ├── ConfirmEmail.jsx
│ │ ├── DeleteUserModal.jsx
│ │ ├── EditUserForm.jsx
│ │ ├── LoginForm.jsx
│ │ ├── NewsletterSubscription.jsx
│ │ ├── PublicAchievementUnlocker.jsx
│ │ ├── ResetPasswordConfirmForm.jsx
│ │ ├── ResetPasswordRequestForm.jsx
│ │ ├── ScoreTag.jsx
│ │ ├── SignInUpContainer.tsx
│ │ ├── SignupForm.jsx
│ │ ├── User.jsx
│ │ ├── UserAppellation.jsx
│ │ ├── UserFormFields.jsx
│ │ ├── UserMenu.jsx
│ │ ├── UserPicture.tsx
│ │ ├── UserProfile.jsx
│ │ └── UserSettings.jsx
│ ├── UsersActions
│ │ ├── ActionDiff.jsx
│ │ ├── ActionEntityLink.jsx
│ │ ├── ActionIcon.jsx
│ │ ├── ActionsDirectionFilter.js
│ │ ├── ActionsTable.jsx
│ │ ├── ReputationChangeTag.js
│ │ └── UserAction.jsx
│ ├── Utils
│ │ ├── ClickableIcon.tsx
│ │ ├── DismissableMessage.jsx
│ │ ├── ErrorView.jsx
│ │ ├── ExternalLinkNewTab.jsx
│ │ ├── FacetButton.jsx
│ │ ├── LoadingFrame.jsx
│ │ ├── Message.jsx
│ │ ├── MessageView.jsx
│ │ ├── PaginationMenu.jsx
│ │ ├── ProgressBar.jsx
│ │ ├── ReputationGuard.jsx
│ │ ├── ReputationGuardTooltip.tsx
│ │ ├── ShareModal.jsx
│ │ ├── TimeDisplay.jsx
│ │ ├── TimeEdit.jsx
│ │ ├── TimeSince.jsx
│ │ └── __tests__
│ │ │ ├── Message.spec.jsx
│ │ │ └── __snapshots__
│ │ │ └── Message.spec.jsx.snap
│ ├── VideoDebate
│ │ ├── ActionBubbleMenu.jsx
│ │ ├── Actions
│ │ │ ├── ActionButton.jsx
│ │ │ └── Actions.jsx
│ │ ├── CaptionsExtractor.jsx
│ │ ├── ColumnDebate.jsx
│ │ ├── ColumnVideo.jsx
│ │ ├── ModalHistory.jsx
│ │ ├── Presence.jsx
│ │ ├── ResizableColumn.jsx
│ │ ├── VideoDebate.jsx
│ │ ├── VideoDebateHistory.jsx
│ │ └── VideoDebatePlayer.jsx
│ ├── Videos
│ │ ├── AddVideoBtn.jsx
│ │ ├── AddVideoForm.jsx
│ │ ├── EditVideoModal.jsx
│ │ ├── PaginatedVideosContainer.jsx
│ │ ├── UserAddedVideos.jsx
│ │ ├── VideoCard.jsx
│ │ ├── VideoSourceFiltersSelect.jsx
│ │ ├── VideosFilterBar.jsx
│ │ ├── VideosGrid.jsx
│ │ └── VideosIndexPage.jsx
│ └── ui
│ │ ├── alert-dialog.tsx
│ │ ├── alert.tsx
│ │ ├── avatar.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── pagination.tsx
│ │ ├── radio-group.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── skeleton.tsx
│ │ ├── spinner.tsx
│ │ ├── switch.tsx
│ │ ├── table.tsx
│ │ ├── tabs.tsx
│ │ ├── textarea.tsx
│ │ ├── toast.tsx
│ │ ├── toaster.tsx
│ │ └── tooltip.tsx
├── config.js
├── constants.js
├── custom.d.ts
├── hooks
│ └── use-toast.ts
├── i18n
│ ├── ar
│ │ ├── achievements.json
│ │ ├── errors.json
│ │ ├── extension.json
│ │ ├── help.json
│ │ ├── history.json
│ │ ├── home.json
│ │ ├── main.json
│ │ ├── moderation.json
│ │ ├── notifications.json
│ │ ├── user.json
│ │ └── videoDebate.json
│ ├── browser_locale.js
│ ├── en
│ │ ├── achievements.json
│ │ ├── errors.json
│ │ ├── extension.json
│ │ ├── help.json
│ │ ├── history.json
│ │ ├── home.json
│ │ ├── main.json
│ │ ├── moderation.json
│ │ ├── notifications.json
│ │ ├── user.json
│ │ └── videoDebate.json
│ ├── eo
│ │ ├── achievements.json
│ │ ├── errors.json
│ │ ├── extension.json
│ │ ├── help.json
│ │ ├── history.json
│ │ ├── main.json
│ │ ├── moderation.json
│ │ ├── notifications.json
│ │ ├── user.json
│ │ └── videoDebate.json
│ ├── es
│ │ ├── achievements.json
│ │ ├── errors.json
│ │ ├── extension.json
│ │ ├── help.json
│ │ ├── history.json
│ │ ├── home.json
│ │ ├── main.json
│ │ ├── moderation.json
│ │ ├── notifications.json
│ │ ├── user.json
│ │ └── videoDebate.json
│ ├── fr
│ │ ├── achievements.json
│ │ ├── errors.json
│ │ ├── extension.json
│ │ ├── help.json
│ │ ├── history.json
│ │ ├── home.json
│ │ ├── main.json
│ │ ├── moderation.json
│ │ ├── notifications.json
│ │ ├── user.json
│ │ └── videoDebate.json
│ ├── i18n.js
│ ├── it
│ │ └── main.json
│ ├── locale-dates.js
│ ├── nb_NO
│ │ ├── achievements.json
│ │ ├── errors.json
│ │ ├── extension.json
│ │ ├── help.json
│ │ ├── history.json
│ │ ├── home.json
│ │ ├── main.json
│ │ ├── moderation.json
│ │ ├── notifications.json
│ │ ├── user.json
│ │ └── videoDebate.json
│ ├── pt_BR
│ │ ├── achievements.json
│ │ ├── errors.json
│ │ ├── extension.json
│ │ ├── help.json
│ │ ├── history.json
│ │ ├── home.json
│ │ ├── main.json
│ │ ├── moderation.json
│ │ ├── notifications.json
│ │ ├── user.json
│ │ └── videoDebate.json
│ ├── pt_PT
│ │ ├── achievements.json
│ │ ├── errors.json
│ │ ├── extension.json
│ │ ├── help.json
│ │ ├── history.json
│ │ ├── home.json
│ │ ├── main.json
│ │ ├── moderation.json
│ │ ├── notifications.json
│ │ ├── user.json
│ │ └── videoDebate.json
│ └── ru
│ │ ├── .gitkeep
│ │ ├── achievements.json
│ │ ├── errors.json
│ │ ├── extension.json
│ │ ├── help.json
│ │ ├── history.json
│ │ ├── home.json
│ │ ├── main.json
│ │ ├── moderation.json
│ │ ├── notifications.json
│ │ ├── user.json
│ │ └── videoDebate.json
├── index.html
├── index.jsx
├── lib
│ ├── __tests__
│ │ ├── iterate_with_separators.js
│ │ ├── seconds_formatter.js
│ │ └── url_utils.js
│ ├── algolia.js
│ ├── browser-extension.ts
│ ├── cf_routes.js
│ ├── clean_str.js
│ ├── css-utils.js
│ ├── errors.js
│ ├── form_validators.js
│ ├── handle_effect_response.js
│ ├── iterate_with_separators.js
│ ├── local_storage.js
│ ├── name_formatter.js
│ ├── parse_datetime.js
│ ├── react_select_theme.js
│ ├── router.tsx
│ ├── seconds_formatter.js
│ ├── statements_utils.js
│ ├── styled_components_custom.js
│ ├── toasts.tsx
│ ├── url_utils.js
│ ├── user_action_entity_id.js
│ └── video_utils.js
├── logger.js
├── router.jsx
├── state
│ ├── help
│ │ ├── __tests__
│ │ │ ├── __snapshots__
│ │ │ │ └── reducer.spec.js.snap
│ │ │ └── reducer.spec.js
│ │ ├── effects.js
│ │ └── reducer.js
│ ├── index.js
│ ├── modals
│ │ ├── __tests__
│ │ │ ├── __snapshots__
│ │ │ │ └── reducer.spec.js.snap
│ │ │ └── reducer.spec.js
│ │ └── reducer.js
│ ├── moderation
│ │ ├── effects.js
│ │ ├── record.js
│ │ └── reducer.js
│ ├── speakers
│ │ ├── effects.js
│ │ ├── record.js
│ │ └── reducer.js
│ ├── user_actions
│ │ ├── record.js
│ │ └── reducer.js
│ ├── user_preferences
│ │ ├── __tests__
│ │ │ ├── __snapshots__
│ │ │ │ └── reducer.spec.js.snap
│ │ │ └── reducer.spec.js
│ │ └── reducer.js
│ ├── users
│ │ ├── displayed_user
│ │ │ ├── __tests__
│ │ │ │ └── effects.js
│ │ │ ├── effects.js
│ │ │ └── reducer.js
│ │ └── record.js
│ ├── utils.js
│ ├── video_debate
│ │ ├── actions.js
│ │ ├── comments
│ │ │ ├── __tests__
│ │ │ │ ├── __fixtures__
│ │ │ │ │ └── fetch_all_success.js
│ │ │ │ ├── __snapshots__
│ │ │ │ │ └── reducer.spec.js.snap
│ │ │ │ └── reducer.spec.js
│ │ │ ├── effects.js
│ │ │ ├── record.js
│ │ │ ├── reducer.js
│ │ │ ├── selectors.js
│ │ │ └── source_record.js
│ │ ├── effects.js
│ │ ├── history
│ │ │ └── effects.js
│ │ ├── presence
│ │ │ ├── __tests__
│ │ │ │ ├── __snapshots__
│ │ │ │ │ └── reducer.spec.js.snap
│ │ │ │ └── reducer.spec.js
│ │ │ ├── reducer.js
│ │ │ └── selectors.js
│ │ ├── reducer.js
│ │ ├── selectors.js
│ │ ├── statements
│ │ │ ├── effects.js
│ │ │ ├── record.js
│ │ │ ├── reducer.js
│ │ │ └── selectors.js
│ │ └── video
│ │ │ ├── __tests__
│ │ │ ├── __fixtures__
│ │ │ │ └── fetch_all_success.js
│ │ │ ├── __snapshots__
│ │ │ │ └── reducer.spec.js.snap
│ │ │ └── reducer.spec.js
│ │ │ └── reducer.js
│ └── videos
│ │ ├── __tests__
│ │ ├── __snapshots__
│ │ │ └── reducer.spec.js.snap
│ │ └── reducer.spec.js
│ │ ├── effects.js
│ │ ├── record.js
│ │ └── reducer.js
├── static
│ ├── .well-known
│ │ ├── pgp-key.txt
│ │ └── security.txt
│ ├── assets
│ │ ├── documents
│ │ │ ├── Privacy_EN.pdf
│ │ │ └── Privacy_FR.pdf
│ │ ├── help
│ │ │ ├── ar
│ │ │ │ ├── about.md
│ │ │ │ ├── achievements.md
│ │ │ │ ├── ambassadors.md
│ │ │ │ ├── bug_report.md
│ │ │ │ ├── censorship_requests.md
│ │ │ │ ├── contact.md
│ │ │ │ ├── contribute
│ │ │ │ │ ├── code.md
│ │ │ │ │ └── tasks.md
│ │ │ │ ├── contributionGuidelines.md
│ │ │ │ ├── credits.md
│ │ │ │ ├── extension.md
│ │ │ │ ├── jobs.md
│ │ │ │ ├── moderation.md
│ │ │ │ ├── privacy.md
│ │ │ │ ├── privileges.md
│ │ │ │ └── reputation.md
│ │ │ ├── en
│ │ │ │ ├── about.md
│ │ │ │ ├── achievements.md
│ │ │ │ ├── ambassadors.md
│ │ │ │ ├── bug_report.md
│ │ │ │ ├── censorship_requests.md
│ │ │ │ ├── contact.md
│ │ │ │ ├── contribute
│ │ │ │ │ ├── code.md
│ │ │ │ │ └── tasks.md
│ │ │ │ ├── contributionGuidelines.md
│ │ │ │ ├── credits.md
│ │ │ │ ├── extension.md
│ │ │ │ ├── jobs.md
│ │ │ │ ├── moderation.md
│ │ │ │ ├── privacy.md
│ │ │ │ ├── privileges.md
│ │ │ │ └── reputation.md
│ │ │ ├── es
│ │ │ │ ├── about.md
│ │ │ │ ├── achievements.md
│ │ │ │ ├── ambassadors.md
│ │ │ │ ├── bug_report.md
│ │ │ │ ├── censorship_requests.md
│ │ │ │ ├── contact.md
│ │ │ │ ├── contribute
│ │ │ │ │ ├── code.md
│ │ │ │ │ └── tasks.md
│ │ │ │ ├── contributionGuidelines.md
│ │ │ │ ├── credits.md
│ │ │ │ ├── extension.md
│ │ │ │ ├── jobs.md
│ │ │ │ ├── moderation.md
│ │ │ │ ├── privacy.md
│ │ │ │ ├── privileges.md
│ │ │ │ └── reputation.md
│ │ │ ├── fr
│ │ │ │ ├── about.md
│ │ │ │ ├── achievements.md
│ │ │ │ ├── ambassadors.md
│ │ │ │ ├── bug_report.md
│ │ │ │ ├── censorship_requests.md
│ │ │ │ ├── contact.md
│ │ │ │ ├── contribute
│ │ │ │ │ ├── code.md
│ │ │ │ │ └── tasks.md
│ │ │ │ ├── contributionGuidelines.md
│ │ │ │ ├── credits.md
│ │ │ │ ├── extension.md
│ │ │ │ ├── jobs.md
│ │ │ │ ├── moderation.md
│ │ │ │ ├── privacy.md
│ │ │ │ ├── privileges.md
│ │ │ │ └── reputation.md
│ │ │ └── ru
│ │ │ │ ├── about.md
│ │ │ │ ├── achievements.md
│ │ │ │ ├── ambassadors.md
│ │ │ │ ├── bug_report.md
│ │ │ │ ├── censorship_requests.md
│ │ │ │ ├── contact.md
│ │ │ │ ├── contribute
│ │ │ │ ├── code.md
│ │ │ │ └── tasks.md
│ │ │ │ ├── contributionGuidelines.md
│ │ │ │ ├── credits.md
│ │ │ │ ├── extension.md
│ │ │ │ ├── jobs.md
│ │ │ │ ├── moderation.md
│ │ │ │ ├── privacy.md
│ │ │ │ ├── privileges.md
│ │ │ │ └── reputation.md
│ │ └── img
│ │ │ ├── CaptainFact-borderless.png
│ │ │ ├── CaptainFact.png
│ │ │ ├── LogoWithText.png
│ │ │ ├── LogoWithText_600.png
│ │ │ ├── banner.jpg
│ │ │ ├── banner.png
│ │ │ ├── banner_twitter.png
│ │ │ ├── logo-borderless.svg
│ │ │ ├── logo-nobackground.svg
│ │ │ ├── logo.png
│ │ │ └── logo.svg
│ ├── favicon.ico
│ └── favicons
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-384x384.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon.png
│ │ ├── browserconfig.xml
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.ico
│ │ ├── manifest.json
│ │ ├── mstile-150x150.png
│ │ ├── safari-pinned-tab.svg
│ │ └── site.webmanifest
└── styles
│ ├── fonts
│ ├── NotoSansJP-VariableFont_wght.ttf
│ └── PlayfairDisplay-VariableFont_wght.ttf
│ ├── main.css
│ └── theme.js
├── components.json
├── config
└── env
│ ├── dev.env
│ ├── prod.env
│ └── staging.env
├── cypress.config.js
├── cypress
├── e2e
│ ├── navbar
│ │ ├── logged_out.spec.js
│ │ └── user_section.spec.js
│ ├── user
│ │ ├── password_login.spec.js
│ │ └── settings.spec.js
│ └── video_debate
│ │ └── comment_form.spec.js
├── plugins
│ └── index.js
└── support
│ ├── commands.js
│ ├── e2e.js
│ └── helpers.js
├── dev
├── check_docker_release.sh
├── check_nginx_config.sh
├── depcheck.sh
├── dev_docker_api_resources
│ └── .keep
├── dev_localhost_keys
│ ├── fullchain.pem
│ └── privkey.pem
└── tests_setup.js
├── docker-compose.yml
├── jsconfig.json
├── netlify.toml
├── package-lock.json
├── package.json
├── rel
├── maintenance.html
├── robots_private.txt
└── robots_public.txt
├── tailwind.config.js
├── tsconfig.json
├── webpack.config.js
├── webpack.loaders.js
└── webpack.production.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"],
3 | "plugins": [
4 | ["babel-plugin-wildcard", { "exts": ["json"], "nostrip": true, "noModifyCase": true }],
5 | [
6 | "@babel/plugin-proposal-decorators",
7 | {
8 | "legacy": true
9 | }
10 | ],
11 | "@babel/plugin-transform-runtime",
12 | "@babel/plugin-proposal-class-properties",
13 | "@babel/plugin-proposal-object-rest-spread",
14 | "babel-plugin-styled-components"
15 | ],
16 | "env": {
17 | "production": {
18 | "plugins": [
19 | "transform-react-remove-prop-types",
20 | ["react-remove-properties", { "properties": ["data-cy"] }]
21 | ]
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | .github
3 | .idea
4 | .vscode
5 | .eslintrc.yml
6 | .gitignore
7 | .travis.yml
8 |
9 | node_modules
10 | public
11 |
12 | dev
13 | rel/dev*
14 | docker-compose.yml
15 | Dockerfile
16 | netlify.toml
17 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: '/'
5 | schedule:
6 | interval: weekly
7 | day: wednesday
8 | open-pull-requests-limit: 20
9 | reviewers:
10 | - betree
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Numerous always-ignore extensions
2 | *.diff
3 | *.err
4 | *.orig
5 | *.log
6 | *.rej
7 | *.swo
8 | *.swp
9 | *.vi
10 | *~
11 |
12 | # OS or Editor folders
13 | .DS_Store
14 | .cache
15 | .project
16 | .vscode
17 | .settings
18 | .tmproj
19 | nbproject
20 | Thumbs.db
21 | .history
22 |
23 | # NPM packages folder.
24 | node_modules/
25 |
26 | # Brunch output folder.
27 | public/
28 |
29 | # Ignore tests coverage
30 | coverage
31 |
32 | # Ignore cypress videos and screenshots
33 | cypress/videos
34 |
35 | # Ignore styleguide build
36 | styleguide
37 |
38 | # API dev image resources
39 | dev/dev_docker_api_resources/*
40 | !dev/dev_docker_api_resources/.keep
41 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "singleQuote": true,
4 | "semi": false,
5 | "printWidth": 100
6 | }
7 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | nodejs 18.20.6
2 | npm 10.8.2
3 |
--------------------------------------------------------------------------------
/app/API/graphql_api.js:
--------------------------------------------------------------------------------
1 | import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client'
2 | import { setContext } from '@apollo/client/link/context'
3 |
4 | import { GRAPHQL_API_URL } from '../config'
5 | import { getFromLocalStorage, LOCAL_STORAGE_KEYS } from '../lib/local_storage'
6 |
7 | const httpLink = createHttpLink({
8 | uri: GRAPHQL_API_URL,
9 | })
10 |
11 | const authLink = setContext((_, { headers }) => {
12 | const token = getFromLocalStorage(LOCAL_STORAGE_KEYS.TOKEN)
13 | if (token) {
14 | return { headers: { ...headers, authorization: `Bearer ${token}` } }
15 | } else {
16 | return { headers }
17 | }
18 | })
19 |
20 | const GraphQLClient = new ApolloClient({
21 | link: authLink.concat(httpLink),
22 | cache: new InMemoryCache(),
23 | })
24 |
25 | export default GraphQLClient
26 |
--------------------------------------------------------------------------------
/app/API/index.js:
--------------------------------------------------------------------------------
1 | export { default as HttpApi } from './http_api'
2 | export { default as SocketApi } from './socket_api'
3 |
--------------------------------------------------------------------------------
/app/API/server_error.js:
--------------------------------------------------------------------------------
1 | export default function parseServerError(responseBody) {
2 | if (responseBody.error) {
3 | return responseBody.error
4 | }
5 | if (responseBody.errors) {
6 | return responseBody.errors
7 | }
8 | return 'unknown'
9 | }
10 |
--------------------------------------------------------------------------------
/app/API/wikidata.js:
--------------------------------------------------------------------------------
1 | import wikidata from 'wikidata-sdk'
2 |
3 | import { logWarn } from '../logger'
4 |
5 | export const searchOnWikidata = async (search, locale) => {
6 | if (!search || search.length < 3) {
7 | return []
8 | }
9 |
10 | try {
11 | const language = locale || 'en'
12 | const url = wikidata.searchEntities({ search, language, format: 'json', limit: 10 })
13 | const response = await fetch(url)
14 | const body = await response.json()
15 | return body.search || []
16 | } catch (e) {
17 | logWarn(`Wikidata query failed: ${e}`)
18 | return []
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/assets/achievements/ambassador.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/ambassador.jpg
--------------------------------------------------------------------------------
/app/assets/achievements/ambassador.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/ambassador.mp4
--------------------------------------------------------------------------------
/app/assets/achievements/artist.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/artist.jpg
--------------------------------------------------------------------------------
/app/assets/achievements/bug.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/bug.jpg
--------------------------------------------------------------------------------
/app/assets/achievements/bug.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/bug.mp4
--------------------------------------------------------------------------------
/app/assets/achievements/bulletproof.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/bulletproof.png
--------------------------------------------------------------------------------
/app/assets/achievements/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/default.png
--------------------------------------------------------------------------------
/app/assets/achievements/famous.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/famous.jpg
--------------------------------------------------------------------------------
/app/assets/achievements/famous.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/famous.mp4
--------------------------------------------------------------------------------
/app/assets/achievements/goodVibes.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/goodVibes.jpg
--------------------------------------------------------------------------------
/app/assets/achievements/help.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/help.jpg
--------------------------------------------------------------------------------
/app/assets/achievements/robot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/robot.jpg
--------------------------------------------------------------------------------
/app/assets/achievements/robot.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/robot.mp4
--------------------------------------------------------------------------------
/app/assets/achievements/socialAddict.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/socialAddict.jpg
--------------------------------------------------------------------------------
/app/assets/achievements/socialAddict.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/socialAddict.mp4
--------------------------------------------------------------------------------
/app/assets/achievements/trump.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/trump.png
--------------------------------------------------------------------------------
/app/assets/achievements/welcome.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/welcome.jpg
--------------------------------------------------------------------------------
/app/assets/achievements/welcome.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/achievements/welcome.mp4
--------------------------------------------------------------------------------
/app/assets/browsers/chrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/browsers/chrome.png
--------------------------------------------------------------------------------
/app/assets/browsers/firefox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/browsers/firefox.png
--------------------------------------------------------------------------------
/app/assets/browsers/internet_explorer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/browsers/internet_explorer.png
--------------------------------------------------------------------------------
/app/assets/demos/demo-extension.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/demos/demo-extension.mp4
--------------------------------------------------------------------------------
/app/assets/example1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/example1.jpg
--------------------------------------------------------------------------------
/app/assets/examples/image-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/examples/image-1.jpg
--------------------------------------------------------------------------------
/app/assets/examples/image-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/examples/image-2.jpg
--------------------------------------------------------------------------------
/app/assets/examples/image-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/examples/image-3.jpg
--------------------------------------------------------------------------------
/app/assets/landing-illustration.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/landing-illustration.jpg
--------------------------------------------------------------------------------
/app/assets/landing-illustration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/landing-illustration.png
--------------------------------------------------------------------------------
/app/assets/media.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/media.jpg
--------------------------------------------------------------------------------
/app/assets/partners/democratie-ouverte.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/partners/democratie-ouverte.jpg
--------------------------------------------------------------------------------
/app/assets/partners/heureka.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/partners/heureka.jpg
--------------------------------------------------------------------------------
/app/assets/partners/imago.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/partners/imago.jpg
--------------------------------------------------------------------------------
/app/assets/partners/larchipelle.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/partners/larchipelle.jpg
--------------------------------------------------------------------------------
/app/assets/partners/lecanardrefractaire.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/partners/lecanardrefractaire.jpg
--------------------------------------------------------------------------------
/app/assets/partners/maiamater.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/partners/maiamater.jpg
--------------------------------------------------------------------------------
/app/assets/partners/systemed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/partners/systemed.jpg
--------------------------------------------------------------------------------
/app/assets/partners/tedxnoumea.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/partners/tedxnoumea.jpg
--------------------------------------------------------------------------------
/app/assets/partners/thinkerview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/partners/thinkerview.jpg
--------------------------------------------------------------------------------
/app/assets/partners/troncheenbiais.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/partners/troncheenbiais.jpg
--------------------------------------------------------------------------------
/app/assets/partners/yeswehack.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/partners/yeswehack.jpg
--------------------------------------------------------------------------------
/app/assets/sounds/background_statement_confirm.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/sounds/background_statement_confirm.mp3
--------------------------------------------------------------------------------
/app/assets/sounds/background_statement_neutral.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/sounds/background_statement_neutral.mp3
--------------------------------------------------------------------------------
/app/assets/sounds/background_statement_refute.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/sounds/background_statement_refute.mp3
--------------------------------------------------------------------------------
/app/assets/team/basile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/team/basile.jpg
--------------------------------------------------------------------------------
/app/assets/team/benjamin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/team/benjamin.png
--------------------------------------------------------------------------------
/app/assets/team/florence.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/team/florence.jpg
--------------------------------------------------------------------------------
/app/assets/team/frederic.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/team/frederic.jpg
--------------------------------------------------------------------------------
/app/assets/team/mathieu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainFact/captain-fact-frontend/8d9d559c8b2b8594de183d1085f5381b0e17a4a4/app/assets/team/mathieu.jpg
--------------------------------------------------------------------------------
/app/components/App/Logo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import logo from '../../assets/logo.svg'
4 | import borderlessLogo from '../../assets/logo-borderless.svg'
5 |
6 | /**
7 | * The main website logo.
8 | */
9 | const Logo = ({ borderless = false }) => (
10 |
11 |

16 |
aptain
17 |
Fact
18 |
19 | )
20 |
21 | export default Logo
22 |
--------------------------------------------------------------------------------
/app/components/App/MenuToggleSwitch.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React from 'react'
3 | import { connect } from 'react-redux'
4 | import styled, { withTheme } from 'styled-components'
5 | import { Menu as MenuIcon, X as XIcon } from 'styled-icons/boxicons-regular'
6 | import { themeGet } from 'styled-system'
7 |
8 | import { toggleSidebar } from '../../state/user_preferences/reducer'
9 |
10 | const Button = styled.button`
11 | background: none;
12 | outline: none;
13 | border: 0;
14 | padding: 0;
15 | height: 100%;
16 | width: 45px;
17 | cursor: pointer;
18 | user-select: none;
19 | color: ${themeGet('colors.black.400')};
20 |
21 | &:hover {
22 | color: ${themeGet('colors.black.500')};
23 | }
24 | `
25 |
26 | const MenuToggleSwitch = ({ toggleSidebar, sidebarExpended, toggleableIcon }) => (
27 |
30 | )
31 |
32 | MenuToggleSwitch.propTypes = {
33 | toggleSidebar: PropTypes.func.isRequired,
34 | sidebarExpended: PropTypes.bool.isRequired,
35 | toggleableIcon: PropTypes.bool.isRequired,
36 | }
37 |
38 | export default withTheme(
39 | connect(({ UserPreferences: { sidebarExpended } }) => ({ sidebarExpended }), { toggleSidebar })(
40 | MenuToggleSwitch,
41 | ),
42 | )
43 |
--------------------------------------------------------------------------------
/app/components/Comments/CommentAction.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { Button } from '../ui/button'
4 |
5 | const CommentAction = ({ className, variant = 'link', onClick, title, icon = null, disabled }) => (
6 |
10 | )
11 |
12 | export default CommentAction
13 |
--------------------------------------------------------------------------------
/app/components/Comments/CommentContent.jsx:
--------------------------------------------------------------------------------
1 | import { ChevronUp } from 'lucide-react'
2 | import React from 'react'
3 |
4 | import { Badge } from '../ui/badge'
5 | import { Source } from './Source'
6 |
7 | const COLLAPSE_CONTENT_ABOVE_NESTING = 6
8 |
9 | const CommentContent = ({ comment: { source, text }, nesting, replyingTo, richMedias }) => {
10 | const isCollapsed = replyingTo && nesting > COLLAPSE_CONTENT_ABOVE_NESTING
11 | const shouldRenderTextBlock = text || isCollapsed
12 |
13 | return (
14 |
15 | {shouldRenderTextBlock && (
16 |
17 |
21 | {isCollapsed && (
22 |
23 | @{replyingTo.username}
24 |
25 | )}
26 | {text}
27 |
28 | )}
29 | {source && (
30 |
31 |
32 |
33 | )}
34 |
35 | )
36 | }
37 |
38 | export default CommentContent
39 |
--------------------------------------------------------------------------------
/app/components/Comments/CommentHeader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { withTranslation } from 'react-i18next'
3 |
4 | import { USER_PICTURE_SMALL } from '../../constants'
5 | import UserAppellation from '../Users/UserAppellation'
6 | import UserPicture from '../Users/UserPicture'
7 | import { TimeSince } from '../Utils/TimeSince'
8 |
9 | const CommentHeader = ({ comment: { user, inserted_at }, withoutActions }) => (
10 |
11 | {user ? (
12 |
13 |
14 |
15 |
16 | ) : (
17 |
18 | )}
19 |
-
20 |
21 |
22 |
23 |
24 | )
25 |
26 | export default withTranslation('main')(CommentHeader)
27 |
--------------------------------------------------------------------------------
/app/components/Comments/CommentsListExpender.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { withTranslation } from 'react-i18next'
3 |
4 | import { Button } from '../ui/button'
5 |
6 | const CommentsListExpender = ({ t, onClick, nesting, count }) => (
7 |
8 |
14 |
15 | )
16 |
17 | export default withTranslation('videoDebate')(CommentsListExpender)
18 |
--------------------------------------------------------------------------------
/app/components/Comments/CommentsListHeader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | /**
4 | * Empty divs are used by CSS to draw lines
5 | */
6 | const CommentsListHeader = ({ header }) => (
7 |
8 |
9 |
{header}
10 |
11 |
12 | )
13 |
14 | export default CommentsListHeader
15 |
--------------------------------------------------------------------------------
/app/components/Comments/ModalDeleteComment.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { withTranslation } from 'react-i18next'
3 | import { connect } from 'react-redux'
4 |
5 | import ModalConfirmDelete from '../Modal/ModalConfirmDelete'
6 | import { CommentDisplay } from './CommentDisplay'
7 |
8 | const ModalDeleteComment = ({ handleAbort, comment, replies, t, ...otherProps }) => (
9 | }
13 | {...otherProps}
14 | />
15 | )
16 |
17 | export default connect((state, props) => ({
18 | replies: state.VideoDebate.comments.replies.get(props.comment.id),
19 | }))(withTranslation('videoDebate')(ModalDeleteComment))
20 |
--------------------------------------------------------------------------------
/app/components/Comments/OtherCommentActions.jsx:
--------------------------------------------------------------------------------
1 | import { Flag, Reply } from 'lucide-react'
2 | import React from 'react'
3 | import { withTranslation } from 'react-i18next'
4 |
5 | import { cn } from '@/lib/css-utils'
6 |
7 | import { MIN_REPUTATION_FLAG } from '../../constants'
8 | import ReputationGuard from '../Utils/ReputationGuard'
9 | import CommentAction from './CommentAction'
10 |
11 | const OtherCommentActions = ({ t, isFlagged, handleReply, handleFlag }) => (
12 |
13 | } onClick={handleReply} />
14 |
15 | }
19 | onClick={handleFlag}
20 | disabled={isFlagged}
21 | />
22 |
23 |
24 | )
25 |
26 | export default withTranslation('main')(OtherCommentActions)
27 |
--------------------------------------------------------------------------------
/app/components/Comments/OwnCommentActions.jsx:
--------------------------------------------------------------------------------
1 | import { Delete, Plus } from 'lucide-react'
2 | import React from 'react'
3 | import { withTranslation } from 'react-i18next'
4 |
5 | import CommentAction from './CommentAction'
6 |
7 | const OwnCommentActions = ({ t, handleAddToThread, handleDelete }) => (
8 |
9 | }
12 | onClick={handleAddToThread}
13 | />
14 | }
17 | onClick={handleDelete}
18 | />
19 |
20 | )
21 |
22 | export default withTranslation('main')(OwnCommentActions)
23 |
--------------------------------------------------------------------------------
/app/components/FormUtils/ControlInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { cn } from '@/lib/css-utils'
4 |
5 | import { Input } from '../ui/input'
6 |
7 | const ControlInput = (props) => {
8 | const {
9 | icon,
10 | className,
11 | meta: { touched, error },
12 | input,
13 | } = props
14 |
15 | return (
16 |
17 |
18 |
26 | {icon &&
{icon}
}
27 |
28 |
34 | {error}
35 |
36 |
37 | )
38 | }
39 |
40 | export default ControlInput
41 |
--------------------------------------------------------------------------------
/app/components/FormUtils/FieldWithButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { cn } from '@/lib/css-utils'
4 |
5 | import { Button } from '../ui/button'
6 | import { Input } from '../ui/input'
7 |
8 | const FieldWithButton = (props) => {
9 | const { submitting, invalid } = props.meta || {}
10 | const { buttonClassName, buttonLabel, buttonClickHandler, ...inputProps } = props
11 | return (
12 |
13 |
20 |
21 |
30 |
31 |
32 | )
33 | }
34 |
35 | export default FieldWithButton
36 |
--------------------------------------------------------------------------------
/app/components/FormUtils/FieldWithLabelAddon.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const FieldWithLabelAddon = ({ label, children, inputId }) => {
4 | return (
5 |
6 |
9 |
{children}
10 |
11 | )
12 | }
13 |
14 | export default FieldWithLabelAddon
15 |
--------------------------------------------------------------------------------
/app/components/FormUtils/TextareaLengthCounter.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface TextareaLengthCounterProps {
4 | length: number
5 | minLength?: number
6 | maxLength: number
7 | }
8 |
9 | const TextareaLengthCounter: React.FC = ({
10 | length,
11 | minLength,
12 | maxLength,
13 | }) => (
14 |
18 | maxLength ? 'text-red-400' : minLength && length < minLength ? 'text-yellow-600' : ''}`}
20 | >
21 | {length}
22 |
23 | / {maxLength}
24 |
25 | )
26 |
27 | export default TextareaLengthCounter
28 |
--------------------------------------------------------------------------------
/app/components/Help/HelpModal.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React, { PureComponent } from 'react'
3 | import { withTranslation } from 'react-i18next'
4 | import { connect } from 'react-redux'
5 |
6 | import { popModal } from '../../state/modals/reducer'
7 | import Modal from '../Modal/Modal'
8 | import HelpPageContent from './HelpPageContent'
9 |
10 | @withTranslation('help')
11 | @connect(null, { popModal })
12 | class HelpModal extends PureComponent {
13 | render() {
14 | return (
15 |
16 |
17 |
18 | )
19 | }
20 | }
21 |
22 | HelpModal.propTypes = {
23 | page: PropTypes.string.isRequired,
24 | }
25 |
26 | export default HelpModal
27 |
--------------------------------------------------------------------------------
/app/components/Home/CFSocialProfiles.jsx:
--------------------------------------------------------------------------------
1 | import { Facebook, Github, Twitter } from 'lucide-react'
2 | import React from 'react'
3 | import { Discord } from 'styled-icons/remix-line'
4 |
5 | import ExternalLinkNewTab from '../Utils/ExternalLinkNewTab'
6 |
7 | const SocialIconLink = ({ Icon, size, name, url }) => {
8 | return (
9 |
14 |
15 |
16 | )
17 | }
18 |
19 | const CFSocialProfiles = () => {
20 | return (
21 |
22 |
28 |
29 |
30 |
35 |
36 | )
37 | }
38 |
39 | export default CFSocialProfiles
40 |
--------------------------------------------------------------------------------
/app/components/Home/LastVideos.jsx:
--------------------------------------------------------------------------------
1 | import { Query } from '@apollo/client/react/components'
2 | import React from 'react'
3 |
4 | import { VideosQuery } from '../../API/graphql_queries'
5 | import { VideoCard } from '../Videos/VideoCard'
6 |
7 | const LastVideos = () => {
8 | return (
9 |
10 |
11 | {({ data }) => {
12 | return data && data.videos && data.videos.entries.length > 0
13 | ? data.videos.entries.map((video) => )
14 | : null
15 | }}
16 |
17 |
18 | )
19 | }
20 |
21 | export default LastVideos
22 |
--------------------------------------------------------------------------------
/app/components/Home/OpenCollectiveContributors.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React from 'react'
3 |
4 | import { optionsToQueryString } from '../../lib/url_utils'
5 |
6 | /**
7 | * Embed https://opencollective.com contributors.
8 | */
9 | const OpenCollectiveContributors = ({
10 | tier,
11 | avatarHeight = 72,
12 | button = true,
13 | width = 325,
14 | limit = 50,
15 | }) => {
16 | const queryParams = optionsToQueryString({
17 | avatarHeight,
18 | button,
19 | width,
20 | limit,
21 | })
22 |
23 | return (
24 |
30 | )
31 | }
32 |
33 | OpenCollectiveContributors.propTypes = {
34 | tier: PropTypes.oneOf(['soutien-regulier', 'donateur', 'adhesion-membre']).isRequired,
35 | avatarHeight: PropTypes.number,
36 | width: PropTypes.number,
37 | button: PropTypes.bool,
38 | }
39 |
40 | // ts-unused-exports:disable-next-line
41 | export default OpenCollectiveContributors
42 |
--------------------------------------------------------------------------------
/app/components/LoggedInUser/LogoutPage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { withTranslation } from 'react-i18next'
3 | import { withRouter } from 'react-router-dom'
4 |
5 | import { LoadingFrame } from '../Utils/LoadingFrame'
6 | import { withLoggedInUser } from './UserProvider'
7 |
8 | @withRouter
9 | @withTranslation('user')
10 | @withLoggedInUser
11 | export default class LogoutPage extends React.Component {
12 | componentDidMount() {
13 | this.logoutAndRedirect()
14 | }
15 |
16 | componentDidUpdate() {
17 | this.logoutAndRedirect()
18 | }
19 |
20 | async logoutAndRedirect() {
21 | // Redirect if user is loading
22 | if (this.props.loggedInUserLoading) {
23 | return
24 | }
25 |
26 | // Already unauthenticated, redirect
27 | if (this.props.isAuthenticated) {
28 | await this.props.logout()
29 | }
30 |
31 | this.props.history.replace('/')
32 | }
33 |
34 | render() {
35 | return
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/components/LoggedInUser/NotificationBell.jsx:
--------------------------------------------------------------------------------
1 | import { Query } from '@apollo/client/react/components'
2 | import { get } from 'lodash'
3 | import React from 'react'
4 | import { Bell } from 'styled-icons/fa-solid'
5 |
6 | import { loggedInUserUnreadNotificationsCount } from '../../API/graphql_queries'
7 | import { Badge } from '../ui/badge'
8 | import { Button } from '../ui/button'
9 |
10 | const NotificationBell = (props, ref) => (
11 |
35 | )
36 |
37 | export default React.forwardRef(NotificationBell)
38 |
--------------------------------------------------------------------------------
/app/components/LoggedInUser/UserLanguageSelector.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { updateUserInfo } from '../../API/http_api/current_user'
4 | import i18n from '../../i18n/i18n'
5 | import LanguageSelector from '../App/LanguageSelector'
6 | import { withLoggedInUser } from './UserProvider'
7 |
8 | /**
9 | * Updates the locale for loggedInUser, notify i18n to refresh the
10 | * interface.
11 | */
12 | const UserLanguageSelector = ({ isAuthenticated, updateLoggedInUser, className, size }) => {
13 | return (
14 | {
20 | i18n.changeLanguage(locale)
21 | if (isAuthenticated) {
22 | return updateUserInfo({ locale }).then((user) => {
23 | updateLoggedInUser(user)
24 | })
25 | }
26 | }}
27 | />
28 | )
29 | }
30 |
31 | export default withLoggedInUser(UserLanguageSelector)
32 |
--------------------------------------------------------------------------------
/app/components/Modal/MainModalContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | import { addModal, popModal } from '../../state/modals/reducer'
5 |
6 | @connect(({ Modals }) => ({ modal: Modals.first() }), { addModal, popModal })
7 | export class MainModalContainer extends React.PureComponent {
8 | renderModal() {
9 | const { Modal, props } = this.props.modal
10 | return
11 | }
12 |
13 | render() {
14 | return {this.props.modal && this.renderModal()}
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/components/Modal/ModalConfirmDelete.tsx:
--------------------------------------------------------------------------------
1 | import { CircleX, Trash } from 'lucide-react'
2 | import React from 'react'
3 | import { withTranslation } from 'react-i18next'
4 |
5 | import { ModalConfirm } from './ModalConfirm'
6 |
7 | const ModalConfirmDelete = ({ t, isRemove = false, ...props }) => (
8 | : }
10 | confirmText={isRemove ? t('actions.remove') : t('actions.delete')}
11 | abortText={t('actions.cancel')}
12 | {...props}
13 | />
14 | )
15 |
16 | export default withTranslation('main')(ModalConfirmDelete)
17 |
--------------------------------------------------------------------------------
/app/components/Moderation/FlagReasonSelect.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { withTranslation } from 'react-i18next'
3 | import { Field } from 'redux-form'
4 |
5 | import { Label } from '@/components/ui/label'
6 | import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
7 |
8 | const VALID_REASONS = ['1', '2']
9 |
10 | const FlagReasonSelect = ({ t }) => {
11 | const labels = t('reason', { returnObjects: true })
12 |
13 | return (
14 |
15 |
(
18 |
19 | {VALID_REASONS.map((key) => (
20 |
21 |
22 |
25 |
26 | ))}
27 |
28 | )}
29 | />
30 |
31 | )
32 | }
33 |
34 | export default withTranslation('moderation')(FlagReasonSelect)
35 |
--------------------------------------------------------------------------------
/app/components/Pages/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { ErrorView } from '../Utils/ErrorView'
4 |
5 | export const NotFound = () => (
6 |
7 |
8 |
9 | )
10 |
--------------------------------------------------------------------------------
/app/components/Pages/index.js:
--------------------------------------------------------------------------------
1 | export * from './NotFound'
2 | export * from './BrowserExtensionsPage'
3 |
--------------------------------------------------------------------------------
/app/components/Search/IndexSearchEntriesCount.js:
--------------------------------------------------------------------------------
1 | import { get } from 'lodash'
2 | import PropTypes from 'prop-types'
3 | import React from 'react'
4 | import { connectStateResults, Index } from 'react-instantsearch-dom'
5 |
6 | const SearchResultsCount = connectStateResults(({ searchResults }) => {
7 | return get(searchResults, 'nbHits', 0)
8 | })
9 |
10 | const IndexSearchEntriesCount = ({ indexName }) => {
11 | return (
12 |
13 |
14 |
15 | )
16 | }
17 |
18 | IndexSearchEntriesCount.propTypes = {
19 | indexName: PropTypes.string,
20 | }
21 |
22 | export default IndexSearchEntriesCount
23 |
--------------------------------------------------------------------------------
/app/components/Search/SpeakerHit.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { SpeakerPreview } from '../Speakers/SpeakerPreview'
4 |
5 | export const SpeakerHit = ({ hit }) => {
6 | return (
7 |
8 |
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/app/components/Search/StatementHit.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { withTranslation } from 'react-i18next'
3 | import { Link } from 'react-router-dom'
4 |
5 | import { statementURL } from '../../lib/cf_routes'
6 | import Statement from '../Statements/Statement'
7 | import Container from '../StyledUtils/Container'
8 |
9 | const StatementHit = ({ t, hit }) => {
10 | return (
11 |
12 |
13 | {hit.video && (
14 |
15 | {t('videos.see')}
16 |
17 | )}
18 |
19 | )
20 | }
21 |
22 | export default withTranslation('main')(StatementHit)
23 |
--------------------------------------------------------------------------------
/app/components/Search/VideoHit.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { VideoCard } from '../Videos/VideoCard'
4 |
5 | export const VideoHit = ({ hit }) => {
6 | return (
7 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/app/components/Speakers/ModalRemoveSpeaker.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { withTranslation } from 'react-i18next'
3 |
4 | import ModalConfirmDelete from '../Modal/ModalConfirmDelete'
5 | import { SpeakerPreview } from './SpeakerPreview'
6 |
7 | const ModalRemoveSpeaker = ({ handleAbort, speaker, t, ...props }) => (
8 | }
13 | message={t('speaker.confirmRemove', { speaker })}
14 | {...props}
15 | />
16 | )
17 |
18 | export default withTranslation('videoDebate')(ModalRemoveSpeaker)
19 |
--------------------------------------------------------------------------------
/app/components/Speakers/SpeakersSelect.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Select from 'react-select'
3 |
4 | import { cn } from '@/lib/css-utils'
5 |
6 | import { ReactSelectStyles, ReactSelectTheme } from '../../lib/react_select_theme'
7 |
8 | const SpeakersSelect = ({ input, speakers, placeholder, styles, className }) => {
9 | const selectedSpeakerId = input.value
10 | const speaker = selectedSpeakerId && speakers.find((s) => s.id === selectedSpeakerId)
11 | const getOption = (speaker) => ({ value: speaker, label: speaker.full_name })
12 |
13 | return (
14 |