From bfdc2319c4865a5dd22b75091a2d271b5e79e545 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Aug 2021 22:06:22 +0000 Subject: [PATCH 01/11] Bump path-parse from 1.0.6 to 1.0.7 Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7. - [Release notes](https://github.com/jbgutierrez/path-parse/releases) - [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7) --- updated-dependencies: - dependency-name: path-parse dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9ac3c7109..0862f56f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12020,15 +12020,11 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.5: +path-parse@^1.0.5, path-parse@^1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" -- 2.45.3 From cc3600631e22ff07ad960ca872a9427ba592748c Mon Sep 17 00:00:00 2001 From: Franco Montenegro Date: Tue, 10 Aug 2021 23:03:47 -0300 Subject: [PATCH 02/11] Disable cancel button when submitting a comment. --- ui/component/commentCreate/view.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/component/commentCreate/view.jsx b/ui/component/commentCreate/view.jsx index 8e113f269..3d511dd69 100644 --- a/ui/component/commentCreate/view.jsx +++ b/ui/component/commentCreate/view.jsx @@ -332,7 +332,12 @@ export function CommentCreate(props: Props) { } onClick={handleSupportComment} /> - + {isPinned && ( + + + {__('Pinned')} + + )} +
@@ -78,6 +97,8 @@ function LivestreamComment(props: Props) { authorUri={authorUri} commentIsMine={commentIsMine} disableEdit + isTopLevel + isPinned={isPinned} disableRemove={supportAmount > 0} /> diff --git a/ui/component/livestreamComments/index.js b/ui/component/livestreamComments/index.js index a90e75abe..97cac2e75 100644 --- a/ui/component/livestreamComments/index.js +++ b/ui/component/livestreamComments/index.js @@ -3,6 +3,7 @@ import { makeSelectClaimForUri, selectMyChannelClaims } from 'lbry-redux'; import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket'; import { doCommentList, doSuperChatList } from 'redux/actions/comments'; import { + selectPinnedCommentsById, makeSelectTopLevelCommentsForUri, selectIsFetchingComments, makeSelectSuperChatsForUri, @@ -17,6 +18,7 @@ const select = (state, props) => ({ superChats: makeSelectSuperChatsForUri(props.uri)(state), superChatsTotalAmount: makeSelectSuperChatTotalAmountForUri(props.uri)(state), myChannels: selectMyChannelClaims(state), + pinnedCommentsById: selectPinnedCommentsById(state), }); export default connect(select, { diff --git a/ui/component/livestreamComments/view.jsx b/ui/component/livestreamComments/view.jsx index 2e3c73cdb..4341acc87 100644 --- a/ui/component/livestreamComments/view.jsx +++ b/ui/component/livestreamComments/view.jsx @@ -23,6 +23,7 @@ type Props = { doSuperChatList: (string) => void, superChats: Array, myChannels: ?Array, + pinnedCommentsById: { [claimId: string]: Array }, }; const VIEW_MODE_CHAT = 'view_chat'; @@ -43,6 +44,7 @@ export default function LivestreamComments(props: Props) { doSuperChatList, myChannels, superChats: superChatsByTipAmount, + pinnedCommentsById, } = props; let superChatsFiatAmount, superChatsTotalAmount; @@ -58,6 +60,12 @@ export default function LivestreamComments(props: Props) { const discussionElement = document.querySelector('.livestream__comments'); const commentElement = document.querySelector('.livestream-comment'); + let pinnedComment; + const pinnedCommentIds = (claimId && pinnedCommentsById[claimId]) || []; + if (pinnedCommentIds.length > 0) { + pinnedComment = commentsByChronologicalOrder.find((c) => c.comment_id === pinnedCommentIds[0]); + } + React.useEffect(() => { if (claimId) { doCommentList(uri, '', 1, 75); @@ -234,6 +242,22 @@ export default function LivestreamComments(props: Props) { )} + {pinnedComment && ( +
+ +
+ )} + {/* top to bottom comment display */} {!fetchingComments && commentsByChronologicalOrder.length > 0 ? (
diff --git a/ui/redux/actions/websocket.js b/ui/redux/actions/websocket.js index 7c5532ea4..78552ab18 100644 --- a/ui/redux/actions/websocket.js +++ b/ui/redux/actions/websocket.js @@ -110,6 +110,17 @@ export const doCommentSocketConnect = (uri, claimId) => (dispatch) => { data: { connected, claimId }, }); } + if (response.type === 'pinned') { + const pinnedComment = response.data.comment; + dispatch({ + type: ACTIONS.COMMENT_PIN_COMPLETED, + data: { + pinnedComment: pinnedComment, + claimId, + unpin: !pinnedComment.is_pinned, + }, + }); + } }); }; diff --git a/ui/redux/reducers/comments.js b/ui/redux/reducers/comments.js index c81725a1e..4b5af666a 100644 --- a/ui/redux/reducers/comments.js +++ b/ui/redux/reducers/comments.js @@ -19,6 +19,7 @@ const defaultState: CommentsState = { commentsByUri: {}, // URI -> claimId linkedCommentAncestors: {}, // {"linkedCommentId": ["parentId", "grandParentId", ...]} superChatsByUri: {}, + pinnedCommentsById: {}, // ClaimId -> array of pinned comment IDs isLoading: false, isLoadingByParentId: {}, isCommenting: false, @@ -285,6 +286,7 @@ export default handleActions( const commentsByUri = Object.assign({}, state.commentsByUri); const repliesByParentId = Object.assign({}, state.repliesByParentId); const totalCommentsById = Object.assign({}, state.totalCommentsById); + const pinnedCommentsById = Object.assign({}, state.pinnedCommentsById); const totalRepliesByParentId = Object.assign({}, state.totalRepliesByParentId); const isLoadingByParentId = Object.assign({}, state.isLoadingByParentId); @@ -315,6 +317,9 @@ export default handleActions( const comment = comments[i]; commonUpdateAction(comment, commentById, commentIds, i); pushToArrayInObject(topLevelCommentsById, claimId, comment.comment_id); + if (comment.is_pinned) { + pushToArrayInObject(pinnedCommentsById, claimId, comment.comment_id); + } } } // --- Replies --- @@ -337,6 +342,7 @@ export default handleActions( topLevelTotalPagesById, repliesByParentId, totalCommentsById, + pinnedCommentsById, totalRepliesByParentId, byId, commentById, @@ -621,12 +627,20 @@ export default handleActions( const { pinnedComment, claimId, unpin } = action.data; const commentById = Object.assign({}, state.commentById); const topLevelCommentsById = Object.assign({}, state.topLevelCommentsById); + const pinnedCommentsById = Object.assign({}, state.pinnedCommentsById); if (pinnedComment && topLevelCommentsById[claimId]) { const index = topLevelCommentsById[claimId].indexOf(pinnedComment.comment_id); if (index > -1) { topLevelCommentsById[claimId].splice(index, 1); + if (pinnedCommentsById[claimId]) { + // Remove here so that the 'unshift' below will be a unique entry. + pinnedCommentsById[claimId] = pinnedCommentsById[claimId].filter((x) => x !== pinnedComment.comment_id); + } else { + pinnedCommentsById[claimId] = []; + } + if (unpin) { // Without the sort score, I have no idea where to put it. Just // dump it at the bottom. Users can refresh if they want it back to @@ -634,9 +648,26 @@ export default handleActions( topLevelCommentsById[claimId].push(pinnedComment.comment_id); } else { topLevelCommentsById[claimId].unshift(pinnedComment.comment_id); + pinnedCommentsById[claimId].unshift(pinnedComment.comment_id); } - commentById[pinnedComment.comment_id] = pinnedComment; + if (commentById[pinnedComment.comment_id]) { + // Commentron's `comment.Pin` response places the creator's credentials + // in the 'channel_*' fields, which doesn't make sense. Maybe it is to + // show who signed/pinned it, but even if so, it shouldn't overload + // these variables which are already used by existing comment data structure. + // Ensure we don't override the existing/correct values, but fallback + // to whatever was given. + const { channel_id, channel_name, channel_url } = commentById[pinnedComment.comment_id]; + commentById[pinnedComment.comment_id] = { + ...pinnedComment, + channel_id: channel_id || pinnedComment.channel_id, + channel_name: channel_name || pinnedComment.channel_name, + channel_url: channel_url || pinnedComment.channel_url, + }; + } else { + commentById[pinnedComment.comment_id] = pinnedComment; + } } } @@ -644,6 +675,7 @@ export default handleActions( ...state, commentById, topLevelCommentsById, + pinnedCommentsById, }; }, diff --git a/ui/redux/selectors/comments.js b/ui/redux/selectors/comments.js index 8d7a898ff..1a2ec9e78 100644 --- a/ui/redux/selectors/comments.js +++ b/ui/redux/selectors/comments.js @@ -17,6 +17,7 @@ export const selectCommentsDisabledChannelIds = createSelector( (state) => state.commentsDisabledChannelIds ); export const selectOthersReactsById = createSelector(selectState, (state) => state.othersReactsByCommentId); +export const selectPinnedCommentsById = createSelector(selectState, (state) => state.pinnedCommentsById); export const selectModerationBlockList = createSelector(selectState, (state) => state.moderationBlockList ? state.moderationBlockList.reverse() : [] diff --git a/ui/scss/component/_comments.scss b/ui/scss/component/_comments.scss index e99a2345a..065987e7f 100644 --- a/ui/scss/component/_comments.scss +++ b/ui/scss/component/_comments.scss @@ -217,6 +217,7 @@ $thumbnailWidthSmall: 1rem; .comment__pin { margin-left: var(--spacing-s); + font-size: var(--font-xsmall); .icon { padding-top: 1px; diff --git a/ui/scss/component/_livestream.scss b/ui/scss/component/_livestream.scss index 22fdbe239..4654cb9a4 100644 --- a/ui/scss/component/_livestream.scss +++ b/ui/scss/component/_livestream.scss @@ -223,6 +223,27 @@ $discussion-header__height: 3rem; } } +.livestream-pinned__wrapper { + flex-shrink: 0; + position: relative; + padding: var(--spacing-s) var(--spacing-xs); + border-bottom: 1px solid var(--color-border); + font-size: var(--font-small); + background-color: var(--color-card-background-highlighted); + width: 100%; + + .livestream-comment { + padding-top: var(--spacing-xs); + max-height: 6rem; + overflow-y: scroll; + } + + @media (min-width: $breakpoint-small) { + padding: var(--spacing-xs); + width: var(--livestream-comments-width); + } +} + .livestream-superchat__amount-large { .credit-amount { display: flex; -- 2.45.3 From ff9ca662f2dfba48da189bf6ed7b96e3bf55f7b9 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Sun, 25 Jul 2021 20:52:45 +0800 Subject: [PATCH 07/11] Option to change commments-server (desktop) ## Issue > 5459 Add setting for changing your comment server. Visible on desktop (and possibly defaulting to Odysee URL), hidden on odysee. ## Comments Not sure how this would actually work properly without the user recompiling the app to handle server differences. For example, even when we use our own server but switch between v1 and v2, some code changes are need to handle the differences. At that point, it seems easier for the user to just change the .env file? Anyway... ## Changes - Added Desktop-only options to define custom server. [Settings > Advanced Settings > "Comment server" section]. --- .env.defaults | 1 + config.js | 1 + static/app-strings.json | 7 +++ ui/comments.js | 13 +++- ui/component/settingCommentsServer/index.js | 17 +++++ ui/component/settingCommentsServer/view.jsx | 70 +++++++++++++++++++++ ui/page/settingsAdvanced/view.jsx | 5 ++ ui/redux/actions/comments.js | 42 ++++++++----- ui/redux/reducers/settings.js | 10 +++ 9 files changed, 149 insertions(+), 17 deletions(-) create mode 100644 ui/component/settingCommentsServer/index.js create mode 100644 ui/component/settingCommentsServer/view.jsx diff --git a/.env.defaults b/.env.defaults index 7a244cd80..bfeb12fff 100644 --- a/.env.defaults +++ b/.env.defaults @@ -12,6 +12,7 @@ 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.odysee.com/api/v2 +COMMENT_SERVER_NAME=Odysee SEARCH_SERVER_API=https://lighthouse.odysee.com/search SOCKETY_SERVER_API=wss://sockety.odysee.com/ws THUMBNAIL_CDN_URL=https://image-processor.vanwanet.com/optimize/ diff --git a/config.js b/config.js index a6c6b2a5c..89c916654 100644 --- a/config.js +++ b/config.js @@ -15,6 +15,7 @@ const config = { LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API, SEARCH_SERVER_API: process.env.SEARCH_SERVER_API, COMMENT_SERVER_API: process.env.COMMENT_SERVER_API, + COMMENT_SERVER_NAME: process.env.COMMENT_SERVER_NAME, SOCKETY_SERVER_API: process.env.SOCKETY_SERVER_API, WELCOME_VERSION: process.env.WELCOME_VERSION, DOMAIN: process.env.DOMAIN, diff --git a/static/app-strings.json b/static/app-strings.json index a4476a78e..86ee18263 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2038,6 +2038,13 @@ "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..", + "Comments server": "Comments server", + "Default comments server (%name%)": "Default comments server (%name%)", + "Custom comments server": "Custom comments server", + "Failed to fetch comments.": "Failed to fetch comments.", + "Failed to fetch comments. Verify custom server settings.": "Failed to fetch comments. Verify custom server settings.", + "Commenting server is not set.": "Commenting server is not set.", + "Comments are not currently enabled.": "Comments are not currently enabled.", "See All": "See All", "Supporting content requires %lbc%": "Supporting content requires %lbc%", "With %lbc%, you can send tips to your favorite creators, or help boost their content for more people to see.": "With %lbc%, you can send tips to your favorite creators, or help boost their content for more people to see.", diff --git a/ui/comments.js b/ui/comments.js index 8e9c5121e..9069e49d3 100644 --- a/ui/comments.js +++ b/ui/comments.js @@ -4,6 +4,13 @@ import { COMMENT_SERVER_API } from 'config'; const Comments = { url: COMMENT_SERVER_API, enabled: Boolean(COMMENT_SERVER_API), + isCustomServer: false, + + setServerUrl: (customUrl: ?string) => { + Comments.url = customUrl === undefined ? COMMENT_SERVER_API : customUrl; + Comments.enabled = Boolean(Comments.url); + Comments.isCustomServer = Comments.url !== COMMENT_SERVER_API; + }, moderation_block: (params: ModerationBlockParams) => fetchCommentsApi('moderation.Block', params), moderation_unblock: (params: ModerationBlockParams) => fetchCommentsApi('moderation.UnBlock', params), @@ -31,8 +38,10 @@ const Comments = { }; function fetchCommentsApi(method: string, params: {}) { - if (!Comments.enabled) { - return Promise.reject('Comments are not currently enabled'); // eslint-disable-line + if (!Comments.url) { + return Promise.reject(new Error('Commenting server is not set.')); + } else if (!Comments.enabled) { + return Promise.reject('Comments are not currently enabled.'); // eslint-disable-line } const url = `${Comments.url}?m=${method}`; diff --git a/ui/component/settingCommentsServer/index.js b/ui/component/settingCommentsServer/index.js new file mode 100644 index 000000000..9ad06707b --- /dev/null +++ b/ui/component/settingCommentsServer/index.js @@ -0,0 +1,17 @@ +import { connect } from 'react-redux'; +import { SETTINGS } from 'lbry-redux'; +import { doSetClientSetting } from 'redux/actions/settings'; +import { makeSelectClientSetting } from 'redux/selectors/settings'; +import SettingCommentsServer from './view'; + +const select = (state) => ({ + customServerEnabled: makeSelectClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVER_ENABLED)(state), + customServerUrl: makeSelectClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVER_URL)(state), +}); + +const perform = (dispatch) => ({ + setCustomServerEnabled: (val) => dispatch(doSetClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVER_ENABLED, val, true)), + setCustomServerUrl: (url) => dispatch(doSetClientSetting(SETTINGS.CUSTOM_COMMENTS_SERVER_URL, url, true)), +}); + +export default connect(select, perform)(SettingCommentsServer); diff --git a/ui/component/settingCommentsServer/view.jsx b/ui/component/settingCommentsServer/view.jsx new file mode 100644 index 000000000..5421398da --- /dev/null +++ b/ui/component/settingCommentsServer/view.jsx @@ -0,0 +1,70 @@ +// @flow +import { COMMENT_SERVER_NAME } from 'config'; +import React from 'react'; +import Comments from 'comments'; +import { FormField } from 'component/common/form'; + +const DEBOUNCE_TEXT_INPUT_MS = 500; + +type Props = { + customServerEnabled: boolean, + customServerUrl: string, + setCustomServerEnabled: (boolean) => void, + setCustomServerUrl: (string) => void, +}; + +function SettingCommentsServer(props: Props) { + const { customServerEnabled, customServerUrl, setCustomServerEnabled, setCustomServerUrl } = props; + const [customUrl, setCustomUrl] = React.useState(customServerUrl); + + React.useEffect(() => { + const timer = setTimeout(() => { + setCustomServerUrl(customUrl); + Comments.url = customUrl; + }, DEBOUNCE_TEXT_INPUT_MS); + + return () => clearTimeout(timer); + }, [customUrl, setCustomServerUrl]); + + return ( + + + { + if (e.target.checked) { + setCustomServerEnabled(false); + } + }} + /> + { + if (e.target.checked) { + setCustomServerEnabled(true); + } + }} + /> + + {customServerEnabled && ( +
+ setCustomUrl(e.target.value)} + /> +
+ )} +
+
+ ); +} + +export default SettingCommentsServer; diff --git a/ui/page/settingsAdvanced/view.jsx b/ui/page/settingsAdvanced/view.jsx index 4850d35f0..4807eaa9e 100644 --- a/ui/page/settingsAdvanced/view.jsx +++ b/ui/page/settingsAdvanced/view.jsx @@ -5,6 +5,7 @@ import { FormField, FormFieldPrice } from 'component/common/form'; import Button from 'component/button'; import I18nMessage from 'component/i18nMessage'; import Page from 'component/page'; +import SettingCommentsServer from 'component/settingCommentsServer'; import SettingWalletServer from 'component/settingWalletServer'; import SettingAutoLaunch from 'component/settingAutoLaunch'; import SettingClosingBehavior from 'component/settingClosingBehavior'; @@ -507,6 +508,10 @@ class SettingsAdvancedPage extends React.PureComponent { /> )} + {/* @if TARGET='app' */} + } /> + {/* @endif */} + } /> {/* @if TARGET='app' */} diff --git a/ui/redux/actions/comments.js b/ui/redux/actions/comments.js index 36bb55353..b4eac5e5e 100644 --- a/ui/redux/actions/comments.js +++ b/ui/redux/actions/comments.js @@ -28,6 +28,7 @@ import { selectPrefsReady } from 'redux/selectors/sync'; import { doAlertWaitingForSync } from 'redux/actions/app'; const isDev = process.env.NODE_ENV !== 'production'; +const FETCH_API_FAILED_TO_FETCH = 'Failed to fetch'; const COMMENTRON_MSG_REMAP = { // <-- Commentron msg --> : <-- App msg --> @@ -111,20 +112,32 @@ export function doCommentList( return result; }) .catch((error) => { - if (error.message === 'comments are disabled by the creator') { - dispatch({ - type: ACTIONS.COMMENT_LIST_COMPLETED, - data: { - authorClaimId: authorChannelClaim ? authorChannelClaim.claim_id : undefined, - disabled: true, - }, - }); - } else { - devToast(dispatch, `doCommentList: ${error.message}`); - dispatch({ - type: ACTIONS.COMMENT_LIST_FAILED, - data: error, - }); + switch (error.message) { + case 'comments are disabled by the creator': + dispatch({ + type: ACTIONS.COMMENT_LIST_COMPLETED, + data: { + authorClaimId: authorChannelClaim ? authorChannelClaim.claim_id : undefined, + disabled: true, + }, + }); + break; + + case FETCH_API_FAILED_TO_FETCH: + dispatch( + doToast({ + isError: true, + message: Comments.isCustomServer + ? __('Failed to fetch comments. Verify custom server settings.') + : __('Failed to fetch comments.'), + }) + ); + dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: error }); + break; + + default: + dispatch(doToast({ isError: true, message: `${error.message}` })); + dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: error }); } }); }; @@ -261,7 +274,6 @@ export function doCommentReactList(commentIds: Array) { }); }) .catch((error) => { - devToast(dispatch, `doCommentReactList: ${error.message}`); dispatch({ type: ACTIONS.COMMENT_REACTION_LIST_FAILED, data: error, diff --git a/ui/redux/reducers/settings.js b/ui/redux/reducers/settings.js index dc87fca6b..3b791e34a 100644 --- a/ui/redux/reducers/settings.js +++ b/ui/redux/reducers/settings.js @@ -4,6 +4,7 @@ import { ACTIONS as LBRY_REDUX_ACTIONS, SETTINGS, SHARED_PREFERENCES } from 'lbr import { getSubsetFromKeysArray } from 'util/sync-settings'; import { getDefaultLanguage } from 'util/default-languages'; import { UNSYNCED_SETTINGS, SIMPLE_SITE } from 'config'; +import Comments from 'comments'; const { CLIENT_SYNC_KEYS } = SHARED_PREFERENCES; const settingsToIgnore = (UNSYNCED_SETTINGS && UNSYNCED_SETTINGS.trim().split(' ')) || []; @@ -51,6 +52,8 @@ const defaultState = { [SETTINGS.VIDEO_THEATER_MODE]: false, [SETTINGS.VIDEO_PLAYBACK_RATE]: 1, [SETTINGS.DESKTOP_WINDOW_ZOOM]: 1, + [SETTINGS.CUSTOM_COMMENTS_SERVER_ENABLED]: false, + [SETTINGS.CUSTOM_COMMENTS_SERVER_URL]: '', [SETTINGS.DARK_MODE_TIMES]: { from: { hour: '21', min: '00', formattedTime: '21:00' }, @@ -169,6 +172,13 @@ reducers[LBRY_REDUX_ACTIONS.USER_STATE_POPULATE] = (state, action) => { const selectedSettings = sharedPreferences ? getSubsetFromKeysArray(sharedPreferences, clientSyncKeys) : {}; const mergedClientSettings = { ...currentClientSettings, ...selectedSettings }; const newSharedPreferences = sharedPreferences || {}; + + Comments.setServerUrl( + mergedClientSettings[SETTINGS.CUSTOM_COMMENTS_SERVER_ENABLED] + ? mergedClientSettings[SETTINGS.CUSTOM_COMMENTS_SERVER_URL] + : undefined + ); + return Object.assign({}, state, { sharedPreferences: newSharedPreferences, clientSettings: mergedClientSettings, -- 2.45.3 From 4cd6dc01effa4d0e6b10f1437535e314aff0f2ca Mon Sep 17 00:00:00 2001 From: zeppi Date: Thu, 12 Aug 2021 17:06:45 -0400 Subject: [PATCH 08/11] send recsys powered-by --- flow-typed/search.js | 3 +- .../internal/plugins/videojs-recsys/plugin.js | 3 +- ui/redux/actions/search.js | 10 ++-- ui/redux/reducers/search.js | 12 +++-- ui/redux/selectors/search.js | 46 ++++++++++++++++--- ui/util/handle-fetch.js | 7 ++- 6 files changed, 62 insertions(+), 19 deletions(-) diff --git a/flow-typed/search.js b/flow-typed/search.js index adfb768f6..b795f9c12 100644 --- a/flow-typed/search.js +++ b/flow-typed/search.js @@ -28,7 +28,7 @@ declare type SearchOptions = { declare type SearchState = { options: SearchOptions, - urisByQuery: {}, + resultsByQuery: {}, hasReachedMaxResultsLength: {}, searching: boolean, }; @@ -40,6 +40,7 @@ declare type SearchSuccess = { from: number, size: number, uris: Array, + recsys: string, }, }; diff --git a/ui/component/viewers/videoViewer/internal/plugins/videojs-recsys/plugin.js b/ui/component/viewers/videoViewer/internal/plugins/videojs-recsys/plugin.js index 5ee3f3c8d..01fd9a117 100644 --- a/ui/component/viewers/videoViewer/internal/plugins/videojs-recsys/plugin.js +++ b/ui/component/viewers/videoViewer/internal/plugins/videojs-recsys/plugin.js @@ -6,6 +6,7 @@ import { makeSelectRecommendedClaimIds, makeSelectRecommendationClicks, } from 'redux/selectors/content'; +import { makeSelectRecommendedRecsysIdForClaimId } from 'redux/selectors/search'; const VERSION = '0.0.1'; @@ -36,7 +37,7 @@ function createRecsys(claimId, userId, events, loadedAt, isEmbed) { claimId: claimId, pageLoadedAt: pageLoadedAt, pageExitedAt: pageExitedAt, - recsysId: recsysId, + recsysId: makeSelectRecommendedRecsysIdForClaimId(claimId)(state) || recsysId, recClaimIds: makeSelectRecommendedClaimIds(claimId)(state), recClickedVideoIdx: makeSelectRecommendationClicks(claimId)(state), events: events, diff --git a/ui/redux/actions/search.js b/ui/redux/actions/search.js index 0da0039d0..9d74a4db9 100644 --- a/ui/redux/actions/search.js +++ b/ui/redux/actions/search.js @@ -61,13 +61,14 @@ export const doSearch = (rawQuery: string, searchOptions: SearchOptions) => ( lighthouse .search(queryWithOptions) - .then((data: Array<{ name: string, claimId: string }>) => { + .then((data: { body: Array<{ name: string, claimId: string }>, poweredBy: string }) => { + const { body: result, poweredBy } = data; const uris = []; const actions = []; - data.forEach((result) => { - if (result) { - const { name, claimId } = result; + result.forEach((item) => { + if (item) { + const { name, claimId } = item; const urlObj: LbryUrlObj = {}; if (name.startsWith('@')) { @@ -94,6 +95,7 @@ export const doSearch = (rawQuery: string, searchOptions: SearchOptions) => ( from: from, size: size, uris, + recsys: poweredBy, }, }); dispatch(batchActions(...actions)); diff --git a/ui/redux/reducers/search.js b/ui/redux/reducers/search.js index 7730eeaa5..e97f868ec 100644 --- a/ui/redux/reducers/search.js +++ b/ui/redux/reducers/search.js @@ -17,7 +17,7 @@ const defaultState: SearchState = { [SEARCH_OPTIONS.MEDIA_IMAGE]: defaultSearchTypes.includes(SEARCH_OPTIONS.MEDIA_IMAGE), [SEARCH_OPTIONS.MEDIA_APPLICATION]: defaultSearchTypes.includes(SEARCH_OPTIONS.MEDIA_APPLICATION), }, - urisByQuery: {}, + resultsByQuery: {}, hasReachedMaxResultsLength: {}, searching: false, }; @@ -29,21 +29,23 @@ export default handleActions( searching: true, }), [ACTIONS.SEARCH_SUCCESS]: (state: SearchState, action: SearchSuccess): SearchState => { - const { query, uris, from, size } = action.data; + const { query, uris, from, size, recsys } = action.data; const normalizedQuery = createNormalizedSearchKey(query); + const urisForQuery = state.resultsByQuery[normalizedQuery] && state.resultsByQuery[normalizedQuery]['uris']; let newUris = uris; - if (from !== 0 && state.urisByQuery[normalizedQuery]) { - newUris = Array.from(new Set(state.urisByQuery[normalizedQuery].concat(uris))); + if (from !== 0 && urisForQuery) { + newUris = Array.from(new Set(urisForQuery.concat(uris))); } // The returned number of urls is less than the page size, so we're on the last page const noMoreResults = size && uris.length < size; + const results = { uris: newUris, recsys }; return { ...state, searching: false, - urisByQuery: Object.assign({}, state.urisByQuery, { [normalizedQuery]: newUris }), + resultsByQuery: Object.assign({}, state.resultsByQuery, { [normalizedQuery]: results }), hasReachedMaxResultsLength: Object.assign({}, state.hasReachedMaxResultsLength, { [normalizedQuery]: noMoreResults, }), diff --git a/ui/redux/selectors/search.js b/ui/redux/selectors/search.js index d864ea00d..24606fadd 100644 --- a/ui/redux/selectors/search.js +++ b/ui/redux/selectors/search.js @@ -4,6 +4,7 @@ import { selectShowMatureContent } from 'redux/selectors/settings'; import { parseURI, makeSelectClaimForUri, + makeSelectClaimForClaimId, makeSelectClaimIsNsfw, buildURI, isClaimNsfw, @@ -28,7 +29,7 @@ export const selectIsSearching: (state: State) => boolean = createSelector(selec export const selectSearchUrisByQuery: (state: State) => { [string]: Array } = createSelector( selectState, - (state) => state.urisByQuery + (state) => state.resultsByQuery ); export const selectHasReachedMaxResultsLength: (state: State) => { [boolean]: Array } = createSelector( @@ -42,9 +43,9 @@ export const makeSelectSearchUris = (query: string): ((state: State) => Array boolean) => @@ -84,16 +85,47 @@ export const makeSelectRecommendedContentForUri = (uri: string) => const searchQuery = getSearchQueryString(title.replace(/\//, ' '), options); const normalizedSearchQuery = createNormalizedSearchKey(searchQuery); - let searchUris = searchUrisByQuery[normalizedSearchQuery]; - if (searchUris) { - searchUris = searchUris.filter((searchUri) => searchUri !== currentUri); - recommendedContent = searchUris; + let searchResult = searchUrisByQuery[normalizedSearchQuery]; + if (searchResult) { + recommendedContent = searchResult['uris'].filter((searchUri) => searchUri !== currentUri); } } return recommendedContent; } ); +export const makeSelectRecommendedRecsysIdForClaimId = (claimId: string) => + createSelector(makeSelectClaimForClaimId(claimId), selectSearchUrisByQuery, (claim, searchUrisByQuery) => { + // TODO: DRY this out. + let poweredBy; + if (claim) { + const isMature = isClaimNsfw(claim); + const { title } = claim.value; + + if (!title) { + return; + } + + const options: { + related_to?: string, + nsfw?: boolean, + isBackgroundSearch?: boolean, + } = { related_to: claim.claim_id, isBackgroundSearch: true }; + + options['nsfw'] = isMature; + const searchQuery = getSearchQueryString(title.replace(/\//, ' '), options); + const normalizedSearchQuery = createNormalizedSearchKey(searchQuery); + + let searchResult = searchUrisByQuery[normalizedSearchQuery]; + if (searchResult) { + poweredBy = searchResult.recsys; + } else { + return normalizedSearchQuery; + } + } + return poweredBy; + }); + export const makeSelectWinningUriForQuery = (query: string) => { const uriFromQuery = `lbry://${query}`; diff --git a/ui/util/handle-fetch.js b/ui/util/handle-fetch.js index 83ce10513..4686b9b1a 100644 --- a/ui/util/handle-fetch.js +++ b/ui/util/handle-fetch.js @@ -1,4 +1,9 @@ // @flow export default function handleFetchResponse(response: Response): Promise { - return response.status === 200 ? Promise.resolve(response.json()) : Promise.reject(new Error(response.statusText)); + const headers = response.headers; + const poweredBy = headers.get('x-powered-by'); + + return response.status === 200 + ? response.json().then((body) => ({ body, poweredBy })) + : Promise.reject(new Error(response.statusText)); } -- 2.45.3 From b2386cc400fee476561728cfca409e9bb4fddc27 Mon Sep 17 00:00:00 2001 From: zeppi Date: Thu, 12 Aug 2021 22:49:20 -0400 Subject: [PATCH 09/11] update lighthouse call in useLighthouse --- ui/effects/use-lighthouse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/effects/use-lighthouse.js b/ui/effects/use-lighthouse.js index 029668c20..b5b9f31e5 100644 --- a/ui/effects/use-lighthouse.js +++ b/ui/effects/use-lighthouse.js @@ -25,7 +25,7 @@ export default function useLighthouse( let isSubscribed = true; lighthouse .search(throttledQuery) - .then((results) => { + .then(({ body: results }) => { if (isSubscribed) { setResults( results.map((result) => `lbry://${result.name}#${result.claimId}`).filter((uri) => isURIValid(uri)) -- 2.45.3 From 1628cca2c17e002dccde91f1f0f5d314f5978ca1 Mon Sep 17 00:00:00 2001 From: zeppi Date: Fri, 13 Aug 2021 12:44:26 -0400 Subject: [PATCH 10/11] rename select selectors --- ui/page/search/index.js | 4 ++-- ui/redux/actions/search.js | 4 ++-- ui/redux/selectors/search.js | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ui/page/search/index.js b/ui/page/search/index.js index 30558e61a..d1c140fd1 100644 --- a/ui/page/search/index.js +++ b/ui/page/search/index.js @@ -3,7 +3,7 @@ import { withRouter } from 'react-router'; import { doSearch } from 'redux/actions/search'; import { selectIsSearching, - makeSelectSearchUris, + makeSelectSearchUrisForQuery, selectSearchOptions, makeSelectHasReachedMaxResultsLength, } from 'redux/selectors/search'; @@ -28,7 +28,7 @@ const select = (state, props) => { }; const query = getSearchQueryString(urlQuery, searchOptions); - const uris = makeSelectSearchUris(query)(state); + const uris = makeSelectSearchUrisForQuery(query)(state); const hasReachedMaxResultsLength = makeSelectHasReachedMaxResultsLength(query)(state); return { diff --git a/ui/redux/actions/search.js b/ui/redux/actions/search.js index 9d74a4db9..28b1a724c 100644 --- a/ui/redux/actions/search.js +++ b/ui/redux/actions/search.js @@ -2,7 +2,7 @@ import * as ACTIONS from 'constants/action_types'; import { SEARCH_OPTIONS } from 'constants/search'; import { buildURI, doResolveUris, batchActions, isURIValid, makeSelectClaimForUri } from 'lbry-redux'; -import { makeSelectSearchUris, selectSearchValue } from 'redux/selectors/search'; +import { makeSelectSearchUrisForQuery, selectSearchValue } from 'redux/selectors/search'; import handleFetchResponse from 'util/handle-fetch'; import { getSearchQueryString } from 'util/query-params'; import { SIMPLE_SITE, SEARCH_SERVER_API } from 'config'; @@ -48,7 +48,7 @@ export const doSearch = (rawQuery: string, searchOptions: SearchOptions) => ( const from = searchOptions.from; // If we have already searched for something, we don't need to do anything - const urisForQuery = makeSelectSearchUris(queryWithOptions)(state); + const urisForQuery = makeSelectSearchUrisForQuery(queryWithOptions)(state); if (urisForQuery && !!urisForQuery.length) { if (!size || !from || from + size < urisForQuery.length) { return; diff --git a/ui/redux/selectors/search.js b/ui/redux/selectors/search.js index 24606fadd..1d97b1976 100644 --- a/ui/redux/selectors/search.js +++ b/ui/redux/selectors/search.js @@ -27,7 +27,7 @@ export const selectSearchOptions: (state: State) => SearchOptions = createSelect export const selectIsSearching: (state: State) => boolean = createSelector(selectState, (state) => state.searching); -export const selectSearchUrisByQuery: (state: State) => { [string]: Array } = createSelector( +export const selectSearchResultByQuery: (state: State) => { [string]: Array } = createSelector( selectState, (state) => state.resultsByQuery ); @@ -37,9 +37,9 @@ export const selectHasReachedMaxResultsLength: (state: State) => { [boolean]: Ar (state) => state.hasReachedMaxResultsLength ); -export const makeSelectSearchUris = (query: string): ((state: State) => Array) => +export const makeSelectSearchUrisForQuery = (query: string): ((state: State) => Array) => // replace statement below is kind of ugly, and repeated in doSearch action - createSelector(selectSearchUrisByQuery, (byQuery) => { + createSelector(selectSearchResultByQuery, (byQuery) => { if (query) { query = query.replace(/^lbry:\/\//i, '').replace(/\//, ' '); const normalizedQuery = createNormalizedSearchKey(query); @@ -61,7 +61,7 @@ export const makeSelectHasReachedMaxResultsLength = (query: string): ((state: St export const makeSelectRecommendedContentForUri = (uri: string) => createSelector( makeSelectClaimForUri(uri), - selectSearchUrisByQuery, + selectSearchResultByQuery, makeSelectClaimIsNsfw(uri), (claim, searchUrisByQuery, isMature) => { let recommendedContent; @@ -95,7 +95,7 @@ export const makeSelectRecommendedContentForUri = (uri: string) => ); export const makeSelectRecommendedRecsysIdForClaimId = (claimId: string) => - createSelector(makeSelectClaimForClaimId(claimId), selectSearchUrisByQuery, (claim, searchUrisByQuery) => { + createSelector(makeSelectClaimForClaimId(claimId), selectSearchResultByQuery, (claim, searchUrisByQuery) => { // TODO: DRY this out. let poweredBy; if (claim) { -- 2.45.3 From 137bc77d69ce2fdd8942cf2a7c18cdc0621767fa Mon Sep 17 00:00:00 2001 From: zeppi Date: Mon, 16 Aug 2021 17:19:36 -0400 Subject: [PATCH 11/11] update channel search too --- ui/component/channelContent/view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/component/channelContent/view.jsx b/ui/component/channelContent/view.jsx index 3498e5799..c069428a5 100644 --- a/ui/component/channelContent/view.jsx +++ b/ui/component/channelContent/view.jsx @@ -81,7 +81,7 @@ function ChannelContent(props: Props) { !showMature ? '&nsfw=false&size=50&from=0' : '' }` ) - .then((results) => { + .then(({ body: results }) => { const urls = results.map(({ name, claimId }) => { return `lbry://${name}#${claimId}`; }); -- 2.45.3