{
+ const filters = {}
+
+ filters.commented = commentedStatements
+
+ return filters
+}
+
+const PaginatedStatementsContainer = ({
+ baseURL,
+ query = StatementsQuery,
+ queryArgs = {},
+ statementsPath = 'statements',
+ showPagination = true,
+ currentPage = 1,
+ limit = 16,
+ ...props
+}) => {
+ const filters = buildFiltersFromProps(props)
+ return (
+
+ {({ loading, error, data }) => {
+ const statements = get(data, statementsPath, INITIAL_STATEMENTS)
+ if (error) {
+ return
+ } else if (!loading && statements.entries.length === 0) {
+ return No statement yet!
+ }
+
+ const paginationMenu = !showPagination ? null : (
+ window.scrollTo({ top: 0 })}
+ LinkBuilder={({ 'data-page': page, ...props }) => {
+ const urlParams = page > 1 ? `?page=${page}` : ''
+ return
+ }}
+ />
+ )
+
+ return (
+
+ {paginationMenu}
+ {loading ? : }
+ {paginationMenu}
+
+ )
+ }}
+
+ )
+}
+
+export default withNamespaces('main')(PaginatedStatementsContainer)
diff --git a/app/components/Statements/StatementCard.jsx b/app/components/Statements/StatementCard.jsx
new file mode 100644
index 000000000..dad7c0d61
--- /dev/null
+++ b/app/components/Statements/StatementCard.jsx
@@ -0,0 +1,230 @@
+import React from 'react'
+import { Fragment } from 'react'
+import { Link } from 'react-router'
+import { List } from 'immutable'
+
+import styled from 'styled-components'
+import { MicrophoneAlt } from 'styled-icons/boxicons-solid'
+
+import StatementComments from './StatementComments'
+import RawIcon from '../Utils/RawIcon'
+import { statementURL } from '../../lib/cf_routes'
+
+const Statement = styled.div`
+ background: #31455d;
+ border: 1px solid lightgrey;
+ transition: box-shadow 0.5s, max-width 0.5s;
+ box-shadow: rgba(10, 10, 10, 0.15) 0px 6px 14px -5px;
+`
+
+const StatementColumn = styled.div`
+ max-width: 1300px;
+ padding: 1em 0;
+`
+
+const StatementHeader = styled.div`
+ padding: 5px;
+ text-align: left;
+
+ .speaker {
+ color: #e0e7f1;
+ }
+
+ .speaker-title {
+ color: lightgrey;
+ }
+`
+
+const StatementText = styled.div`
+ text-align: left;
+ color: #e0e7f1;
+ margin-left: 30px;
+ padding: 1em;
+
+ &:before {
+ content: '\\201C';
+ font-family: serif;
+ font-style: normal;
+ font-weight: 700;
+ font-size: 45px;
+ position: absolute;
+ margin: -25px 0 0 -30px;
+ }
+`
+
+const ArrowIcon = styled.span`
+ && {
+ display: flex;
+ align-items: center;
+ margin-right: 10px;
+ }
+`
+
+const StatementCardFooter = styled.div`
+ display: flex;
+ justify-content: space-between;
+
+ &:hover {
+ background: #e0e0e0;
+ cursor: pointer;
+
+ ${ArrowIcon} {
+ color: black;
+ }
+ }
+`
+
+const CommentsContainer = styled.div`
+ background-color: #ffffff;
+
+ li {
+ color: #ffffff;
+ display: inline-block;
+ border-width: 1px;
+ border-style: solid;
+ border-radius: 8px;
+ margin: 5px;
+ padding: 0 0.5em;
+ }
+
+ li:first-child {
+ margin-left: 10px;
+ }
+
+ li:last-child {
+ margin-right: 0px;
+ }
+
+ li.approvingFacts {
+ background-color: #39b714;
+ border-color: #39b714;
+ }
+
+ li.refutingFacts {
+ background-color: #e0454e;
+ border-color: #e0454e;
+ }
+
+ li.regularComments {
+ border-color: #c4c4c4;
+ background-color: #c4c4c4;
+ }
+
+ .sourcesType {
+ border-top: 1px solid #dbdbdb;
+ background: #f1f1f1;
+ }
+
+ .comment-form {
+ border-top: 1px solid #dbdbdb;
+ }
+`
+
+const parseComment = (comments, speakerId) => {
+ const selfComments = []
+ const approvingFacts = []
+ const refutingFacts = []
+ const regularComments = []
+
+ for (const comment of comments) {
+ if (comment.user && comment.user.id === speakerId) {
+ // TODO: not really a regular comment to count ... Should we add a counter for speaker's comments ?
+ selfComments.push(comment)
+ } else if (!comment.source || comment.approve === null) {
+ regularComments.push(comment)
+ } else if (comment.approve) {
+ approvingFacts.push(comment)
+ } else {
+ refutingFacts.push(comment)
+ }
+ }
+
+ return {
+ regularComments: new List(regularComments),
+ speakerComments: new List(selfComments),
+ approvingFacts: new List(approvingFacts),
+ refutingFacts: new List(refutingFacts),
+ }
+}
+
+const StatementCard = ({ statement }) => {
+ const hasComments = statement.comments.length > 0
+ const [showing, setShowing] = React.useState(false)
+ const parsedComment = React.useMemo(
+ () => parseComment(statement.comments, statement.speakerId),
+ [statement]
+ )
+ const { approvingFacts, refutingFacts, regularComments, speakerComments } = parsedComment
+ return (
+
+
+ {statement.speaker && (
+
+
+ {statement.speaker.picture ? (
+
![]({statement.speaker.picture})
+ ) : (
+
+ )}
+
+
+ {statement.speaker.fullName}
+
+
+
+ {
+ // Since speaker.title can be null, we only display it if set
+ statement.speaker.title && (
+ {statement.speaker.title}
+ )
+ }
+
+ )}
+ {statement.text}
+
+
+
+ {statement.video.title}
+
+
+ {hasComments === true && (
+
+ setShowing(!showing)}>
+
+ {approvingFacts.size > 0 && (
+ - {approvingFacts.size}
+ )}
+ {refutingFacts.size > 0 && (
+ - {refutingFacts.size}
+ )}
+ {regularComments.size > 0 && (
+ - {regularComments.size}
+ )}
+
+
+
+ {showing && (
+
+ {
+ /* TODO */
+ }}
+ comments={regularComments}
+ speakerComments={speakerComments}
+ approvingFacts={approvingFacts}
+ refutingFacts={refutingFacts}
+ withoutActions
+ />
+
+ )}
+
+ )}
+
+
+
+ )
+}
+
+export default StatementCard
diff --git a/app/components/Statements/StatementComments.jsx b/app/components/Statements/StatementComments.jsx
index 87d9f7468..4ea4314d4 100644
--- a/app/components/Statements/StatementComments.jsx
+++ b/app/components/Statements/StatementComments.jsx
@@ -11,12 +11,19 @@ import SpeakerComments from './SpeakerComments'
@withNamespaces('videoDebate')
@connect((state, props) => {
- const classifiedComments = classifyComments(state, props)
- return {
- comments: classifiedComments.regularComments,
- speakerComments: classifiedComments.selfComments,
- approvingFacts: classifiedComments.approvingFacts,
- refutingFacts: classifiedComments.refutingFacts,
+ if (props.comments === undefined
+ || props.speakerComments === undefined
+ || props.approvingFacts === undefined
+ || props.refutingFacts === undefined) {
+ const classifiedComments = classifyComments(state, props)
+ return {
+ comments: classifiedComments.regularComments,
+ speakerComments: classifiedComments.selfComments,
+ approvingFacts: classifiedComments.approvingFacts,
+ refutingFacts: classifiedComments.refutingFacts,
+ }
+ } else {
+ return { }
}
})
export default class StatementComments extends React.PureComponent {
diff --git a/app/components/Statements/StatementsGrid.jsx b/app/components/Statements/StatementsGrid.jsx
new file mode 100644
index 000000000..6c5d6cb92
--- /dev/null
+++ b/app/components/Statements/StatementsGrid.jsx
@@ -0,0 +1,30 @@
+import React from 'react'
+
+import StatementCard from './StatementCard'
+import styled from 'styled-components'
+
+const StatementsList = styled.div`
+ border-top: 1px solid #e8e8e8;
+ border-bottom: 1px solid #e8e8e8;
+ justify-content: center;
+ max-width: 1300px;
+ padding: 1em 0;
+
+ // the .columns css class set the margins to negative values, this
+ // allow to override the style to force the value
+ && {
+ margin: 0 auto;
+ }
+`
+
+export class StatementsGrid extends React.PureComponent {
+ render() {
+ return (
+
+ {this.props.statements.map((statement) => {
+ return
+ })}
+
+ )
+ }
+}
diff --git a/app/components/Statements/StatementsIndexPage.jsx b/app/components/Statements/StatementsIndexPage.jsx
new file mode 100644
index 000000000..065e649a1
--- /dev/null
+++ b/app/components/Statements/StatementsIndexPage.jsx
@@ -0,0 +1,106 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import { withNamespaces } from 'react-i18next'
+import capitalize from 'voca/capitalize'
+import { Helmet } from 'react-helmet'
+
+import { toAbsoluteURL } from '../../lib/cf_routes'
+import { Icon } from '../Utils'
+import Button from '../Utils/Button'
+import { commentedStatmentsFilter } from '../../state/user_preferences/reducer'
+import PaginatedStatementsContainer from './PaginatedStatementsContainer'
+import styled from 'styled-components'
+import { themeGet } from 'styled-system'
+import { Comments } from 'styled-icons/fa-solid'
+import { QuestionCircle } from 'styled-icons/fa-solid'
+
+const StatementPage = styled.div`
+ text-align: center;
+ margin: 0 auto;
+ padding: 5vh 0.5em;
+`
+
+const StatementPageHeader = styled.section`
+ min-height: 60px;
+ margin-bottom: 20px;
+`
+
+const IsCommentedFilterButton = styled(Button)`
+ &&.set {
+ background-color: ${themeGet('colors.primary')};
+ color: ${themeGet('colors.white')};
+ }
+`
+
+const NavBar = styled.nav`
+ && {
+ justify-content: center;
+ }
+`
+
+const StatementsWithCommentsFilterBar = ({ commentedStatmentsFilter, isCommented, t }) => {
+ return (
+
+ commentedStatmentsFilter(true)}
+ >
+
+ Commented
+
+ commentedStatmentsFilter(false)}
+ >
+
+ To verify
+
+
+ )
+}
+
+@connect(
+ (state) => ({
+ commentedStatements: state.UserPreferences.commentedStatements,
+ }),
+ { commentedStatmentsFilter }
+)
+@withNamespaces('main')
+export default class StatementsIndexPage extends React.PureComponent {
+ render() {
+ const { commentedStatements, commentedStatmentsFilter, t, location } = this.props
+ const currentPage = parseInt(location.query.page) || 1
+
+ return (
+
+
+
+
+
+
+
+
+
+ {capitalize(t('entities.latestStatements'))}
+
+ commentedStatmentsFilter(v)}
+ isCommented={() => this.isCommented()}
+ />
+
+
+
+ )
+ }
+
+ isCommented() {
+ return this.props.commentedStatements
+ }
+}
diff --git a/app/components/Users/User.jsx b/app/components/Users/User.jsx
index aaf768b60..cd30da5cb 100644
--- a/app/components/Users/User.jsx
+++ b/app/components/Users/User.jsx
@@ -21,11 +21,11 @@ import { withLoggedInUser } from '../LoggedInUser/UserProvider'
import UserMenu from './UserMenu'
@connect(
- ({ DisplayedUser: { isLoading, errors, data } }) => ({
- isLoading,
- errors,
- user: data,
- }),
+ ({ DisplayedUser: { isLoading, errors, data } }) => ({
+ isLoading,
+ errors,
+ user: data,
+ }),
{ fetchUser, resetUser }
)
@withNamespaces('main')
@@ -69,7 +69,6 @@ export default class User extends React.PureComponent {
const user = this.props.user || {}
const prettyUsername = `@${user.username}`
-
return (
diff --git a/app/i18n/en/main.json b/app/i18n/en/main.json
index ab8ad7412..e9c1da526 100644
--- a/app/i18n/en/main.json
+++ b/app/i18n/en/main.json
@@ -6,7 +6,8 @@
"speaker_plural": "speakers",
"statement": "statement",
"statement_plural": "statements",
- "videoFactChecking": "Video fact-checking"
+ "videoFactChecking": "Video fact-checking",
+ "latestStatements": "Latest statements"
},
"menu": {
"login": "Log in",
diff --git a/app/i18n/fr/main.json b/app/i18n/fr/main.json
index 400a68329..fbc436c3d 100644
--- a/app/i18n/fr/main.json
+++ b/app/i18n/fr/main.json
@@ -6,7 +6,8 @@
"speaker_plural": "intervenant(e)s",
"statement": "déclaration",
"statement_plural": "déclarations",
- "videoFactChecking": "Vérification de vidéos"
+ "videoFactChecking": "Vérification de vidéos",
+ "latestStatements": "Les dernières citations"
},
"menu": {
"login": "Se connecter",
diff --git a/app/router.jsx b/app/router.jsx
index c235f918b..572d85635 100644
--- a/app/router.jsx
+++ b/app/router.jsx
@@ -29,6 +29,7 @@ import SubscriptionsPage from './components/LoggedInUser/SubscriptionsPage'
import LogoutPage from './components/LoggedInUser/LogoutPage'
import SupportUs from './components/SupportUs'
import SearchPage from './components/Search/SearchPage'
+import StatementsIndexPage from './components/Statements/StatementsIndexPage'
const CFRouter = () => (
@@ -56,6 +57,7 @@ const CFRouter = () => (
+
diff --git a/app/state/user_preferences/reducer.js b/app/state/user_preferences/reducer.js
index d35c4a491..4aabb1310 100644
--- a/app/state/user_preferences/reducer.js
+++ b/app/state/user_preferences/reducer.js
@@ -13,6 +13,7 @@ export const changeVideosLanguageFilter = createAction(
export const setVideosFilter = createAction('USER_PREFERENCES/SET_VIDEOS_FILTER')
export const toggleAutoscroll = createAction('STATEMENTS/TOGGLE_AUTOSCROLL')
export const toggleBackgroundSound = createAction('STATEMENTS/TOGGLE_BACKGROUND_SOUND')
+export const commentedStatmentsFilter = createAction('USER_PREFERENCES/COMMENTED_STATEMENTS_FILTER')
const isMobile = window.innerWidth <= MOBILE_WIDTH_THRESHOLD
@@ -24,6 +25,7 @@ const Preferences = new Record({
enableSoundOnBackgroundFocus: true,
videosLanguageFilter: null,
videosFilter: ONLY_FEATURED,
+ commentedStatements: true,
})
const loadState = () => {
@@ -62,6 +64,7 @@ const UserPreferencesReducer = handleActions(
[toggleBackgroundSound]: (state) => {
return updateState(state, 'enableSoundOnBackgroundFocus', !state.enableSoundOnBackgroundFocus)
},
+ [commentedStatmentsFilter]: (state, { payload }) => updateState(state, 'commentedStatements', payload)
},
loadState()
)