Moderation: resolve chan name or URL instead of using Lighthouse.

While Lighthouse allows looser searching like "Tom from LBRY", it doesn't show the expected results when direct channel name with partial ID is entered to disambiguate.
This commit is contained in:
infinite-persistence 2021-06-19 16:28:00 +08:00 committed by infinite-persistence
parent f02dcb7fe7
commit 9f9d0a651b
4 changed files with 149 additions and 20 deletions

View file

@ -1911,6 +1911,7 @@
"Enabling a minimum amount to hyperchat will force all TIPPED comments to have this value in order to be shown. This still allows regular comments to be posted.": "Enabling a minimum amount to hyperchat will force all TIPPED comments to have this value in order to be shown. This still allows regular comments to be posted.",
"Settings unavailable for this channel": "Settings unavailable for this channel",
"This channel isn't staking enough LBRY Credits to enable Creator Settings.": "This channel isn't staking enough LBRY Credits to enable Creator Settings.",
"Not a channel (prefix with \"@\", or enter the channel URL)": "Not a channel (prefix with \"@\", or enter the channel URL)",
"We apologize for this inconvenience, but have added this additional step to prevent abuse. Users on VPN or shared connections will continue to see this message and are not eligible for Rewards.": "We apologize for this inconvenience, but have added this additional step to prevent abuse. Users on VPN or shared connections will continue to see this message and are not eligible for Rewards.",
"Help LBRY Save Crypto": "Help LBRY Save Crypto",
"The US government is attempting to destroy the cryptocurrency industry. Can you help?": "The US government is attempting to destroy the cryptocurrency industry. Can you help?",
@ -1994,5 +1995,6 @@
"Item added to Watch Later": "Item added to Watch Later",
"Your publish is being confirmed and will be live soon": "Your publish is being confirmed and will be live soon",
"Clear Edits": "Clear Edits",
"Something not quite right..": "Something not quite right..",
"--end--": "--end--"
}

View file

@ -78,6 +78,7 @@ type Props = {
isCollectionMine: boolean,
collectionUris: Array<Collection>,
collectionIndex?: number,
disableNavigation?: boolean,
};
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
@ -134,6 +135,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
editCollection,
isCollectionMine,
collectionUris,
disableNavigation,
} = props;
const isRepost = claim && claim.repost_channel_url;
const WrapperElement = wrapperElement || 'li';
@ -214,7 +216,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
onClick(e);
}
if (claim && !pending) {
if (claim && !pending && !disableNavigation) {
history.push(navigateUrl);
}
}
@ -423,7 +425,9 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
)}
</div>
</div>
{!hideMenu && <ClaimMenuList uri={uri} collectionId={collectionId} channelUri={channelUri} isRepost={isRepost} />}
{!hideMenu && (
<ClaimMenuList uri={uri} collectionId={collectionId} channelUri={channelUri} isRepost={isRepost} />
)}
</>
</WrapperElement>
);

View file

@ -3,13 +3,15 @@ import * as React from 'react';
import Card from 'component/common/card';
import TagsSearch from 'component/tagsSearch';
import Page from 'component/page';
import Button from 'component/button';
import ChannelSelector from 'component/channelSelector';
import Spinner from 'component/spinner';
import { FormField } from 'component/common/form-components/form-field';
import LbcSymbol from 'component/common/lbc-symbol';
import I18nMessage from 'component/i18nMessage';
import { parseURI } from 'lbry-redux';
import WunderBar from 'component/wunderbar';
import { isNameValid, parseURI } from 'lbry-redux';
import ClaimPreview from 'component/claimPreview';
import { getUriForSearchTerm } from 'util/search';
const DEBOUNCE_REFRESH_MS = 1000;
@ -49,6 +51,9 @@ export default function SettingsCreatorPage(props: Props) {
const [commentsEnabled, setCommentsEnabled] = React.useState(true);
const [mutedWordTags, setMutedWordTags] = React.useState([]);
const [moderatorTags, setModeratorTags] = React.useState([]);
const [moderatorSearchTerm, setModeratorSearchTerm] = React.useState('');
const [moderatorSearchError, setModeratorSearchError] = React.useState('');
const [moderatorSearchClaimUri, setModeratorSearchClaimUri] = React.useState('');
const [minTipAmountComment, setMinTipAmountComment] = React.useState(0);
const [minTipAmountSuperChat, setMinTipAmountSuperChat] = React.useState(0);
const [slowModeMinGap, setSlowModeMinGap] = React.useState(0);
@ -142,16 +147,38 @@ export default function SettingsCreatorPage(props: Props) {
}
}
function handleChannelSearchSelect(value: string) {
let uriInfo;
try {
uriInfo = parseURI(value);
} catch (e) {}
function handleChannelSearchSelect(claim) {
if (claim && claim.name && claim.claim_id) {
addModerator([{ name: claim.name + '#' + claim.claim_id }]);
}
}
if (uriInfo && uriInfo.path) {
addModerator([{ name: uriInfo.path }]);
// 'moderatorSearchTerm' to 'moderatorSearchClaimUri'
React.useEffect(() => {
if (!moderatorSearchTerm) {
setModeratorSearchError('');
setModeratorSearchClaimUri('');
} else {
const [searchUri, error] = getUriForSearchTerm(moderatorSearchTerm);
setModeratorSearchError(error ? __('Something not quite right..') : '');
try {
const { streamName, channelName, isChannel } = parseURI(searchUri);
if (!isChannel && streamName && isNameValid(streamName)) {
setModeratorSearchError(__('Not a channel (prefix with "@", or enter the channel URL)'));
setModeratorSearchClaimUri('');
} else if (isChannel && channelName && isNameValid(channelName)) {
setModeratorSearchClaimUri(searchUri);
}
} catch (e) {
if (moderatorSearchTerm !== '@') {
setModeratorSearchError('');
}
setModeratorSearchClaimUri('');
}
}
}, [moderatorSearchTerm, setModeratorSearchError]);
// Update local moderator states with data from API.
React.useEffect(() => {
@ -318,18 +345,44 @@ export default function SettingsCreatorPage(props: Props) {
className="card--enable-overflow"
actions={
<div className="tag--blocked-words">
<label>{__('Add moderator')}</label>
<WunderBar
channelsOnly
noTopSuggestion
noBottomLinks
customSelectAction={handleChannelSearchSelect}
placeholder={__('Add moderator')}
<FormField
type="text"
name="moderator_search"
className="form-field--address"
label={__('Add moderator')}
placeholder={__('Enter a @username or URL')}
helper={__('examples: @channel, @channel#3, https://odysee.com/@Odysee:8, lbry://@Odysee#8')}
value={moderatorSearchTerm}
onChange={(e) => setModeratorSearchTerm(e.target.value)}
error={moderatorSearchError}
/>
{moderatorSearchClaimUri && (
<div className="section">
<ClaimPreview
key={moderatorSearchClaimUri}
uri={moderatorSearchClaimUri}
// type={'small'}
// showNullPlaceholder
hideMenu
hideRepostLabel
disableNavigation
properties={''}
renderActions={(claim) => {
return (
<Button
requiresAuth
button="primary"
label={__('Add as moderator')}
onClick={() => handleChannelSearchSelect(claim)}
/>
);
}}
/>
</div>
)}
<TagsSearch
label={__('Moderators')}
labelAddNew={__('Add moderators')}
placeholder={__('Add moderator channel URL (e.g. "@lbry#3f")')}
onRemove={removeModerator}
onSelect={addModerator}
tagsPassedIn={moderatorTags}

View file

@ -1,5 +1,8 @@
// @flow
import { isNameValid, isURIValid, normalizeURI, parseURI } from 'lbry-redux';
import { URL as SITE_URL, URL_LOCAL, URL_DEV } from 'config';
export function createNormalizedSearchKey(query: string) {
const FROM = '&from=';
@ -33,3 +36,70 @@ export function getLivestreamOnlyOptions(options: any) {
newOptions.has_no_source = true;
return newOptions;
}
/**
* getUriForSearchTerm
* @param term
* @returns {string[]|*[]|(string|string)[]}
*/
export function getUriForSearchTerm(term: string) {
const WEB_DEV_PREFIX = `${URL_DEV}/`;
const WEB_LOCAL_PREFIX = `${URL_LOCAL}/`;
const WEB_PROD_PREFIX = `${SITE_URL}/`;
const ODYSEE_PREFIX = `https://odysee.com/`;
const includesLbryTvProd = term.includes(WEB_PROD_PREFIX);
const includesOdysee = term.includes(ODYSEE_PREFIX);
const includesLbryTvLocal = term.includes(WEB_LOCAL_PREFIX);
const includesLbryTvDev = term.includes(WEB_DEV_PREFIX);
const wasCopiedFromWeb = includesLbryTvDev || includesLbryTvLocal || includesLbryTvProd || includesOdysee;
const isLbryUrl = term.startsWith('lbry://') && term !== 'lbry://';
const error = '';
const addLbryIfNot = (term) => {
return term.startsWith('lbry://') ? term : `lbry://${term}`;
};
if (wasCopiedFromWeb) {
let prefix = WEB_PROD_PREFIX;
if (includesLbryTvLocal) prefix = WEB_LOCAL_PREFIX;
if (includesLbryTvDev) prefix = WEB_DEV_PREFIX;
if (includesOdysee) prefix = ODYSEE_PREFIX;
let query = (term && term.slice(prefix.length).replace(/:/g, '#')) || '';
try {
const lbryUrl = `lbry://${query}`;
parseURI(lbryUrl);
return [lbryUrl, null];
} catch (e) {
return [query, 'error'];
}
}
if (!isLbryUrl) {
if (term.startsWith('@')) {
if (isNameValid(term.slice(1))) {
return [term, null];
} else {
return [term, error];
}
}
return [addLbryIfNot(term), null];
} else {
try {
const isValid = isURIValid(term);
if (isValid) {
let uri;
try {
uri = normalizeURI(term);
} catch (e) {
return [term, null];
}
return [uri, null];
} else {
return [term, null];
}
} catch (e) {
return [term, 'error'];
}
}
}