mirror of
https://github.com/wassname/talk.git
synced 2026-07-03 14:20:14 +08:00
5.4.0 Release Bug Fixes (#2789)
* fix: addresses CORL-848 Fixed copy for new commenters feature. * fix: address CORL-847 Revert the line hight changes on select fields for now. * fix: addressed CORL-851 Changed copy on CSS field. * fix: addressed CORL-840 Changed deletion window to 24 hours. Refactored durations to use TIME enum.
This commit is contained in:
Vendored
+3
@@ -55,4 +55,7 @@
|
||||
"__generated__"
|
||||
],
|
||||
"debug.node.autoAttach": "on",
|
||||
"search.exclude": {
|
||||
"package-lock.json": true
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import { Localized } from "@fluent/react/compat";
|
||||
import cn from "classnames";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
|
||||
import { reduceSeconds } from "coral-common/helpers/i18n";
|
||||
import TIME from "coral-common/time";
|
||||
import {
|
||||
Flex,
|
||||
HorizontalGutter,
|
||||
@@ -25,7 +26,7 @@ const RecentHistory: FunctionComponent<Props> = ({
|
||||
rejectionRate,
|
||||
submitted,
|
||||
}) => {
|
||||
const { scaled, unit } = reduceSeconds(timeFrame, [UNIT.DAYS]);
|
||||
const { scaled, unit } = reduceSeconds(timeFrame, [TIME.DAY]);
|
||||
|
||||
return (
|
||||
<HorizontalGutter spacing={2}>
|
||||
|
||||
@@ -30,13 +30,10 @@ const CustomCSSConfig: FunctionComponent<Props> = ({ disabled }) => (
|
||||
}
|
||||
>
|
||||
<FormField>
|
||||
<Localized
|
||||
id="configure-advanced-customCSS-explanation"
|
||||
strong={<strong />}
|
||||
>
|
||||
<Localized id="configure-advanced-customCSS-override" strong={<strong />}>
|
||||
<FormFieldDescription>
|
||||
URL of a CSS stylesheet that will override default Embed Stream
|
||||
styles. Can be internal or external.
|
||||
styles.
|
||||
</FormFieldDescription>
|
||||
</Localized>
|
||||
<Field name="customCSSURL" parse={parseEmptyAsNull} format={formatEmpty}>
|
||||
|
||||
+3
-3
@@ -80,9 +80,9 @@ const ClosingCommentStreamsConfig: FunctionComponent<Props> = ({
|
||||
<DurationField
|
||||
{...input}
|
||||
units={[
|
||||
DURATION_UNIT.HOURS,
|
||||
DURATION_UNIT.DAYS,
|
||||
DURATION_UNIT.WEEKS,
|
||||
DURATION_UNIT.HOUR,
|
||||
DURATION_UNIT.DAY,
|
||||
DURATION_UNIT.WEEK,
|
||||
]}
|
||||
disabled={disabled}
|
||||
color={colorFromMeta(meta)}
|
||||
|
||||
@@ -67,9 +67,9 @@ const CommentEditingConfig: FunctionComponent<Props> = ({ disabled }) => (
|
||||
<DurationField
|
||||
{...input}
|
||||
units={[
|
||||
DURATION_UNIT.SECONDS,
|
||||
DURATION_UNIT.MINUTES,
|
||||
DURATION_UNIT.HOURS,
|
||||
DURATION_UNIT.SECOND,
|
||||
DURATION_UNIT.MINUTE,
|
||||
DURATION_UNIT.HOUR,
|
||||
]}
|
||||
color={colorFromMeta(meta)}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -60,7 +60,7 @@ const NewCommentersConfig: FunctionComponent<Props> = ({ disabled }) => {
|
||||
</FormField>
|
||||
<FormField>
|
||||
<Localized id="configure-moderation-newCommenters-approvedCommentsThreshold">
|
||||
<Label>Number of first comments sent for approval</Label>
|
||||
<Label>Number of comments that must be approved</Label>
|
||||
</Localized>
|
||||
<Field
|
||||
name="newCommenters.approvedCommentsThreshold"
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ const RecentCommentHistoryConfig: FunctionComponent<Props> = ({ disabled }) => {
|
||||
{({ input, meta }) => (
|
||||
<>
|
||||
<DurationField
|
||||
units={[DURATION_UNIT.DAYS]}
|
||||
units={[DURATION_UNIT.DAY]}
|
||||
disabled={disabled}
|
||||
color={hasError(meta) ? "error" : "regular"}
|
||||
{...input}
|
||||
|
||||
@@ -234,7 +234,7 @@ each of your site’s stories.
|
||||
<p
|
||||
className="FormFieldDescription-root"
|
||||
>
|
||||
URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external.
|
||||
URL of a CSS stylesheet that will override default Embed Stream styles.
|
||||
</p>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-spacing-2"
|
||||
|
||||
@@ -367,7 +367,7 @@ for moderator approval before publication.
|
||||
<label
|
||||
className="Label-root"
|
||||
>
|
||||
Number of first comments sent for approval
|
||||
Number of comments that must be approved
|
||||
</label>
|
||||
<div
|
||||
className="TextField-root"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {
|
||||
DEFAULT_SESSION_LENGTH,
|
||||
DEFAULT_SESSION_DURATION,
|
||||
TOXICITY_THRESHOLD_DEFAULT,
|
||||
} from "coral-common/constants";
|
||||
import TIME from "coral-common/time";
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import {
|
||||
GQLComment,
|
||||
@@ -77,8 +78,7 @@ export const settings = createFixture<GQLSettings>({
|
||||
},
|
||||
recentCommentHistory: {
|
||||
enabled: false,
|
||||
// 7 days in seconds.
|
||||
timeFrame: 604800,
|
||||
timeFrame: 7 * TIME.DAY,
|
||||
// Rejection rate defaulting to 30%, once exceeded, comments will be
|
||||
// pre-moderated.
|
||||
triggerRejectionRate: 0.3,
|
||||
@@ -94,7 +94,7 @@ export const settings = createFixture<GQLSettings>({
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
sessionDuration: DEFAULT_SESSION_LENGTH,
|
||||
sessionDuration: DEFAULT_SESSION_DURATION,
|
||||
integrations: {
|
||||
local: {
|
||||
enabled: true,
|
||||
@@ -175,7 +175,7 @@ export const settingsWithEmptyAuth = createFixture<GQLSettings>(
|
||||
{
|
||||
id: "settings",
|
||||
auth: {
|
||||
sessionDuration: DEFAULT_SESSION_LENGTH,
|
||||
sessionDuration: DEFAULT_SESSION_DURATION,
|
||||
integrations: {
|
||||
local: {
|
||||
enabled: true,
|
||||
|
||||
@@ -24,7 +24,7 @@ it("renders correctly with specified units", () => {
|
||||
value: "",
|
||||
disabled: false,
|
||||
onChange: noop,
|
||||
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.HOURS],
|
||||
units: [DURATION_UNIT.SECOND, DURATION_UNIT.HOUR],
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<DurationField {...props} />);
|
||||
@@ -37,7 +37,7 @@ it("use best matching unit", () => {
|
||||
value: "3600",
|
||||
disabled: false,
|
||||
onChange: noop,
|
||||
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
|
||||
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<DurationField {...props} />);
|
||||
@@ -50,7 +50,7 @@ it("use initial unit if 0", () => {
|
||||
value: "0",
|
||||
disabled: false,
|
||||
onChange: noop,
|
||||
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
|
||||
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<DurationField {...props} />);
|
||||
@@ -63,7 +63,7 @@ it("accepts invalid input", () => {
|
||||
value: "this is so invalid",
|
||||
disabled: false,
|
||||
onChange: noop,
|
||||
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
|
||||
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<DurationField {...props} />);
|
||||
|
||||
@@ -8,7 +8,7 @@ import React, {
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import { UNIT } from "coral-common/helpers/i18n";
|
||||
import TIME from "coral-common/time";
|
||||
import {
|
||||
Flex,
|
||||
Option,
|
||||
@@ -23,14 +23,14 @@ import styles from "./DurationField.css";
|
||||
* DURATION_UNIT are units that can be used in the
|
||||
* DurationField components.
|
||||
*/
|
||||
export const DURATION_UNIT = UNIT;
|
||||
export const DURATION_UNIT = TIME;
|
||||
|
||||
const DURATION_UNIT_MAP = {
|
||||
[DURATION_UNIT.SECONDS]: "second",
|
||||
[DURATION_UNIT.MINUTES]: "minute",
|
||||
[DURATION_UNIT.HOURS]: "hour",
|
||||
[DURATION_UNIT.DAYS]: "day",
|
||||
[DURATION_UNIT.WEEKS]: "week",
|
||||
[DURATION_UNIT.SECOND]: "second",
|
||||
[DURATION_UNIT.MINUTE]: "minute",
|
||||
[DURATION_UNIT.HOUR]: "hour",
|
||||
[DURATION_UNIT.DAY]: "day",
|
||||
[DURATION_UNIT.WEEK]: "week",
|
||||
};
|
||||
|
||||
interface Props {
|
||||
@@ -39,18 +39,18 @@ interface Props {
|
||||
disabled: boolean;
|
||||
onChange: (v: string) => void;
|
||||
/** Specifiy units to include */
|
||||
units?: ReadonlyArray<UNIT>;
|
||||
units?: ReadonlyArray<TIME>;
|
||||
}
|
||||
|
||||
function convertToSeconds(value: string, unit?: UNIT) {
|
||||
function convertToSeconds(value: string, unit?: TIME) {
|
||||
const parsed = parseInt(value, 10);
|
||||
return (isNaN(parsed) || !unit ? value : parsed * unit).toString();
|
||||
}
|
||||
|
||||
function convertFromSeconds(
|
||||
value: string,
|
||||
units: ReadonlyArray<UNIT>,
|
||||
unit?: UNIT
|
||||
units: ReadonlyArray<TIME>,
|
||||
unit?: TIME
|
||||
) {
|
||||
const parsed = parseInt(value, 10);
|
||||
|
||||
@@ -82,7 +82,7 @@ function convertFromSeconds(
|
||||
*/
|
||||
const DurationField: FunctionComponent<Props> = ({
|
||||
value,
|
||||
units = [UNIT.HOURS, UNIT.DAYS, UNIT.WEEKS],
|
||||
units = [TIME.HOUR, TIME.DAY, TIME.WEEK],
|
||||
onChange,
|
||||
disabled,
|
||||
name,
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
urlMiddleware,
|
||||
} from "react-relay-network-modern/es";
|
||||
|
||||
import TIME from "coral-common/time";
|
||||
|
||||
import clientIDMiddleware from "./clientIDMiddleware";
|
||||
import { ManagedSubscriptionClient } from "./createManagedSubscriptionClient";
|
||||
import customErrorMiddleware from "./customErrorMiddleware";
|
||||
@@ -45,7 +47,7 @@ export default function createNetwork(
|
||||
customErrorMiddleware,
|
||||
cacheMiddleware({
|
||||
size: 100, // max 100 requests
|
||||
ttl: 900000, // 15 minutes
|
||||
ttl: 15 * TIME.MINUTE,
|
||||
clearOnMutation: true,
|
||||
}),
|
||||
urlMiddleware({
|
||||
|
||||
+7
-9
@@ -2,7 +2,7 @@ import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent, useCallback } from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { SCHEDULED_DELETION_TIMESPAN_DAYS } from "coral-common/constants";
|
||||
import { SCHEDULED_DELETION_WINDOW_DURATION } from "coral-common/constants";
|
||||
import { useCoralContext } from "coral-framework/lib/bootstrap";
|
||||
import { useMutation, withFragmentContainer } from "coral-framework/lib/relay";
|
||||
import CLASSES from "coral-stream/classes";
|
||||
@@ -24,8 +24,8 @@ interface Props {
|
||||
viewer: StreamDeletionRequestCalloutContainer_viewer;
|
||||
}
|
||||
|
||||
const formatter = (locales: string[], date: Date) => {
|
||||
return Intl.DateTimeFormat(locales, {
|
||||
const formatter = (locales: string[], date: Date) =>
|
||||
Intl.DateTimeFormat(locales, {
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
@@ -33,11 +33,9 @@ const formatter = (locales: string[], date: Date) => {
|
||||
minute: "numeric",
|
||||
second: "numeric",
|
||||
}).format(date);
|
||||
};
|
||||
|
||||
const subtractDays = (date: Date, days: number) => {
|
||||
const millisecondsInADay = 86400000;
|
||||
return new Date(date.getTime() - days * millisecondsInADay);
|
||||
const subtractSeconds = (date: Date, seconds: number) => {
|
||||
return new Date(date.getTime() - seconds * 1000);
|
||||
};
|
||||
|
||||
const StreamDeletionRequestCalloutContainer: FunctionComponent<Props> = ({
|
||||
@@ -57,9 +55,9 @@ const StreamDeletionRequestCalloutContainer: FunctionComponent<Props> = ({
|
||||
const requestDate = viewer.scheduledDeletionDate
|
||||
? formatter(
|
||||
locales,
|
||||
subtractDays(
|
||||
subtractSeconds(
|
||||
new Date(viewer.scheduledDeletionDate),
|
||||
SCHEDULED_DELETION_TIMESPAN_DAYS
|
||||
SCHEDULED_DELETION_WINDOW_DURATION
|
||||
)
|
||||
)
|
||||
: null;
|
||||
|
||||
+12
-7
@@ -9,8 +9,9 @@ import React, {
|
||||
} from "react";
|
||||
import { Field, Form } from "react-final-form";
|
||||
|
||||
import { ALLOWED_USERNAME_CHANGE_FREQUENCY } from "coral-common/constants";
|
||||
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
|
||||
import { ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION } from "coral-common/constants";
|
||||
import { reduceSeconds } from "coral-common/helpers/i18n";
|
||||
import TIME from "coral-common/time";
|
||||
import getAuthenticationIntegrations from "coral-framework/helpers/getAuthenticationIntegrations";
|
||||
import { useCoralContext } from "coral-framework/lib/bootstrap";
|
||||
import { InvalidRequestError } from "coral-framework/lib/errors";
|
||||
@@ -50,9 +51,10 @@ import UpdateUsernameMutation from "./UpdateUsernameMutation";
|
||||
|
||||
import styles from "./ChangeUsernameContainer.css";
|
||||
|
||||
const FREQUENCYSCALED = reduceSeconds(ALLOWED_USERNAME_CHANGE_FREQUENCY, [
|
||||
UNIT.DAYS,
|
||||
]);
|
||||
const FREQUENCYSCALED = reduceSeconds(
|
||||
ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION,
|
||||
[TIME.DAY]
|
||||
);
|
||||
|
||||
interface Props {
|
||||
viewer: ViewerData;
|
||||
@@ -107,7 +109,8 @@ const ChangeUsernameContainer: FunctionComponent<Props> = ({
|
||||
if (username && username.history.length > 1) {
|
||||
const lastUsernameEditAllowed = new Date();
|
||||
lastUsernameEditAllowed.setSeconds(
|
||||
lastUsernameEditAllowed.getSeconds() - ALLOWED_USERNAME_CHANGE_FREQUENCY
|
||||
lastUsernameEditAllowed.getSeconds() -
|
||||
ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION
|
||||
);
|
||||
const lastUsernameEdit =
|
||||
username.history[username.history.length - 1].createdAt;
|
||||
@@ -122,7 +125,9 @@ const ChangeUsernameContainer: FunctionComponent<Props> = ({
|
||||
const date = new Date(
|
||||
username.history[username.history.length - 1].createdAt
|
||||
);
|
||||
date.setSeconds(date.getSeconds() + ALLOWED_USERNAME_CHANGE_FREQUENCY);
|
||||
date.setSeconds(
|
||||
date.getSeconds() + ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION
|
||||
);
|
||||
return date;
|
||||
}
|
||||
return null;
|
||||
|
||||
+4
-5
@@ -1,8 +1,7 @@
|
||||
import { DateTime } from "luxon";
|
||||
import { graphql } from "react-relay";
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
import { SCHEDULED_DELETION_TIMESPAN_DAYS } from "coral-common/constants";
|
||||
import { SCHEDULED_DELETION_WINDOW_DURATION } from "coral-common/constants";
|
||||
import { getViewer } from "coral-framework/helpers";
|
||||
import {
|
||||
commitMutationPromiseNormalized,
|
||||
@@ -49,9 +48,9 @@ const RequestAccountDeletionMutation = createMutation(
|
||||
},
|
||||
optimisticUpdater: store => {
|
||||
const viewer = getViewer(environment)!;
|
||||
const deletionDate = DateTime.fromJSDate(new Date())
|
||||
.plus({ days: SCHEDULED_DELETION_TIMESPAN_DAYS })
|
||||
.toISO();
|
||||
const deletionDate = new Date(
|
||||
Date.now() + SCHEDULED_DELETION_WINDOW_DURATION * 1000
|
||||
).toISOString();
|
||||
const viewerProxy = store.get(viewer.id);
|
||||
if (viewerProxy !== null) {
|
||||
viewerProxy.setValue(deletionDate, "scheduledDeletionDate");
|
||||
|
||||
@@ -3,8 +3,9 @@ import cn from "classnames";
|
||||
import React, { FunctionComponent, useCallback } from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { DOWNLOAD_LIMIT_TIMEFRAME } from "coral-common/constants";
|
||||
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
|
||||
import { DOWNLOAD_LIMIT_TIMEFRAME_DURATION } from "coral-common/constants";
|
||||
import { reduceSeconds } from "coral-common/helpers/i18n";
|
||||
import TIME from "coral-common/time";
|
||||
import { useCoralContext } from "coral-framework/lib/bootstrap";
|
||||
import { useMutation, withFragmentContainer } from "coral-framework/lib/relay";
|
||||
import CLASSES from "coral-stream/classes";
|
||||
@@ -34,8 +35,8 @@ const DownloadCommentsContainer: FunctionComponent<Props> = ({ viewer }) => {
|
||||
? Math.ceil((Date.now() - lastDownloadedAt.getTime()) / 1000)
|
||||
: 0;
|
||||
const canDownload =
|
||||
!lastDownloadedAt || sinceLastDownload >= DOWNLOAD_LIMIT_TIMEFRAME;
|
||||
const tilCanDownload = DOWNLOAD_LIMIT_TIMEFRAME - sinceLastDownload;
|
||||
!lastDownloadedAt || sinceLastDownload >= DOWNLOAD_LIMIT_TIMEFRAME_DURATION;
|
||||
const tilCanDownload = DOWNLOAD_LIMIT_TIMEFRAME_DURATION - sinceLastDownload;
|
||||
const formatter = new Intl.DateTimeFormat(locales, {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
@@ -46,9 +47,9 @@ const DownloadCommentsContainer: FunctionComponent<Props> = ({ viewer }) => {
|
||||
timeZoneName: "short",
|
||||
});
|
||||
const { scaled, unit } = reduceSeconds(tilCanDownload, [
|
||||
UNIT.DAYS,
|
||||
UNIT.HOURS,
|
||||
UNIT.MINUTES,
|
||||
TIME.DAY,
|
||||
TIME.HOUR,
|
||||
TIME.MINUTE,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -24,7 +24,7 @@ it("renders correctly with specified units", () => {
|
||||
value: "",
|
||||
disabled: false,
|
||||
onChange: noop,
|
||||
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.HOURS],
|
||||
units: [DURATION_UNIT.SECOND, DURATION_UNIT.HOUR],
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<DurationField {...props} />);
|
||||
@@ -37,7 +37,7 @@ it("use best matching unit", () => {
|
||||
value: "3600",
|
||||
disabled: false,
|
||||
onChange: noop,
|
||||
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
|
||||
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<DurationField {...props} />);
|
||||
@@ -50,7 +50,7 @@ it("use initial unit if 0", () => {
|
||||
value: "0",
|
||||
disabled: false,
|
||||
onChange: noop,
|
||||
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
|
||||
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<DurationField {...props} />);
|
||||
@@ -63,7 +63,7 @@ it("accepts invalid input", () => {
|
||||
value: "this is so invalid",
|
||||
disabled: false,
|
||||
onChange: noop,
|
||||
units: [DURATION_UNIT.SECONDS, DURATION_UNIT.MINUTES, DURATION_UNIT.HOURS],
|
||||
units: [DURATION_UNIT.SECOND, DURATION_UNIT.MINUTE, DURATION_UNIT.HOUR],
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<DurationField {...props} />);
|
||||
|
||||
@@ -8,7 +8,7 @@ import React, {
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import { UNIT } from "coral-common/helpers/i18n";
|
||||
import TIME from "coral-common/time";
|
||||
import { Flex } from "coral-ui/components";
|
||||
|
||||
import { Option, SelectField } from "../SelectField";
|
||||
@@ -20,32 +20,32 @@ import styles from "./DurationField.css";
|
||||
* DURATION_UNIT are units that can be used in the
|
||||
* DurationField components.
|
||||
*/
|
||||
export const DURATION_UNIT = UNIT;
|
||||
export const DURATION_UNIT = TIME;
|
||||
|
||||
const DURATION_UNIT_MAP = {
|
||||
[DURATION_UNIT.SECONDS]: "second",
|
||||
[DURATION_UNIT.MINUTES]: "minute",
|
||||
[DURATION_UNIT.HOURS]: "hour",
|
||||
[DURATION_UNIT.DAYS]: "day",
|
||||
[DURATION_UNIT.WEEKS]: "week",
|
||||
[DURATION_UNIT.SECOND]: "second",
|
||||
[DURATION_UNIT.MINUTE]: "minute",
|
||||
[DURATION_UNIT.HOUR]: "hour",
|
||||
[DURATION_UNIT.DAY]: "day",
|
||||
[DURATION_UNIT.WEEK]: "week",
|
||||
};
|
||||
|
||||
interface Props extends Pick<TextFieldProps, "color" | "name" | "disabled"> {
|
||||
value: string;
|
||||
onChange: (v: string) => void;
|
||||
/** Specifiy units to include */
|
||||
units?: ReadonlyArray<UNIT>;
|
||||
units?: ReadonlyArray<TIME>;
|
||||
}
|
||||
|
||||
function convertToSeconds(value: string, unit?: UNIT) {
|
||||
function convertToSeconds(value: string, unit?: TIME) {
|
||||
const parsed = parseInt(value, 10);
|
||||
return (isNaN(parsed) || !unit ? value : parsed * unit).toString();
|
||||
}
|
||||
|
||||
function convertFromSeconds(
|
||||
value: string,
|
||||
units: ReadonlyArray<UNIT>,
|
||||
unit?: UNIT
|
||||
units: ReadonlyArray<TIME>,
|
||||
unit?: TIME
|
||||
) {
|
||||
const parsed = parseInt(value, 10);
|
||||
|
||||
@@ -77,7 +77,7 @@ function convertFromSeconds(
|
||||
*/
|
||||
const DurationField: FunctionComponent<Props> = ({
|
||||
value,
|
||||
units = [UNIT.HOURS, UNIT.DAYS, UNIT.WEEKS],
|
||||
units = [TIME.HOUR, TIME.DAY, TIME.WEEK],
|
||||
onChange,
|
||||
disabled,
|
||||
name,
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
font-family: var(--v2-font-family-primary);
|
||||
font-weight: var(--v2-font-weight-primary-regular);
|
||||
font-size: var(--v2-font-size-3);
|
||||
line-height: var(--v2-line-height-tall);
|
||||
line-height: var(--v2-line-height-reset);
|
||||
appearance: none;
|
||||
outline: none;
|
||||
color: var(--v2-palette-input-value);
|
||||
|
||||
@@ -277,7 +277,6 @@ const variables2 = {
|
||||
bodyShort: 1.3,
|
||||
reset: 1,
|
||||
title: 1.15,
|
||||
tall: 1.3,
|
||||
},
|
||||
fontSize: {
|
||||
1: "0.75rem",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import TIME from "./time";
|
||||
|
||||
/**
|
||||
* CLIENT_ID_HEADER references the name of the header used to extract/send the
|
||||
* client ID to enable automatic de-duplication.
|
||||
@@ -36,30 +38,32 @@ export const TOXICITY_ENDPOINT_DEFAULT =
|
||||
"https://commentanalyzer.googleapis.com/v1alpha1";
|
||||
|
||||
/**
|
||||
* DOWNLOAD_LIMIT_TIMEFRAME is the number of seconds that a given download may
|
||||
* be made within.
|
||||
* DOWNLOAD_LIMIT_TIMEFRAME_DURATION is the number of seconds that a given
|
||||
* download may be made within.
|
||||
*/
|
||||
export const DOWNLOAD_LIMIT_TIMEFRAME = 14 * 86400;
|
||||
export const DOWNLOAD_LIMIT_TIMEFRAME_DURATION = 14 * TIME.DAY;
|
||||
|
||||
/**
|
||||
* ALLOWED_USERNAME_CHANGE_FREQUENCY is the length of time in seconds a user must wait after changing their username to change it again.
|
||||
* ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION is the length of time in seconds
|
||||
* a user must wait after changing their username to change it again.
|
||||
*/
|
||||
export const ALLOWED_USERNAME_CHANGE_FREQUENCY = 14 * 86400;
|
||||
export const ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION = 14 * TIME.DAY;
|
||||
|
||||
/**
|
||||
* SCHEDULED_DELETION_TIMESPAN_DAYS is the length of time in days a user
|
||||
* will have to wait for their account to be deleted after requesting a
|
||||
* deletion.
|
||||
* SCHEDULED_DELETION_TIMEFRAME is the length of time in seconds a user will
|
||||
* have to wait for their account to be deleted after requesting a deletion.
|
||||
*/
|
||||
export const SCHEDULED_DELETION_TIMESPAN_DAYS = 14;
|
||||
export const SCHEDULED_DELETION_WINDOW_DURATION = 1 * TIME.DAY;
|
||||
|
||||
/**
|
||||
* DEFAULT_SESSION_LENTTH is the length of time in seconds a session is valid for unless configured in tenant.
|
||||
* DEFAULT_SESSION_DURATION is the length of time in seconds a session is valid
|
||||
* for unless configured in tenant.
|
||||
*/
|
||||
export const DEFAULT_SESSION_LENGTH = 7776000;
|
||||
export const DEFAULT_SESSION_DURATION = 90 * TIME.DAY;
|
||||
|
||||
/**
|
||||
* COMMENT_REPEAT_POST_TIMESPAN is the length of time in seconds that a previous comment ID is stored for a
|
||||
* user to prevent them from posting the same comment repeatedly.
|
||||
* COMMENT_REPEAT_POST_DURATION is the length of time in seconds that a
|
||||
* previous comment ID is stored for a user to prevent them from posting the
|
||||
* same comment repeatedly.
|
||||
*/
|
||||
export const COMMENT_REPEAT_POST_TIMESPAN = 21600;
|
||||
export const COMMENT_REPEAT_POST_DURATION = 6 * TIME.MINUTE;
|
||||
|
||||
@@ -1,29 +1,19 @@
|
||||
/**
|
||||
* UNIT are units that can be used in the
|
||||
* DurationField components.
|
||||
*/
|
||||
export enum UNIT {
|
||||
SECONDS = 1,
|
||||
MINUTES = 60,
|
||||
HOURS = 3600,
|
||||
DAYS = 86400,
|
||||
WEEKS = 604800,
|
||||
}
|
||||
import TIME from "coral-common/time";
|
||||
|
||||
export const UNIT_MAP = {
|
||||
[UNIT.SECONDS]: "second",
|
||||
[UNIT.MINUTES]: "minute",
|
||||
[UNIT.HOURS]: "hour",
|
||||
[UNIT.DAYS]: "day",
|
||||
[UNIT.WEEKS]: "week",
|
||||
[TIME.SECOND]: "second",
|
||||
[TIME.MINUTE]: "minute",
|
||||
[TIME.HOUR]: "hour",
|
||||
[TIME.DAY]: "day",
|
||||
[TIME.WEEK]: "week",
|
||||
};
|
||||
|
||||
export const DEFAULT_UNITS = [
|
||||
UNIT.WEEKS,
|
||||
UNIT.DAYS,
|
||||
UNIT.HOURS,
|
||||
UNIT.MINUTES,
|
||||
UNIT.SECONDS,
|
||||
TIME.WEEK,
|
||||
TIME.DAY,
|
||||
TIME.HOUR,
|
||||
TIME.MINUTE,
|
||||
TIME.SECOND,
|
||||
];
|
||||
|
||||
type ValueOf<T> = T[keyof T];
|
||||
@@ -37,11 +27,11 @@ export interface ScaledUnit {
|
||||
|
||||
export default function reduceSeconds(
|
||||
value: number,
|
||||
units: UNIT[] = DEFAULT_UNITS
|
||||
units: TIME[] = DEFAULT_UNITS
|
||||
): ScaledUnit {
|
||||
// Find the largest match for the smallest number.
|
||||
const unit: keyof typeof UNIT_MAP =
|
||||
units.find(compare => value >= compare) || UNIT.SECONDS;
|
||||
units.find(compare => value >= compare) || TIME.SECOND;
|
||||
|
||||
// Scale the value to the unit.
|
||||
const scaled = Math.round((value / unit) * 100) / 100;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* TIME represends various constants for second representations of times.
|
||||
*/
|
||||
enum TIME {
|
||||
SECOND = 1,
|
||||
MINUTE = 60 * TIME.SECOND,
|
||||
HOUR = 60 * TIME.MINUTE,
|
||||
DAY = 24 * TIME.HOUR,
|
||||
WEEK = 7 * TIME.DAY,
|
||||
}
|
||||
|
||||
export default TIME;
|
||||
@@ -5,13 +5,15 @@ import { MongoError } from "mongodb";
|
||||
import uuid from "uuid";
|
||||
import { VError } from "verror";
|
||||
|
||||
import { ALLOWED_USERNAME_CHANGE_FREQUENCY } from "coral-common/constants";
|
||||
import { ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION } from "coral-common/constants";
|
||||
import { ERROR_CODES, ERROR_TYPES } from "coral-common/errors";
|
||||
import { reduceSeconds, UNIT } from "coral-common/helpers/i18n";
|
||||
import { reduceSeconds } from "coral-common/helpers/i18n";
|
||||
import TIME from "coral-common/time";
|
||||
import { Writable } from "coral-common/types";
|
||||
import { translate } from "coral-server/services/i18n";
|
||||
|
||||
import { Writable } from "coral-common/types";
|
||||
import { GQLUSER_AUTH_CONDITIONS } from "coral-server/graph/schema/__generated__/types";
|
||||
|
||||
import { ERROR_TRANSLATIONS } from "./translations";
|
||||
|
||||
/**
|
||||
@@ -315,9 +317,10 @@ export class UsernameAlreadySetError extends CoralError {
|
||||
|
||||
export class UsernameUpdatedWithinWindowError extends CoralError {
|
||||
constructor(lastUpdate: Date) {
|
||||
const { scaled, unit } = reduceSeconds(ALLOWED_USERNAME_CHANGE_FREQUENCY, [
|
||||
UNIT.DAYS,
|
||||
]);
|
||||
const { scaled, unit } = reduceSeconds(
|
||||
ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION,
|
||||
[TIME.DAY]
|
||||
);
|
||||
super({
|
||||
code: ERROR_CODES.USERNAME_UPDATED_WITHIN_WINDOW,
|
||||
context: {
|
||||
|
||||
@@ -45,13 +45,9 @@ export function getStoryClosedAt(
|
||||
if (tenant.closeCommenting.auto) {
|
||||
// Auto-close stream has been enabled, convert the createdAt time into the
|
||||
// closedAt time by adding the closedTimeout.
|
||||
return (
|
||||
DateTime.fromJSDate(story.createdAt)
|
||||
// closedTimeout is in seconds, so multiply by 1000 to get
|
||||
// milliseconds.
|
||||
.plus(tenant.closeCommenting.timeout * 1000)
|
||||
.toJSDate()
|
||||
);
|
||||
return DateTime.fromJSDate(story.createdAt)
|
||||
.plus({ seconds: tenant.closeCommenting.timeout })
|
||||
.toJSDate();
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
@@ -2,8 +2,9 @@ import { isEmpty } from "lodash";
|
||||
import { Db } from "mongodb";
|
||||
import uuid from "uuid";
|
||||
|
||||
import { DEFAULT_SESSION_LENGTH } from "coral-common/constants";
|
||||
import { DEFAULT_SESSION_DURATION } from "coral-common/constants";
|
||||
import { LanguageCode } from "coral-common/helpers/i18n/locales";
|
||||
import TIME from "coral-common/time";
|
||||
import { DeepPartial, Omit, Sub } from "coral-common/types";
|
||||
import { dotize } from "coral-common/utils/dotize";
|
||||
import { Settings } from "coral-server/models/settings";
|
||||
@@ -99,16 +100,12 @@ export async function createTenant(
|
||||
premodLinksEnable: false,
|
||||
closeCommenting: {
|
||||
auto: false,
|
||||
// 2 weeks timeout.
|
||||
timeout: 60 * 60 * 24 * 7 * 2,
|
||||
timeout: 2 * TIME.WEEK,
|
||||
},
|
||||
disableCommenting: {
|
||||
enabled: false,
|
||||
},
|
||||
|
||||
// 30 seconds edit window length.
|
||||
editCommentWindowLength: 30,
|
||||
|
||||
editCommentWindowLength: 30 * TIME.SECOND,
|
||||
charCount: {
|
||||
enabled: false,
|
||||
},
|
||||
@@ -117,7 +114,7 @@ export async function createTenant(
|
||||
banned: [],
|
||||
},
|
||||
auth: {
|
||||
sessionDuration: DEFAULT_SESSION_LENGTH,
|
||||
sessionDuration: DEFAULT_SESSION_DURATION,
|
||||
integrations: {
|
||||
local: {
|
||||
enabled: true,
|
||||
@@ -169,8 +166,7 @@ export async function createTenant(
|
||||
},
|
||||
recentCommentHistory: {
|
||||
enabled: false,
|
||||
// 7 days in seconds.
|
||||
timeFrame: 604800,
|
||||
timeFrame: 7 * TIME.DAY,
|
||||
// Rejection rate defaulting to 30%, once exceeded, comments will be
|
||||
// pre-moderated.
|
||||
triggerRejectionRate: 0.3,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Queue, { Job, Queue as QueueType } from "bull";
|
||||
import Logger from "bunyan";
|
||||
|
||||
import TIME from "coral-common/time";
|
||||
import logger from "coral-server/logger";
|
||||
|
||||
export interface TaskOptions<T, U = any> {
|
||||
@@ -31,11 +32,10 @@ export default class Task<T, U = any> {
|
||||
// with completed entries if we don't need to.
|
||||
removeOnComplete: true,
|
||||
|
||||
// By default, configure jobs to use an exponential backoff
|
||||
// strategy starting at a 10 second delay.
|
||||
// By default, configure jobs to use an exponential backoff strategy.
|
||||
backoff: {
|
||||
type: "exponential",
|
||||
delay: 10000,
|
||||
delay: 10 * TIME.SECOND,
|
||||
},
|
||||
|
||||
// Be default, try all jobs at least 5 times.
|
||||
|
||||
@@ -23,13 +23,9 @@ export function getCommentEditableUntilDate(
|
||||
tenant: Pick<Tenant, "editCommentWindowLength">,
|
||||
createdAt: Date
|
||||
): Date {
|
||||
return (
|
||||
DateTime.fromJSDate(createdAt)
|
||||
// editCommentWindowLength is in seconds, so multiply by 1000 to get
|
||||
// milliseconds.
|
||||
.plus(tenant.editCommentWindowLength * 1000)
|
||||
.toJSDate()
|
||||
);
|
||||
return DateTime.fromJSDate(createdAt)
|
||||
.plus({ seconds: tenant.editCommentWindowLength })
|
||||
.toJSDate();
|
||||
}
|
||||
|
||||
export async function addTag(
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DateTime } from "luxon";
|
||||
import { Bearer, BearerOptions } from "permit";
|
||||
import uuid from "uuid/v4";
|
||||
|
||||
import { DEFAULT_SESSION_LENGTH } from "coral-common/constants";
|
||||
import { DEFAULT_SESSION_DURATION } from "coral-common/constants";
|
||||
import { Omit } from "coral-common/types";
|
||||
import {
|
||||
AuthenticationError,
|
||||
@@ -262,7 +262,7 @@ export const signTokenString = async (
|
||||
secret,
|
||||
{
|
||||
jwtid: uuid(),
|
||||
expiresIn: DEFAULT_SESSION_LENGTH,
|
||||
expiresIn: DEFAULT_SESSION_DURATION,
|
||||
...options,
|
||||
issuer: tenant.id,
|
||||
subject: user.id,
|
||||
|
||||
@@ -2,11 +2,11 @@ import { DateTime } from "luxon";
|
||||
import { Db } from "mongodb";
|
||||
|
||||
import {
|
||||
ALLOWED_USERNAME_CHANGE_FREQUENCY,
|
||||
COMMENT_REPEAT_POST_TIMESPAN,
|
||||
DOWNLOAD_LIMIT_TIMEFRAME,
|
||||
ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION,
|
||||
COMMENT_REPEAT_POST_DURATION,
|
||||
DOWNLOAD_LIMIT_TIMEFRAME_DURATION,
|
||||
} from "coral-common/constants";
|
||||
import { SCHEDULED_DELETION_TIMESPAN_DAYS } from "coral-common/constants";
|
||||
import { SCHEDULED_DELETION_WINDOW_DURATION } from "coral-common/constants";
|
||||
import { Config } from "coral-server/config";
|
||||
import {
|
||||
DuplicateEmailError,
|
||||
@@ -369,7 +369,7 @@ export async function requestAccountDeletion(
|
||||
}
|
||||
|
||||
const deletionDate = DateTime.fromJSDate(now).plus({
|
||||
days: SCHEDULED_DELETION_TIMESPAN_DAYS,
|
||||
seconds: SCHEDULED_DELETION_WINDOW_DURATION,
|
||||
});
|
||||
|
||||
const updatedUser = await scheduleDeletionDate(
|
||||
@@ -528,7 +528,7 @@ export async function updateUsername(
|
||||
// Get the earliest date that the username could have been edited before to/
|
||||
// allow it now.
|
||||
const lastUsernameEditAllowed = DateTime.fromJSDate(now)
|
||||
.plus({ seconds: -ALLOWED_USERNAME_CHANGE_FREQUENCY })
|
||||
.plus({ seconds: -ALLOWED_USERNAME_CHANGE_TIMEFRAME_DURATION })
|
||||
.toJSDate();
|
||||
|
||||
const { history } = user.status.username;
|
||||
@@ -1118,7 +1118,7 @@ export async function requestCommentsDownload(
|
||||
if (
|
||||
user.lastDownloadedAt &&
|
||||
DateTime.fromJSDate(user.lastDownloadedAt)
|
||||
.plus({ seconds: DOWNLOAD_LIMIT_TIMEFRAME })
|
||||
.plus({ seconds: DOWNLOAD_LIMIT_TIMEFRAME_DURATION })
|
||||
.toSeconds() >= DateTime.fromJSDate(now).toSeconds()
|
||||
) {
|
||||
throw new Error("requested download too early");
|
||||
@@ -1212,7 +1212,7 @@ export async function updateUserLastCommentID(
|
||||
) {
|
||||
const key = userLastCommentIDKey(tenant, user);
|
||||
|
||||
await redis.set(key, commentID, "EX", COMMENT_REPEAT_POST_TIMESPAN);
|
||||
await redis.set(key, commentID, "EX", COMMENT_REPEAT_POST_DURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,13 +42,9 @@ function getLastCommentEditableUntilDate(
|
||||
tenant: Pick<Tenant, "editCommentWindowLength">,
|
||||
now = new Date()
|
||||
): Date {
|
||||
return (
|
||||
DateTime.fromJSDate(now)
|
||||
// editCommentWindowLength is in seconds, so multiply by 1000 to get
|
||||
// milliseconds.
|
||||
.minus(tenant.editCommentWindowLength * 1000)
|
||||
.toJSDate()
|
||||
);
|
||||
return DateTime.fromJSDate(now)
|
||||
.minus({ seconds: tenant.editCommentWindowLength })
|
||||
.toJSDate();
|
||||
}
|
||||
|
||||
export type EditComment = Omit<
|
||||
|
||||
@@ -307,8 +307,8 @@ configure-wordList-suspect-wordList = Mistænkte ordliste
|
||||
|
||||
### Advanced
|
||||
configure-advanced-customCSS = Tilpasset CSS
|
||||
configure-advanced-customCSS-explanation =
|
||||
URL til et CSS-stilark, der tilsidesætter standardindlejring af streams. Kan være intern eller ekstern.
|
||||
configure-advanced-customCSS-override =
|
||||
URL til et CSS-stilark, der tilsidesætter standardindlejring af streams.
|
||||
|
||||
configure-advanced-permittedDomains = Tilladte domæner
|
||||
configure-advanced-liveUpdates = Kommentar Stream Live-opdateringer
|
||||
|
||||
@@ -334,7 +334,7 @@ configure-moderation-newCommenters-description =
|
||||
When this is active, initial comments by a new commenter will be sent to Pending
|
||||
for moderator approval before publication.
|
||||
configure-moderation-newCommenters-enable-description = Enable pre-moderation for new commenters
|
||||
configure-moderation-newCommenters-approvedCommentsThreshold = Number of first comments sent for approval
|
||||
configure-moderation-newCommenters-approvedCommentsThreshold = Number of comments that must be approved
|
||||
configure-moderation-newCommenters-approvedCommentsThreshold-description =
|
||||
The number of comments a user must have approved before they do
|
||||
not have to be premoderated
|
||||
@@ -361,8 +361,8 @@ configure-wordList-suspect-wordListDetailInstructions =
|
||||
|
||||
### Advanced
|
||||
configure-advanced-customCSS = Custom CSS
|
||||
configure-advanced-customCSS-explanation =
|
||||
URL of a CSS stylesheet that will override default Embed Stream styles. Can be internal or external.
|
||||
configure-advanced-customCSS-override =
|
||||
URL of a CSS stylesheet that will override default Embed Stream styles.
|
||||
|
||||
configure-advanced-permittedDomains = Permitted domains
|
||||
configure-advanced-permittedDomains-description =
|
||||
|
||||
+878
-878
File diff suppressed because it is too large
Load Diff
@@ -343,8 +343,8 @@ configure-wordList-suspect-wordListDetailInstructions =
|
||||
|
||||
### Advanced
|
||||
configure-advanced-customCSS = CSS Customizado
|
||||
configure-advanced-customCSS-explanation =
|
||||
URL de uma folha de estilo CSS que substituirá o estilo padrão dos fluxos de comentário das páginas. Pode ser interno ou externo.
|
||||
configure-advanced-customCSS-override =
|
||||
URL de uma folha de estilo CSS que substituirá o estilo padrão dos fluxos de comentário das páginas.
|
||||
|
||||
configure-advanced-permittedDomains = Domínios Permitidos
|
||||
configure-advanced-permittedDomains-description =
|
||||
@@ -366,7 +366,7 @@ configure-advanced-embedCode-comment =
|
||||
Descomente estas linhas e substitua com o ID da
|
||||
história e a URL do seu CMS
|
||||
Substitua essas linhas pelo ID do ID e URL da história do seu CMS para fornecer a maior integração.
|
||||
Consulte a nossa documentação em https://docs.coralproject.net para todas as
|
||||
Consulte a nossa documentação em https://docs.coralproject.net para todas as
|
||||
opções de configuração.
|
||||
|
||||
## Decision History
|
||||
@@ -374,7 +374,7 @@ decisionHistory-popover =
|
||||
.description = Uma caixa de diálogo mostrando o histórico de decisões
|
||||
decisionHistory-youWillSeeAList =
|
||||
Você verá uma lista de suas ações de moderação de postagens aqui.
|
||||
decisionHistory-showMoreButton =
|
||||
decisionHistory-showMoreButton =
|
||||
Mostrar mais
|
||||
decisionHistory-yourDecisionHistory = Seu Histórico de Decisão
|
||||
decisionHistory-rejectedCommentBy = Comentário Rejeitado por <username></username>
|
||||
@@ -386,7 +386,7 @@ decisionHistory-goToComment = Ir para o comentário
|
||||
configure-slack-header-title = Integração com o Slack
|
||||
configure-slack-description =
|
||||
Encia automaticamente os comentários da fila de moderação do Coral para canais do Slack.
|
||||
Você precisa de acesso admin do slack para realizar esta configuração. Para as etapas de
|
||||
Você precisa de acesso admin do slack para realizar esta configuração. Para as etapas de
|
||||
como criar uma app no Slack veja nossa <externalLink>documentação</externalLink>.
|
||||
configure-slack-addChannel = Adicionar Canal
|
||||
|
||||
@@ -686,8 +686,8 @@ community-banModal-banUser = Banir Usuário
|
||||
community-banModal-customize = Customizar mensagem de e-mail de banimento
|
||||
|
||||
community-suspendModal-areYouSure = Banir <strong>{ $username }</strong>?
|
||||
community-suspendModal-consequence =
|
||||
Uma vez banido, este usuário não poderá mais comentar, reagir
|
||||
community-suspendModal-consequence =
|
||||
Uma vez banido, este usuário não poderá mais comentar, reagir
|
||||
ou reportar comentários
|
||||
community-suspendModal-duration-3600 = 1 hora
|
||||
community-suspendModal-duration-10800 = 3 horas
|
||||
@@ -837,7 +837,7 @@ configure-account-features-no = Não
|
||||
configure-account-features-download-comments = Fazer o download de seus comentários
|
||||
configure-account-features-download-comments-details = Comentaristas podem fazer download de um csv do histórico de comentarista
|
||||
configure-account-features-delete-account = Excluir suas contas.
|
||||
configure-account-features-delete-account-details =
|
||||
configure-account-features-delete-account-details =
|
||||
Remover todos os dados de comentários, nome de usuário e endereço de email do site e do banco de dados
|
||||
|
||||
configure-account-features-delete-account-fieldDescriptions =
|
||||
|
||||
Reference in New Issue
Block a user