Skip to content
Snippets Groups Projects
Commit 353a2de0 authored by Jason Salyers's avatar Jason Salyers
Browse files

Merge branch '123-empty-community-search' into 'develop'

Resolve "UI: Community Search when results are empty"

Closes #123

See merge request !225
parents 25dfadda 7488e8c1
No related branches found
No related tags found
2 merge requests!227GDPR user list update,!225Resolve "UI: Community Search when results are empty"
......@@ -21,31 +21,26 @@ export default class CommunitiesIndex extends React.Component {
}
componentWillMount = () => {
this.props.performSearch(
this.props.username,
this.state.searchQuery,
this.state.searchOrder
);
const { performSearch, username, searchQuery, searchOrder } = this.props;
performSearch(username, searchQuery, searchOrder);
};
componentDidUpdate = (prevProps, prevState) => {
if (prevProps.username !== this.props.username) {
this.props.performSearch(
this.props.username,
this.state.searchQuery,
this.state.searchOrder
);
componentDidUpdate = prevProps => {
const { performSearch, username, searchQuery, searchOrder } = this.props;
if (prevProps.username !== username) {
performSearch(username, searchQuery, searchOrder);
}
};
render() {
const {
communities,
communities_idx,
username,
walletUrl,
performSearch,
} = this.props;
const ordered = communities_idx.map(name => communities.get(name));
const { communities, communities_idx, username, walletUrl, performSearch } = this.props;
const ordered =
communities_idx !== null
? communities_idx.map(name => {
return communities.get(name);
})
: [];
const sortOptions = [
{
......@@ -62,31 +57,18 @@ export default class CommunitiesIndex extends React.Component {
},
];
if (communities_idx.size === 0) {
const role = comm => {
return (
<center>
<LoadingIndicator
style={{ marginBottom: '2rem' }}
type="circle"
/>
</center>
);
}
const role = comm =>
comm.context &&
comm.context.role !== 'guest' && (
<span className="user_role">{comm.context.role}</span>
comm.context && comm.context.role !== 'guest' && <span className="user_role">{comm.context.role}</span>
);
};
const communityAdmins = admins => {
if (!admins || admins.length === 0) return;
if (!admins || admins.length === 0) return null;
return (
<div>
{admins.length === 1
? `${tt('g.administrator')}: `
: `${tt('g.administrators')}: `}
{admins.length === 1 ? `${tt('g.administrator')}: ` : `${tt('g.administrators')}: `}
<UserNames names={admins} />
</div>
);
......@@ -104,9 +86,8 @@ export default class CommunitiesIndex extends React.Component {
<br />
{comm.about}
<small>
{comm.subscribers} subscribers &bull;{' '}
{comm.num_authors} posters &bull; {comm.num_pending}{' '}
posts
{comm.subscribers}
subscribers &bull; {comm.num_authors} posters &bull; {comm.num_pending} posts
{admins}
</small>
</th>
......@@ -117,18 +98,14 @@ export default class CommunitiesIndex extends React.Component {
);
};
const { searchQuery, searchOrder } = this.state;
return (
<PostsIndexLayout
category={null}
enableAds={false}
blogmode={false}
>
<PostsIndexLayout category={null} enableAds={false} blogmode={false}>
<div className="CommunitiesIndex c-sidebar__module">
{username && (
<div style={{ float: 'right' }}>
<a href={`${walletUrl}/@${username}/communities`}>
Create a Community
</a>
<a href={`${walletUrl}/@${username}/communities`}>Create a Community</a>
</div>
)}
......@@ -139,16 +116,12 @@ export default class CommunitiesIndex extends React.Component {
<div className="articles__header row">
<div className="small-8 medium-7 large-8 column">
<ElasticSearchInput
expanded={true}
expanded
handleSubmit={q => {
this.setState({
searchQuery: q,
});
performSearch(
username,
q,
this.state.searchOrder
);
performSearch(username, q, searchOrder);
}}
redirect={false}
/>
......@@ -156,24 +129,34 @@ export default class CommunitiesIndex extends React.Component {
<div className="small-4 medium-3 large-4 column">
<NativeSelect
options={sortOptions}
currentlySelected={this.state.searchOrder}
currentlySelected={searchOrder}
onChange={opt => {
this.setState({
searchOrder: opt.value,
});
performSearch(
username,
this.state.searchQuery,
opt.value
);
performSearch(username, searchQuery, opt.value);
}}
/>
</div>
</div>
<hr />
<table>
<tbody>{ordered.map(comm => row(comm.toJS()))}</tbody>
</table>
{ordered.size > 0 && (
<div>
<table>
<tbody>
{ordered.map(comm => {
return row(comm.toJS());
})}
</tbody>
</table>
</div>
)}
{ordered.size === 0 && <div>{tt('g.community_search_no_result')}</div>}
{communities === null && (
<center>
<LoadingIndicator style={{ marginBottom: '2rem' }} type="circle" />
</center>
)}
</div>
</PostsIndexLayout>
);
......@@ -195,6 +178,7 @@ module.exports = {
dispatch => {
return {
performSearch: (observer, query, sort = 'rank') => {
console.log('search', query);
dispatch(
fetchDataSagaActions.listCommunities({
observer,
......
......@@ -15,6 +15,16 @@
}
}
table tbody {
border: none;
}
table tr {
@include themify($themes) {
border: themed('border');
}
}
table th {
width: 600px;
......
This diff is collapsed.
import {
call,
put,
select,
fork,
takeLatest,
takeEvery,
} from 'redux-saga/effects';
import { call, put, select, fork, takeLatest, takeEvery } from 'redux-saga/effects';
import { api } from '@hiveio/hive-js';
import { loadFollows } from 'app/redux/FollowSaga';
import * as globalActions from './GlobalReducer';
......@@ -14,10 +7,7 @@ import * as transactionActions from './TransactionReducer';
import constants from './constants';
import { fromJS, Map, Set } from 'immutable';
import { getStateAsync, callBridge } from 'app/utils/steemApi';
import {
fetchCrossPosts,
augmentContentWithCrossPost,
} from 'app/utils/CrossPosts';
import { fetchCrossPosts, augmentContentWithCrossPost } from 'app/utils/CrossPosts';
const REQUEST_DATA = 'fetchDataSaga/REQUEST_DATA';
const FETCH_STATE = 'fetchDataSaga/FETCH_STATE';
......@@ -26,8 +16,7 @@ const GET_COMMUNITY = 'fetchDataSaga/GET_COMMUNITY';
const LIST_COMMUNITIES = 'fetchDataSaga/LIST_COMMUNITIES';
const GET_SUBSCRIPTIONS = 'fetchDataSaga/GET_SUBSCRIPTIONS';
const GET_ACCOUNT_NOTIFICATIONS = 'fetchDataSaga/GET_ACCOUNT_NOTIFICATIONS';
const GET_UNREAD_ACCOUNT_NOTIFICATIONS =
'fetchDataSaga/GET_UNREAD_ACCOUNT_NOTIFICATIONS';
const GET_UNREAD_ACCOUNT_NOTIFICATIONS = 'fetchDataSaga/GET_UNREAD_ACCOUNT_NOTIFICATIONS';
const MARK_NOTIFICATIONS_AS_READ = 'fetchDataSaga/MARK_NOTIFICATIONS_AS_READ';
const GET_REWARDS_DATA = 'fetchDataSaga/GET_REWARDS_DATA';
......@@ -41,10 +30,7 @@ export const fetchDataWatches = [
takeLatest(GET_SUBSCRIPTIONS, getSubscriptions),
takeEvery(LIST_COMMUNITIES, listCommunities),
takeEvery(GET_ACCOUNT_NOTIFICATIONS, getAccountNotifications),
takeEvery(
GET_UNREAD_ACCOUNT_NOTIFICATIONS,
getUnreadAccountNotificationsSaga
),
takeEvery(GET_UNREAD_ACCOUNT_NOTIFICATIONS, getUnreadAccountNotificationsSaga),
takeEvery(GET_REWARDS_DATA, getRewardsDataSaga),
takeEvery(MARK_NOTIFICATIONS_AS_READ, markNotificationsAsReadSaga),
];
......@@ -68,21 +54,14 @@ export function* fetchState(location_change_action) {
// `ignore_fetch` case should only trigger on initial page load. No need to call
// fetchState immediately after loading fresh state from the server. Details: #593
const server_location = yield select(state =>
state.offchain.get('server_location')
);
const server_location = yield select(state => state.offchain.get('server_location'));
const ignore_fetch = pathname === server_location && is_initial_state;
if (ignore_fetch) {
return;
}
is_initial_state = false;
if (
process.env.BROWSER &&
window &&
window.optimize &&
window.optimize.isInitialized
) {
if (process.env.BROWSER && window && window.optimize && window.optimize.isInitialized) {
window.optimize.refreshAll({ refresh: false });
}
const url = pathname;
......@@ -91,9 +70,7 @@ export function* fetchState(location_change_action) {
try {
let username = null;
if (process.env.BROWSER) {
[username] = yield select(state => [
state.user.getIn(['current', 'username']),
]);
[username] = yield select(state => [state.user.getIn(['current', 'username'])]);
}
const state = yield call(getStateAsync, url, username, false);
yield put(globalActions.receiveState(state));
......@@ -111,26 +88,18 @@ function* syncSpecialPosts() {
if (!process.env.BROWSER) return null;
// Get special posts from the store.
const specialPosts = yield select(state =>
state.offchain.get('special_posts')
);
const specialPosts = yield select(state => state.offchain.get('special_posts'));
// Mark seen featured posts.
const seenFeaturedPosts = specialPosts.get('featured_posts').map(post => {
const id = `${post.get('author')}/${post.get('permlink')}`;
return post.set(
'seen',
localStorage.getItem(`featured-post-seen:${id}`) === 'true'
);
return post.set('seen', localStorage.getItem(`featured-post-seen:${id}`) === 'true');
});
// Mark seen promoted posts.
const seenPromotedPosts = specialPosts.get('promoted_posts').map(post => {
const id = `${post.get('author')}/${post.get('permlink')}`;
return post.set(
'seen',
localStorage.getItem(`promoted-post-seen:${id}`) === 'true'
);
return post.set('seen', localStorage.getItem(`promoted-post-seen:${id}`) === 'true');
});
// Look up seen post URLs.
......@@ -173,14 +142,13 @@ function* getAccounts(usernames) {
export function* listCommunities(action) {
const { observer, query, sort } = action.payload;
try {
yield put(globalActions.receiveCommunities(null));
const communities = yield call(callBridge, 'list_communities', {
observer,
query,
query: query !== '' ? query : null,
sort,
});
if (communities.length > 0) {
yield put(globalActions.receiveCommunities(communities));
}
yield put(globalActions.receiveCommunities(communities));
} catch (error) {
console.log('Error requesting communities:', error);
}
......@@ -245,17 +213,10 @@ export function* getAccountNotifications(action) {
if (!action.payload) throw 'no account specified';
yield put(globalActions.notificationsLoading(true));
try {
const notifications = yield call(
callBridge,
'account_notifications',
action.payload
);
const notifications = yield call(callBridge, 'account_notifications', action.payload);
if (notifications && notifications.error) {
console.error(
'~~ Saga getAccountNotifications error ~~>',
notifications.error
);
console.error('~~ Saga getAccountNotifications error ~~>', notifications.error);
yield put(appActions.steemApiError(notifications.error.message));
} else {
const limit = action.payload.limit ? action.payload.limit : 100;
......@@ -285,19 +246,10 @@ export function* getUnreadAccountNotificationsSaga(action) {
if (!action.payload) throw 'no account specified';
yield put(globalActions.notificationsLoading(true));
try {
const unreadNotifications = yield call(
callBridge,
'unread_notifications',
action.payload
);
const unreadNotifications = yield call(callBridge, 'unread_notifications', action.payload);
if (unreadNotifications && unreadNotifications.error) {
console.error(
'~~ Saga getUnreadAccountNotifications error ~~>',
unreadNotifications.error
);
yield put(
appActions.steemApiError(unreadNotifications.error.message)
);
console.error('~~ Saga getUnreadAccountNotifications error ~~>', unreadNotifications.error);
yield put(appActions.steemApiError(unreadNotifications.error.message));
} else {
yield put(
globalActions.receiveUnreadNotifications({
......@@ -330,9 +282,7 @@ export function* markNotificationsAsReadSaga(action) {
successCallback(username, timeNow);
},
errorCallback: () => {
console.log(
'There was an error marking notifications as read!'
);
console.log('There was an error marking notifications as read!');
globalActions.notificationsLoading(false);
},
})
......@@ -396,16 +346,8 @@ export function* fetchData(action) {
const contentKey = keys[ki];
let post = content[contentKey];
if (
Object.prototype.hasOwnProperty.call(
post,
'cross_post_key'
)
) {
post = augmentContentWithCrossPost(
post,
crossPosts[post.cross_post_key]
);
if (Object.prototype.hasOwnProperty.call(post, 'cross_post_key')) {
post = augmentContentWithCrossPost(post, crossPosts[post.cross_post_key]);
}
data.push(post);
......@@ -428,14 +370,9 @@ export function* fetchData(action) {
// Still return all data but only count ones matching the filter.
// Rely on UI to actually hide the posts.
fetched += postFilter
? data.filter(postFilter).length
: data.length;
fetched += postFilter ? data.filter(postFilter).length : data.length;
fetchDone =
endOfData ||
fetchLimitReached ||
fetched >= constants.FETCH_DATA_BATCH_SIZE;
fetchDone = endOfData || fetchLimitReached || fetched >= constants.FETCH_DATA_BATCH_SIZE;
yield put(
globalActions.receiveData({
......@@ -460,9 +397,7 @@ export function* fetchData(action) {
@arg {string} url
@arg {object} body (for JSON.stringify)
*/
function* fetchJson({
payload: { id, url, body, successCallback, skipLoading = false },
}) {
function* fetchJson({ payload: { id, url, body, successCallback, skipLoading = false } }) {
try {
const payload = {
method: body ? 'POST' : 'GET',
......@@ -472,9 +407,7 @@ function* fetchJson({
},
body: body ? JSON.stringify(body) : undefined,
};
let result = yield skipLoading
? fetch(url, payload)
: call(fetch, url, payload);
let result = yield skipLoading ? fetch(url, payload) : call(fetch, url, payload);
result = yield result.json();
if (successCallback) result = successCallback(result);
yield put(globalActions.fetchJsonResult({ id, result }));
......@@ -488,10 +421,7 @@ export function* getRewardsDataSaga(action) {
try {
const rewards = yield call(callBridge, 'get_payout_stats', {});
if (rewards && rewards.error) {
console.error(
'~~ Saga getRewardsDataSaga error ~~>',
rewards.error
);
console.error('~~ Saga getRewardsDataSaga error ~~>', rewards.error);
yield put(appActions.steemApiError(rewards.error.message));
} else {
yield put(globalActions.receiveRewards({ rewards }));
......
......@@ -61,9 +61,7 @@ const transformAccount = account =>
*/
const mergeAccounts = (state, account) => {
return state.updateIn(['accounts', account.get('name')], Map(), a =>
a.mergeDeep(account)
);
return state.updateIn(['accounts', account.get('name')], Map(), a => a.mergeDeep(account));
};
export default function reducer(state = defaultState, action = {}) {
......@@ -72,9 +70,7 @@ export default function reducer(state = defaultState, action = {}) {
switch (action.type) {
case SET_COLLAPSED: {
return state.withMutations(map => {
map.updateIn(['content', payload.post], value =>
value.merge(Map({ collapsed: payload.collapsed }))
);
map.updateIn(['content', payload.post], value => value.merge(Map({ collapsed: payload.collapsed })));
});
}
......@@ -92,15 +88,10 @@ export default function reducer(state = defaultState, action = {}) {
flag_weight: 0,
},
};
return state.updateIn(['content', key], Map(), c =>
c.mergeDeep(update)
);
return state.updateIn(['content', key], Map(), c => c.mergeDeep(update));
case RECEIVE_STATE: {
console.log(
'Merging state',
state.mergeDeep(fromJS(payload)).toJS()
);
console.log('Merging state', state.mergeDeep(fromJS(payload)).toJS());
return state.mergeDeep(fromJS(payload));
}
......@@ -109,9 +100,7 @@ export default function reducer(state = defaultState, action = {}) {
return state.updateIn(['notifications', payload.name], Map(), n =>
n.withMutations(nmut =>
nmut
.update('notifications', List(), a =>
a.concat(fromJS(payload.notifications))
)
.update('notifications', List(), a => a.concat(fromJS(payload.notifications)))
.set('isLastPage', payload.isLastPage)
)
);
......@@ -141,20 +130,22 @@ export default function reducer(state = defaultState, action = {}) {
}
case RECEIVE_POST_HEADER: {
return state.update('headers', Map(), a =>
a.mergeDeep(fromJS(payload))
);
return state.update('headers', Map(), a => a.mergeDeep(fromJS(payload)));
}
case RECEIVE_COMMUNITIES: {
const map = Map(payload.map(c => [c.name, fromJS(c)]));
const idx = List(payload.map(c => c.name));
if (map.length <= 0) {
debugger;
let map = null;
let idx = null;
if (payload !== null) {
map = Map(payload.map(c => [c.name, fromJS(c)]));
idx = List(payload.map(c => c.name));
if (map.length <= 0) {
debugger;
}
}
return state
.setIn(['community'], map)
.setIn(['community_idx'], idx);
return state.setIn(['community'], map).setIn(['community_idx'], idx);
}
case RECEIVE_COMMUNITY: {
......@@ -166,10 +157,7 @@ export default function reducer(state = defaultState, action = {}) {
}
case RECEIVE_SUBSCRIPTIONS: {
return state.setIn(
['subscriptions', payload.username],
fromJS(payload.subscriptions)
);
return state.setIn(['subscriptions', payload.username], fromJS(payload.subscriptions));
}
case RECEIVE_REWARDS: {
return state.set('rewards', fromJS(payload.rewards));
......@@ -177,17 +165,11 @@ export default function reducer(state = defaultState, action = {}) {
// Interleave special posts into the map of posts.
case SYNC_SPECIAL_POSTS: {
return payload.featuredPosts
.concat(payload.promotedPosts)
.reduce((acc, specialPost) => {
const author = specialPost.get('author');
const permlink = specialPost.get('permlink');
return acc.updateIn(
['content', `${author}/${permlink}`],
Map(),
p => p.mergeDeep(specialPost)
);
}, state);
return payload.featuredPosts.concat(payload.promotedPosts).reduce((acc, specialPost) => {
const author = specialPost.get('author');
const permlink = specialPost.get('permlink');
return acc.updateIn(['content', `${author}/${permlink}`], Map(), p => p.mergeDeep(specialPost));
}, state);
}
case RECEIVE_CONTENT: {
......@@ -196,9 +178,7 @@ export default function reducer(state = defaultState, action = {}) {
console.log('received content...', payload.content);
// merge content object into map
let new_state = state.updateIn(['content', key], Map(), c =>
c.mergeDeep(content)
);
let new_state = state.updateIn(['content', key], Map(), c => c.mergeDeep(content));
// merge vote info taking pending votes into account
let votes_key = ['content', key, 'active_votes'];
......@@ -219,12 +199,7 @@ export default function reducer(state = defaultState, action = {}) {
}
case LINK_REPLY: {
const {
author,
permlink,
parent_author = '',
parent_permlink = '',
} = payload;
const { author, permlink, parent_author = '', parent_permlink = '' } = payload;
const parent_key = postKey(parent_author, parent_permlink);
if (!parent_key) return state;
const key = author + '/' + permlink;
......@@ -234,15 +209,8 @@ export default function reducer(state = defaultState, action = {}) {
List(),
l => (l.findIndex(i => i === key) === -1 ? l.push(key) : l)
);
const children = updatedState.getIn(
['content', parent_key, 'replies'],
List()
).size;
updatedState = updatedState.updateIn(
['content', parent_key, 'children'],
0,
() => children
);
const children = updatedState.getIn(['content', parent_key, 'replies'], List()).size;
updatedState = updatedState.updateIn(['content', parent_key, 'children'], 0, () => children);
return updatedState;
}
......@@ -250,16 +218,11 @@ export default function reducer(state = defaultState, action = {}) {
const { author, permlink } = payload;
const key = author + '/' + permlink;
const content = state.getIn(['content', key]);
const parent_key = postKey(
content.get('parent_author'),
content.get('parent_permlink')
);
const parent_key = postKey(content.get('parent_author'), content.get('parent_permlink'));
let updatedState = state.deleteIn(['content', key]);
if (parent_key) {
updatedState = updatedState.updateIn(
['content', parent_key, 'replies'],
List(),
r => r.filter(i => i !== key)
updatedState = updatedState.updateIn(['content', parent_key, 'replies'], List(), r =>
r.filter(i => i !== key)
);
}
return updatedState;
......@@ -281,12 +244,9 @@ export default function reducer(state = defaultState, action = {}) {
case FETCHING_DATA: {
const { order, category } = payload;
const new_state = state.updateIn(
['status', category || '', order],
() => {
return { fetching: true };
}
);
const new_state = state.updateIn(['status', category || '', order], () => {
return { fetching: true };
});
return new_state;
}
......@@ -316,15 +276,12 @@ export default function reducer(state = defaultState, action = {}) {
});
// update status
new_state = new_state.updateIn(
['status', category || '', order],
() => {
if (endOfData) {
return { fetching, last_fetch: new Date() };
}
return { fetching };
new_state = new_state.updateIn(['status', category || '', order], () => {
if (endOfData) {
return { fetching, last_fetch: new Date() };
}
);
return { fetching };
});
return new_state;
}
......@@ -335,9 +292,7 @@ export default function reducer(state = defaultState, action = {}) {
}
case REMOVE: {
const key = Array.isArray(payload.key)
? payload.key
: [payload.key];
const key = Array.isArray(payload.key) ? payload.key : [payload.key];
return state.removeIn(key);
}
......@@ -357,9 +312,7 @@ export default function reducer(state = defaultState, action = {}) {
case SHOW_DIALOG: {
const { name, params = {} } = payload;
return state.update('active_dialogs', Map(), d =>
d.set(name, fromJS({ params }))
);
return state.update('active_dialogs', Map(), d => d.set(name, fromJS({ params })));
}
case HIDE_DIALOG: {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment