Merge branch 'master' into accessibility
This commit is contained in:
commit
c983af927f
21 changed files with 401 additions and 295 deletions
107
.env.defaults
107
.env.defaults
|
@ -1,36 +1,72 @@
|
|||
####### .env.default #########
|
||||
|
||||
# Copy this file to .env to make modifications
|
||||
|
||||
# Base config
|
||||
MATOMO_URL=https://analytics.lbry.com/
|
||||
MATOMO_ID=4
|
||||
WEBPACK_WEB_PORT=9090
|
||||
WEBPACK_ELECTRON_PORT=9091
|
||||
WEB_SERVER_PORT=1337
|
||||
|
||||
## APIS
|
||||
LBRY_WEB_API=https://api.na-backend.odysee.com
|
||||
LBRY_WEB_STREAMING_API=https://cdn.lbryplayer.xyz
|
||||
LBRY_WEB_BUFFER_API=https://collector-service.api.lbry.tv/api/v1/events/video
|
||||
COMMENT_SERVER_API=https://comments.lbry.com/api/v2
|
||||
THUMBNAIL_CDN_URL=https://image-processor.vanwanet.com/optimize/
|
||||
WELCOME_VERSION=1.0
|
||||
|
||||
# Custom Site info
|
||||
DOMAIN=lbry.tv
|
||||
URL=https://lbry.tv
|
||||
THUMBNAIL_CDN_URL=https://image-processor.vanwanet.com/optimize/
|
||||
# STRIPE
|
||||
STRIPE_PUBLIC_KEY='pk_test_NoL1JWL7i1ipfhVId5KfDZgo'
|
||||
|
||||
# Analytics
|
||||
MATOMO_URL=https://analytics.lbry.com/
|
||||
MATOMO_ID=4
|
||||
|
||||
# OG
|
||||
OG_TITLE_SUFFIX=| lbry.tv
|
||||
OG_HOMEPAGE_TITLE=lbry.tv
|
||||
OG_IMAGE_URL=
|
||||
SITE_CANONICAL_URL=https://lbry.tv
|
||||
|
||||
# UI
|
||||
## Custom Site info
|
||||
DOMAIN=lbry.tv
|
||||
URL=https://lbry.tv
|
||||
SITE_TITLE=lbry.tv
|
||||
SITE_NAME=lbry.tv
|
||||
SITE_DESCRIPTION=Meet LBRY, an open, free, and community-controlled content wonderland.
|
||||
SITE_HELP_EMAIL=help@lbry.com
|
||||
LOGO_TITLE=lbry.tv
|
||||
SIMPLE_SITE=false
|
||||
SHOW_ADS=true
|
||||
|
||||
## IMAGE ASSETS
|
||||
YRBL_HAPPY_IMG_URL=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-happy/7aa50a7e5adaf48691935d55e45d697547392929/839d9a
|
||||
YRBL_SAD_IMG_URL=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
|
||||
#FAVICON=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
|
||||
#LOGIN_IMG_URL=https://cdn.lbryplayer.xyz/api/v3/streams/free/login/b671946e911c66c5fa7233afb35de2badd9eceb8/0e1d81
|
||||
#LOGO=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
|
||||
#LOGO_TEXT_LIGHT=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
|
||||
#LOGO_TEXT_DARK=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
|
||||
#AVATAR_DEFAULT=
|
||||
|
||||
# LOCALE
|
||||
DEFAULT_LANGUAGE=en
|
||||
|
||||
## CUSTOM SETTINGS
|
||||
# Additional settings for below are found in ui/constants/settings and are for
|
||||
# preventing user settings from applying to custom sites without overwriting them.
|
||||
# UNSYNCED_SETTINGS='theme dark_mode_times automatic_dark_mode_enabled'
|
||||
|
||||
## LINKED CONTENT WHITELIST
|
||||
KNOWN_APP_DOMAINS=lbry.tv,lbry.lat,odysee.com
|
||||
|
||||
## CUSTOM CONTENT
|
||||
# If the following is true, copy custom/homepage.example.js to custom/homepage.js and modify
|
||||
CUSTOM_HOMEPAGE=false
|
||||
|
||||
# Add channels to auto-follow on first run
|
||||
AUTO_FOLLOW_CHANNELS=lbry://@lbry#3fda836a92faaceedfe398225fb9b2ee2ed1f01a
|
||||
|
||||
## FEATURES AND LIMITS
|
||||
SIMPLE_SITE=false
|
||||
|
||||
ENABLE_COMMENT_REACTIONS=true
|
||||
ENABLE_FILE_REACTIONS=false
|
||||
|
@ -41,53 +77,26 @@ CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS=4
|
|||
CHANNEL_STAKED_LEVEL_LIVESTREAM=5
|
||||
WEB_PUBLISH_SIZE_LIMIT_GB=4
|
||||
LOADING_BAR_COLOR=#2bbb90
|
||||
SHOW_ADS=true
|
||||
|
||||
# OG
|
||||
OG_TITLE_SUFFIX=| lbry.tv
|
||||
OG_HOMEPAGE_TITLE=lbry.tv
|
||||
OG_IMAGE_URL=
|
||||
SITE_CANONICAL_URL=https://lbry.tv
|
||||
|
||||
# LOCALE
|
||||
DEFAULT_LANGUAGE=en
|
||||
|
||||
# Custom Settings
|
||||
# Additional settings for below are found in ui/constants/settings and are for
|
||||
# preventing user settings from applying to custom sites without overwriting them.
|
||||
# UNSYNCED_SETTINGS='theme dark_mode_times automatic_dark_mode_enabled'
|
||||
KNOWN_APP_DOMAINS=lbry.tv,lbry.lat,odysee.com
|
||||
|
||||
# Custom Content
|
||||
# If the following is true, copy custom/homepage.example.js to custom/homepage.js and modify
|
||||
CUSTOM_HOMEPAGE=false
|
||||
# Add channels to auto-follow on first run
|
||||
AUTO_FOLLOW_CHANNELS=lbry://@lbry#3fda836a92faaceedfe398225fb9b2ee2ed1f01a
|
||||
# Add up to 2 sidebar links:
|
||||
# PINNED_URI_1=@Lbrylatam#2/Integracionesporseguridad#4
|
||||
# PINNED_LABEL_1=LBRY LATAM
|
||||
# PINNED_URI_2=$/discover?t=lbrytvpaidbeta&fee_amount=>0&claim_type=stream&channel_ids=5af39f818f668d8c00943c9326c5201c4fe3c423,cda9c4e92f19d6fe0764524a2012056e06ca2055,760da3ba3dd85830a843beaaed543a89b7a367e7,40c36948f0da072dcba3e4833e90f71e16de78be,e8f68563d242f6ac9784dcbc41dd86c28a9391d6,7236fc5d2783ea7314d9076ae6c8a250e3992d1a,cf7792c2a37d0d76aaaff84aff0b99a8c791429d,8316ac90764fedf3147799b7b81a6575a9cc398e,8627af93c1a1219150f06b698f4b33e6ed2f1c1e,8972a1bd06de5186e5e89292b05aac8aaa817791,c5b0b17838df2f6c31162f64d55f60f34ae8bfc6,f576d5dba905fc179de880c3fe3eb3281ea74f59,97dd77c93c9603cbb2583f3589f7f5a6c92baa43,f399d873e0c37cf24de9569b5f22bbb30a5c6709,dba870d0620d41b2b9a152c961e0c06cf875ccfc,ca1fd651c9d14bf2e5088bb2aa0146ee7aeb2ae0,50ad846a4b1543b847bf3fdafb7b45f6b2f5844c,e09ff5abe9fb44dd0dd0576894a6db60a6211603,7b6f7517f6b816827d076fa0eaad550aa315a4e7,2068452c41d8da3bd68961335da0072a99258a1a,5da63df97c8255ae94a88940695b8471657dd5a1,3645cf2f5d0bdac0523f945be1c3ff60758f7845,4da85b12244839d6368b9290f1619ff9514ab2a8,4ad942982e43326c7700b1b6443049b3cfd82161,55304f219244abf82f684f759cc0c7769242f3b4,8f42e5b592bb7f7a03f4a94a86a41b1236bb099f,e2a014d885a48f5be2dc6409610996337312facb,c18996ca488753f714d36d4654715927c1d7f9c2,ebc4214424cfa683a7046e1f794fea1e44788d84,06b6d6d6a893fb589ec2ded948f5122856921ed5,07e4546674268fc0222b2ca22d31d0549dc217ee,060940e41973d4f7f16d72a2733138e931c35f41,f8d6eccd887c9cebd36b1d42aa349279b7f5c3ed,68098b8426f967b8d04cc566348b5c128823219e,2bfe6cdb24a21bdc1b76fb7c416edd50e9e85945,1f9bb08bfa2259629f4aaa9ed40f97e9a41b6fa1,2f20148495612946675fe1c8ea99171e4d950b81,bc6938fa1e09e840056c2e831abf9664f397c472,2a6194792beac5130641e932b5ac6e5a99b5ca4f,185ba2bd547a5e4a77d29fe6c1484f47db5e058f,29cc7f6081268eaa5b3f2946e0cd0b952a94812c,49389450b1241f5d8f4c8c4271a3eb56bba33965,ffdc62ac2f7549398d3aca9d2119e83d80d588d5,d7a4d2808074b0c55d6b239f69d90e7a4930f943,d58aa4a0b2f6c2504c3abce8de3f1afb71800acc,77ae23dc7eb8a75609881d4548a79e4935a89d37,f79bce8a60fbece671f6265adc39f6469f3b9b8c,051995fdf0af634e4911704057a551e9392e62b1
|
||||
# PINNED_LABEL_2=Paid Beta
|
||||
|
||||
# Stripe
|
||||
STRIPE_PUBLIC_KEY='pk_test_NoL1JWL7i1ipfhVId5KfDZgo'
|
||||
|
||||
# SIMPLE_SITE REPLACEMENTS
|
||||
## SIMPLE_SITE REPLACEMENTS
|
||||
ENABLE_MATURE=true
|
||||
ENABLE_UI_NOTIFICATIONS=false
|
||||
|
||||
#ENABLE_LINK_TO_APP=true
|
||||
#FORCE_ANALYTICS=true
|
||||
#ENABLE_ADVANCED_FILTER=true
|
||||
#ENABLE_PAID_CONTENT=true
|
||||
#USE_FOOTER=true
|
||||
#USE_DISCOVER_WHITELIST=false
|
||||
#ENABLE_WILD_WEST=false
|
||||
#FULL_SIDE_LINKS=true
|
||||
#SHOW_TAGS_INTRO=false
|
||||
|
||||
# SEARCH TYPES
|
||||
#VIDEO_ENABLED=true
|
||||
#AUDIO_ENABLED=true
|
||||
#POSTS_ENABLED=true
|
||||
#IMAGES_ENABLED=true
|
||||
#FILES_ENABLED=true
|
||||
#MODELS_ENABLED=true
|
||||
|
||||
#ENABLE_LINK_TO_APP=true
|
||||
#FORCE_ANALYTiCS=true
|
||||
#ENABLE_ADVACNED_FILTER=true
|
||||
#ENABLE_PAID_CONTENT=true
|
||||
#USE_FOOTER=true
|
||||
#USE_DISCOVER_WHITELIST=false
|
||||
#ENABLE_WILD_WEST=false
|
||||
#FULL_SIDE_LINKS=true
|
||||
SHOW_TAGS_INTRO=true
|
||||
|
|
|
@ -141,6 +141,7 @@ function ClaimListDiscover(props: Props) {
|
|||
const { search } = location;
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [forceRefresh, setForceRefresh] = React.useState();
|
||||
const [finalUris, setFinalUris] = React.useState([]);
|
||||
const isLargeScreen = useIsLargeScreen();
|
||||
const [orderParamEntry, setOrderParamEntry] = usePersistedState(`entry-${location.pathname}`, CS.ORDER_BY_TRENDING);
|
||||
const [orderParamUser, setOrderParamUser] = usePersistedState(`orderUser-${location.pathname}`, CS.ORDER_BY_TRENDING);
|
||||
|
@ -176,7 +177,7 @@ function ClaimListDiscover(props: Props) {
|
|||
const durationParam = urlParams.get(CS.DURATION_KEY) || null;
|
||||
const channelIdsInUrl = urlParams.get(CS.CHANNEL_IDS_KEY);
|
||||
const channelIdsParam = channelIdsInUrl ? channelIdsInUrl.split(',') : channelIds;
|
||||
const feeAmountParam = urlParams.get('fee_amount') || feeAmount || SIMPLE_SITE ? CS.FEE_AMOUNT_ONLY_FREE : undefined;
|
||||
const feeAmountParam = urlParams.get('fee_amount') || feeAmount;
|
||||
const originalPageSize = pageSize || CS.PAGE_SIZE;
|
||||
const dynamicPageSize = isLargeScreen ? Math.ceil(originalPageSize * (3 / 2)) : originalPageSize;
|
||||
const historyAction = history.action;
|
||||
|
@ -360,9 +361,10 @@ function ClaimListDiscover(props: Props) {
|
|||
}
|
||||
|
||||
const hasMatureTags = tagsParam && tagsParam.split(',').some((t) => MATURE_TAGS.includes(t));
|
||||
const claimSearchCacheQuery = createNormalizedClaimSearchKey(options);
|
||||
let claimSearchResult = claimSearchByQuery[claimSearchCacheQuery];
|
||||
const claimSearchResultLastPageReached = claimSearchByQueryLastPageReached[claimSearchCacheQuery];
|
||||
|
||||
const mainSearchKey = createNormalizedClaimSearchKey(options);
|
||||
let claimSearchResult = claimSearchByQuery[mainSearchKey];
|
||||
const claimSearchResultLastPageReached = claimSearchByQueryLastPageReached[mainSearchKey];
|
||||
|
||||
// uncomment to fix an item on a page
|
||||
// const fixUri = 'lbry://@corbettreport#0/lbryodysee#5';
|
||||
|
@ -380,6 +382,11 @@ function ClaimListDiscover(props: Props) {
|
|||
// claimSearchResult.splice(2, 0, fixUri);
|
||||
// }
|
||||
|
||||
const livestreamSearchKey = liveLivestreamsFirst
|
||||
? createNormalizedClaimSearchKey(getLivestreamOnlyOptions(options))
|
||||
: undefined;
|
||||
const livestreamSearchResult = livestreamSearchKey && claimSearchByQuery[livestreamSearchKey];
|
||||
|
||||
const [prevOptions, setPrevOptions] = React.useState(null);
|
||||
|
||||
if (!isJustScrollingToNewPage(prevOptions, options)) {
|
||||
|
@ -482,6 +489,17 @@ function ClaimListDiscover(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
function urisEqual(prev: Array<string>, next: Array<string>) {
|
||||
if (!prev || !next) {
|
||||
// From 'ClaimList', "null" and "undefined" have special meaning,
|
||||
// so we can't just compare array length here.
|
||||
// - null = "timed out"
|
||||
// - undefined = "no result".
|
||||
return prev === next;
|
||||
}
|
||||
return prev.length === next.length && prev.every((value, index) => value === next[index]);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (shouldPerformSearch) {
|
||||
const searchOptions = JSON.parse(optionsStringForEffect);
|
||||
|
@ -493,6 +511,21 @@ function ClaimListDiscover(props: Props) {
|
|||
}
|
||||
}, [doClaimSearch, shouldPerformSearch, optionsStringForEffect, forceRefresh]);
|
||||
|
||||
// Resolve 'finalUri'
|
||||
React.useEffect(() => {
|
||||
if (uris) {
|
||||
if (!urisEqual(uris, finalUris)) {
|
||||
setFinalUris(uris);
|
||||
}
|
||||
} else {
|
||||
// Wait until all queries are done before updating the uris to avoid layout shifts.
|
||||
const pending = claimSearchResult === undefined || (liveLivestreamsFirst && livestreamSearchResult === undefined);
|
||||
if (!pending && !urisEqual(claimSearchResult, finalUris)) {
|
||||
setFinalUris(claimSearchResult);
|
||||
}
|
||||
}
|
||||
}, [uris, claimSearchResult, finalUris, setFinalUris, liveLivestreamsFirst, livestreamSearchResult]);
|
||||
|
||||
const headerToUse = header || (
|
||||
<ClaimListHeader
|
||||
channelIds={channelIds}
|
||||
|
@ -503,7 +536,7 @@ function ClaimListDiscover(props: Props) {
|
|||
claimType={claimType}
|
||||
streamType={streamType}
|
||||
defaultStreamType={defaultStreamType}
|
||||
feeAmount={SIMPLE_SITE ? undefined : feeAmount} // ENABLE_PAID_CONTENT_DISCOVER or something
|
||||
feeAmount={feeAmount} // ENABLE_PAID_CONTENT_DISCOVER or something
|
||||
orderBy={orderBy}
|
||||
defaultOrderBy={defaultOrderBy}
|
||||
hideAdvancedFilter={hideAdvancedFilter}
|
||||
|
@ -529,9 +562,9 @@ function ClaimListDiscover(props: Props) {
|
|||
)}
|
||||
<ClaimList
|
||||
tileLayout
|
||||
id={claimSearchCacheQuery}
|
||||
id={mainSearchKey}
|
||||
loading={loading}
|
||||
uris={uris || claimSearchResult}
|
||||
uris={finalUris}
|
||||
onScrollBottom={handleScrollBottom}
|
||||
page={page}
|
||||
pageSize={dynamicPageSize}
|
||||
|
@ -563,10 +596,10 @@ function ClaimListDiscover(props: Props) {
|
|||
</div>
|
||||
)}
|
||||
<ClaimList
|
||||
id={claimSearchCacheQuery}
|
||||
id={mainSearchKey}
|
||||
type={type}
|
||||
loading={loading}
|
||||
uris={uris || claimSearchResult}
|
||||
uris={finalUris}
|
||||
onScrollBottom={handleScrollBottom}
|
||||
page={page}
|
||||
pageSize={dynamicPageSize}
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
makeSelectFileInfoForUri,
|
||||
doPrepareEdit,
|
||||
makeSelectCollectionForIdHasClaimUrl,
|
||||
makeSelectNameForCollectionId,
|
||||
makeSelectCollectionIsMine,
|
||||
COLLECTIONS_CONSTS,
|
||||
makeSelectEditedCollectionForId,
|
||||
|
@ -34,20 +33,28 @@ import fs from 'fs';
|
|||
|
||||
const select = (state, props) => {
|
||||
const claim = makeSelectClaimForUri(props.uri, false)(state);
|
||||
const permanentUri = claim && claim.permanent_url;
|
||||
const repostedClaim = claim && claim.reposted_claim;
|
||||
const contentClaim = repostedClaim || claim;
|
||||
const contentSigningChannel = contentClaim && contentClaim.signing_channel;
|
||||
const contentPermanentUri = contentClaim && contentClaim.permanent_url;
|
||||
const contentChannelUri = (contentSigningChannel && contentSigningChannel.permanent_url) || contentPermanentUri;
|
||||
|
||||
return {
|
||||
claim,
|
||||
repostedClaim,
|
||||
contentClaim,
|
||||
contentSigningChannel,
|
||||
contentChannelUri,
|
||||
claimIsMine: makeSelectSigningIsMine(props.uri)(state),
|
||||
hasClaimInWatchLater: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.WATCH_LATER_ID, permanentUri)(state),
|
||||
hasClaimInCustom: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.FAVORITES_ID, permanentUri)(state),
|
||||
channelIsMuted: makeSelectChannelIsMuted(props.uri)(state),
|
||||
channelIsBlocked: makeSelectChannelIsBlocked(props.uri)(state),
|
||||
hasClaimInWatchLater: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.WATCH_LATER_ID, contentPermanentUri)(state),
|
||||
hasClaimInCustom: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.FAVORITES_ID, contentPermanentUri)(state),
|
||||
channelIsMuted: makeSelectChannelIsMuted(contentChannelUri)(state),
|
||||
channelIsBlocked: makeSelectChannelIsBlocked(contentChannelUri)(state),
|
||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||
isSubscribed: makeSelectIsSubscribed(props.channelUri, true)(state),
|
||||
isSubscribed: makeSelectIsSubscribed(contentChannelUri, true)(state),
|
||||
channelIsAdminBlocked: makeSelectChannelIsAdminBlocked(props.uri)(state),
|
||||
isAdmin: selectHasAdminChannel(state),
|
||||
claimInCollection: makeSelectCollectionForIdHasClaimUrl(props.collectionId, permanentUri)(state),
|
||||
collectionName: makeSelectNameForCollectionId(props.collectionId)(state),
|
||||
claimInCollection: makeSelectCollectionForIdHasClaimUrl(props.collectionId, contentPermanentUri)(state),
|
||||
isMyCollection: makeSelectCollectionIsMine(props.collectionId)(state),
|
||||
editedCollection: makeSelectEditedCollectionForId(props.collectionId)(state),
|
||||
isAuthenticated: Boolean(selectUserVerifiedEmail(state)),
|
||||
|
@ -71,7 +78,8 @@ const perform = (dispatch) => ({
|
|||
doChannelUnmute: (channelUri) => dispatch(doChannelUnmute(channelUri)),
|
||||
doCommentModBlock: (channelUri) => dispatch(doCommentModBlock(channelUri)),
|
||||
doCommentModUnBlock: (channelUri) => dispatch(doCommentModUnBlock(channelUri)),
|
||||
doCommentModBlockAsAdmin: (commenterUri, blockerId) => dispatch(doCommentModBlockAsAdmin(commenterUri, blockerId)),
|
||||
doCommentModBlockAsAdmin: (commenterUri, blockerId) =>
|
||||
dispatch(doCommentModBlockAsAdmin(commenterUri, blockerId)),
|
||||
doCommentModUnBlockAsAdmin: (commenterUri, blockerId) =>
|
||||
dispatch(doCommentModUnBlockAsAdmin(commenterUri, blockerId)),
|
||||
doChannelSubscribe: (subscription) => dispatch(doChannelSubscribe(subscription)),
|
||||
|
|
|
@ -23,8 +23,11 @@ type SubscriptionArgs = {
|
|||
|
||||
type Props = {
|
||||
uri: string,
|
||||
channelUri: string,
|
||||
claim: ?Claim,
|
||||
repostedClaim: ?Claim,
|
||||
contentClaim: ?Claim,
|
||||
contentSigningChannel: ?Claim,
|
||||
contentChannelUri: string,
|
||||
openModal: (id: string, {}) => void,
|
||||
inline?: boolean,
|
||||
channelIsMuted: boolean,
|
||||
|
@ -37,12 +40,10 @@ type Props = {
|
|||
doCommentModUnBlock: (string) => void,
|
||||
doCommentModBlockAsAdmin: (string, string) => void,
|
||||
doCommentModUnBlockAsAdmin: (string, string) => void,
|
||||
isRepost: boolean,
|
||||
doCollectionEdit: (string, any) => void,
|
||||
hasClaimInWatchLater: boolean,
|
||||
hasClaimInCustom: boolean,
|
||||
claimInCollection: boolean,
|
||||
collectionName?: string,
|
||||
collectionId: string,
|
||||
isMyCollection: boolean,
|
||||
doToast: ({ message: string, isError?: boolean }) => void,
|
||||
|
@ -60,8 +61,11 @@ type Props = {
|
|||
function ClaimMenuList(props: Props) {
|
||||
const {
|
||||
uri,
|
||||
channelUri,
|
||||
claim,
|
||||
repostedClaim,
|
||||
contentClaim,
|
||||
contentSigningChannel,
|
||||
contentChannelUri,
|
||||
openModal,
|
||||
inline = false,
|
||||
doChannelMute,
|
||||
|
@ -72,14 +76,12 @@ function ClaimMenuList(props: Props) {
|
|||
isAdmin,
|
||||
doCommentModBlock,
|
||||
doCommentModUnBlock,
|
||||
isRepost,
|
||||
doCommentModBlockAsAdmin,
|
||||
doCommentModUnBlockAsAdmin,
|
||||
doCollectionEdit,
|
||||
hasClaimInWatchLater,
|
||||
hasClaimInCustom,
|
||||
collectionId,
|
||||
collectionName,
|
||||
isMyCollection,
|
||||
doToast,
|
||||
claimIsMine,
|
||||
|
@ -92,15 +94,16 @@ function ClaimMenuList(props: Props) {
|
|||
editedCollection,
|
||||
isAuthenticated,
|
||||
} = props;
|
||||
const repostedContent = claim && claim.reposted_claim;
|
||||
const contentClaim = repostedContent || claim;
|
||||
const incognitoClaim = channelUri && !channelUri.includes('@');
|
||||
const signingChannel = claim && (claim.signing_channel || claim);
|
||||
const permanentUrl = String(channelUri);
|
||||
const isChannel = !incognitoClaim && signingChannel === claim;
|
||||
const incognitoClaim = contentChannelUri && !contentChannelUri.includes('@');
|
||||
const isChannel = !incognitoClaim && !contentSigningChannel;
|
||||
const { channelName } = parseURI(contentChannelUri);
|
||||
const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed > 0));
|
||||
const subscriptionLabel = isSubscribed ? __('Unfollow') : __('Follow');
|
||||
const subscriptionLabel = __('%action%' + '%user%', {
|
||||
action: isSubscribed ? __('Unfollow') : __('Follow'),
|
||||
user: repostedClaim ? __(' @' + channelName) : '',
|
||||
});
|
||||
const lastCollectionName = 'Favorites';
|
||||
const lastCollectionId = COLLECTIONS_CONSTS.FAVORITES_ID;
|
||||
|
||||
const { push, replace } = useHistory();
|
||||
if (!claim) {
|
||||
|
@ -121,36 +124,46 @@ function ClaimMenuList(props: Props) {
|
|||
// $FlowFixMe
|
||||
(contentClaim.value.stream_type === 'audio' || contentClaim.value.stream_type === 'video');
|
||||
|
||||
function handleAdd(source, name, collectionId) {
|
||||
doToast({
|
||||
message: source ? __('Item removed from %name%', { name }) : __('Item added to %name%', { name }),
|
||||
});
|
||||
doCollectionEdit(collectionId, {
|
||||
claims: [contentClaim],
|
||||
remove: source,
|
||||
type: 'playlist',
|
||||
});
|
||||
}
|
||||
|
||||
function handleFollow() {
|
||||
const { channelName } = parseURI(permanentUrl);
|
||||
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
|
||||
|
||||
subscriptionHandler({
|
||||
channelName: '@' + channelName,
|
||||
uri: permanentUrl,
|
||||
uri: contentChannelUri,
|
||||
notificationsDisabled: true,
|
||||
});
|
||||
}
|
||||
|
||||
function handleToggleMute() {
|
||||
if (channelIsMuted) {
|
||||
doChannelUnmute(channelUri);
|
||||
doChannelUnmute(contentChannelUri);
|
||||
} else {
|
||||
doChannelMute(channelUri);
|
||||
doChannelMute(contentChannelUri);
|
||||
}
|
||||
}
|
||||
|
||||
function handleToggleBlock() {
|
||||
if (channelIsBlocked) {
|
||||
doCommentModUnBlock(channelUri);
|
||||
doCommentModUnBlock(contentChannelUri);
|
||||
} else {
|
||||
doCommentModBlock(channelUri);
|
||||
doCommentModBlock(contentChannelUri);
|
||||
}
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
if (!isChannel) {
|
||||
const signingChannelName = signingChannel && signingChannel.name;
|
||||
const signingChannelName = contentSigningChannel && contentSigningChannel.name;
|
||||
|
||||
const uriObject: { streamName: string, streamClaimId: string, channelName?: string } = {
|
||||
streamName: claim.name,
|
||||
|
@ -170,10 +183,10 @@ function ClaimMenuList(props: Props) {
|
|||
}
|
||||
|
||||
function handleDelete() {
|
||||
if (!isRepost && !isChannel) {
|
||||
openModal(MODALS.CONFIRM_FILE_REMOVE, { uri });
|
||||
if (!repostedClaim && !isChannel) {
|
||||
openModal(MODALS.CONFIRM_FILE_REMOVE, { uri, doGoBack: false });
|
||||
} else {
|
||||
openModal(MODALS.CONFIRM_CLAIM_REVOKE, { claim, cb: !isRepost && (() => replace(`/$/${PAGES.CHANNELS}`)) });
|
||||
openModal(MODALS.CONFIRM_CLAIM_REVOKE, { claim, cb: isChannel && (() => replace(`/$/${PAGES.CHANNELS}`)) });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,9 +196,9 @@ function ClaimMenuList(props: Props) {
|
|||
|
||||
function handleToggleAdminBlock() {
|
||||
if (channelIsAdminBlocked) {
|
||||
doCommentModUnBlockAsAdmin(channelUri, '');
|
||||
doCommentModUnBlockAsAdmin(contentChannelUri, '');
|
||||
} else {
|
||||
doCommentModBlockAsAdmin(channelUri, '');
|
||||
doCommentModBlockAsAdmin(contentChannelUri, '');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,7 +223,7 @@ function ClaimMenuList(props: Props) {
|
|||
|
||||
function handleReportContent() {
|
||||
// $FlowFixMe
|
||||
push(`/$/${PAGES.REPORT_CONTENT}?claimId=${(repostedContent && repostedContent.claim_id) || claim.claim_id}`);
|
||||
push(`/$/${PAGES.REPORT_CONTENT}?claimId=${contentClaim && contentClaim.claim_id}`);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -228,101 +241,79 @@ function ClaimMenuList(props: Props) {
|
|||
{(!IS_WEB || (IS_WEB && isAuthenticated)) && (
|
||||
<>
|
||||
<>
|
||||
{/* WATCH LATER */}
|
||||
{isPlayable && !collectionId && (
|
||||
<MenuItem
|
||||
className="comment__menu-option"
|
||||
onSelect={() => {
|
||||
doToast({
|
||||
message: hasClaimInWatchLater
|
||||
? __('Item removed from Watch Later')
|
||||
: __('Item added to Watch Later'),
|
||||
});
|
||||
doCollectionEdit(COLLECTIONS_CONSTS.WATCH_LATER_ID, {
|
||||
claims: [contentClaim],
|
||||
remove: hasClaimInWatchLater,
|
||||
type: 'playlist',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={hasClaimInWatchLater ? ICONS.DELETE : ICONS.TIME} />
|
||||
{hasClaimInWatchLater ? __('In Watch Later') : __('Watch Later')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
{/* CUSTOM LIST */}
|
||||
{isPlayable && !collectionId && (
|
||||
<MenuItem
|
||||
className="comment__menu-option"
|
||||
onSelect={() => {
|
||||
doToast({
|
||||
message: hasClaimInCustom
|
||||
? __('Item removed from %lastCollectionName%', { lastCollectionName })
|
||||
: __('Item added to %lastCollectionName%', { lastCollectionName }),
|
||||
});
|
||||
doCollectionEdit(COLLECTIONS_CONSTS.FAVORITES_ID, {
|
||||
claims: [contentClaim],
|
||||
remove: hasClaimInCustom,
|
||||
type: 'playlist',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={hasClaimInCustom ? ICONS.DELETE : ICONS.STAR} />
|
||||
{hasClaimInCustom
|
||||
? __('In %lastCollectionName%', { lastCollectionName })
|
||||
: __(`${lastCollectionName}`)}
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
{/* COLLECTION OPERATIONS */}
|
||||
{collectionId && collectionName && isCollectionClaim && (
|
||||
{collectionId && isCollectionClaim ? (
|
||||
<>
|
||||
{Boolean(editedCollection) && (
|
||||
<MenuItem
|
||||
className="comment__menu-option"
|
||||
onSelect={() => push(`/$/${PAGES.LIST}/${collectionId}?view=edit`)}
|
||||
>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden iconColor={'red'} icon={ICONS.PUBLISH} />
|
||||
{__('Publish')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem className="comment__menu-option" onSelect={() => push(`/$/${PAGES.LIST}/${collectionId}`)}>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.VIEW} />
|
||||
{__('View List')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="comment__menu-option"
|
||||
onSelect={() => openModal(MODALS.COLLECTION_DELETE, { collectionId })}
|
||||
>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.DELETE} />
|
||||
{__('Delete List')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
{isMyCollection && (
|
||||
<>
|
||||
<MenuItem
|
||||
className="comment__menu-option"
|
||||
onSelect={() => push(`/$/${PAGES.LIST}/${collectionId}?view=edit`)}
|
||||
>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden iconColor={'red'} icon={ICONS.PUBLISH} />
|
||||
{editedCollection ? __('Publish') : __('Edit List')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="comment__menu-option"
|
||||
onSelect={() => openModal(MODALS.COLLECTION_DELETE, { collectionId })}
|
||||
>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.DELETE} />
|
||||
{__('Delete List')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* CURRENTLY ONLY SUPPORT PLAYLISTS FOR PLAYABLE; LATER DIFFERENT TYPES */}
|
||||
{isPlayable && (
|
||||
<MenuItem
|
||||
className="comment__menu-option"
|
||||
onSelect={() => openModal(MODALS.COLLECTION_ADD, { uri, type: 'playlist' })}
|
||||
>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.STACK} />
|
||||
{__('Add to Lists')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
) : (
|
||||
isPlayable && (
|
||||
<>
|
||||
{/* WATCH LATER */}
|
||||
<MenuItem
|
||||
className="comment__menu-option"
|
||||
onSelect={() => handleAdd(hasClaimInWatchLater, 'Watch Later', COLLECTIONS_CONSTS.WATCH_LATER_ID)}
|
||||
>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={hasClaimInWatchLater ? ICONS.DELETE : ICONS.TIME} />
|
||||
{hasClaimInWatchLater ? __('In Watch Later') : __('Watch Later')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
{/* CUSTOM LIST */}
|
||||
<MenuItem
|
||||
className="comment__menu-option"
|
||||
onSelect={() => handleAdd(hasClaimInCustom, lastCollectionName, lastCollectionId)}
|
||||
>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={hasClaimInCustom ? ICONS.DELETE : ICONS.STAR} />
|
||||
{hasClaimInCustom ? __(`In ${lastCollectionName}`) : __(`${lastCollectionName}`)}
|
||||
</div>
|
||||
</MenuItem>
|
||||
{/* CURRENTLY ONLY SUPPORT PLAYLISTS FOR PLAYABLE; LATER DIFFERENT TYPES */}
|
||||
<MenuItem
|
||||
className="comment__menu-option"
|
||||
onSelect={() => openModal(MODALS.COLLECTION_ADD, { uri, type: 'playlist' })}
|
||||
>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.STACK} />
|
||||
{__('Add to Lists')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
<hr className="menu__separator" />
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
|
||||
{!isChannelPage && (
|
||||
<>
|
||||
<hr className="menu__separator" />
|
||||
<MenuItem className="comment__menu-option" onSelect={handleSupport}>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.LBC} />
|
||||
|
@ -332,7 +323,7 @@ function ClaimMenuList(props: Props) {
|
|||
</>
|
||||
)}
|
||||
|
||||
{!incognitoClaim && !isRepost && !claimIsMine && !isChannelPage && (
|
||||
{!incognitoClaim && !claimIsMine && !isChannelPage && (
|
||||
<>
|
||||
<hr className="menu__separator" />
|
||||
<MenuItem className="comment__menu-option" onSelect={handleFollow}>
|
||||
|
@ -343,11 +334,11 @@ function ClaimMenuList(props: Props) {
|
|||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!isMyCollection && (
|
||||
<>
|
||||
{(!claimIsMine || channelIsBlocked) && channelUri ? (
|
||||
!incognitoClaim &&
|
||||
!isRepost && (
|
||||
{(!claimIsMine || channelIsBlocked) && contentChannelUri ? (
|
||||
!incognitoClaim && (
|
||||
<>
|
||||
<hr className="menu__separator" />
|
||||
<MenuItem className="comment__menu-option" onSelect={handleToggleBlock}>
|
||||
|
@ -376,7 +367,7 @@ function ClaimMenuList(props: Props) {
|
|||
)
|
||||
) : (
|
||||
<>
|
||||
{!isChannelPage && !isRepost && (
|
||||
{!isChannelPage && !repostedClaim && (
|
||||
<MenuItem className="comment__menu-option" onSelect={handleEdit}>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.EDIT} />
|
||||
|
@ -384,7 +375,6 @@ function ClaimMenuList(props: Props) {
|
|||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{showDelete && (
|
||||
<MenuItem className="comment__menu-option" onSelect={handleDelete}>
|
||||
<div className="menu__link">
|
||||
|
@ -412,7 +402,7 @@ function ClaimMenuList(props: Props) {
|
|||
|
||||
<MenuItem className="comment__menu-option" onSelect={handleCopyLink}>
|
||||
<div className="menu__link">
|
||||
<Icon aria-hidden icon={ICONS.COPY_LINK} />
|
||||
<Icon aria-hidden icon={ICONS.SHARE} />
|
||||
{__('Copy Link')}
|
||||
</div>
|
||||
</MenuItem>
|
||||
|
|
|
@ -150,12 +150,14 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
collectionUris,
|
||||
disableNavigation,
|
||||
} = props;
|
||||
const isRepost = claim && claim.repost_channel_url;
|
||||
const isCollection = claim && claim.value_type === 'collection';
|
||||
const collectionClaimId = isCollection && claim && claim.claim_id;
|
||||
const listId = collectionId || collectionClaimId;
|
||||
const WrapperElement = wrapperElement || 'li';
|
||||
const shouldFetch =
|
||||
claim === undefined || (claim !== null && claim.value_type === 'channel' && isEmpty(claim.meta) && !pending);
|
||||
const abandoned = !isResolvingUri && !claim;
|
||||
const isMyCollection = collectionId && (isCollectionMine || collectionId.includes('-'));
|
||||
const isMyCollection = listId && (isCollectionMine || listId.includes('-'));
|
||||
const shouldHideActions = hideActions || isMyCollection || type === 'small' || type === 'tooltip';
|
||||
const canonicalUrl = claim && claim.canonical_url;
|
||||
const lastCollectionIndex = collectionUris ? collectionUris.length - 1 : 0;
|
||||
|
@ -177,7 +179,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
claim.value.stream_type &&
|
||||
// $FlowFixMe
|
||||
(claim.value.stream_type === 'audio' || claim.value.stream_type === 'video');
|
||||
const isCollection = claim && claim.value_type === 'collection';
|
||||
const isChannelUri = isValid ? parseURI(uri).isChannel : false;
|
||||
const signingChannel = claim && claim.signing_channel;
|
||||
|
||||
|
@ -208,12 +209,11 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
}
|
||||
|
||||
let navigateUrl = formatLbryUrlForWeb((claim && claim.canonical_url) || uri || '/');
|
||||
if (collectionId) {
|
||||
if (listId) {
|
||||
const collectionParams = new URLSearchParams();
|
||||
collectionParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, collectionId);
|
||||
collectionParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, listId);
|
||||
navigateUrl = navigateUrl + `?` + collectionParams.toString();
|
||||
}
|
||||
const channelUri = !isChannelUri ? signingChannel && signingChannel.permanent_url : claim && claim.permanent_url;
|
||||
const navLinkProps = {
|
||||
to: navigateUrl,
|
||||
onClick: (e) => e.stopPropagation(),
|
||||
|
@ -398,7 +398,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
{!pending && (
|
||||
<>
|
||||
{renderActions && claim && renderActions(claim)}
|
||||
{Boolean(isMyCollection && collectionId) && (
|
||||
{Boolean(isMyCollection && listId) && (
|
||||
<>
|
||||
<div className="collection-preview__edit-buttons">
|
||||
<div className="collection-preview__edit-group">
|
||||
|
@ -412,7 +412,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
e.stopPropagation();
|
||||
if (editCollection) {
|
||||
// $FlowFixMe
|
||||
editCollection(collectionId, {
|
||||
editCollection(listId, {
|
||||
order: { from: collectionIndex, to: Number(collectionIndex) - 1 },
|
||||
});
|
||||
}
|
||||
|
@ -428,7 +428,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
e.stopPropagation();
|
||||
if (editCollection) {
|
||||
// $FlowFixMe
|
||||
editCollection(collectionId, {
|
||||
editCollection(listId, {
|
||||
order: { from: collectionIndex, to: Number(collectionIndex + 1) },
|
||||
});
|
||||
}
|
||||
|
@ -443,7 +443,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// $FlowFixMe
|
||||
if (editCollection) editCollection(collectionId, { claims: [claim], remove: true });
|
||||
if (editCollection) editCollection(listId, { claims: [claim], remove: true });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -485,7 +485,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
</div>
|
||||
</div>
|
||||
{!hideMenu && (
|
||||
<ClaimMenuList uri={uri} collectionId={collectionId} channelUri={channelUri} isRepost={isRepost} />
|
||||
<ClaimMenuList uri={uri} collectionId={listId} />
|
||||
)}
|
||||
</>
|
||||
</WrapperElement>
|
||||
|
|
|
@ -106,11 +106,10 @@ function ClaimPreviewTile(props: Props) {
|
|||
onClick: (e) => e.stopPropagation(),
|
||||
};
|
||||
|
||||
let isChannel;
|
||||
let isValid = false;
|
||||
if (uri) {
|
||||
try {
|
||||
({ isChannel } = parseURI(uri));
|
||||
parseURI(uri);
|
||||
isValid = true;
|
||||
} catch (e) {
|
||||
isValid = false;
|
||||
|
@ -118,6 +117,7 @@ function ClaimPreviewTile(props: Props) {
|
|||
}
|
||||
|
||||
const signingChannel = claim && claim.signing_channel;
|
||||
const isChannel = claim && claim.value_type === 'channel';
|
||||
const channelUri = !isChannel ? signingChannel && signingChannel.permanent_url : claim && claim.permanent_url;
|
||||
const channelTitle = signingChannel && (signingChannel.value.title || signingChannel.name);
|
||||
|
||||
|
@ -258,7 +258,7 @@ function ClaimPreviewTile(props: Props) {
|
|||
)}
|
||||
</h2>
|
||||
</NavLink>
|
||||
<ClaimMenuList uri={uri} collectionId={listId} channelUri={channelUri} isRepost={isRepost} />
|
||||
<ClaimMenuList uri={uri} collectionId={listId} channelUri={channelUri} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="claim-tile__info">
|
||||
|
|
|
@ -29,6 +29,7 @@ type Props = {
|
|||
myChannels: ?Array<ChannelClaim>,
|
||||
doToast: ({ message: string }) => void,
|
||||
clearPlayingUri: () => void,
|
||||
hideRepost?: boolean,
|
||||
isLivestreamClaim: boolean,
|
||||
reactionsDisabled: boolean,
|
||||
download: (string) => void,
|
||||
|
@ -48,6 +49,7 @@ function FileActions(props: Props) {
|
|||
myChannels,
|
||||
clearPlayingUri,
|
||||
doToast,
|
||||
hideRepost,
|
||||
isLivestreamClaim,
|
||||
reactionsDisabled,
|
||||
download,
|
||||
|
@ -113,23 +115,26 @@ function FileActions(props: Props) {
|
|||
|
||||
const lhsSection = (
|
||||
<>
|
||||
{ENABLE_FILE_REACTIONS && !reactionsDisabled && <FileReactions uri={uri} />}
|
||||
{ENABLE_FILE_REACTIONS && !reactionsDisabled && <FileReactions uri={uri} livestream={isLivestreamClaim} />}
|
||||
<ClaimSupportButton uri={uri} fileAction />
|
||||
<ClaimCollectionAddButton uri={uri} fileAction />
|
||||
<Button
|
||||
className="button--file-action"
|
||||
icon={ICONS.REPOST}
|
||||
label={
|
||||
claim.meta.reposted > 1 ? __(`%repost_total% Reposts`, { repost_total: claim.meta.reposted }) : __('Repost')
|
||||
}
|
||||
description={__('Repost')}
|
||||
requiresAuth={IS_WEB}
|
||||
onClick={handleRepostClick}
|
||||
/>
|
||||
{!hideRepost && (
|
||||
<Button
|
||||
button="alt"
|
||||
className="button--file-action"
|
||||
icon={ICONS.REPOST}
|
||||
label={
|
||||
claim.meta.reposted > 1 ? __(`%repost_total% Reposts`, { repost_total: claim.meta.reposted }) : __('Repost')
|
||||
}
|
||||
description={__('Repost')}
|
||||
requiresAuth={IS_WEB}
|
||||
onClick={handleRepostClick}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
className="button--file-action"
|
||||
icon={ICONS.SHARE}
|
||||
label={__('Share')}
|
||||
label={isMobile ? undefined : __('Share')}
|
||||
title={__('Share')}
|
||||
onClick={() => openModal(MODALS.SOCIAL_SHARE, { uri, webShareable, collectionId })}
|
||||
/>
|
||||
|
|
|
@ -55,6 +55,9 @@ export default function LivestreamComments(props: Props) {
|
|||
const commentsLength = comments && comments.length;
|
||||
const commentsToDisplay = viewMode === VIEW_MODE_CHAT ? comments : superChats;
|
||||
|
||||
const discussionElement = document.querySelector('.livestream__comments');
|
||||
const commentElement = document.querySelector('.livestream-comment');
|
||||
|
||||
// todo: implement comment_list --mine in SDK so redux can grab with selectCommentIsMine
|
||||
function isMyComment(channelId: string) {
|
||||
if (myChannels != null && channelId != null) {
|
||||
|
@ -81,19 +84,16 @@ export default function LivestreamComments(props: Props) {
|
|||
};
|
||||
}, [claimId, uri, doCommentList, doSuperChatList, doCommentSocketConnect, doCommentSocketDisconnect]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const discussionElement = document.querySelector('.livestream__comments');
|
||||
const commentElement = document.querySelector('.livestream-comment');
|
||||
const handleScroll = React.useCallback(() => {
|
||||
if (discussionElement) {
|
||||
const negativeCommentHeight = commentElement && -1 * commentElement.offsetHeight;
|
||||
const isAtRecent = negativeCommentHeight && discussionElement.scrollTop >= negativeCommentHeight;
|
||||
|
||||
function handleScroll() {
|
||||
if (discussionElement) {
|
||||
const negativeCommentHeight = commentElement && -1 * commentElement.offsetHeight;
|
||||
const isAtRecent = negativeCommentHeight && discussionElement.scrollTop >= negativeCommentHeight;
|
||||
|
||||
setScrollBottom(isAtRecent);
|
||||
}
|
||||
setScrollBottom(isAtRecent);
|
||||
}
|
||||
}, [commentElement, discussionElement]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (discussionElement) {
|
||||
discussionElement.addEventListener('scroll', handleScroll);
|
||||
|
||||
|
@ -113,15 +113,17 @@ export default function LivestreamComments(props: Props) {
|
|||
|
||||
return () => discussionElement.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
}, [commentsLength, performedInitialScroll, setPerformedInitialScroll, setScrollBottom]);
|
||||
}, [commentsLength, discussionElement, handleScroll, performedInitialScroll, setPerformedInitialScroll]);
|
||||
|
||||
if (!claim) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function scrollBack() {
|
||||
const element = document.querySelector('.livestream__comments');
|
||||
if (element) element.scrollTop = 0;
|
||||
if (discussionElement) {
|
||||
discussionElement.scrollTop = 0;
|
||||
setScrollBottom(true);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -209,7 +211,7 @@ export default function LivestreamComments(props: Props) {
|
|||
button="alt"
|
||||
className="livestream__comments-scroll__down"
|
||||
label={__('Recent Comments')}
|
||||
onClick={() => scrollBack()}
|
||||
onClick={scrollBack}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
@ -89,7 +89,9 @@ function OptimizedImage(props: Props) {
|
|||
} else {
|
||||
setOptimizedSrc(src);
|
||||
}
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
// We only want to run this on (1) initial mount and (2) 'src' change. Nothing else.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [src]);
|
||||
|
||||
if (!src) {
|
||||
return null;
|
||||
|
|
|
@ -512,7 +512,7 @@ function PublishFile(props: Props) {
|
|||
{fileSelectSource === SOURCE_UPLOAD && showFileUpload && (
|
||||
<>
|
||||
<FileSelector
|
||||
label={SIMPLE_SITE ? __('Video/audio file') : __('File')}
|
||||
label={__('File')}
|
||||
disabled={disabled}
|
||||
currentPath={currentFile}
|
||||
onFileChosen={handleFileChange}
|
||||
|
|
|
@ -305,6 +305,11 @@ function SideNavigation(props: Props) {
|
|||
<li className="navigation-link">
|
||||
<Button label={__('FAQ')} href="https://odysee.com/@OdyseeHelp:b" />
|
||||
</li>
|
||||
{SIMPLE_SITE && ( // GUIDELINES_URL?
|
||||
<li className="navigation-link">
|
||||
<Button label={__('Community Guidelines')} href="https://odysee.com/@OdyseeHelp:b/Community-Guidelines:c" />
|
||||
</li>
|
||||
)}
|
||||
<li className="navigation-link">
|
||||
<Button label={__('Support --[used in footer; general help/support]--')} href="https://lbry.com/support" />
|
||||
</li>
|
||||
|
|
|
@ -12,7 +12,7 @@ import LbcSymbol from 'component/common/lbc-symbol';
|
|||
type Props = {
|
||||
errorMessage: ?string,
|
||||
isPending: boolean,
|
||||
verifyUserIdentity: string => void,
|
||||
verifyUserIdentity: (string) => void,
|
||||
verifyPhone: () => void,
|
||||
fetchUser: () => void,
|
||||
skipLink?: string,
|
||||
|
@ -74,7 +74,7 @@ class UserVerify extends React.PureComponent<Props> {
|
|||
icon={ICONS.PHONE}
|
||||
title={__('Verify phone number')}
|
||||
subtitle={__(
|
||||
'You will receive an SMS text message confirming your phone number is valid. Does not work for Canada and possibly other regions.'
|
||||
'You will receive an SMS text message confirming your phone number is valid. May not be available in all regions.'
|
||||
)}
|
||||
actions={
|
||||
<Fragment>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { SIMPLE_SITE } from 'config';
|
||||
// import { SIMPLE_SITE } from 'config';
|
||||
import Button from 'component/button';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import classnames from 'classnames';
|
||||
|
@ -13,7 +13,7 @@ import hlsQualitySelector from './plugins/videojs-hls-quality-selector/plugin';
|
|||
import recsys from './plugins/videojs-recsys/plugin';
|
||||
import qualityLevels from 'videojs-contrib-quality-levels';
|
||||
import isUserTyping from 'util/detect-typing';
|
||||
import './plugins/videojs-aniview/plugin';
|
||||
// import './plugins/videojs-aniview/plugin';
|
||||
|
||||
const isDev = process.env.NODE_ENV !== 'production';
|
||||
|
||||
|
@ -192,7 +192,7 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
adUrl,
|
||||
claimId,
|
||||
userId,
|
||||
allowPreRoll,
|
||||
// allowPreRoll,
|
||||
} = props;
|
||||
|
||||
const [reload, setReload] = useState('initial');
|
||||
|
@ -586,9 +586,9 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
// This must be initialized earlier than everything else
|
||||
// otherwise a race condition occurs if we place this in the onReady call back
|
||||
// allow if isDev because otherwise you'll never see ads when basing to master
|
||||
if ((allowPreRoll && SIMPLE_SITE) || isDev) {
|
||||
vjs.aniview();
|
||||
}
|
||||
// if ((allowPreRoll && SIMPLE_SITE) || isDev) {
|
||||
// vjs.aniview();
|
||||
// }
|
||||
|
||||
// fixes #3498 (https://github.com/lbryio/lbry-desktop/issues/3498)
|
||||
// summary: on firefox the focus would stick to the fullscreen button which caused buggy behavior with spacebar
|
||||
|
@ -663,18 +663,19 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
}, [source, reload]);
|
||||
|
||||
// Load IMA3 SDK for aniview
|
||||
useEffect(() => {
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://imasdk.googleapis.com/js/sdkloader/ima3.js`;
|
||||
script.async = true;
|
||||
// $FlowFixMe
|
||||
document.body.appendChild(script);
|
||||
|
||||
return () => {
|
||||
// $FlowFixMe
|
||||
document.body.removeChild(script);
|
||||
};
|
||||
});
|
||||
// disabled for now
|
||||
// useEffect(() => {
|
||||
// const script = document.createElement('script');
|
||||
// script.src = `https://imasdk.googleapis.com/js/sdkloader/ima3.js`;
|
||||
// script.async = true;
|
||||
// // $FlowFixMe
|
||||
// document.body.appendChild(script);
|
||||
//
|
||||
// return () => {
|
||||
// // $FlowFixMe
|
||||
// document.body.removeChild(script);
|
||||
// };
|
||||
// });
|
||||
|
||||
return (
|
||||
// $FlowFixMe
|
||||
|
|
|
@ -2,15 +2,16 @@ import { connect } from 'react-redux';
|
|||
import { doDeleteFileAndMaybeGoBack } from 'redux/actions/file';
|
||||
import {
|
||||
makeSelectTitleForUri,
|
||||
makeSelectClaimIsMine,
|
||||
doResolveUri,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectIsAbandoningClaimForUri,
|
||||
} from 'lbry-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import ModalRemoveFile from './view';
|
||||
import { makeSelectSigningIsMine } from 'redux/selectors/content';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
claimIsMine: makeSelectSigningIsMine(props.uri)(state),
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
isAbandoning: makeSelectIsAbandoningClaimForUri(props.uri)(state),
|
||||
|
@ -18,8 +19,9 @@ const select = (state, props) => ({
|
|||
|
||||
const perform = dispatch => ({
|
||||
closeModal: () => dispatch(doHideModal()),
|
||||
deleteFile: (uri, deleteFromComputer, abandonClaim) => {
|
||||
dispatch(doDeleteFileAndMaybeGoBack(uri, deleteFromComputer, abandonClaim));
|
||||
doResolveUri: (uri) => dispatch(doResolveUri(uri)),
|
||||
deleteFile: (uri, deleteFromComputer, abandonClaim, doGoBack) => {
|
||||
dispatch(doDeleteFileAndMaybeGoBack(uri, deleteFromComputer, abandonClaim, doGoBack));
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -12,8 +12,10 @@ type Props = {
|
|||
uri: string,
|
||||
claim: StreamClaim,
|
||||
claimIsMine: boolean,
|
||||
doResolveUri: (string) => void,
|
||||
closeModal: () => void,
|
||||
deleteFile: (string, boolean, boolean) => void,
|
||||
deleteFile: (string, boolean, boolean, boolean) => void,
|
||||
doGoBack: boolean,
|
||||
title: string,
|
||||
fileInfo?: {
|
||||
outpoint: ?string,
|
||||
|
@ -22,10 +24,16 @@ type Props = {
|
|||
};
|
||||
|
||||
function ModalRemoveFile(props: Props) {
|
||||
const { uri, claimIsMine, closeModal, deleteFile, title, claim, isAbandoning } = props;
|
||||
const { uri, claimIsMine, doResolveUri, closeModal, deleteFile, doGoBack = true, title, claim, isAbandoning } = props;
|
||||
const [deleteChecked, setDeleteChecked] = usePersistedState('modal-remove-file:delete', true);
|
||||
const [abandonChecked, setAbandonChecked] = usePersistedState('modal-remove-file:abandon', true);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (uri) {
|
||||
doResolveUri(uri);
|
||||
}
|
||||
}, [uri, doResolveUri]);
|
||||
|
||||
return (
|
||||
<Modal isOpen contentLabel={__('Confirm File Remove')} type="card" onAborted={closeModal}>
|
||||
<Card
|
||||
|
@ -52,9 +60,7 @@ function ModalRemoveFile(props: Props) {
|
|||
<FormField
|
||||
name="claim_abandon"
|
||||
label={
|
||||
<I18nMessage
|
||||
tokens={{ lbc: <LbcSymbol prefix={__('reclaim %amount%', { amount: claim.amount })} /> }}
|
||||
>
|
||||
<I18nMessage tokens={{ lbc: <LbcSymbol postfix={claim.amount} /> }}>
|
||||
Remove from blockchain (%lbc%)
|
||||
</I18nMessage>
|
||||
}
|
||||
|
@ -87,7 +93,7 @@ function ModalRemoveFile(props: Props) {
|
|||
button="primary"
|
||||
label={isAbandoning ? __('Removing...') : __('OK')}
|
||||
disabled={isAbandoning || !(deleteChecked || abandonChecked)}
|
||||
onClick={() => deleteFile(uri, deleteChecked, claimIsMine ? abandonChecked : false)}
|
||||
onClick={() => deleteFile(uri, deleteChecked, claimIsMine ? abandonChecked : false, doGoBack)}
|
||||
/>
|
||||
<Button button="link" label={__('Cancel')} onClick={closeModal} />
|
||||
</div>
|
||||
|
|
|
@ -218,7 +218,7 @@ function ChannelPage(props: Props) {
|
|||
{!(isBlocked || isMuted) && <ClaimSupportButton uri={uri} />}
|
||||
{!(isBlocked || isMuted) && (!channelIsBlackListed || isSubscribed) && <SubscribeButton uri={permanentUrl} />}
|
||||
{/* TODO: add channel collections <ClaimCollectionAddButton uri={uri} fileAction /> */}
|
||||
<ClaimMenuList uri={claim.permanent_url} channelUri={claim.permanent_url} inline isChannelPage />
|
||||
<ClaimMenuList uri={claim.permanent_url} inline isChannelPage />
|
||||
</div>
|
||||
{cover && <img className={classnames('channel-cover__custom')} src={PlaceholderTx} />}
|
||||
{cover && <OptimizedImage className={classnames('channel-cover__custom')} src={cover} objectFit="cover" />}
|
||||
|
|
|
@ -8,7 +8,7 @@ import ClaimTilesDiscover from 'component/claimTilesDiscover';
|
|||
import ClaimListDiscover from 'component/claimListDiscover';
|
||||
import * as CS from 'constants/claim_search';
|
||||
import { toCapitalCase } from 'util/string';
|
||||
import { CUSTOM_HOMEPAGE } from 'config';
|
||||
import { CUSTOM_HOMEPAGE, SIMPLE_SITE } from 'config';
|
||||
|
||||
const MORE_CHANNELS_ANCHOR = 'MoreChannels';
|
||||
|
||||
|
@ -49,16 +49,6 @@ function ChannelsFollowingDiscover(props: Props) {
|
|||
},
|
||||
});
|
||||
|
||||
rowData.push({
|
||||
title: 'Latest From @lbrycast',
|
||||
link: `/@lbrycast:4`,
|
||||
options: {
|
||||
orderBy: ['release_time'],
|
||||
pageSize: 8,
|
||||
channelIds: ['4c29f8b013adea4d5cca1861fb2161d5089613ea'],
|
||||
},
|
||||
});
|
||||
|
||||
rowData.push({
|
||||
title: 'Trending Channels',
|
||||
link: `/$/${PAGES.DISCOVER}?claim_type=channel`,
|
||||
|
@ -105,29 +95,32 @@ function ChannelsFollowingDiscover(props: Props) {
|
|||
|
||||
return (
|
||||
<Page>
|
||||
{rowDataWithGenericOptions.map(({ title, link, help, options = {} }) => (
|
||||
<div key={title} className="claim-grid__wrapper">
|
||||
<h1 className="section__actions">
|
||||
{link ? (
|
||||
<Button
|
||||
className="claim-grid__title"
|
||||
button="link"
|
||||
navigate={link}
|
||||
iconRight={ICONS.ARROW_RIGHT}
|
||||
label={__(title)}
|
||||
/>
|
||||
) : (
|
||||
<span className="claim-grid__title">{__(title)}</span>
|
||||
)}
|
||||
{help}
|
||||
</h1>
|
||||
{!SIMPLE_SITE &&
|
||||
rowDataWithGenericOptions.map(({ title, link, help, options = {} }) => (
|
||||
<div key={title} className="claim-grid__wrapper">
|
||||
<h1 className="section__actions">
|
||||
{link ? (
|
||||
<Button
|
||||
className="claim-grid__title"
|
||||
button="link"
|
||||
navigate={link}
|
||||
iconRight={ICONS.ARROW_RIGHT}
|
||||
label={__(title)}
|
||||
/>
|
||||
) : (
|
||||
<span className="claim-grid__title">{__(title)}</span>
|
||||
)}
|
||||
{help}
|
||||
</h1>
|
||||
|
||||
<ClaimTilesDiscover {...options} />
|
||||
</div>
|
||||
))}
|
||||
<h1 id={MORE_CHANNELS_ANCHOR} className="claim-grid__title">
|
||||
{__('More Channels')}
|
||||
</h1>
|
||||
<ClaimTilesDiscover {...options} />
|
||||
</div>
|
||||
))}
|
||||
{!SIMPLE_SITE && (
|
||||
<h1 id={MORE_CHANNELS_ANCHOR} className="claim-grid__title">
|
||||
{__('More Channels')}
|
||||
</h1>
|
||||
)}
|
||||
<ClaimListDiscover
|
||||
defaultOrderBy={CS.ORDER_BY_TRENDING}
|
||||
defaultFreshness={CS.FRESH_ALL}
|
||||
|
|
|
@ -24,6 +24,8 @@ import { makeSelectNotificationForCommentId } from 'redux/selectors/notification
|
|||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import { toHex } from 'util/hex';
|
||||
import Comments from 'comments';
|
||||
import { selectPrefsReady } from 'redux/selectors/sync';
|
||||
import { doAlertWaitingForSync } from 'redux/actions/app';
|
||||
|
||||
const isDev = process.env.NODE_ENV !== 'production';
|
||||
|
||||
|
@ -726,8 +728,22 @@ function doCommentModToggleBlock(
|
|||
) {
|
||||
return async (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
|
||||
const ready = selectPrefsReady(state);
|
||||
let blockerChannelClaims = selectMyChannelClaims(state);
|
||||
|
||||
if (!ready) {
|
||||
return dispatch(doAlertWaitingForSync());
|
||||
}
|
||||
|
||||
if (!blockerChannelClaims) {
|
||||
return dispatch(
|
||||
doToast({
|
||||
message: __('Create a channel to change this setting.'),
|
||||
isError: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (blockerIds.length === 0) {
|
||||
// Specific blockers not provided, so find one based on block-level.
|
||||
switch (blockLevel) {
|
||||
|
|
|
@ -53,7 +53,7 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim, cb) {
|
|||
};
|
||||
}
|
||||
|
||||
export function doDeleteFileAndMaybeGoBack(uri, deleteFromComputer, abandonClaim) {
|
||||
export function doDeleteFileAndMaybeGoBack(uri, deleteFromComputer, abandonClaim, doGoBack) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const playingUri = selectPlayingUri(state);
|
||||
|
@ -70,7 +70,9 @@ export function doDeleteFileAndMaybeGoBack(uri, deleteFromComputer, abandonClaim
|
|||
doDeleteFile(outpoint || claimOutpoint, deleteFromComputer, abandonClaim, (abandonState) => {
|
||||
if (abandonState === ABANDON_STATES.DONE) {
|
||||
if (abandonClaim) {
|
||||
dispatch(goBack());
|
||||
if (doGoBack) {
|
||||
dispatch(goBack());
|
||||
}
|
||||
dispatch(doHideModal());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,9 @@ async function redirectMiddleware(ctx, next) {
|
|||
request: { url },
|
||||
} = ctx;
|
||||
|
||||
if (STATIC_ASSET_PATHS.includes(url) || (url.startsWith('/public/ui-') && url.endsWith('.js'))) {
|
||||
const HASHED_JS_REGEX = /^\/public\/.*[a-fA-F0-9]{12}\.js$/i;
|
||||
|
||||
if (STATIC_ASSET_PATHS.includes(url) || HASHED_JS_REGEX.test(url)) {
|
||||
ctx.set('Cache-Control', `public, max-age=${SIX_MONTHS_IN_SECONDS}`);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
const { generateDownloadUrl } = require('../../ui/util/web');
|
||||
const { URL, SITE_NAME, LBRY_WEB_API } = require('../../config.js');
|
||||
const { Lbry } = require('lbry-redux');
|
||||
const Feed = require('feed').Feed;
|
||||
|
@ -44,7 +45,9 @@ async function getClaimsFromChannel(claimId, count) {
|
|||
|
||||
async function getFeed(channelClaim, feedLink) {
|
||||
const replaceLineFeeds = (str) => str.replace(/(?:\r\n|\r|\n)/g, '<br />');
|
||||
|
||||
const fmtDescription = (description) => replaceLineFeeds(description);
|
||||
|
||||
const sanitizeThumbsUrl = (url) => {
|
||||
if (typeof url === 'string' && url.startsWith('https://')) {
|
||||
return encodeURI(url).replace(/&/g, '%26');
|
||||
|
@ -52,6 +55,29 @@ async function getFeed(channelClaim, feedLink) {
|
|||
return '';
|
||||
};
|
||||
|
||||
const getEnclosure = (claim) => {
|
||||
const value = claim.value;
|
||||
if (!value || !value.stream_type || !value.source || !value.source.media_type) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
switch (value.stream_type) {
|
||||
case 'video':
|
||||
case 'audio':
|
||||
case 'image':
|
||||
case 'document':
|
||||
case 'software':
|
||||
return {
|
||||
url: encodeURI(generateDownloadUrl(claim.name, claim.claim_id)),
|
||||
type: value.source.media_type,
|
||||
length: value.source.size || 0, // Per spec, 0 is a valid fallback.
|
||||
};
|
||||
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const value = channelClaim.value;
|
||||
const title = value ? value.title : channelClaim.name;
|
||||
|
||||
|
@ -78,14 +104,18 @@ async function getFeed(channelClaim, feedLink) {
|
|||
const meta = c.meta;
|
||||
const value = c.value;
|
||||
|
||||
const title = value && value.title ? value.title : c.name;
|
||||
const thumbnailUrl = value && value.thumbnail ? value.thumbnail.url : '';
|
||||
const thumbnailHtml = thumbnailUrl ? `<p><img src="${thumbnailUrl}" alt="thumbnail" title="${title}" /></p>` : '';
|
||||
|
||||
feed.addItem({
|
||||
id: c.claim_id,
|
||||
guid: encodeURI(URL + '/' + c.name + ':' + c.claim_id),
|
||||
title: value && value.title ? value.title : c.name,
|
||||
description: fmtDescription(value && value.description ? value.description : ''),
|
||||
image: sanitizeThumbsUrl(value && value.thumbnail ? value.thumbnail.url : ''),
|
||||
description: thumbnailHtml + fmtDescription(value && value.description ? value.description : ''),
|
||||
link: encodeURI(URL + '/' + c.name + ':' + c.claim_id),
|
||||
date: new Date(meta ? meta.creation_timestamp * 1000 : null),
|
||||
enclosure: getEnclosure(c),
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue