├── .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 | C 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 | 16 | 17 | 18 | 19 | 20 | {t('all')} 21 | {t('featured')} 22 | {t('partners')} 23 | {t('users')} 24 | 25 | 26 | ) 27 | 28 | export default withTranslation('main')(VideoSourceFiltersSelect) 29 | -------------------------------------------------------------------------------- /app/components/Videos/VideosFilterBar.jsx: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable' 2 | import React from 'react' 3 | import { withTranslation } from 'react-i18next' 4 | 5 | import LanguageSelector from '../App/LanguageSelector' 6 | import FieldWithLabelAddon from '../FormUtils/FieldWithLabelAddon' 7 | import VideoSourceFiltersSelect from './VideoSourceFiltersSelect' 8 | 9 | const VideosFilterBar = ({ onLanguageChange, onSourceChange, source, language, t }) => { 10 | return ( 11 | 33 | ) 34 | } 35 | 36 | export default withTranslation('main')(VideosFilterBar) 37 | -------------------------------------------------------------------------------- /app/components/Videos/VideosGrid.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { VideoCard } from './VideoCard' 4 | 5 | export const VideosGrid = ({ videos }) => { 6 | return ( 7 |
8 | {videos.map((video) => ( 9 | 10 | ))} 11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /app/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { cva, type VariantProps } from 'class-variance-authority' 3 | 4 | import { cn } from '@/lib/css-utils' 5 | 6 | const badgeVariants = cva( 7 | 'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', 8 | { 9 | variants: { 10 | variant: { 11 | default: 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80', 12 | secondary: 13 | 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', 14 | destructive: 15 | 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80', 16 | outline: 'text-foreground', 17 | success: 'border-transparent bg-green-500 text-white shadow hover:bg-green-600', 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: 'default', 22 | }, 23 | }, 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return
32 | } 33 | 34 | export { Badge, badgeVariants } 35 | -------------------------------------------------------------------------------- /app/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/css-utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /app/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as LabelPrimitive from '@radix-ui/react-label' 3 | import { cva, type VariantProps } from 'class-variance-authority' 4 | 5 | import { cn } from '@/lib/css-utils' 6 | 7 | const labelVariants = cva( 8 | 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', 9 | ) 10 | 11 | const Label = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef & VariantProps 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )) 21 | Label.displayName = LabelPrimitive.Root.displayName 22 | 23 | export { Label } 24 | -------------------------------------------------------------------------------- /app/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | import * as SeparatorPrimitive from '@radix-ui/react-separator' 2 | import * as React from 'react' 3 | 4 | import { cn } from '@/lib/css-utils' 5 | 6 | const Separator = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => ( 10 | 21 | )) 22 | Separator.displayName = SeparatorPrimitive.Root.displayName 23 | 24 | export { Separator } 25 | -------------------------------------------------------------------------------- /app/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { cn } from '@/lib/css-utils' 4 | 5 | function Skeleton({ className, ...props }: React.HTMLAttributes) { 6 | return
7 | } 8 | 9 | export { Skeleton } 10 | -------------------------------------------------------------------------------- /app/components/ui/spinner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Spinner({ size = 24 }) { 4 | return ( 5 | 6 | 10 | 11 | 18 | 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /app/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | import * as SwitchPrimitives from '@radix-ui/react-switch' 2 | import * as React from 'react' 3 | 4 | import { cn } from '@/lib/css-utils' 5 | 6 | const Switch = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, ...props }, ref) => ( 10 | 18 | 23 | 24 | )) 25 | Switch.displayName = SwitchPrimitives.Root.displayName 26 | 27 | export { Switch } 28 | -------------------------------------------------------------------------------- /app/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { cn } from '@/lib/css-utils' 4 | 5 | const Textarea = React.forwardRef>( 6 | ({ className, ...props }, ref) => { 7 | return ( 8 |