This commit is contained in:
Sean Yesmunt 2019-06-26 19:59:27 -04:00
parent 42a632eb88
commit a4d103c1d6
15 changed files with 147 additions and 149 deletions

View file

@ -0,0 +1,7 @@
import { connect } from 'react-redux';
import Comment from './view';
export default connect(
null,
null
)(Comment);

View file

@ -0,0 +1,36 @@
// @flow
import React from 'react';
import relativeDate from 'tiny-relative-date';
type Props = {
author: string,
message: string,
timePosted: number,
};
function Comment(props: Props) {
const { author, timePosted, message } = props;
return (
<li className="comment">
<div className="comment__meta card__actions--between">
<strong>{author || __('Anonymous')}</strong>
<time dateTime={timePosted}>{relativeDate(timePosted)}</time>
</div>
<p>{message}</p>
{/* The following is for adding threaded replies, upvoting and downvoting */}
{/* <div className="comment__actions card__actions--between"> */}
{/* <button className={'button button--primary'}>Reply</button> */}
{/* <span className="comment__actions-wrap"> */}
{/* <button className="comment__action upvote">Up</button> */}
{/* <button className="comment__action downvote">Down</button> */}
{/* </span> */}
{/* </div> */}
</li>
);
}
export default Comment;

View file

@ -1,11 +1,11 @@
// @flow // @flow
import { CHANNEL_NEW } from 'constants/claim';
import React from 'react'; import React from 'react';
import { FormField, Form } from 'component/common/form'; import { FormField, Form } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import ChannelSection from 'component/selectChannel'; import ChannelSection from 'component/selectChannel';
import usePersistedState from 'util/use-persisted-state'; import usePersistedState from 'util/use-persisted-state';
// props:
type Props = { type Props = {
uri: string, uri: string,
claim: StreamClaim, claim: StreamClaim,
@ -13,12 +13,11 @@ type Props = {
}; };
export function CommentCreate(props: Props) { export function CommentCreate(props: Props) {
const COMMENT_ACKNOWLEDGED = 'COMMENT_ACKNOWLEDGED';
const { createComment, claim } = props; const { createComment, claim } = props;
const { claim_id: claimId } = claim; const { claim_id: claimId } = claim;
const [commentValue, setCommentValue] = usePersistedState(`comment-${claimId}`, ''); const [commentValue, setCommentValue] = usePersistedState(`comment-${claimId}`, '');
const [commentAck, setCommentAck] = usePersistedState(COMMENT_ACKNOWLEDGED, false); const [commentAck, setCommentAck] = usePersistedState('comment-acknowledge', false);
const [channel, setChannel] = usePersistedState('COMMENT_CHANNEL', 'anonymous'); const [channel, setChannel] = usePersistedState('comment-channel', 'anonymous');
function handleCommentChange(event) { function handleCommentChange(event) {
setCommentValue(event.target.value); setCommentValue(event.target.value);
@ -32,19 +31,19 @@ export function CommentCreate(props: Props) {
setCommentAck(true); setCommentAck(true);
} }
function handleSubmit() { function handleSubmit() {
if (channel !== 'new' && commentValue.length) createComment(commentValue, claimId, channel); if (channel !== CHANNEL_NEW && commentValue.length) createComment(commentValue, claimId, channel);
setCommentValue(''); setCommentValue('');
} }
return ( return (
<React.Fragment> <React.Fragment>
{commentAck !== true && ( {commentAck !== true && (
<section className="card card--section"> <section>
<div className="card__content"> <div className="card__content">
<div className="media__title">About comments..</div> <div className="media__title">About comments..</div>
</div> </div>
<div className="card__content"> <div className="card__content">
<div className="media__subtitle">Seriously, don&apos;t comment.</div> <div className="media__subtitle">I acknowledge something.</div>
</div> </div>
<div className="card__content"> <div className="card__content">
<Button button="primary" onClick={handleCommentAck} label={__('Got it!')} /> <Button button="primary" onClick={handleCommentAck} label={__('Got it!')} />
@ -52,26 +51,26 @@ export function CommentCreate(props: Props) {
</section> </section>
)} )}
{commentAck === true && ( {commentAck === true && (
<section className="card card--section"> <section>
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<div className="card__content"> <div className="card__content">
<ChannelSection channel={channel} onChannelChange={handleChannelChange} /> <ChannelSection channel={channel} onChannelChange={handleChannelChange} />
</div> </div>
<div className="card__content"> <div className="card__content">
<FormField <FormField
disabled={channel === 'new'} disabled={channel === CHANNEL_NEW}
type="textarea" type="textarea"
name="content_description" name="content_description"
label={__('Text')} label={__('Comment')}
placeholder={__('Your comment')} placeholder={__('Your comment')}
value={commentValue} value={commentValue}
onChange={handleCommentChange} onChange={handleCommentChange}
/> />
</div> </div>
<div className="card__content"> <div className="card__actions">
<Button <Button
button="primary" button="primary"
disabled={channel === 'new' || !commentValue.length} disabled={channel === CHANNEL_NEW || !commentValue.length}
type="submit" type="submit"
label={__('Post')} label={__('Post')}
/> />

View file

@ -1,15 +1,15 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeSelectCommentsForUri, makeSelectClaimForUri, doCommentList } from 'lbry-redux'; import { makeSelectCommentsForUri, doCommentList } from 'lbry-redux';
import CommentsList from './view'; import CommentsList from './view';
const select = (state, props) => ({ const select = (state, props) => ({
comments: makeSelectCommentsForUri(props.uri)(state), comments: makeSelectCommentsForUri(props.uri)(state),
claim: makeSelectClaimForUri(props.uri)(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
fetchList: uri => dispatch(doCommentList(uri)), fetchComments: uri => dispatch(doCommentList(uri)),
}); });
export default connect( export default connect(
select, select,
perform perform

View file

@ -1,86 +1,38 @@
// @flow // @flow
import * as React from 'react'; import React, { useEffect } from 'react';
import relativeDate from 'tiny-relative-date'; import Comment from 'component/comment';
type CommentListProps = { type Props = {
comments: Array<any>, comments: Array<any>,
fetchList: string => void, fetchComments: string => void,
uri: string, uri: string,
isLoading: boolean,
}; };
type CommentProps = { function CommentList(props: Props) {
commentId: string, const { fetchComments, uri, comments } = props;
claimId: string,
author: string,
message: string,
parentId: number,
timePosted: number,
};
class CommentList extends React.PureComponent<CommentListProps> { useEffect(() => {
componentDidMount() { fetchComments(uri);
this.fetchComments(this.props); }, [fetchComments, uri]);
}
fetchComments = (props: CommentListProps) => { return (
const { fetchList, uri } = props; <ul className="comments">
fetchList(uri); {comments &&
}; comments.map(comment => {
return (
render() { <Comment
const { comments } = this.props; author={comment.channel_name}
claimId={comment.channel_id}
if (!comments) { commentId={comment.comment_id}
return null; key={comment.channel_id + comment.comment_id}
} message={comment.comment}
parentId={comment.parent_id || null}
return ( timePosted={comment.timestamp * 1000}
<section> />
<ul className="comments"> );
{comments.map(comment => { })}
return ( </ul>
<Comment );
author={comment.channel_name}
claimId={comment.channel_id}
commentId={comment.comment_id}
key={comment.channel_id + comment.comment_id}
message={comment.comment}
parentId={comment.parent_id || null}
timePosted={comment.timestamp * 1000}
/>
);
})}
</ul>
</section>
);
}
}
class Comment extends React.PureComponent<CommentProps> {
render() {
return (
<li className={this.props.parentId ? 'comment reply' : 'comment'}>
<div className="comment__meta card__actions--between">
{this.props.author && <strong>{this.props.author}</strong>}
{!this.props.author && <strong>Anonymous</strong>}
<time dateTime={this.props.timePosted}>{relativeDate(this.props.timePosted)}</time>
</div>
<div className="comment__content">{this.props.message}</div>
{/* The following is for adding threaded replies, upvoting and downvoting */}
{/* <div className="comment__actions card__actions--between"> */}
{/* <button className={'button button--primary'}>Reply</button> */}
{/* <span className="comment__actions-wrap"> */}
{/* <button className="comment__action upvote">Up</button> */}
{/* <button className="comment__action downvote">Down</button> */}
{/* </span> */}
{/* </div> */}
</li>
);
}
} }
export default CommentList; export default CommentList;

View file

@ -154,6 +154,7 @@ export class FormField extends React.PureComponent<Props> {
} else if (type === 'textarea') { } else if (type === 'textarea') {
input = ( input = (
<fieldset-section> <fieldset-section>
<label htmlFor={name}>{label}</label>
<textarea type={type} id={name} {...inputProps} /> <textarea type={type} id={name} {...inputProps} />
</fieldset-section> </fieldset-section>
); );

View file

@ -76,6 +76,8 @@ class FileViewer extends React.PureComponent<Props> {
} }
this.handleAutoplay(this.props); this.handleAutoplay(this.props);
// Commented out because it would play/pause if you were typing in the comment field
// Need a way to check if you are typing
// window.addEventListener('keydown', this.handleKeyDown); // window.addEventListener('keydown', this.handleKeyDown);
} }

View file

@ -157,7 +157,13 @@ class ChannelSection extends React.PureComponent<Props, State> {
<BusyIndicator message="Updating channels" /> <BusyIndicator message="Updating channels" />
) : ( ) : (
<fieldset-section> <fieldset-section>
<FormField name="channel" type="select" onChange={this.handleChannelChange} value={channel}> <FormField
name="channel"
label={__('Channel')}
type="select"
onChange={this.handleChannelChange}
value={channel}
>
<option value={CHANNEL_ANONYMOUS}>{__('Anonymous')}</option> <option value={CHANNEL_ANONYMOUS}>{__('Anonymous')}</option>
{channels.map(({ name }) => ( {channels.map(({ name }) => (
<option key={name} value={name}> <option key={name} value={name}>

View file

@ -287,9 +287,8 @@ class FilePage extends React.Component<Props> {
</div> </div>
<div className="media__info--large"> <div className="media__info--large">
<FileDetails uri={uri} /> <FileDetails uri={uri} />
<header className="card__header">
<h2 className="card__header">Comments</h2> <div className="media__info-title">{__('Comments')}</div>
</header>
<CommentCreate uri={uri} /> <CommentCreate uri={uri} />
<CommentsList uri={uri} /> <CommentsList uri={uri} />
</div> </div>

View file

@ -373,7 +373,7 @@ export function doChangeVolume(volume) {
}); });
}; };
} }
// HERE
export function doClickCommentButton() { export function doClickCommentButton() {
return { return {
type: ACTIONS.ADD_COMMENT, type: ACTIONS.ADD_COMMENT,

View file

@ -195,7 +195,6 @@ reducers[ACTIONS.CLEAR_UPGRADE_TIMER] = state =>
Object.assign({}, state, { Object.assign({}, state, {
checkUpgradeTimer: undefined, checkUpgradeTimer: undefined,
}); });
// HERE
reducers[ACTIONS.ADD_COMMENT] = state => reducers[ACTIONS.ADD_COMMENT] = state =>
Object.assign({}, state, { Object.assign({}, state, {

View file

@ -25,10 +25,6 @@
.card--section { .card--section {
position: relative; position: relative;
padding: var(--spacing-large); padding: var(--spacing-large);
.card__content:not(:last-of-type) {
margin-bottom: var(--spacing-large);
}
} }
.card--space-between { .card--space-between {
@ -100,6 +96,10 @@
// C O N T E N T // C O N T E N T
.card__content { .card__content {
&:not(:last-of-type) {
margin-bottom: var(--spacing-medium);
}
p:not(:last-child) { p:not(:last-child) {
margin-bottom: var(--spacing-medium); margin-bottom: var(--spacing-medium);
} }

View file

@ -1,16 +1,9 @@
.comments {
margin-top: var(--spacing-large);
}
.comment { .comment {
padding-bottom: var(--spacing-vertical-large); padding: var(--spacing-medium) 0;
&.reply {
border-left: 3px solid var(--lbry-teal-5);
padding-left: var(--spacing-vertical-large);
position: relative;
top: -1px;
}
&:not(:first-of-type) {
padding-top: var(--spacing-vertical-large);
}
&:not(:last-of-type) { &:not(:last-of-type) {
border-bottom: 1px solid var(--lbry-gray-1); border-bottom: 1px solid var(--lbry-gray-1);
@ -21,44 +14,39 @@
} }
} }
.comment__actions-wrap { // .comment__actions-wrap {
align-items: center; // align-items: center;
display: flex; // display: flex;
justify-content: space-between; // justify-content: space-between;
width: 4.5rem; // width: 4.5rem;
} // }
.comment__action { // .comment__action {
@include hide-text; // @include hide-text;
width: 0; height: 0; // width: 0;
transition: border-color 0.2s; // height: 0;
// transition: border-color 0.2s;
&.downvote { // &.downvote {
border-top: 2rem solid var(--lbry-orange-3); // border-top: 2rem solid var(--lbry-orange-3);
border-right: 1rem solid transparent; // border-right: 1rem solid transparent;
border-left: 1rem solid transparent; // border-left: 1rem solid transparent;
&:hover { // &:hover {
border-top-color: var(--lbry-yellow-3); // border-top-color: var(--lbry-yellow-3);
} // }
} // }
&.upvote { // &.upvote {
border-right: 1rem solid transparent; // border-right: 1rem solid transparent;
border-bottom: 2rem solid var(--lbry-teal-4); // border-bottom: 2rem solid var(--lbry-teal-4);
border-left: 1rem solid transparent; // border-left: 1rem solid transparent;
&:hover { // &:hover {
border-bottom-color: var(--lbry-green-2); // border-bottom-color: var(--lbry-green-2);
} // }
} // }
} // }
.comment__content {
font-size: 1.15rem;
padding-top: var(--spacing-vertical-medium);
padding-bottom: var(--spacing-vertical-large);
}
.comment__meta { .comment__meta {
time { time {

View file

@ -4,7 +4,12 @@ export default function usePersistedState(key, firstTimeDefault) {
// If no key is passed in, act as a normal `useState` // If no key is passed in, act as a normal `useState`
let defaultValue; let defaultValue;
if (key) { if (key) {
defaultValue = localStorage.getItem(key); const item = localStorage.getItem(key);
if (item === 'true') {
defaultValue = true;
} else if (item === 'false') {
defaultValue = false;
}
} }
if (!defaultValue) { if (!defaultValue) {

View file

@ -311,6 +311,10 @@
"Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.": "Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.", "Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.": "Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.",
"If you still have issues, your anti-virus software or firewall may be preventing startup.": "If you still have issues, your anti-virus software or firewall may be preventing startup.", "If you still have issues, your anti-virus software or firewall may be preventing startup.": "If you still have issues, your anti-virus software or firewall may be preventing startup.",
"Reach out to hello@lbry.com for help, or check out": "Reach out to hello@lbry.com for help, or check out", "Reach out to hello@lbry.com for help, or check out": "Reach out to hello@lbry.com for help, or check out",
"Got it!": "Got it!" "Got it!": "Got it!",
} "URI does not include name.": "URI does not include name.",
>>>>>>> comments cleanup "Invalid claim ID %s.": "Invalid claim ID %s.",
"Comment": "Comment",
"Channel": "Channel",
"Comments": "Comments"
}>>>>>>> comments cleanup