add initial support for comment API outside of SDK
This commit is contained in:
parent
db87125dc8
commit
1f117e43bd
22 changed files with 421 additions and 118 deletions
|
@ -9,6 +9,7 @@ WEB_SERVER_PORT=1337
|
||||||
LBRY_WEB_API=https://api.lbry.tv
|
LBRY_WEB_API=https://api.lbry.tv
|
||||||
LBRY_WEB_STREAMING_API=https://cdn.lbryplayer.xyz
|
LBRY_WEB_STREAMING_API=https://cdn.lbryplayer.xyz
|
||||||
LBRY_WEB_BUFFER_API=https://collector-service.api.lbry.tv/api/v1/events/video
|
LBRY_WEB_BUFFER_API=https://collector-service.api.lbry.tv/api/v1/events/video
|
||||||
|
COMMENT_SERVER_API=https://comments.lbry.com/api/v2
|
||||||
WELCOME_VERSION=1.0
|
WELCOME_VERSION=1.0
|
||||||
|
|
||||||
# Custom Site info
|
# Custom Site info
|
||||||
|
|
|
@ -33,6 +33,7 @@ module.name_mapper='^analytics\(.*\)$' -> '<PROJECT_ROOT>/ui/analytics\1'
|
||||||
module.name_mapper='^rewards\(.*\)$' -> '<PROJECT_ROOT>/ui/rewards\1'
|
module.name_mapper='^rewards\(.*\)$' -> '<PROJECT_ROOT>/ui/rewards\1'
|
||||||
module.name_mapper='^i18n\(.*\)$' -> '<PROJECT_ROOT>/ui/i18n\1'
|
module.name_mapper='^i18n\(.*\)$' -> '<PROJECT_ROOT>/ui/i18n\1'
|
||||||
module.name_mapper='^effects\(.*\)$' -> '<PROJECT_ROOT>/ui/effects\1'
|
module.name_mapper='^effects\(.*\)$' -> '<PROJECT_ROOT>/ui/effects\1'
|
||||||
|
module.name_mapper='^comments\(.*\)$' -> '<PROJECT_ROOT>/ui/comments\1'
|
||||||
module.name_mapper='^config\(.*\)$' -> '<PROJECT_ROOT>/config\1'
|
module.name_mapper='^config\(.*\)$' -> '<PROJECT_ROOT>/config\1'
|
||||||
module.name_mapper='^web\/component\(.*\)$' -> '<PROJECT_ROOT>/web/component\1'
|
module.name_mapper='^web\/component\(.*\)$' -> '<PROJECT_ROOT>/web/component\1'
|
||||||
module.name_mapper='^web\/effects\(.*\)$' -> '<PROJECT_ROOT>/web/effects\1'
|
module.name_mapper='^web\/effects\(.*\)$' -> '<PROJECT_ROOT>/web/effects\1'
|
||||||
|
|
|
@ -12,6 +12,7 @@ const config = {
|
||||||
LBRY_API_URL: process.env.LBRY_API_URL, //api.lbry.com',
|
LBRY_API_URL: process.env.LBRY_API_URL, //api.lbry.com',
|
||||||
LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //cdn.lbryplayer.xyz',
|
LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //cdn.lbryplayer.xyz',
|
||||||
LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API,
|
LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API,
|
||||||
|
COMMENT_SERVER_API: process.env.COMMENT_SERVER_API,
|
||||||
WELCOME_VERSION: process.env.WELCOME_VERSION,
|
WELCOME_VERSION: process.env.WELCOME_VERSION,
|
||||||
DOMAIN: process.env.DOMAIN,
|
DOMAIN: process.env.DOMAIN,
|
||||||
SHARE_DOMAIN_URL: process.env.SHARE_DOMAIN_URL,
|
SHARE_DOMAIN_URL: process.env.SHARE_DOMAIN_URL,
|
||||||
|
|
16
flow-typed/comments.js
vendored
Normal file
16
flow-typed/comments.js
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// @flow
|
||||||
|
declare type CommentListParams = {
|
||||||
|
page: number,
|
||||||
|
page_size: number,
|
||||||
|
claim_id: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
declare type CommentAbandonParams = {
|
||||||
|
comment_id: string,
|
||||||
|
creator_channel_id?: string,
|
||||||
|
creator_channel_name?: string,
|
||||||
|
channel_id?: string,
|
||||||
|
hexdata?: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
declare type ModerationBlockParams = {};
|
|
@ -142,7 +142,7 @@
|
||||||
"imagesloaded": "^4.1.4",
|
"imagesloaded": "^4.1.4",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||||
"lbry-redux": "lbryio/lbry-redux#f0849b4ce19e5e9600b74d61c6db82f0b853b9e8",
|
"lbry-redux": "lbryio/lbry-redux#d90cbd18925788b01fa9c4055c81300cb39e0b3a",
|
||||||
"lbryinc": "lbryio/lbryinc#eee2cb730ecec95a1344a755035755b0d4dad5cf",
|
"lbryinc": "lbryio/lbryinc#eee2cb730ecec95a1344a755035755b0d4dad5cf",
|
||||||
"lint-staged": "^7.0.2",
|
"lint-staged": "^7.0.2",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
|
|
37
ui/comments.js
Normal file
37
ui/comments.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// @flow
|
||||||
|
import { COMMENT_SERVER_API } from 'config';
|
||||||
|
|
||||||
|
const Comments = {
|
||||||
|
url: COMMENT_SERVER_API,
|
||||||
|
enabled: Boolean(COMMENT_SERVER_API),
|
||||||
|
|
||||||
|
moderation_block: (params: ModerationBlockParams) => fetchCommentsApi('moderation.Block', params),
|
||||||
|
comment_list: (params: CommentListParams) => fetchCommentsApi('comment.List', params),
|
||||||
|
comment_abandon: (params: CommentAbandonParams) => fetchCommentsApi('comment.Abandon', params),
|
||||||
|
};
|
||||||
|
|
||||||
|
function fetchCommentsApi(method: string, params: {}) {
|
||||||
|
if (!Comments.enabled) {
|
||||||
|
return Promise.reject('Comments are not currently enabled'); // eslint-disable-line
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${Comments.url}?m=${method}`;
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
id: 1,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return fetch(url, options)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => res.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Comments;
|
|
@ -16,7 +16,7 @@ import {
|
||||||
selectIsUpgradeAvailable,
|
selectIsUpgradeAvailable,
|
||||||
selectAutoUpdateDownloaded,
|
selectAutoUpdateDownloaded,
|
||||||
selectModal,
|
selectModal,
|
||||||
selectActiveChannelId,
|
selectActiveChannelClaim,
|
||||||
} from 'redux/selectors/app';
|
} from 'redux/selectors/app';
|
||||||
import { doGetWalletSyncPreference, doSetLanguage } from 'redux/actions/settings';
|
import { doGetWalletSyncPreference, doSetLanguage } from 'redux/actions/settings';
|
||||||
import { doSyncLoop } from 'redux/actions/sync';
|
import { doSyncLoop } from 'redux/actions/sync';
|
||||||
|
@ -44,7 +44,7 @@ const select = state => ({
|
||||||
isAuthenticated: selectUserVerifiedEmail(state),
|
isAuthenticated: selectUserVerifiedEmail(state),
|
||||||
currentModal: selectModal(state),
|
currentModal: selectModal(state),
|
||||||
syncFatalError: selectSyncFatalError(state),
|
syncFatalError: selectSyncFatalError(state),
|
||||||
activeChannelId: selectActiveChannelId(state),
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
myChannelUrls: selectMyChannelUrls(state),
|
myChannelUrls: selectMyChannelUrls(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -80,9 +80,9 @@ type Props = {
|
||||||
syncEnabled: boolean,
|
syncEnabled: boolean,
|
||||||
currentModal: any,
|
currentModal: any,
|
||||||
syncFatalError: boolean,
|
syncFatalError: boolean,
|
||||||
activeChannelId: ?string,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
myChannelUrls: ?Array<string>,
|
myChannelUrls: ?Array<string>,
|
||||||
setActiveChannelIfNotSet: (?string) => void,
|
setActiveChannelIfNotSet: () => void,
|
||||||
setIncognito: boolean => void,
|
setIncognito: boolean => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -110,8 +110,8 @@ function App(props: Props) {
|
||||||
syncLoop,
|
syncLoop,
|
||||||
currentModal,
|
currentModal,
|
||||||
syncFatalError,
|
syncFatalError,
|
||||||
activeChannelId,
|
|
||||||
myChannelUrls,
|
myChannelUrls,
|
||||||
|
activeChannelClaim,
|
||||||
setActiveChannelIfNotSet,
|
setActiveChannelIfNotSet,
|
||||||
setIncognito,
|
setIncognito,
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -144,6 +144,7 @@ function App(props: Props) {
|
||||||
const hasMyChannels = myChannelUrls && myChannelUrls.length > 0;
|
const hasMyChannels = myChannelUrls && myChannelUrls.length > 0;
|
||||||
const hasNoChannels = myChannelUrls && myChannelUrls.length === 0;
|
const hasNoChannels = myChannelUrls && myChannelUrls.length === 0;
|
||||||
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
|
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
|
||||||
|
const hasActiveChannelClaim = activeChannelClaim !== undefined;
|
||||||
|
|
||||||
let uri;
|
let uri;
|
||||||
try {
|
try {
|
||||||
|
@ -238,12 +239,12 @@ function App(props: Props) {
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasMyChannels && !activeChannelId) {
|
if (hasMyChannels && !hasActiveChannelClaim) {
|
||||||
setActiveChannelIfNotSet();
|
setActiveChannelIfNotSet();
|
||||||
} else if (hasNoChannels) {
|
} else if (hasNoChannels) {
|
||||||
setIncognito(true);
|
setIncognito(true);
|
||||||
}
|
}
|
||||||
}, [hasMyChannels, activeChannelId, setActiveChannelIfNotSet]);
|
}, [hasMyChannels, hasNoChannels, hasActiveChannelClaim, setActiveChannelIfNotSet, setIncognito]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!languages.includes(language)) {
|
if (!languages.includes(language)) {
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectThumbnailForUri, selectMyChannelClaims, makeSelectChannelPermUrlForClaimUri } from 'lbry-redux';
|
import { makeSelectThumbnailForUri } from 'lbry-redux';
|
||||||
import { doCommentAbandon, doCommentUpdate, doCommentPin, doCommentList } from 'redux/actions/comments';
|
import { doCommentUpdate } from 'redux/actions/comments';
|
||||||
import { doToggleBlockChannel } from 'redux/actions/blocked';
|
|
||||||
import { selectChannelIsBlocked } from 'redux/selectors/blocked';
|
import { selectChannelIsBlocked } from 'redux/selectors/blocked';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
import { doToast } from 'redux/actions/notifications';
|
||||||
import { doSetPlayingUri } from 'redux/actions/content';
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import { selectIsFetchingComments, makeSelectOthersReactionsForComment } from 'redux/selectors/comments';
|
import { makeSelectOthersReactionsForComment } from 'redux/selectors/comments';
|
||||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
import Comment from './view';
|
import Comment from './view';
|
||||||
|
|
||||||
|
@ -14,21 +13,14 @@ const select = (state, props) => ({
|
||||||
thumbnail: props.authorUri && makeSelectThumbnailForUri(props.authorUri)(state),
|
thumbnail: props.authorUri && makeSelectThumbnailForUri(props.authorUri)(state),
|
||||||
channelIsBlocked: props.authorUri && selectChannelIsBlocked(props.authorUri)(state),
|
channelIsBlocked: props.authorUri && selectChannelIsBlocked(props.authorUri)(state),
|
||||||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||||
isFetchingComments: selectIsFetchingComments(state),
|
|
||||||
myChannels: selectMyChannelClaims(state),
|
|
||||||
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
|
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
|
||||||
contentChannelPermanentUrl: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
|
|
||||||
activeChannelClaim: selectActiveChannelClaim(state),
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
closeInlinePlayer: () => dispatch(doSetPlayingUri({ uri: null })),
|
closeInlinePlayer: () => dispatch(doSetPlayingUri({ uri: null })),
|
||||||
updateComment: (commentId, comment) => dispatch(doCommentUpdate(commentId, comment)),
|
updateComment: (commentId, comment) => dispatch(doCommentUpdate(commentId, comment)),
|
||||||
deleteComment: commentId => dispatch(doCommentAbandon(commentId)),
|
|
||||||
blockChannel: channelUri => dispatch(doToggleBlockChannel(channelUri)),
|
|
||||||
doToast: options => dispatch(doToast(options)),
|
doToast: options => dispatch(doToast(options)),
|
||||||
pinComment: (commentId, remove) => dispatch(doCommentPin(commentId, remove)),
|
|
||||||
fetchComments: uri => dispatch(doCommentList(uri)),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(Comment);
|
export default connect(select, perform)(Comment);
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Button from 'component/button';
|
||||||
import Expandable from 'component/expandable';
|
import Expandable from 'component/expandable';
|
||||||
import MarkdownPreview from 'component/common/markdown-preview';
|
import MarkdownPreview from 'component/common/markdown-preview';
|
||||||
import ChannelThumbnail from 'component/channelThumbnail';
|
import ChannelThumbnail from 'component/channelThumbnail';
|
||||||
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
import { Menu, MenuButton } from '@reach/menu-button';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import { FormField, Form } from 'component/common/form';
|
import { FormField, Form } from 'component/common/form';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
@ -19,6 +19,7 @@ import CommentReactions from 'component/commentReactions';
|
||||||
import CommentsReplies from 'component/commentsReplies';
|
import CommentsReplies from 'component/commentsReplies';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import CommentCreate from 'component/commentCreate';
|
import CommentCreate from 'component/commentCreate';
|
||||||
|
import CommentMenuList from 'component/commentMenuList';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
closeInlinePlayer: () => void,
|
closeInlinePlayer: () => void,
|
||||||
|
@ -32,8 +33,7 @@ type Props = {
|
||||||
claimIsMine: boolean, // if you control the claim which this comment was posted on
|
claimIsMine: boolean, // if you control the claim which this comment was posted on
|
||||||
commentIsMine: boolean, // if this comment was signed by an owned channel
|
commentIsMine: boolean, // if this comment was signed by an owned channel
|
||||||
updateComment: (string, string) => void,
|
updateComment: (string, string) => void,
|
||||||
deleteComment: string => void,
|
commentModBlock: string => void,
|
||||||
blockChannel: string => void,
|
|
||||||
linkedComment?: any,
|
linkedComment?: any,
|
||||||
myChannels: ?Array<ChannelClaim>,
|
myChannels: ?Array<ChannelClaim>,
|
||||||
commentingEnabled: boolean,
|
commentingEnabled: boolean,
|
||||||
|
@ -45,10 +45,7 @@ type Props = {
|
||||||
like: number,
|
like: number,
|
||||||
dislike: number,
|
dislike: number,
|
||||||
},
|
},
|
||||||
pinComment: (string, boolean) => Promise<any>,
|
|
||||||
fetchComments: string => void,
|
|
||||||
commentIdentityChannel: any,
|
commentIdentityChannel: any,
|
||||||
contentChannelPermanentUrl: any,
|
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,8 +64,6 @@ function Comment(props: Props) {
|
||||||
commentIsMine,
|
commentIsMine,
|
||||||
commentId,
|
commentId,
|
||||||
updateComment,
|
updateComment,
|
||||||
deleteComment,
|
|
||||||
blockChannel,
|
|
||||||
linkedComment,
|
linkedComment,
|
||||||
commentingEnabled,
|
commentingEnabled,
|
||||||
myChannels,
|
myChannels,
|
||||||
|
@ -76,11 +71,7 @@ function Comment(props: Props) {
|
||||||
isTopLevel,
|
isTopLevel,
|
||||||
threadDepth,
|
threadDepth,
|
||||||
isPinned,
|
isPinned,
|
||||||
pinComment,
|
|
||||||
fetchComments,
|
|
||||||
othersReacts,
|
othersReacts,
|
||||||
contentChannelPermanentUrl,
|
|
||||||
activeChannelClaim,
|
|
||||||
} = props;
|
} = props;
|
||||||
const {
|
const {
|
||||||
push,
|
push,
|
||||||
|
@ -133,20 +124,11 @@ function Comment(props: Props) {
|
||||||
setCommentValue(!SIMPLE_SITE && advancedEditor ? event : event.target.value);
|
setCommentValue(!SIMPLE_SITE && advancedEditor ? event : event.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePinComment(commentId, remove) {
|
|
||||||
pinComment(commentId, remove).then(() => fetchComments(uri));
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleEditComment() {
|
function handleEditComment() {
|
||||||
closeInlinePlayer();
|
closeInlinePlayer();
|
||||||
setEditing(true);
|
setEditing(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDeleteComment() {
|
|
||||||
closeInlinePlayer();
|
|
||||||
deleteComment(commentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
updateComment(commentId, editedMessage);
|
updateComment(commentId, editedMessage);
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
|
@ -228,38 +210,15 @@ function Comment(props: Props) {
|
||||||
icon={ICONS.MORE_VERTICAL}
|
icon={ICONS.MORE_VERTICAL}
|
||||||
/>
|
/>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList className="menu__list--comments">
|
<CommentMenuList
|
||||||
{commentIsMine ? (
|
uri={uri}
|
||||||
<>
|
isTopLevel={isTopLevel}
|
||||||
<MenuItem className="comment__menu-option menu__link" onSelect={handleEditComment}>
|
isPinned={isPinned}
|
||||||
<Icon aria-hidden icon={ICONS.EDIT} />
|
commentId={commentId}
|
||||||
{__('Edit')}
|
authorUri={authorUri}
|
||||||
</MenuItem>
|
commentIsMine={commentIsMine}
|
||||||
<MenuItem className="comment__menu-option menu__link" onSelect={handleDeleteComment}>
|
handleEditComment={handleEditComment}
|
||||||
<Icon aria-hidden icon={ICONS.DELETE} />
|
/>
|
||||||
{__('Delete')}
|
|
||||||
</MenuItem>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<MenuItem className="comment__menu-option menu__link" onSelect={() => blockChannel(authorUri)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.NO} />
|
|
||||||
{__('Block Channel')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
{activeChannelClaim && activeChannelClaim.permanent_url === contentChannelPermanentUrl && isTopLevel && (
|
|
||||||
<MenuItem
|
|
||||||
className="comment__menu-option menu__link"
|
|
||||||
onSelect={
|
|
||||||
isPinned ? () => handlePinComment(commentId, true) : () => handlePinComment(commentId, false)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span className={'button__content'}>
|
|
||||||
<Icon aria-hidden icon={ICONS.PIN} className={'icon'} />
|
|
||||||
{isPinned ? __('Unpin') : __('Pin')}
|
|
||||||
</span>
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
32
ui/component/commentMenuList/index.js
Normal file
32
ui/component/commentMenuList/index.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makeSelectChannelPermUrlForClaimUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
||||||
|
import {
|
||||||
|
doCommentAbandon,
|
||||||
|
doCommentPin,
|
||||||
|
doCommentList,
|
||||||
|
// doCommentModBlock,
|
||||||
|
} from 'redux/actions/comments';
|
||||||
|
import { doToggleBlockChannel } from 'redux/actions/blocked';
|
||||||
|
// import { doSetActiveChannel } from 'redux/actions/app';
|
||||||
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
|
import CommentMenuList from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
|
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
|
contentChannelPermanentUrl: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
|
||||||
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
closeInlinePlayer: () => dispatch(doSetPlayingUri({ uri: null })),
|
||||||
|
deleteComment: (commentId, creatorChannelUrl) => dispatch(doCommentAbandon(commentId, creatorChannelUrl)),
|
||||||
|
blockChannel: channelUri => dispatch(doToggleBlockChannel(channelUri)),
|
||||||
|
pinComment: (commentId, remove) => dispatch(doCommentPin(commentId, remove)),
|
||||||
|
fetchComments: uri => dispatch(doCommentList(uri)),
|
||||||
|
// setActiveChannel: channelId => dispatch(doSetActiveChannel(channelId)),
|
||||||
|
// commentModBlock: commentAuthor => dispatch(doCommentModBlock(commentAuthor)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(CommentMenuList);
|
149
ui/component/commentMenuList/view.jsx
Normal file
149
ui/component/commentMenuList/view.jsx
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
// @flow
|
||||||
|
import * as ICONS from 'constants/icons';
|
||||||
|
import React from 'react';
|
||||||
|
import { MenuList, MenuItem } from '@reach/menu-button';
|
||||||
|
import ChannelThumbnail from 'component/channelThumbnail';
|
||||||
|
import Icon from 'component/common/icon';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
uri: string,
|
||||||
|
closeInlinePlayer: () => void,
|
||||||
|
authorUri: string, // full LBRY Channel URI: lbry://@channel#123...
|
||||||
|
commentId: string, // sha256 digest identifying the comment
|
||||||
|
claimIsMine: boolean, // if you control the claim which this comment was posted on
|
||||||
|
commentIsMine: boolean, // if this comment was signed by an owned channel
|
||||||
|
deleteComment: (string, ?string) => void,
|
||||||
|
linkedComment?: any,
|
||||||
|
isPinned: boolean,
|
||||||
|
pinComment: (string, boolean) => Promise<any>,
|
||||||
|
blockChannel: string => void,
|
||||||
|
fetchComments: string => void,
|
||||||
|
handleEditComment: () => void,
|
||||||
|
contentChannelPermanentUrl: any,
|
||||||
|
activeChannelClaim: ?ChannelClaim,
|
||||||
|
claimIsMine: boolean,
|
||||||
|
isTopLevel: boolean,
|
||||||
|
// commentModBlock: string => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function CommentMenuList(props: Props) {
|
||||||
|
const {
|
||||||
|
uri,
|
||||||
|
authorUri,
|
||||||
|
commentIsMine,
|
||||||
|
commentId,
|
||||||
|
deleteComment,
|
||||||
|
blockChannel,
|
||||||
|
pinComment,
|
||||||
|
claimIsMine,
|
||||||
|
closeInlinePlayer,
|
||||||
|
activeChannelClaim,
|
||||||
|
contentChannelPermanentUrl,
|
||||||
|
isTopLevel,
|
||||||
|
isPinned,
|
||||||
|
handleEditComment,
|
||||||
|
fetchComments,
|
||||||
|
// commentModBlock,
|
||||||
|
// setActiveChannel,
|
||||||
|
} = props;
|
||||||
|
const activeChannelIsCreator = activeChannelClaim && activeChannelClaim.permanent_url === contentChannelPermanentUrl;
|
||||||
|
|
||||||
|
// let authorChannel;
|
||||||
|
// try {
|
||||||
|
// const { claimName } = parseURI(authorUri);
|
||||||
|
// authorChannel = claimName;
|
||||||
|
// } catch (e) {}
|
||||||
|
|
||||||
|
function handlePinComment(commentId, remove) {
|
||||||
|
pinComment(commentId, remove).then(() => fetchComments(uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteComment() {
|
||||||
|
closeInlinePlayer();
|
||||||
|
deleteComment(commentId, commentIsMine ? undefined : contentChannelPermanentUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCommentBlock() {
|
||||||
|
if (claimIsMine) {
|
||||||
|
// Block them from commenting on future content
|
||||||
|
// commentModBlock(authorUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
blockChannel(authorUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
// function handleChooseChannel() {
|
||||||
|
// const { channelClaimId } = parseURI(authorUri);
|
||||||
|
// setActiveChannel(channelClaimId);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuList className="menu__list--comments">
|
||||||
|
{/* {commentIsMine && activeChannelClaim && activeChannelClaim.permanent_url !== authorUri && (
|
||||||
|
<MenuItem className="comment__menu-option" onSelect={handleChooseChannel}>
|
||||||
|
<div className="menu__link">
|
||||||
|
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
||||||
|
{__('Use this channel')}
|
||||||
|
</div>
|
||||||
|
<span className="comment__menu-help">
|
||||||
|
{__('Switch to %channel_name% to interact with this comment', { channel_name: authorChannel })}.
|
||||||
|
</span>
|
||||||
|
</MenuItem>
|
||||||
|
)} */}
|
||||||
|
|
||||||
|
{activeChannelIsCreator && <div className="comment__menu-title">{__('Creator tools')}</div>}
|
||||||
|
|
||||||
|
{activeChannelIsCreator && isTopLevel && (
|
||||||
|
<MenuItem
|
||||||
|
className="comment__menu-option menu__link"
|
||||||
|
onSelect={isPinned ? () => handlePinComment(commentId, true) : () => handlePinComment(commentId, false)}
|
||||||
|
>
|
||||||
|
<span className={'button__content'}>
|
||||||
|
<Icon aria-hidden icon={ICONS.PIN} className={'icon'} />
|
||||||
|
{isPinned ? __('Unpin') : __('Pin')}
|
||||||
|
</span>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeChannelClaim &&
|
||||||
|
(activeChannelClaim.permanent_url === authorUri ||
|
||||||
|
activeChannelClaim.permanent_url === contentChannelPermanentUrl) && (
|
||||||
|
<MenuItem className="comment__menu-option" onSelect={handleDeleteComment}>
|
||||||
|
<div className="menu__link">
|
||||||
|
<Icon aria-hidden icon={ICONS.DELETE} />
|
||||||
|
{__('Remove')}
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{commentIsMine && activeChannelClaim && activeChannelClaim.permanent_url === authorUri && (
|
||||||
|
<MenuItem className="comment__menu-option menu__link" onSelect={handleEditComment}>
|
||||||
|
<Icon aria-hidden icon={ICONS.EDIT} />
|
||||||
|
{__('Edit')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Disabled until we deal with current app blocklist parity */}
|
||||||
|
{!commentIsMine && (
|
||||||
|
<MenuItem className="comment__menu-option" onSelect={handleCommentBlock}>
|
||||||
|
<div className="menu__link">
|
||||||
|
<Icon aria-hidden icon={ICONS.BLOCK} />
|
||||||
|
{__('Block')}
|
||||||
|
</div>
|
||||||
|
{/* {activeChannelIsCreator && (
|
||||||
|
<span className="comment__menu-help">Hide this channel's comments and block them from commenting.</span>
|
||||||
|
)} */}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeChannelClaim && (
|
||||||
|
<div className="comment__menu-active">
|
||||||
|
<ChannelThumbnail uri={activeChannelClaim.permanent_url} />
|
||||||
|
<div className="comment__menu-channel">Interacting as {activeChannelClaim.name}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</MenuList>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CommentMenuList;
|
|
@ -1,11 +1,12 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectBalance } from 'lbry-redux';
|
import { selectBalance, selectFetchingMyChannels } from 'lbry-redux';
|
||||||
import { selectUnclaimedRewardValue } from 'redux/selectors/rewards';
|
import { selectUnclaimedRewardValue } from 'redux/selectors/rewards';
|
||||||
import PublishPage from './view';
|
import PublishPage from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
balance: selectBalance(state),
|
balance: selectBalance(state),
|
||||||
totalRewardValue: selectUnclaimedRewardValue(state),
|
totalRewardValue: selectUnclaimedRewardValue(state),
|
||||||
|
fetchingChannels: selectFetchingMyChannels(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, null)(PublishPage);
|
export default connect(select, null)(PublishPage);
|
||||||
|
|
|
@ -3,13 +3,15 @@ import React from 'react';
|
||||||
import PublishForm from 'component/publishForm';
|
import PublishForm from 'component/publishForm';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import YrblWalletEmpty from 'component/yrblWalletEmpty';
|
import YrblWalletEmpty from 'component/yrblWalletEmpty';
|
||||||
|
import Spinner from 'component/spinner';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
balance: number,
|
balance: number,
|
||||||
|
fetchingChannels: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function PublishPage(props: Props) {
|
function PublishPage(props: Props) {
|
||||||
const { balance } = props;
|
const { balance, fetchingChannels } = props;
|
||||||
|
|
||||||
function scrollToTop() {
|
function scrollToTop() {
|
||||||
const mainContent = document.querySelector('main');
|
const mainContent = document.querySelector('main');
|
||||||
|
@ -32,7 +34,13 @@ function PublishPage(props: Props) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{balance === 0 && <YrblWalletEmpty />}
|
{balance === 0 && <YrblWalletEmpty />}
|
||||||
<PublishForm scrollToTop={scrollToTop} disabled={balance === 0} />
|
{balance !== 0 && fetchingChannels ? (
|
||||||
|
<div className="main--empty">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<PublishForm scrollToTop={scrollToTop} disabled={balance === 0} />
|
||||||
|
)}
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ import {
|
||||||
} from 'redux/selectors/comments';
|
} from 'redux/selectors/comments';
|
||||||
import { makeSelectNotificationForCommentId } from 'redux/selectors/notifications';
|
import { makeSelectNotificationForCommentId } from 'redux/selectors/notifications';
|
||||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
|
import { toHex } from 'util/hex';
|
||||||
|
import Comments from 'comments';
|
||||||
|
|
||||||
export function doCommentList(uri: string, page: number = 1, pageSize: number = 99999) {
|
export function doCommentList(uri: string, page: number = 1, pageSize: number = 99999) {
|
||||||
return (dispatch: Dispatch, getState: GetState) => {
|
return (dispatch: Dispatch, getState: GetState) => {
|
||||||
|
@ -18,15 +20,23 @@ export function doCommentList(uri: string, page: number = 1, pageSize: number =
|
||||||
const claim = selectClaimsByUri(state)[uri];
|
const claim = selectClaimsByUri(state)[uri];
|
||||||
const claimId = claim ? claim.claim_id : null;
|
const claimId = claim ? claim.claim_id : null;
|
||||||
|
|
||||||
|
if (!claimId) {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.COMMENT_LIST_FAILED,
|
||||||
|
data: 'unable to find claim for uri',
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_LIST_STARTED,
|
type: ACTIONS.COMMENT_LIST_STARTED,
|
||||||
});
|
});
|
||||||
return Lbry.comment_list({
|
|
||||||
claim_id: claimId,
|
return Comments.comment_list({
|
||||||
page,
|
page,
|
||||||
|
claim_id: claimId,
|
||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
include_replies: true,
|
|
||||||
skip_validation: true,
|
|
||||||
})
|
})
|
||||||
.then((result: CommentListResponse) => {
|
.then((result: CommentListResponse) => {
|
||||||
const { items: comments } = result;
|
const { items: comments } = result;
|
||||||
|
@ -238,35 +248,6 @@ export function doCommentCreate(comment: string = '', claim_id: string = '', par
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doCommentHide(comment_id: string) {
|
|
||||||
return (dispatch: Dispatch) => {
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.COMMENT_HIDE_STARTED,
|
|
||||||
});
|
|
||||||
return Lbry.comment_hide({
|
|
||||||
comment_ids: [comment_id],
|
|
||||||
})
|
|
||||||
.then((result: CommentHideResponse) => {
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.COMMENT_HIDE_COMPLETED,
|
|
||||||
data: result,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.COMMENT_HIDE_FAILED,
|
|
||||||
data: error,
|
|
||||||
});
|
|
||||||
dispatch(
|
|
||||||
doToast({
|
|
||||||
message: 'Unable to hide this comment, please try again later.',
|
|
||||||
isError: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function doCommentPin(commentId: string, remove: boolean) {
|
export function doCommentPin(commentId: string, remove: boolean) {
|
||||||
return (dispatch: Dispatch, getState: GetState) => {
|
return (dispatch: Dispatch, getState: GetState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
@ -308,13 +289,33 @@ export function doCommentPin(commentId: string, remove: boolean) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doCommentAbandon(comment_id: string) {
|
export function doCommentAbandon(commentId: string, creatorChannelUri?: string) {
|
||||||
return (dispatch: Dispatch) => {
|
return async (dispatch: Dispatch, getState: GetState) => {
|
||||||
|
const state = getState();
|
||||||
|
const claim = creatorChannelUri ? selectClaimsByUri(state)[creatorChannelUri] : undefined;
|
||||||
|
const creatorChannelId = claim ? claim.claim_id : null;
|
||||||
|
const creatorChannelName = claim ? claim.name : null;
|
||||||
|
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_ABANDON_STARTED,
|
type: ACTIONS.COMMENT_ABANDON_STARTED,
|
||||||
});
|
});
|
||||||
return Lbry.comment_abandon({
|
|
||||||
comment_id: comment_id,
|
let commentIdSignature;
|
||||||
|
if (activeChannelClaim) {
|
||||||
|
try {
|
||||||
|
commentIdSignature = await Lbry.channel_sign({
|
||||||
|
channel_id: activeChannelClaim.claim_id,
|
||||||
|
hexdata: toHex(commentId),
|
||||||
|
});
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Comments.comment_abandon({
|
||||||
|
comment_id: commentId,
|
||||||
|
...(creatorChannelId ? { creator_channel_id: creatorChannelId } : {}),
|
||||||
|
...(creatorChannelName ? { creator_channel_name: creatorChannelName } : {}),
|
||||||
|
...(commentIdSignature || {}),
|
||||||
})
|
})
|
||||||
.then((result: CommentAbandonResponse) => {
|
.then((result: CommentAbandonResponse) => {
|
||||||
// Comment may not be deleted if the signing channel can't be signed.
|
// Comment may not be deleted if the signing channel can't be signed.
|
||||||
|
@ -323,7 +324,7 @@ export function doCommentAbandon(comment_id: string) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_ABANDON_COMPLETED,
|
type: ACTIONS.COMMENT_ABANDON_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
comment_id: comment_id,
|
comment_id: commentId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -343,6 +344,7 @@ export function doCommentAbandon(comment_id: string) {
|
||||||
type: ACTIONS.COMMENT_ABANDON_FAILED,
|
type: ACTIONS.COMMENT_ABANDON_FAILED,
|
||||||
data: error,
|
data: error,
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
doToast({
|
doToast({
|
||||||
message: 'Unable to delete this comment, please try again later.',
|
message: 'Unable to delete this comment, please try again later.',
|
||||||
|
@ -402,3 +404,40 @@ export function doCommentUpdate(comment_id: string, comment: string) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hides a users comments from all creator's claims and prevent them from commenting in the future
|
||||||
|
export function doCommentModBlock(commentAuthor: string) {
|
||||||
|
return async (dispatch: Dispatch, getState: GetState) => {
|
||||||
|
const state = getState();
|
||||||
|
const claim = selectClaimsByUri(state)[commentAuthor];
|
||||||
|
|
||||||
|
if (!claim) {
|
||||||
|
console.error("Can't find claim to block"); // eslint-disable-line
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const creatorIdToBan = claim ? claim.claim_id : null;
|
||||||
|
const creatorNameToBan = claim ? claim.name : null;
|
||||||
|
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||||
|
|
||||||
|
let channelSignature = {};
|
||||||
|
if (activeChannelClaim) {
|
||||||
|
try {
|
||||||
|
channelSignature = await Lbry.channel_sign({
|
||||||
|
channel_id: activeChannelClaim.claim_id,
|
||||||
|
hexdata: toHex(activeChannelClaim.name),
|
||||||
|
});
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Comments.moderation_block({
|
||||||
|
mod_channel_id: activeChannelClaim.claim_id,
|
||||||
|
mod_channel_name: activeChannelClaim.name,
|
||||||
|
signature: channelSignature.signature,
|
||||||
|
signing_ts: channelSignature.signing_ts,
|
||||||
|
banned_channel_id: creatorIdToBan,
|
||||||
|
banned_channel_name: creatorNameToBan,
|
||||||
|
delete_all: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -310,6 +310,7 @@ reducers[ACTIONS.SET_ACTIVE_CHANNEL] = (state, action) => {
|
||||||
activeChannel: action.data.claimId,
|
activeChannel: action.data.claimId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
reducers[ACTIONS.SET_INCOGNITO] = (state, action) => {
|
reducers[ACTIONS.SET_INCOGNITO] = (state, action) => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -212,6 +212,7 @@ $thumbnailWidthSmall: 0rem;
|
||||||
|
|
||||||
.comment__menu {
|
.comment__menu {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
@ -239,7 +240,13 @@ $thumbnailWidthSmall: 0rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: var(--spacing-s);
|
padding: var(--spacing-s);
|
||||||
font-size: var(--font-small);
|
font-size: var(--font-xsmall);
|
||||||
|
|
||||||
|
.menu__link {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment__menu-icon--hovering {
|
.comment__menu-icon--hovering {
|
||||||
|
@ -256,6 +263,26 @@ $thumbnailWidthSmall: 0rem;
|
||||||
padding: var(--spacing-s);
|
padding: var(--spacing-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment__menu-title {
|
||||||
|
@extend .help;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: var(--font-small);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
margin-top: 0;
|
||||||
|
padding-left: var(--spacing-s);
|
||||||
|
padding-bottom: var(--spacing-s);
|
||||||
|
padding-right: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment__menu-help {
|
||||||
|
@extend .help;
|
||||||
|
margin-top: var(--spacing-xs);
|
||||||
|
padding-left: calc(18px + var(--spacing-s));
|
||||||
|
max-width: 15rem;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
.comment__actions {
|
.comment__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top: var(--spacing-s);
|
margin-top: var(--spacing-s);
|
||||||
|
@ -332,3 +359,27 @@ $thumbnailWidthSmall: 0rem;
|
||||||
top: 0.4rem;
|
top: 0.4rem;
|
||||||
left: 0.4rem;
|
left: 0.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment__menu-active {
|
||||||
|
padding-left: var(--spacing-s);
|
||||||
|
padding-top: var(--spacing-s);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
margin-top: var(--spacing-s);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.channel-thumbnail {
|
||||||
|
margin-right: var(--spacing-xs);
|
||||||
|
height: 1.8rem;
|
||||||
|
width: 1.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment__menu-channel {
|
||||||
|
@extend .help;
|
||||||
|
font-size: var(--font-xsmall);
|
||||||
|
margin-top: 0;
|
||||||
|
max-width: 10rem;
|
||||||
|
white-space: pre-line;
|
||||||
|
margin-right: var(--spacing-s);
|
||||||
|
}
|
||||||
|
|
|
@ -446,6 +446,8 @@ fieldset-section {
|
||||||
}
|
}
|
||||||
|
|
||||||
.select--slim {
|
.select--slim {
|
||||||
|
margin-bottom: var(--spacing-xxs);
|
||||||
|
|
||||||
@media (min-width: $breakpoint-small) {
|
@media (min-width: $breakpoint-small) {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,11 @@
|
||||||
@extend .menu__list;
|
@extend .menu__list;
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
padding: var(--spacing-xxs);
|
padding: var(--spacing-s) 0;
|
||||||
|
|
||||||
|
[data-reach-menu-item] {
|
||||||
|
margin: 0 var(--spacing-xs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu__link {
|
.menu__link {
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
--color-placeholder-background: #f0f0f0;
|
--color-placeholder-background: #f0f0f0;
|
||||||
--color-header-background: #ffffff;
|
--color-header-background: #ffffff;
|
||||||
--color-card-background: #ffffff;
|
--color-card-background: #ffffff;
|
||||||
--color-card-background-highlighted: #f0f7ff;
|
--color-card-background-highlighted: #f1f7fe;
|
||||||
--color-list-header: #fff;
|
--color-list-header: #fff;
|
||||||
--color-file-viewer-background: var(--color-card-background);
|
--color-file-viewer-background: var(--color-card-background);
|
||||||
--color-tabs-background: var(--color-card-background);
|
--color-tabs-background: var(--color-card-background);
|
||||||
|
|
8
ui/util/hex.js
Normal file
8
ui/util/hex.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// @flow
|
||||||
|
export function toHex(str: string): string {
|
||||||
|
var result = '';
|
||||||
|
for (var i = 0; i < str.length; i++) {
|
||||||
|
result += str.charCodeAt(i).toString(16);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -6919,9 +6919,9 @@ lazy-val@^1.0.4:
|
||||||
yargs "^13.2.2"
|
yargs "^13.2.2"
|
||||||
zstd-codec "^0.1.1"
|
zstd-codec "^0.1.1"
|
||||||
|
|
||||||
lbry-redux@lbryio/lbry-redux#f0849b4ce19e5e9600b74d61c6db82f0b853b9e8:
|
lbry-redux@lbryio/lbry-redux#d90cbd18925788b01fa9c4055c81300cb39e0b3a:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/f0849b4ce19e5e9600b74d61c6db82f0b853b9e8"
|
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/d90cbd18925788b01fa9c4055c81300cb39e0b3a"
|
||||||
dependencies:
|
dependencies:
|
||||||
proxy-polyfill "0.1.6"
|
proxy-polyfill "0.1.6"
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
|
Loading…
Reference in a new issue