mirror of
https://github.com/wassname/talk.git
synced 2026-07-22 13:00:29 +08:00
[CORL-504] Configure reaction language (#2490)
* add reaction config to settings * fix reaction preview styles * update snaps and fixtures * add i18n strings * update spec * ensure dynamic value shows in reaction config preview
This commit is contained in:
@@ -8,6 +8,7 @@ import ClosingCommentStreamsConfigContainer from "./ClosingCommentStreamsConfigC
|
||||
import CommentEditingConfigContainer from "./CommentEditingConfigContainer";
|
||||
import CommentLengthConfigContainer from "./CommentLengthConfigContainer";
|
||||
import GuidelinesConfigContainer from "./GuidelinesConfigContainer";
|
||||
import ReactionConfigContainer from "./ReactionConfigContainer";
|
||||
import SitewideCommentingConfigContainer from "./SitewideCommentingConfigContainer";
|
||||
|
||||
interface Props {
|
||||
@@ -16,6 +17,7 @@ interface Props {
|
||||
PropTypesOf<typeof CommentLengthConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof CommentEditingConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof ClosedStreamMessageConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof ReactionConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof ClosingCommentStreamsConfigContainer>["settings"] &
|
||||
PropTypesOf<typeof SitewideCommentingConfigContainer>["settings"];
|
||||
onInitValues: (values: any) => void;
|
||||
@@ -57,6 +59,11 @@ const General: FunctionComponent<Props> = ({
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
<ReactionConfigContainer
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
onInitValues={onInitValues}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ const enhanced = withFragmentContainer<Props>({
|
||||
...ClosedStreamMessageConfigContainer_settings
|
||||
...ClosingCommentStreamsConfigContainer_settings
|
||||
...SitewideCommentingConfigContainer_settings
|
||||
...ReactionConfigContainer_settings
|
||||
}
|
||||
`,
|
||||
})(GeneralConfigContainer);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.textInput {
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.reactionButton {
|
||||
cursor: not-allowed !important;
|
||||
color: var(--palette-grey-main) !important;
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Field } from "react-final-form";
|
||||
|
||||
import { ReactionConfigContainer_settings as SettingsData } from "coral-admin/__generated__/ReactionConfigContainer_settings.graphql";
|
||||
import { required } from "coral-framework/lib/validation";
|
||||
import ReactionButton from "coral-stream/tabs/Comments/Comment/ReactionButton/ReactionButton";
|
||||
import {
|
||||
FieldSet,
|
||||
Flex,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputLabel,
|
||||
Option,
|
||||
SelectField,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "coral-ui/components";
|
||||
|
||||
import Header from "../../Header";
|
||||
import ValidationMessage from "../../ValidationMessage";
|
||||
|
||||
import styles from "./ReactionConfig.css";
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
settings: SettingsData;
|
||||
}
|
||||
|
||||
const ReactionsConfig: FunctionComponent<Props> = ({ disabled, settings }) => (
|
||||
<HorizontalGutter size="oneAndAHalf" container={<FieldSet />}>
|
||||
<Localized id="configure-general-reactions-title">
|
||||
<Header container="legend">Reactions</Header>
|
||||
</Localized>
|
||||
<Localized id="configure-general-reactions-explanation" strong={<strong />}>
|
||||
<Typography variant="detail">
|
||||
Allow your community to engage with one another and express themselves
|
||||
with one-click reactions. By default, Coral allows commenters to
|
||||
"Respect" each other's comments, but you may customize reaction text
|
||||
based on the needs of your community.
|
||||
</Typography>
|
||||
</Localized>
|
||||
<HorizontalGutter size="double">
|
||||
<FormField>
|
||||
<Field name="reaction.label" validate={required}>
|
||||
{({ input, meta }) => (
|
||||
<Flex itemGutter="double">
|
||||
<HorizontalGutter>
|
||||
<Localized id="configure-general-reactions-label">
|
||||
<InputLabel>Reaction label</InputLabel>
|
||||
</Localized>
|
||||
<Localized id="configure-general-reactions-input">
|
||||
<TextField
|
||||
className={styles.textInput}
|
||||
id={input.name}
|
||||
type="text"
|
||||
fullWidth
|
||||
placeholder="E.g. Respect"
|
||||
disabled={disabled}
|
||||
{...input}
|
||||
/>
|
||||
</Localized>
|
||||
<ValidationMessage fullWidth meta={meta} />
|
||||
</HorizontalGutter>
|
||||
<HorizontalGutter>
|
||||
<Localized id="configure-general-reactions-preview">
|
||||
<Typography variant="heading3">Preview</Typography>
|
||||
</Localized>
|
||||
<ReactionButton
|
||||
readOnly
|
||||
className={styles.reactionButton}
|
||||
reacted={false}
|
||||
label={input.value}
|
||||
labelActive={settings.reaction.labelActive}
|
||||
icon={settings.reaction.icon}
|
||||
iconActive={settings.reaction.iconActive}
|
||||
totalReactions={0}
|
||||
onClick={() => null}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
</Flex>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
<FormField>
|
||||
<Field name="reaction.labelActive" validate={required}>
|
||||
{({ input, meta }) => (
|
||||
<Flex itemGutter="double">
|
||||
<HorizontalGutter>
|
||||
<Localized id="configure-general-reactions-active-label">
|
||||
<InputLabel>Active reaction label</InputLabel>
|
||||
</Localized>
|
||||
<Localized id="configure-general-reactions-active-input">
|
||||
<TextField
|
||||
className={styles.textInput}
|
||||
id={input.name}
|
||||
type="text"
|
||||
placeholder="E.g. Respected"
|
||||
fullWidth
|
||||
disabled={disabled}
|
||||
{...input}
|
||||
/>
|
||||
</Localized>
|
||||
<ValidationMessage fullWidth meta={meta} />
|
||||
</HorizontalGutter>
|
||||
<HorizontalGutter>
|
||||
<Localized id="configure-general-reactions-preview">
|
||||
<Typography variant="heading3">Preview</Typography>
|
||||
</Localized>
|
||||
<ReactionButton
|
||||
className={styles.reactionButton}
|
||||
readOnly
|
||||
reacted
|
||||
label={settings.reaction.label}
|
||||
labelActive={input.value}
|
||||
icon={settings.reaction.icon}
|
||||
iconActive={settings.reaction.iconActive}
|
||||
totalReactions={0}
|
||||
onClick={() => null}
|
||||
/>
|
||||
</HorizontalGutter>
|
||||
</Flex>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
<FormField>
|
||||
<Field name="reaction.sortLabel" validate={required}>
|
||||
{({ input, meta }) => (
|
||||
<Flex itemGutter="double">
|
||||
<HorizontalGutter>
|
||||
<Localized id="configure-general-reactions-sort-label">
|
||||
<InputLabel>Sort label</InputLabel>
|
||||
</Localized>
|
||||
<Localized id="configure-general-reactions-sort-input">
|
||||
<TextField
|
||||
id={input.name}
|
||||
className={styles.textInput}
|
||||
type="text"
|
||||
placeholder="E.g. Most respected"
|
||||
fullWidth
|
||||
disabled={disabled}
|
||||
{...input}
|
||||
/>
|
||||
</Localized>
|
||||
<ValidationMessage fullWidth meta={meta} />
|
||||
</HorizontalGutter>
|
||||
<HorizontalGutter>
|
||||
<Localized id="configure-general-reactions-preview">
|
||||
<Typography variant="heading3">Preview</Typography>
|
||||
</Localized>
|
||||
<Flex justifyContent="center" itemGutter>
|
||||
<Localized id="configure-general-reaction-sortMenu-sortBy">
|
||||
<Typography variant="bodyCopyBold">Sort By</Typography>
|
||||
</Localized>
|
||||
<SelectField>
|
||||
<Option value={input.value}>{input.value}</Option>{" "}
|
||||
</SelectField>
|
||||
</Flex>
|
||||
</HorizontalGutter>
|
||||
</Flex>
|
||||
)}
|
||||
</Field>
|
||||
</FormField>
|
||||
</HorizontalGutter>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
export default ReactionsConfig;
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { ReactionConfigContainer_settings as SettingsData } from "coral-admin/__generated__/ReactionConfigContainer_settings.graphql";
|
||||
import { withFragmentContainer } from "coral-framework/lib/relay";
|
||||
|
||||
import ReactionConfig from "./ReactionConfig";
|
||||
|
||||
interface Props {
|
||||
settings: SettingsData;
|
||||
onInitValues: (values: SettingsData) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
class ReactionConfigContainer extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
props.onInitValues(props.settings);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { disabled, settings } = this.props;
|
||||
return <ReactionConfig settings={settings} disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment ReactionConfigContainer_settings on Settings {
|
||||
reaction {
|
||||
label
|
||||
labelActive
|
||||
sortLabel
|
||||
icon
|
||||
iconActive
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(ReactionConfigContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -737,6 +737,219 @@ moderation panel.
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset
|
||||
className="FieldSet-root Box-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<legend
|
||||
className="Box-root Typography-root Typography-heading1 Typography-colorTextPrimary Header-root"
|
||||
>
|
||||
Reactions
|
||||
</legend>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-detail Typography-colorTextPrimary"
|
||||
>
|
||||
Allow your community to engage with one another and express themselves
|
||||
with one-click reactions. By default, Coral allows commenters to
|
||||
"Respect" each other's comments, but you may customize reaction text
|
||||
based on the needs of your community.
|
||||
</p>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-doubleItemGutter gutter"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
>
|
||||
Reaction label
|
||||
</label>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth ReactionConfig-textInput"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={false}
|
||||
id="reaction.label"
|
||||
name="reaction.label"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="E.g. Respect"
|
||||
type="text"
|
||||
value="Reaction"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Box-root Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
Preview
|
||||
</h1>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost Button-disabled ReactionButton-readOnly ReactionConfig-reactionButton"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Reaction
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-doubleItemGutter gutter"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
>
|
||||
Active reaction label
|
||||
</label>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth ReactionConfig-textInput"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={false}
|
||||
id="reaction.labelActive"
|
||||
name="reaction.labelActive"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="E.g. Respected"
|
||||
type="text"
|
||||
value="reacted"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Box-root Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
Preview
|
||||
</h1>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost Button-disabled ReactionButton-readOnly ReactionConfig-reactionButton"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
reacted
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-doubleItemGutter gutter"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
>
|
||||
Sort label
|
||||
</label>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth ReactionConfig-textInput"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={false}
|
||||
id="reaction.sortLabel"
|
||||
name="reaction.sortLabel"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="E.g. Most respected"
|
||||
type="text"
|
||||
value="Reactions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Box-root Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
Preview
|
||||
</h1>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-itemGutter Flex-justifyCenter gutter"
|
||||
>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-bodyCopyBold Typography-colorTextPrimary"
|
||||
>
|
||||
Sort by
|
||||
</p>
|
||||
<span
|
||||
className="SelectField-root"
|
||||
>
|
||||
<select
|
||||
className="SelectField-select"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
>
|
||||
<option
|
||||
value="Reactions"
|
||||
>
|
||||
Reactions
|
||||
</option>
|
||||
|
||||
</select>
|
||||
<span
|
||||
aria-hidden={true}
|
||||
className="SelectField-afterWrapper"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
expand_more
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,6 +46,12 @@ export const settings = createFixture<GQLSettings>({
|
||||
timeout: 604800,
|
||||
message: "Comments are closed on this story.",
|
||||
},
|
||||
reaction: {
|
||||
label: "Reaction",
|
||||
labelActive: "reacted",
|
||||
sortLabel: "Reactions",
|
||||
icon: "icon",
|
||||
},
|
||||
email: {
|
||||
enabled: true,
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import cn from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import { Button, ButtonIcon, MatchMedia } from "coral-ui/components";
|
||||
import { ButtonProps } from "coral-ui/components/Button";
|
||||
|
||||
import styles from "./ReactionButton.css";
|
||||
|
||||
@@ -14,19 +15,20 @@ interface ReactionButtonProps {
|
||||
icon: string;
|
||||
iconActive: string | null;
|
||||
readOnly?: boolean;
|
||||
// color: string;
|
||||
className?: string;
|
||||
color?: typeof styles & ButtonProps["color"];
|
||||
}
|
||||
|
||||
class ReactionButton extends React.Component<ReactionButtonProps> {
|
||||
public render() {
|
||||
const { totalReactions, reacted, readOnly } = this.props;
|
||||
const { totalReactions, reacted, readOnly, className } = this.props;
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="small"
|
||||
onClick={this.props.onClick}
|
||||
disabled={readOnly}
|
||||
className={cn({ [styles.readOnly]: readOnly })}
|
||||
className={cn({ [styles.readOnly]: readOnly }, className)}
|
||||
>
|
||||
<MatchMedia gtWidth="xs">
|
||||
<ButtonIcon>
|
||||
|
||||
@@ -3043,6 +3043,45 @@ input StoryConfigurationInput {
|
||||
disableLazy: Boolean
|
||||
}
|
||||
|
||||
"""
|
||||
ReactionConfigurationInput stores the configuration for reactions used across this
|
||||
Tenant.
|
||||
"""
|
||||
input ReactionConfigurationInput {
|
||||
"""
|
||||
icon is the string representing the icon to be used for the reactions.
|
||||
"""
|
||||
icon: String
|
||||
|
||||
"""
|
||||
iconActive is the string representing the icon that should be used when the
|
||||
icon should be considered active.
|
||||
"""
|
||||
iconActive: String
|
||||
|
||||
"""
|
||||
label is the string placed beside the reaction icon to provide better context.
|
||||
"""
|
||||
label: String
|
||||
|
||||
"""
|
||||
labelActive is the string placed beside the reaction icon to provide better
|
||||
context when it has been selected.
|
||||
"""
|
||||
labelActive: String
|
||||
|
||||
"""
|
||||
sortLabel is the string placed inside of the sort menu to sort for comment
|
||||
with most reactions.
|
||||
"""
|
||||
sortLabel: String
|
||||
|
||||
"""
|
||||
color is the hex color code that can be used to change the color of the button.
|
||||
"""
|
||||
color: String
|
||||
}
|
||||
|
||||
"""
|
||||
SettingsInput is the partial type of the Settings type for performing mutations.
|
||||
"""
|
||||
@@ -3136,6 +3175,11 @@ input SettingsInput {
|
||||
stories stores the configuration around stories.
|
||||
"""
|
||||
stories: StoryConfigurationInput
|
||||
|
||||
"""
|
||||
reaction specifies the configuration for reactions.
|
||||
"""
|
||||
reaction: ReactionConfigurationInput
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
@@ -727,3 +727,21 @@ userDetails-banned-by = <strong>by</strong> { $username }
|
||||
userDetails-suspended-by = <strong>Suspended by</strong> { $username }
|
||||
userDetails-suspension-start = <strong>Start:</strong> { $timestamp }
|
||||
userDetails-suspension-end = <strong>End:</strong> { $timestamp }
|
||||
|
||||
configure-general-reactions-title = Reactions
|
||||
configure-general-reactions-explanation =
|
||||
Allow your community to engage with one another and express themselves
|
||||
with one-click reactions. By default, Coral allows commenters to
|
||||
"Respect" each other's comments, but you may customize reaction text
|
||||
based on the needs of your community.
|
||||
configure-general-reactions-label = Reaction label
|
||||
configure-general-reactions-input =
|
||||
.placehodlder = E.g. Respect
|
||||
configure-general-reactions-active-label = Active reaction label
|
||||
configure-general-reactions-active-input =
|
||||
.placehodlder = E.g. Respected
|
||||
configure-general-reactions-sort-label = Sort label
|
||||
configure-general-reactions-sort-input =
|
||||
.placehodlder = E.g. Most Respected
|
||||
configure-general-reactions-preview = Preview
|
||||
configure-general-reaction-sortMenu-sortBy = Sort by
|
||||
Reference in New Issue
Block a user