diff --git a/client/package.json b/client/package.json index 961b5da66e..81a8c28998 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@coralproject/talk", - "version": "9.0.1", + "version": "9.0.2", "author": "The Coral Project", "homepage": "https://coralproject.net/", "sideEffects": [ diff --git a/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx b/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx index 1ed3f0d693..68da961f20 100644 --- a/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx +++ b/client/src/core/client/admin/components/ModerateCard/MarkersContainer.tsx @@ -3,6 +3,7 @@ import React, { useMemo } from "react"; import { graphql } from "react-relay"; import { withFragmentContainer } from "coral-framework/lib/relay"; +import { GQLCOMMENT_STATUS } from "coral-framework/schema"; import { Flex, HorizontalGutter, @@ -27,14 +28,14 @@ const markers: Array< (c: MarkersContainer_comment) => React.ReactElement | null > = [ (c) => - (c.status === "PREMOD" && ( + (c.status === GQLCOMMENT_STATUS.PREMOD && ( Pre-Mod )) || null, (c) => - (c.status === "PREMOD" && + (c.status === GQLCOMMENT_STATUS.PREMOD && c.author && c.author.premoderatedBecauseOfEmailAt && ( @@ -42,6 +43,14 @@ const markers: Array< )) || null, + (c) => + (c.status !== GQLCOMMENT_STATUS.PREMOD && + c.initialStatus === GQLCOMMENT_STATUS.PREMOD && ( + + Pre-Mod + + )) || + null, (c) => (c.revision && c.revision.actionCounts.flag.reasons.COMMENT_DETECTED_LINKS && ( @@ -244,6 +253,7 @@ const enhanced = withFragmentContainer({ comment: graphql` fragment MarkersContainer_comment on Comment { ...ModerateCardDetailsContainer_comment + initialStatus status tags { code diff --git a/client/src/core/client/admin/components/UserHistoryDrawer/UserDrawerAccountHistory.tsx b/client/src/core/client/admin/components/UserHistoryDrawer/UserDrawerAccountHistory.tsx index 2fd93557c4..b9591d0248 100644 --- a/client/src/core/client/admin/components/UserHistoryDrawer/UserDrawerAccountHistory.tsx +++ b/client/src/core/client/admin/components/UserHistoryDrawer/UserDrawerAccountHistory.tsx @@ -50,24 +50,31 @@ const UserDrawerAccountHistory: FunctionComponent = ({ user, viewer, }) => { - const system = ( - ( + + ), + }} + > + - ), - }} - > - - {" "} - System - - + />{" "} + System + + + ), + [] ); const { localeBundles } = useCoralContext(); @@ -80,12 +87,12 @@ const UserDrawerAccountHistory: FunctionComponent = ({ minute: "numeric", second: "numeric", }); - const addSeconds = (date: Date, seconds: number) => { - return new Date(date.getTime() + seconds * 1000); - }; const deletionDescriptionMapping = useCallback( (updateType: string, createdAt: string) => { + const addSeconds = (date: Date, seconds: number) => { + return new Date(date.getTime() + seconds * 1000); + }; const mapping: { [key: string]: string } = { REQUESTED: getMessage( localeBundles, @@ -117,7 +124,13 @@ const UserDrawerAccountHistory: FunctionComponent = ({ }; return mapping[updateType]; }, - [getMessage, localeBundles, addSeconds, deletionFormatter] + [localeBundles, deletionFormatter] + ); + + const accountDomainBannedMessage = getMessage( + localeBundles, + "moderate-user-drawer-account-history-account-domain-banned", + "Account domain banned" ); const combinedHistory = useMemo(() => { @@ -190,7 +203,15 @@ const UserDrawerAccountHistory: FunctionComponent = ({ takenBy: record.createdBy ? record.createdBy.username : system, description: isSiteBanned && !!user.status.ban.sites - ? user.status.ban.sites.map((s) => s.name).join(", ") + ? // does the site ban have sites? If so, add to description + user.status.ban.sites.map((s) => s.name).join(", ") + : // is the ban created? + record.active + ? // If the ban is created, is it created by the system? + record.createdBy + ? "" + : // If the ban is created by the system, show the account domain banned message + accountDomainBannedMessage : "", }; } else { @@ -201,10 +222,19 @@ const UserDrawerAccountHistory: FunctionComponent = ({ }, date: new Date(record.createdAt), takenBy: record.createdBy ? record.createdBy.username : system, - description: - siteBan && record.sites + description: siteBan + ? // does the site ban have sites? If so, add to description + record.sites ? record.sites.map((s) => s.name).join(", ") - : "", + : "" + : // is the ban created? + record.active + ? // If the ban is created, is it created by the system? + record.createdBy + ? "" + : // If the ban is created by the system, show the account domain banned message + accountDomainBannedMessage + : "", }); } }); @@ -301,7 +331,12 @@ const UserDrawerAccountHistory: FunctionComponent = ({ } return dateSortedHistory; - }, [system, user.status, deletionDescriptionMapping]); + }, [ + system, + user.status, + deletionDescriptionMapping, + accountDomainBannedMessage, + ]); const formatter = useDateTimeFormatter({ year: "numeric", month: "long", diff --git a/common/package.json b/common/package.json index ae21ca29f4..195cd83f11 100644 --- a/common/package.json +++ b/common/package.json @@ -1,6 +1,6 @@ { "name": "common", - "version": "9.0.1", + "version": "9.0.2", "description": "", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/config/package.json b/config/package.json index 6ccc460866..60ca699939 100644 --- a/config/package.json +++ b/config/package.json @@ -1,6 +1,6 @@ { "name": "common", - "version": "9.0.1", + "version": "9.0.2", "description": "", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/docs/docs/development.md b/docs/docs/development.md index 945b8e7b68..922ea74b2e 100644 --- a/docs/docs/development.md +++ b/docs/docs/development.md @@ -8,7 +8,7 @@ description: A guide to developing and extending Coral. Running Coral for development is very similar to installing Coral via Source as described in our [Getting Started](/#source) guide. -Coral requires NodeJS ^14.18, we recommend using `nvm` to help manage node +Coral requires NodeJS ^18.16.0, we recommend using `nvm` to help manage node versions: https://github.com/creationix/nvm. ```bash diff --git a/docs/docs/installation.md b/docs/docs/installation.md index 7b33cc55ff..50f1861e1f 100644 --- a/docs/docs/installation.md +++ b/docs/docs/installation.md @@ -19,8 +19,8 @@ Built with ❤️ by Coral by [Vox Media](https://product.voxmedia.com/). - MongoDB ^4.2 - Redis ^3.2 -- NodeJS ^14.18 -- NPM ^8.0 +- NodeJS ^18.16.0 +- PNPM ^8.0 ## Running @@ -114,7 +114,7 @@ Then start Coral with: ```bash cd server -ppnpm run start:development +pnpm run start:development ``` Then head on over to http://localhost:3000 to install Coral! diff --git a/docs/docs/mobile.md b/docs/docs/mobile.md index fb535b9abd..39bd497bda 100644 --- a/docs/docs/mobile.md +++ b/docs/docs/mobile.md @@ -5,7 +5,7 @@ sidebar_label: Native Mobile Apps Integration with native mobile applications is done through web view, you will need to host an HTML page which contains your embed code and open a web view to this page. There are many approaches to integrating native and web applications across different mobile operating systems, but any integration will involve the following steps: -1. Host your embed code (find your **Embed code** under **Configure** > **Organization** > **Site Details**) in an HTML page served over HTTPS. For example, `https://yoursitename.com/coral.html` +1. Host your embed code (find your **Embed code** under **Configure** > **Organization** > **Site Details**) in an HTML page served over HTTPS. For example, `https://yoursitename.com/coral.html`. Alternately, host a [proxy service](https://github.com/coralproject/app-proxy) to return the HTML for your embed code. 2. Add the domain to the list of permitted domains for your site under **Configure** > **Organization** > **Site Details** 3. Create a web view in your native application which points to this URL 4. Pass an SSO access token through to the embed code to log in your user. See [Single Sign On](/sso) for information on how to generate an SSO token. There are multiple ways to pass data from a native application to a web view, one method is to encode the access token in a query parameter on the URL hash (ex. `https://yoursitename.com/coral.html#accessToken=ssoToken`) and retrieve the token from the embed code. You can then pass through the `accessToken` option passed to `createStreamEmbed`: diff --git a/locales/en-US/admin.ftl b/locales/en-US/admin.ftl index acd6313c51..e29bcfbf4b 100644 --- a/locales/en-US/admin.ftl +++ b/locales/en-US/admin.ftl @@ -1205,6 +1205,7 @@ moderate-user-drawer-account-history-system = System moderate-user-drawer-account-history-suspension-ended = Suspension ended moderate-user-drawer-account-history-suspension-removed = Suspension removed moderate-user-drawer-account-history-banned = Banned +moderate-user-drawer-account-history-account-domain-banned = Account domain banned moderate-user-drawer-account-history-ban-removed = Ban removed moderate-user-drawer-account-history-site-banned = Site banned moderate-user-drawer-account-history-site-ban-removed = Site ban removed diff --git a/locales/fr-FR/stream.ftl b/locales/fr-FR/stream.ftl index 4c0576d2b3..e3ede453d0 100755 --- a/locales/fr-FR/stream.ftl +++ b/locales/fr-FR/stream.ftl @@ -530,6 +530,17 @@ profile-account-notifications-updated = Vos paramètres de notification ont ét profile-account-notifications-button = Mettre à jour mes paramètres de notification profile-account-notifications-button-update = Mise à jour +profile-account-notifications-inPageNotifications = Notifications +profile-account-notifications-includeInPageWhen = M'alerter quand + +profile-account-notifications-inPageNotifications-on = Badges activés +profile-account-notifications-inPageNotifications-off = Badges désactivés + +profile-account-notifications-showReplies-fromAnyone = de n'importe qui +profile-account-notifications-showReplies-fromStaff = d'un membre de l'équipe +profile-account-notifications-showReplies = + .aria-label = Montrer les réponses venant de + ## Report Comment Popover comments-reportPopover = .description = Un dialogue pour les commentaires signalés @@ -794,3 +805,82 @@ stream-footer-links-profile = Profile et réponses stream-footer-links-discussions = Plus de discussions .title = Lire plus de discussions + +## Notifications + +notifications-title = Notifications +notifications-loadMore = Charger plus de notifications +notifications-loadNew = Charger nouvelles notifications + +notifications-adjustPreferences = Ajuster les paramètres de notifications dans Mon Profil > + +notification-comment-toggle-default-open = - Commentaire +notification-comment-toggle-default-closed = + Commentaire + +notifications-comment-showRemovedComment = + Afficher le commentaire supprimé +notifications-comment-hideRemovedComment = - Cacher le commentaire supprimé + +notification-comment-description-featured = votre commentaire sur "{ $title }" a été mis en avant par un membre de notre équipe. +notification-comment-description-default = sur "{ $title }" +notification-comment-media-image = Image +notification-comment-media-embed = Embed +notification-comment-media-gif = Gif + +notifications-yourIllegalContentReportHasBeenReviewed = + Votre signalement de contenu potentiellement illégal a été examiné +notifications-yourCommentHasBeenRejected = + Votre commentaire a été rejeté +notifications-yourCommentHasBeenApproved = + Votre commentaire a été approuvé +notifications-yourCommentHasBeenFeatured = + Votre commentaire a été mis en avant +notifications-yourCommentHasReceivedAReply = + Nouvelle réponse de { $author } +notifications-defaultTitle = Notification + +notifications-rejectedComment-body = + Le contenu de votre commentaire ne respecte pas les règles de notre communauté. Le commentaire a été supprimé. +notifications-rejectedComment-wasPending-body = + Le contenu de votre commentaire ne respecte pas les règles de notre communauté. +notifications-reasonForRemoval = Raison de la suppression +notifications-legalGrounds = Motifs légaux +notifications-additionalExplanation = Explication supplémentaire + +notifications-repliedComment-hideReply = - Cacher la réponse +notifications-repliedComment-showReply = + Afficher la réponse +notifications-repliedComment-hideOriginalComment = - Cacher mon commentaire +notifications-repliedComment-showOriginalComment = + Afficher mon commentaire + +notifications-dsaReportLegality-legal = Contenu légal +notifications-dsaReportLegality-illegal = Contenu potentiellement illégal +notifications-dsaReportLegality-unknown = Inconnu + +notifications-rejectionReason-offensive = Ce commentaire contient du langage offesant +notifications-rejectionReason-abusive = Ce commentaire contient du langage abusif +notifications-rejectionReason-spam = Ce commentaire est du spam +notifications-rejectionReason-bannedWord = Mot banni +notifications-rejectionReason-ad = Ce commentaire est une publicité +notifications-rejectionReason-illegalContent = Ce commentaire a du contenu potentiellement illégal +notifications-rejectionReason-harassmentBullying = Ce commentaire contient du harcélement +notifications-rejectionReason-misinformation = Ce commentaire contient des informations inexactes +notifications-rejectionReason-hateSpeech = Ce commentaire contient un discours haineux +notifications-rejectionReason-irrelevant = Ce commentaire n'est pas pertinent à la discussion +notifications-rejectionReason-other = Autre +notifications-rejectionReason-other-customReason = Autre - { $customReason } +notifications-rejectionReason-unknown = Inconnu + +notifications-reportDecisionMade-legal = + Le { $date } vous avez signalé un commentaire écrit par { $author } car il contient du contenu potentiellement illégal. Après avoir évalué votre signalement, notre équipe de modération a décidé que ce commentaire ne semble pas contenir de contenu illégal. Merci pour votre contribution à la sécurité de notre communauté. +notifications-reportDecisionMade-illegal = + Le { $date } vous avez signalé un commentaire écrit par { $author } car il contient du contenu potentiellement illégal. Après avoir évalué votre signalement, notre équipe de modération a décidé que ce commentaire contient du contenu illégal et a été supprimé. D'autres mesures pourront être prises à l'encontre de l'auteur, mais vous n'en serez pas informé. Merci pour votre contribution à la sécurité de notre communauté. + +notifications-methodOfRedress-none = + Toutes les décisions de modération sont finales et sans appel +notifications-methodOfRedress-email = + Pour contester une décision qui apparaît ici, veuillez contacter { $email } +notifications-methodOfRedress-url = + Pour contester une décision qui apparaît ici, veuillez visiter { $url } + +notifications-youDoNotCurrentlyHaveAny = Vous n'avez actuellement aucune notification + +notifications-floatingIcon-close = fermer diff --git a/server/package.json b/server/package.json index 852f838376..090b35acfa 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@coralproject/talk", - "version": "9.0.1", + "version": "9.0.2", "author": "The Coral Project", "homepage": "https://coralproject.net/", "sideEffects": [ diff --git a/server/src/core/server/graph/resolvers/Comment.ts b/server/src/core/server/graph/resolvers/Comment.ts index 6b3430ad77..8e683ee12a 100644 --- a/server/src/core/server/graph/resolvers/Comment.ts +++ b/server/src/core/server/graph/resolvers/Comment.ts @@ -81,6 +81,7 @@ export const Comment: GQLCommentTypeResolver = { c.revisions.length > 0 ? { revision: getLatestRevision(c), comment: c } : null, + initialStatus: (c) => (c.revisions.length > 0 ? c.revisions[0].status : null), canModerate: (c, input, ctx) => { if (!ctx.user) { return false; diff --git a/server/src/core/server/graph/resolvers/CommentRevision.ts b/server/src/core/server/graph/resolvers/CommentRevision.ts index d7fb791738..254162987c 100644 --- a/server/src/core/server/graph/resolvers/CommentRevision.ts +++ b/server/src/core/server/graph/resolvers/CommentRevision.ts @@ -21,4 +21,5 @@ export const CommentRevision: Required< metadata: (w) => w.revision.metadata || {}, createdAt: (w) => w.revision.createdAt, media: (w) => w.revision.media, + status: (w) => w.revision.status, }; diff --git a/server/src/core/server/graph/schema/schema.graphql b/server/src/core/server/graph/schema/schema.graphql index c5e26e4154..15e9184c80 100644 --- a/server/src/core/server/graph/schema/schema.graphql +++ b/server/src/core/server/graph/schema/schema.graphql @@ -4129,6 +4129,11 @@ type CommentRevision { createdAt is the time that the CommentRevision was created. """ createdAt: Time! + + """ + status is the status of the CommentRevision. + """ + status: COMMENT_STATUS } enum TAG { @@ -4304,6 +4309,11 @@ type Comment @cacheControl(maxAge: 5) { """ status: COMMENT_STATUS! + """ + initialStatus represents the Comment's status when it was initially created. + """ + initialStatus: COMMENT_STATUS + """ statusHistory returns a CommentModerationActionConnection that will list the history of moderator actions performed on the Comment, with the most diff --git a/server/src/core/server/models/comment/comment.ts b/server/src/core/server/models/comment/comment.ts index cdf8ea3e0a..ddadca204d 100644 --- a/server/src/core/server/models/comment/comment.ts +++ b/server/src/core/server/models/comment/comment.ts @@ -110,6 +110,11 @@ export interface Comment extends TenantResource { */ status: GQLCOMMENT_STATUS; + /** + * initialStatus is the initial Comment Status. + */ + initialStatus: GQLCOMMENT_STATUS; + /** * actionCounts stores a cached count of all the Action's against this * Comment. @@ -175,7 +180,7 @@ export type CreateCommentInput = Omit< | "revisions" | "deletedAt" > & - Required> & + Required> & Pick & Partial>; @@ -186,7 +191,7 @@ export async function createComment( now = new Date() ) { // Pull out some useful properties from the input. - const { body, actionCounts = {}, metadata, media, ...rest } = input; + const { body, actionCounts = {}, metadata, media, status, ...rest } = input; // Generate the revision. const revision: Readonly = { @@ -196,6 +201,7 @@ export async function createComment( metadata, createdAt: now, media, + status, }; // default are the properties set by the application when a new comment is @@ -214,6 +220,7 @@ export async function createComment( // Defaults for things that always stay the same, or are computed. ...defaults, // Rest for things that are passed in and are not actionCounts. + status, ...rest, // ActionCounts because they may be passed in! actionCounts, @@ -342,6 +349,7 @@ export async function editComment( metadata, createdAt: now, media, + status, }; const update: Record = { diff --git a/server/src/core/server/models/comment/revision.ts b/server/src/core/server/models/comment/revision.ts index 070abc6ef0..d043077ce3 100644 --- a/server/src/core/server/models/comment/revision.ts +++ b/server/src/core/server/models/comment/revision.ts @@ -157,4 +157,9 @@ export interface Revision { * media is the optional media object attached to this revision. */ media?: CommentMedia; + + /** + * status is the comment status for the revision + */ + status: GQLCOMMENT_STATUS; } diff --git a/server/src/core/server/stacks/createComment.ts b/server/src/core/server/stacks/createComment.ts index 723b3b57f5..b85728a879 100644 --- a/server/src/core/server/stacks/createComment.ts +++ b/server/src/core/server/stacks/createComment.ts @@ -88,6 +88,7 @@ export type CreateComment = Omit< | "tags" | "siteID" | "media" + | "initialStatus" > & { rating?: number; media?: CreateCommentMediaInput; @@ -379,6 +380,7 @@ export default async function create( metadata: result.metadata, actionCounts, media, + initialStatus: result.status, }, now ); diff --git a/server/src/core/server/test/fixtures.ts b/server/src/core/server/test/fixtures.ts index 45f5241cf8..fa9b73e5fd 100644 --- a/server/src/core/server/test/fixtures.ts +++ b/server/src/core/server/test/fixtures.ts @@ -384,6 +384,7 @@ export const createCommentFixture = ( actionCounts: {}, metadata: {}, createdAt: new Date(), + status: GQLCOMMENT_STATUS.APPROVED, }, ], actionCounts: {}, @@ -391,6 +392,7 @@ export const createCommentFixture = ( childIDs: [], tags: [], createdAt: new Date(), + initialStatus: GQLCOMMENT_STATUS.NONE, }; return merge(comment, defaults) as Comment;