From 3399813dbf8c4ed394099515af0e071fd76068f9 Mon Sep 17 00:00:00 2001 From: Benjamin Piouffle Date: Sun, 15 Sep 2024 17:25:13 +0200 Subject: [PATCH] feat: Statement draft (#1302) --- app/components/Statements/Statement.jsx | 1 + .../Statements/StatementContainer.jsx | 82 ++++++++++++++++--- app/components/Statements/StatementHeader.jsx | 79 ++++++++++++------ app/components/Statements/StatementsList.jsx | 13 ++- app/components/UsersActions/ActionDiff.jsx | 3 +- app/components/UsersActions/ActionIcon.jsx | 1 + app/i18n/en/history.json | 3 +- app/i18n/en/videoDebate.json | 4 + app/i18n/fr/history.json | 3 +- app/i18n/fr/videoDebate.json | 4 + app/state/video_debate/statements/record.js | 1 + app/styles/_components/statements.sass | 5 ++ app/styles/_global/global.sass | 9 ++ 13 files changed, 165 insertions(+), 43 deletions(-) diff --git a/app/components/Statements/Statement.jsx b/app/components/Statements/Statement.jsx index 9e50e63c8..f86ef3182 100644 --- a/app/components/Statements/Statement.jsx +++ b/app/components/Statements/Statement.jsx @@ -20,6 +20,7 @@ export default class Statement extends React.PureComponent {
{this.renderStatementOrEditForm(speaker, statement)} - - + {statement.is_draft && !isEditing ? ( +
+ + {({ hasReputation }) => ( + { + this.setState({ editDraftAction: 'save' }) + try { + await this.props.updateStatement(statement.set('is_draft', false)) + } finally { + this.setState({ editDraftAction: null }) + } + }} + > + + {t('statement.publish')} + + )} + + + {({ hasReputation }) => ( + { + this.setState({ editDraftAction: 'discard' }) + try { + await this.props.deleteStatement({ id: statement.id }) + } finally { + this.setState({ editDraftAction: null }) + } + }} + > + + {t('statement.discard')} + + )} + +
+ ) : ( + + + {!statement.is_draft && ( + + )} + + )} {isDeleting && ( + {isDraft && ( + + + {t('statement.draft')} + + +
+ } + > + {t('statement.draftDetails')} + + )} {speaker && speaker.picture && ( )} @@ -34,20 +55,22 @@ export default withNamespaces('videoDebate')(
{!withoutActions && ( - - {({ hasReputation }) => ( - - )} - + {!isDraft && ( + + {({ hasReputation }) => ( + + )} + + )} )} - - + {!isDraft && ( + + + + + )} )} diff --git a/app/components/Statements/StatementsList.jsx b/app/components/Statements/StatementsList.jsx index 63724e9e6..5f236c733 100644 --- a/app/components/Statements/StatementsList.jsx +++ b/app/components/Statements/StatementsList.jsx @@ -9,6 +9,7 @@ import { FULLHD_WIDTH_THRESHOLD } from '../../constants' import { postStatement } from '../../state/video_debate/statements/effects' import { closeStatementForm, setScrollTo } from '../../state/video_debate/statements/reducer' import { statementFormValueSelector } from '../../state/video_debate/statements/selectors' +import { withLoggedInUser } from '../LoggedInUser/UserProvider' import StatementContainer from './StatementContainer' import { StatementForm } from './StatementForm' @@ -25,6 +26,7 @@ import { StatementForm } from './StatementForm' ) @withNamespaces('videoDebate') @withRouter +@withLoggedInUser export default class StatementsList extends React.PureComponent { componentDidMount() { const searchParams = new URLSearchParams(this.props.location.search) @@ -54,10 +56,19 @@ export default class StatementsList extends React.PureComponent { time, })) + filterStatements = memoizeOne((statements, isLoggedIn) => { + if (!isLoggedIn) { + return statements.filter((s) => !s.is_draft) + } else { + return statements + } + }) + render() { - const { speakers, statementFormSpeakerId, statements, offset } = this.props + const { speakers, statementFormSpeakerId, offset } = this.props const speakerId = speakers.size === 1 && !statementFormSpeakerId ? speakers.get(0).id : statementFormSpeakerId + const statements = this.filterStatements(this.props.statements, this.props.isAuthenticated) return (
{statementFormSpeakerId !== undefined && ( diff --git a/app/components/UsersActions/ActionDiff.jsx b/app/components/UsersActions/ActionDiff.jsx index 72b2d0879..cfa62450d 100644 --- a/app/components/UsersActions/ActionDiff.jsx +++ b/app/components/UsersActions/ActionDiff.jsx @@ -20,7 +20,6 @@ class ActionDiff extends PureComponent { render() { const allActions = this.props.allActions || new List([this.props.action]) const diff = this.generateDiff(allActions, this.props.action) - if (diff.size === 0) { return null } @@ -63,6 +62,8 @@ class ActionDiff extends PureComponent { formatChangeValue(value, key) { if (key === 'speaker_id' && value) { return #{value} + } else if (key === 'is_draft' && !value) { + return 'No' } return value } diff --git a/app/components/UsersActions/ActionIcon.jsx b/app/components/UsersActions/ActionIcon.jsx index 12a47e477..a62c76031 100644 --- a/app/components/UsersActions/ActionIcon.jsx +++ b/app/components/UsersActions/ActionIcon.jsx @@ -17,6 +17,7 @@ const ACTIONS_ICONS = { revert_vote_up: 'chevron-down', revert_vote_down: 'chevron-up', revert_self_vote: 'chevron-down', + start_automatic_statements_extraction: 'tasks', } const getIconName = (type) => ACTIONS_ICONS[type] || 'question' diff --git a/app/i18n/en/history.json b/app/i18n/en/history.json index c7240b696..dd79904d1 100644 --- a/app/i18n/en/history.json +++ b/app/i18n/en/history.json @@ -40,7 +40,8 @@ "action_banned_not_constructive": "Moderated ($t(moderation:reason.4))", "confirmed_flag": "Confirmed a flag", "abused_flag": "Refuted a flag", - "social_network_linked": "Linked a social network account" + "social_network_linked": "Linked a social network account", + "start_automatic_statements_extraction": "Started automatic statements extraction" }, "actionTarget": { "vote_up": "Received a positive vote", diff --git a/app/i18n/en/videoDebate.json b/app/i18n/en/videoDebate.json index 9b065ede4..27d74dd13 100644 --- a/app/i18n/en/videoDebate.json +++ b/app/i18n/en/videoDebate.json @@ -81,6 +81,10 @@ }, "statement": { "remove": "Remove statement", + "draft": "Draft", + "draftDetails": "This statement was automatically extracted from the video, it is not yet verified and may contain errors. Only registered users can see it.", + "discard": "Discard", + "publish": "Publish", "confirmRemove": "Do you really want to remove this statement?", "textPlaceholder": "Type a raw transcript of what the speaker says", "noSpeakerTextPlaceholder": "Describe what you see or select a speaker", diff --git a/app/i18n/fr/history.json b/app/i18n/fr/history.json index d99bd738b..007af0e30 100644 --- a/app/i18n/fr/history.json +++ b/app/i18n/fr/history.json @@ -40,7 +40,8 @@ "action_banned_not_constructive": "Modéré ($t(moderation:reason.4))", "confirmed_flag": "A confirmé un signalement", "abused_flag": "A réfuté d'un signalement", - "social_network_linked": "Compte lié à un réseau social" + "social_network_linked": "Compte lié à un réseau social", + "start_automatic_statements_extraction": "Démarré l'extraction automatique des citations" }, "actionTarget": { "vote_up": "Reçu un vote positif", diff --git a/app/i18n/fr/videoDebate.json b/app/i18n/fr/videoDebate.json index cb2209404..47a673f77 100644 --- a/app/i18n/fr/videoDebate.json +++ b/app/i18n/fr/videoDebate.json @@ -81,6 +81,10 @@ }, "statement": { "remove": "Retirer la citation", + "draft": "Brouillon", + "draftDetails": "Cette citation a été extraite automatiquement. Elle doit être vérifiée et complétée avant d'être publiée. Seuls les utilisateurs inscrits peuvent voir les brouillons.", + "discard": "Supprimer", + "publish": "Publier", "confirmRemove": "Êtes-vous sûr·e ?", "textPlaceholder": "Retranscrivez ici les propos de l'intervenant(e)", "noSpeakerTextPlaceholder": "Décrivez ce qui apparait à l'image ou ajoutez un intervenant", diff --git a/app/state/video_debate/statements/record.js b/app/state/video_debate/statements/record.js index 4390914db..2c988ccdb 100644 --- a/app/state/video_debate/statements/record.js +++ b/app/state/video_debate/statements/record.js @@ -5,5 +5,6 @@ const Statement = new Record({ text: '', time: 0, speaker_id: 0, + is_draft: false, }) export default Statement diff --git a/app/styles/_components/statements.sass b/app/styles/_components/statements.sass index 9f1d1fbe0..c982f6e0b 100644 --- a/app/styles/_components/statements.sass +++ b/app/styles/_components/statements.sass @@ -43,6 +43,11 @@ $statement-text-color: #e0e7f1 .card-footer border-top: none border-bottom: 1px solid #dbdbdb + .help-tooltip-trigger + display: flex + flex: 1 + &:not(:last-child) + border-right: 1px solid #dbdbdb &.comments display: block .expend-comment-form diff --git a/app/styles/_global/global.sass b/app/styles/_global/global.sass index 954ba8a39..6066c5555 100644 --- a/app/styles/_global/global.sass +++ b/app/styles/_global/global.sass @@ -57,6 +57,12 @@ h1 &:hover color: darken($white, 10) +.is-justify-content-flex-end + justify-content: flex-end + +.is-justify-content-center + justify-content: center + /** Spacing helpers **/ @for $i from 0 through 10 @@ -109,3 +115,6 @@ h1 .py-#{$i} padding-top: #{$value}rem padding-bottom: #{$value}rem + + .gap-#{$i} + gap: #{$value}rem