From da8000303bc13f13f4079631d34ea23073a353bc Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Fri, 9 Jul 2021 16:01:28 +0800 Subject: [PATCH 1/5] Add toast feedback for RSS and Link copy action. 6369 --- static/app-strings.json | 4 ++++ ui/component/claimMenuList/view.jsx | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/static/app-strings.json b/static/app-strings.json index b4cc13f9e..f00d1fbc4 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -701,7 +701,11 @@ "Bid position must be a number.": "Bid position must be a number.", "Copy": "Copy", "Copy Link": "Copy Link", + "Link copied.": "Link copied.", + "Failed to copy link.": "Failed to copy link.", "Copy RSS URL": "Copy RSS URL", + "RSS URL copied.": "RSS URL copied.", + "Failed to copy RSS URL.": "Failed to copy RSS URL.", "Text copied": "Text copied", "Rewards Disabled": "Rewards Disabled", "Wallet servers are used to relay data to and from the LBRY blockchain. They also determine what content shows in trending or is blocked. %learn_more%.": "Wallet servers are used to relay data to and from the LBRY blockchain. They also determine what content shows in trending or is blocked. %learn_more%.", diff --git a/ui/component/claimMenuList/view.jsx b/ui/component/claimMenuList/view.jsx index 9463d8675..271502c47 100644 --- a/ui/component/claimMenuList/view.jsx +++ b/ui/component/claimMenuList/view.jsx @@ -44,7 +44,7 @@ type Props = { collectionName?: string, collectionId: string, isMyCollection: boolean, - doToast: ({ message: string }) => void, + doToast: ({ message: string, isError?: boolean }) => void, claimIsMine: boolean, fileInfo: FileListItem, prepareEdit: ({}, string, {}) => void, @@ -183,12 +183,23 @@ function ClaimMenuList(props: Props) { } } + function copyToClipboard(textToCopy, successMsg, failureMsg) { + navigator.clipboard + .writeText(textToCopy) + .then(() => { + doToast({ message: __(successMsg) }); + }) + .catch(() => { + doToast({ message: __(failureMsg), isError: true }); + }); + } + function handleCopyRssLink() { - navigator.clipboard.writeText(rssUrl); + copyToClipboard(rssUrl, 'RSS URL copied.', 'Failed to copy RSS URL.'); } function handleCopyLink() { - navigator.clipboard.writeText(shareUrl); + copyToClipboard(shareUrl, 'Link copied.', 'Failed to copy link.'); } function handleReportContent() { From f6641ee04565e85b15a99e1af20d55e19c598f76 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Fri, 9 Jul 2021 16:06:28 +0800 Subject: [PATCH 2/5] Bump from 50 to 500 latest entries per `creation_timestamp` --- web/src/rss.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/src/rss.js b/web/src/rss.js index 241521c3b..7258081a9 100644 --- a/web/src/rss.js +++ b/web/src/rss.js @@ -6,6 +6,8 @@ const SDK_API_PATH = `${LBRY_WEB_API}/api/v1`; const proxyURL = `${SDK_API_PATH}/proxy`; Lbry.setDaemonConnectionString(proxyURL); +const NUM_ENTRIES = 500; + async function doClaimSearch(options) { let results; try { @@ -59,7 +61,7 @@ async function getFeed(channelClaim) { const feed = new Feed(options); - const latestClaims = await getClaimsFromChannel(channelClaim.claim_id, 50); + const latestClaims = await getClaimsFromChannel(channelClaim.claim_id, NUM_ENTRIES); latestClaims.forEach((c) => { const meta = c.meta; From e7bed19391f6feb1053d566da93898d4d2a58cd2 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Fri, 9 Jul 2021 17:33:34 +0800 Subject: [PATCH 3/5] Shorten the RSS URL This will be backward compatible with existing format that uses the full claim_id. --- ui/util/url.js | 2 +- web/src/rss.js | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/ui/util/url.js b/ui/util/url.js index ef950d5de..89646ce07 100644 --- a/ui/util/url.js +++ b/ui/util/url.js @@ -150,6 +150,6 @@ export const generateShareUrl = ( export const generateRssUrl = (domain, lbryUrl) => { const { channelName, channelClaimId } = parseURI(lbryUrl); - const url = `${domain}/$/rss/@${channelName}/${channelClaimId}`; + const url = `${domain}/$/rss/@${channelName}/${channelClaimId.slice(0, 2)}`; return url; }; diff --git a/web/src/rss.js b/web/src/rss.js index 7258081a9..691721911 100644 --- a/web/src/rss.js +++ b/web/src/rss.js @@ -16,15 +16,17 @@ async function doClaimSearch(options) { return results ? results.items : undefined; } -async function getChannelClaim(claimId) { - const options = { - claim_ids: [claimId], - page_size: 1, - no_totals: true, - }; +async function getChannelClaim(name, claimId) { + let claim; + try { + const url = `lbry://${name}#${claimId}`; + const response = await Lbry.resolve({ urls: [url] }); - const claims = await doClaimSearch(options); - return claims ? claims[0] : undefined; + if (response && response[url] && !response[url].error) { + claim = response && response[url]; + } + } catch {} + return claim || 'The RSS URL is invalid or is not associated with any channel.'; } async function getClaimsFromChannel(claimId, count) { @@ -86,7 +88,7 @@ async function getRss(ctx) { return 'Invalid URL'; } - const channelClaim = await getChannelClaim(ctx.params.claimId); + const channelClaim = await getChannelClaim(ctx.params.claimName, ctx.params.claimId); if (typeof channelClaim === 'string' || !channelClaim) { return channelClaim; } From cd9337f2d4af730185c8afb8677ac51663ace9fe Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Fri, 9 Jul 2021 17:42:14 +0800 Subject: [PATCH 4/5] Skip RSS URL generation if it's not a channel. --- ui/component/claimMenuList/view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/component/claimMenuList/view.jsx b/ui/component/claimMenuList/view.jsx index 271502c47..822bd13f8 100644 --- a/ui/component/claimMenuList/view.jsx +++ b/ui/component/claimMenuList/view.jsx @@ -103,7 +103,7 @@ function ClaimMenuList(props: Props) { } const shareUrl: string = generateShareUrl(SHARE_DOMAIN, uri); - const rssUrl: string = generateRssUrl(URL, uri); + const rssUrl: string = isChannel ? generateRssUrl(URL, uri) : ''; const isCollectionClaim = claim && claim.value_type === 'collection'; // $FlowFixMe const isPlayable = From 249f06bd21626e607c9c954df0c0a06fa19a740a Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Fri, 9 Jul 2021 17:57:59 +0800 Subject: [PATCH 5/5] w3c validation --- web/src/rss.js | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/web/src/rss.js b/web/src/rss.js index 691721911..d5f6d3077 100644 --- a/web/src/rss.js +++ b/web/src/rss.js @@ -42,27 +42,36 @@ async function getClaimsFromChannel(claimId, count) { return await doClaimSearch(options); } -async function getFeed(channelClaim) { - const replaceLineFeeds = (str) => str.replace(/(?:\r\n|\r|\n)/g, '
'); +async function getFeed(channelClaim, feedLink) { + const replaceLineFeeds = (str) => str.replace(/(?:\r\n|\r|\n)/g, '
'); + const fmtDescription = (description) => replaceLineFeeds(description); + const sanitizeThumbsUrl = (url) => { + if (typeof url === 'string' && url.startsWith('https://')) { + return encodeURI(url).replace(/&/g, '%26'); + } + return ''; + }; const value = channelClaim.value; const title = value ? value.title : channelClaim.name; const options = { - title: title + ' on ' + SITE_NAME, - description: value && value.description ? replaceLineFeeds(value.description) : '', - link: `${URL}/${channelClaim.name}:${channelClaim.claim_id}`, favicon: URL + '/public/favicon.png', generator: SITE_NAME + ' RSS Feed', - image: value && value.thumbnail ? value.thumbnail.url : '', + title: title + ' on ' + SITE_NAME, + description: fmtDescription(value && value.description ? value.description : ''), + link: encodeURI(`${URL}/${channelClaim.name}:${channelClaim.claim_id}`), + image: sanitizeThumbsUrl(value && value.thumbnail ? value.thumbnail.url : ''), + feedLinks: { + rss: encodeURI(feedLink), + }, author: { - name: channelClaim.name, - link: URL + '/' + channelClaim.name + ':' + channelClaim.claim_id, + name: encodeURI(channelClaim.name), + link: encodeURI(URL + '/' + channelClaim.name + ':' + channelClaim.claim_id), }, }; const feed = new Feed(options); - const latestClaims = await getClaimsFromChannel(channelClaim.claim_id, NUM_ENTRIES); latestClaims.forEach((c) => { @@ -70,12 +79,12 @@ async function getFeed(channelClaim) { const value = c.value; feed.addItem({ - guid: c.claim_id, id: c.claim_id, - title: value ? value.title : c.name, - description: value && value.description ? replaceLineFeeds(value.description) : '', - image: value && value.thumbnail ? value.thumbnail.url : '', - link: URL + '/' + c.name + ':' + 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 : ''), + link: encodeURI(URL + '/' + c.name + ':' + c.claim_id), date: new Date(meta ? meta.creation_timestamp * 1000 : null), }); }); @@ -83,6 +92,12 @@ async function getFeed(channelClaim) { return feed; } +function postProcess(feed) { + // Handle 'Feed' creating an invalid MIME type when trying to guess + // from 'https://thumbnails.lbry.com/UCgQ8eREJzR1dO' style of URLs. + return feed.replace(/type="image\/\/.*"\/>/g, 'type="image/*"/>'); +} + async function getRss(ctx) { if (!ctx.params.claimName || !ctx.params.claimId) { return 'Invalid URL'; @@ -93,8 +108,8 @@ async function getRss(ctx) { return channelClaim; } - const feed = await getFeed(channelClaim); - return feed.rss2(); + const feed = await getFeed(channelClaim, `${URL}/$/rss/${ctx.params.claimName}/${ctx.params.claimId}`); + return postProcess(feed.rss2()); } module.exports = { getRss };