This commit is contained in:
mgabdev 2020-04-28 01:33:58 -04:00
parent 763694b5ab
commit c3d0d8bde2
87 changed files with 1392 additions and 826 deletions

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Api::BaseController < ApplicationController
DEFAULT_STATUSES_LIMIT = 20
DEFAULT_STATUSES_LIMIT = 10
DEFAULT_ACCOUNTS_LIMIT = 40
include RateLimitHeaders

View File

@ -1,113 +1,131 @@
import api from '../api';
import { me } from '../initial_state';
import api from '../api'
import { me } from '../initial_state'
export const GROUP_CREATE_REQUEST = 'GROUP_CREATE_REQUEST';
export const GROUP_CREATE_SUCCESS = 'GROUP_CREATE_SUCCESS';
export const GROUP_CREATE_FAIL = 'GROUP_CREATE_FAIL';
export const GROUP_CREATE_REQUEST = 'GROUP_CREATE_REQUEST'
export const GROUP_CREATE_SUCCESS = 'GROUP_CREATE_SUCCESS'
export const GROUP_CREATE_FAIL = 'GROUP_CREATE_FAIL'
export const GROUP_UPDATE_REQUEST = 'GROUP_UPDATE_REQUEST';
export const GROUP_UPDATE_SUCCESS = 'GROUP_UPDATE_SUCCESS';
export const GROUP_UPDATE_FAIL = 'GROUP_UPDATE_FAIL';
export const GROUP_UPDATE_REQUEST = 'GROUP_UPDATE_REQUEST'
export const GROUP_UPDATE_SUCCESS = 'GROUP_UPDATE_SUCCESS'
export const GROUP_UPDATE_FAIL = 'GROUP_UPDATE_FAIL'
export const GROUP_EDITOR_VALUE_CHANGE = 'GROUP_EDITOR_VALUE_CHANGE';
export const GROUP_EDITOR_RESET = 'GROUP_EDITOR_RESET';
export const GROUP_EDITOR_SETUP = 'GROUP_EDITOR_SETUP';
export const GROUP_EDITOR_TITLE_CHANGE = 'GROUP_EDITOR_TITLE_CHANGE'
export const GROUP_EDITOR_DESCRIPTION_CHANGE = 'GROUP_EDITOR_DESCRIPTION_CHANGE'
export const GROUP_EDITOR_COVER_IMAGE_CHANGE = 'GROUP_EDITOR_COVER_IMAGE_CHANGE'
export const GROUP_EDITOR_RESET = 'GROUP_EDITOR_RESET'
export const GROUP_EDITOR_SETUP = 'GROUP_EDITOR_SETUP'
export const submit = (routerHistory) => (dispatch, getState) => {
const groupId = getState().getIn(['group_editor', 'groupId']);
const title = getState().getIn(['group_editor', 'title']);
const description = getState().getIn(['group_editor', 'description']);
const coverImage = getState().getIn(['group_editor', 'coverImage']);
if (!me) return
const groupId = getState().getIn(['group_editor', 'groupId'])
const title = getState().getIn(['group_editor', 'title'])
const description = getState().getIn(['group_editor', 'description'])
const coverImage = getState().getIn(['group_editor', 'coverImage'])
if (groupId === null) {
dispatch(create(title, description, coverImage, routerHistory));
dispatch(create(title, description, coverImage, routerHistory))
} else {
dispatch(update(groupId, title, description, coverImage, routerHistory));
dispatch(update(groupId, title, description, coverImage, routerHistory))
}
};
export const create = (title, description, coverImage, routerHistory) => (dispatch, getState) => {
if (!me) return;
const create = (title, description, coverImage, routerHistory) => (dispatch, getState) => {
if (!me) return
dispatch(createRequest());
dispatch(createRequest())
const formData = new FormData();
formData.append('title', title);
formData.append('description', description);
const formData = new FormData()
formData.append('title', title)
formData.append('description', description)
if (coverImage !== null) {
formData.append('cover_image', coverImage);
formData.append('cover_image', coverImage)
}
api(getState).post('/api/v1/groups', formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(({ data }) => {
dispatch(createSuccess(data));
routerHistory.push(`/groups/${data.id}`);
}).catch(err => dispatch(createFail(err)));
};
export const createRequest = id => ({
api(getState).post('/api/v1/groups', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(({ data }) => {
dispatch(createSuccess(data))
routerHistory.push(`/groups/${data.id}`)
}).catch(err => dispatch(createFail(err)))
};
export const createRequest = (id) => ({
type: GROUP_CREATE_REQUEST,
id,
});
})
export const createSuccess = group => ({
export const createSuccess = (group) => ({
type: GROUP_CREATE_SUCCESS,
group,
});
})
export const createFail = error => ({
type: GROUP_CREATE_FAIL,
error,
});
export const createFail = (error) => ({
type: GROUP_CREATE_FAIL,
error,
})
export const update = (groupId, title, description, coverImage, routerHistory) => (dispatch, getState) => {
if (!me) return;
const update = (groupId, title, description, coverImage, routerHistory) => (dispatch, getState) => {
if (!me) return
dispatch(updateRequest());
dispatch(updateRequest())
const formData = new FormData();
formData.append('title', title);
const formData = new FormData()
formData.append('title', title)
formData.append('description', description);
if (coverImage !== null) {
formData.append('cover_image', coverImage);
}
api(getState).put(`/api/v1/groups/${groupId}`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(({ data }) => {
dispatch(updateSuccess(data));
routerHistory.push(`/groups/${data.id}`);
}).catch(err => dispatch(updateFail(err)));
};
};
export const updateRequest = id => ({
export const updateRequest = (id) => ({
type: GROUP_UPDATE_REQUEST,
id,
});
export const updateSuccess = group => ({
export const updateSuccess = (group) => ({
type: GROUP_UPDATE_SUCCESS,
group,
});
export const updateFail = error => ({
type: GROUP_UPDATE_FAIL,
error,
export const updateFail = (error) => ({
type: GROUP_UPDATE_FAIL,
error,
})
export const resetEditor = () => ({
type: GROUP_EDITOR_RESET
});
export const changeValue = (field, value) => ({
type: GROUP_EDITOR_VALUE_CHANGE,
field,
value,
export const setGroup = (group) => ({
type: GROUP_EDITOR_SETUP,
group,
});
export const reset = () => ({
type: GROUP_EDITOR_RESET
});
export const changeGroupTitle = (title) => ({
type: GROUP_EDITOR_TITLE_CHANGE,
title,
})
export const setUp = (group) => ({
type: GROUP_EDITOR_SETUP,
group,
});
export const changeGroupDescription = (description) => ({
type: GROUP_EDITOR_DESCRIPTION_CHANGE,
description,
})
export const changeGroupCoverImage = (imageData) => ({
type: GROUP_EDITOR_COVER_IMAGE_CHANGE,
value: imageData,
})

View File

@ -30,6 +30,10 @@ export const UNPIN_REQUEST = 'UNPIN_REQUEST';
export const UNPIN_SUCCESS = 'UNPIN_SUCCESS';
export const UNPIN_FAIL = 'UNPIN_FAIL';
export const LIKES_FETCH_REQUEST = 'LIKES_FETCH_REQUEST';
export const LIKES_FETCH_SUCCESS = 'LIKES_FETCH_SUCCESS';
export const LIKES_FETCH_FAIL = 'LIKES_FETCH_FAIL';
export function repost(status) {
return function (dispatch, getState) {
if (!me) return;
@ -308,3 +312,38 @@ export function unpinFail(status, error) {
skipLoading: true,
};
};
export function fetchLikes(id) {
return (dispatch, getState) => {
dispatch(fetchLikesRequest(id));
api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => {
dispatch(importFetchedAccounts(response.data));
dispatch(fetchLikesSuccess(id, response.data));
}).catch(error => {
dispatch(fetchLikesFail(id, error));
});
};
};
export function fetchLikesRequest(id) {
return {
type: LIKES_FETCH_REQUEST,
id,
};
};
export function fetchLikesSuccess(id, accounts) {
return {
type: LIKES_FETCH_SUCCESS,
id,
accounts,
};
};
export function fetchLikesFail(id, error) {
return {
type: LIKES_FETCH_FAIL,
error,
};
};

View File

@ -98,20 +98,22 @@ export const fetchListsRequest = () => ({
type: LISTS_FETCH_REQUEST,
});
export const fetchListsSuccess = lists => ({
export const fetchListsSuccess = (lists) => ({
type: LISTS_FETCH_SUCCESS,
lists,
});
export const fetchListsFail = error => ({
export const fetchListsFail = (error) => ({
type: LISTS_FETCH_FAIL,
error,
});
export const submitListEditor = shouldReset => (dispatch, getState) => {
export const submitListEditor = (shouldReset) => (dispatch, getState) => {
const listId = getState().getIn(['listEditor', 'listId']);
const title = getState().getIn(['listEditor', 'title']);
console.log("submitListEditor:", title)
if (listId === null) {
dispatch(createList(title, shouldReset));
} else {
@ -119,7 +121,7 @@ export const submitListEditor = shouldReset => (dispatch, getState) => {
}
};
export const setupListEditor = listId => (dispatch, getState) => {
export const setupListEditor = (listId) => (dispatch, getState) => {
dispatch({
type: LIST_EDITOR_SETUP,
list: getState().getIn(['lists', listId]),
@ -128,7 +130,7 @@ export const setupListEditor = listId => (dispatch, getState) => {
dispatch(fetchListAccounts(listId));
};
export const changeListEditorTitle = value => ({
export const changeListEditorTitle = (value) => ({
type: LIST_EDITOR_TITLE_CHANGE,
value,
});
@ -151,12 +153,12 @@ export const createListRequest = () => ({
type: LIST_CREATE_REQUEST,
});
export const createListSuccess = list => ({
export const createListSuccess = (list) => ({
type: LIST_CREATE_SUCCESS,
list,
});
export const createListFail = error => ({
export const createListFail = (error) => ({
type: LIST_CREATE_FAIL,
error,
});
@ -195,7 +197,7 @@ export const resetListEditor = () => ({
type: LIST_EDITOR_RESET,
});
export const deleteList = id => (dispatch, getState) => {
export const deleteList = (id) => (dispatch, getState) => {
if (!me) return;
dispatch(deleteListRequest(id));
@ -205,12 +207,12 @@ export const deleteList = id => (dispatch, getState) => {
.catch(err => dispatch(deleteListFail(id, err)));
};
export const deleteListRequest = id => ({
export const deleteListRequest = (id) => ({
type: LIST_DELETE_REQUEST,
id,
});
export const deleteListSuccess = id => ({
export const deleteListSuccess = (id) => ({
type: LIST_DELETE_SUCCESS,
id,
});
@ -221,7 +223,7 @@ export const deleteListFail = (id, error) => ({
error,
});
export const fetchListAccounts = listId => (dispatch, getState) => {
export const fetchListAccounts = (listId) => (dispatch, getState) => {
if (!me) return;
dispatch(fetchListAccountsRequest(listId));
@ -232,7 +234,7 @@ export const fetchListAccounts = listId => (dispatch, getState) => {
}).catch(err => dispatch(fetchListAccountsFail(listId, err)));
};
export const fetchListAccountsRequest = id => ({
export const fetchListAccountsRequest = (id) => ({
type: LIST_ACCOUNTS_FETCH_REQUEST,
id,
});
@ -250,7 +252,7 @@ export const fetchListAccountsFail = (id, error) => ({
error,
});
export const fetchListSuggestions = q => (dispatch, getState) => {
export const fetchListSuggestions = (q) => (dispatch, getState) => {
if (!me) return;
const params = {
@ -277,7 +279,7 @@ export const clearListSuggestions = () => ({
type: LIST_EDITOR_SUGGESTIONS_CLEAR,
});
export const changeListSuggestions = value => ({
export const changeListSuggestions = (value) => ({
type: LIST_EDITOR_SUGGESTIONS_CHANGE,
value,
});
@ -361,7 +363,7 @@ export const setupListAdder = accountId => (dispatch, getState) => {
dispatch(fetchAccountLists(accountId));
};
export const fetchAccountLists = accountId => (dispatch, getState) => {
export const fetchAccountLists = (accountId) => (dispatch, getState) => {
if (!me) return;
dispatch(fetchAccountListsRequest(accountId));
@ -371,7 +373,7 @@ export const fetchAccountLists = accountId => (dispatch, getState) => {
.catch(err => dispatch(fetchAccountListsFail(accountId, err)));
};
export const fetchAccountListsRequest = id => ({
export const fetchAccountListsRequest = (id) => ({
type:LIST_ADDER_LISTS_FETCH_REQUEST,
id,
});
@ -388,10 +390,10 @@ export const fetchAccountListsFail = (id, err) => ({
err,
});
export const addToListAdder = listId => (dispatch, getState) => {
export const addToListAdder = (listId) => (dispatch, getState) => {
dispatch(addToList(listId, getState().getIn(['listAdder', 'accountId'])));
};
export const removeFromListAdder = listId => (dispatch, getState) => {
export const removeFromListAdder = (listId) => (dispatch, getState) => {
dispatch(removeFromList(listId, getState().getIn(['listAdder', 'accountId'])));
};

View File

@ -1,16 +1,16 @@
export const MODAL_OPEN = 'MODAL_OPEN';
export const MODAL_CLOSE = 'MODAL_CLOSE';
export const MODAL_OPEN = 'MODAL_OPEN'
export const MODAL_CLOSE = 'MODAL_CLOSE'
export function openModal(type, props) {
return {
type: MODAL_OPEN,
modalType: type,
modalProps: props,
};
};
}
}
export function closeModal() {
return {
type: MODAL_CLOSE,
};
};
}
}

View File

@ -0,0 +1,24 @@
const CogIcon = ({
className = '',
size = '16px',
title = '',
}) => (
<svg
className={className}
version='1.1'
xmlns='http://www.w3.org/2000/svg'
x='0px'
y='0px'
width={size}
height={size}
viewBox='0 0 24 24'
xmlSpace='preserve'
aria-label={title}
>
<g>
<path d='M 23.40625 9.769531 L 21.261719 9.511719 C 21.042969 8.695312 20.722656 7.921875 20.3125 7.207031 L 21.640625 5.511719 C 21.847656 5.253906 21.824219 4.851562 21.585938 4.617188 L 19.378906 2.410156 C 19.148438 2.175781 18.746094 2.152344 18.484375 2.355469 L 16.789062 3.6875 C 16.074219 3.273438 15.304688 2.953125 14.488281 2.738281 L 14.230469 0.59375 C 14.191406 0.269531 13.890625 0 13.558594 0 L 10.4375 0 C 10.109375 0 9.804688 0.269531 9.765625 0.59375 L 9.507812 2.738281 C 8.695312 2.953125 7.921875 3.277344 7.207031 3.6875 L 5.511719 2.355469 C 5.253906 2.152344 4.851562 2.175781 4.617188 2.410156 L 2.410156 4.621094 C 2.175781 4.851562 2.152344 5.253906 2.359375 5.515625 L 3.6875 7.210938 C 3.277344 7.925781 2.953125 8.695312 2.738281 9.511719 L 0.597656 9.769531 C 0.269531 9.808594 0 10.109375 0 10.441406 L 0 13.5625 C 0 13.894531 0.269531 14.195312 0.597656 14.234375 L 2.738281 14.492188 C 2.957031 15.304688 3.277344 16.078125 3.6875 16.792969 L 2.359375 18.488281 C 2.15625 18.746094 2.179688 19.148438 2.414062 19.382812 L 4.617188 21.59375 C 4.851562 21.824219 5.253906 21.851562 5.511719 21.648438 L 7.207031 20.3125 C 7.921875 20.726562 8.695312 21.046875 9.511719 21.265625 L 9.769531 23.40625 C 9.808594 23.734375 10.109375 24 10.4375 24 L 13.5625 24 C 13.890625 24 14.195312 23.734375 14.230469 23.40625 L 14.488281 21.265625 C 15.304688 21.046875 16.078125 20.726562 16.792969 20.3125 L 18.488281 21.644531 C 18.746094 21.847656 19.148438 21.824219 19.382812 21.589844 L 21.589844 19.382812 C 21.824219 19.148438 21.847656 18.746094 21.644531 18.484375 L 20.3125 16.792969 C 20.722656 16.078125 21.042969 15.304688 21.261719 14.492188 L 23.402344 14.234375 C 23.730469 14.195312 24 13.894531 24 13.5625 L 24 10.441406 C 24 10.109375 23.734375 9.808594 23.40625 9.769531 Z M 12 18 C 8.6875 18 6 15.3125 6 12 C 6 8.6875 8.6875 6 12 6 C 15.316406 6 18 8.6875 18 12 C 18 15.3125 15.316406 18 12 18 Z M 12 18' />
</g>
</svg>
)
export default CogIcon

View File

@ -1,5 +1,5 @@
const ErrorIcon = ({
className = '',
className = _s.fillColorPrimary,
size = '32px',
title = 'Error',
}) => (
@ -16,7 +16,10 @@ const ErrorIcon = ({
aria-label={title}
>
<g>
<path d='M 54.67 9.37 C 42.18 -3.12 21.86 -3.12 9.37 9.37 C -3.13 21.86 -3.12 42.18 9.37 54.67 C 21.86 67.16 42.18 67.16 54.67 54.67 C 67.16 42.18 67.16 21.86 54.67 9.37 Z M 51.18 51.18 C 40.61 61.74 23.43 61.74 12.86 51.18 C 2.3 40.61 2.3 23.42 12.86 12.86 C 23.43 2.3 40.61 2.3 51.18 12.86 C 61.74 23.43 61.74 40.61 51.18 51.18 Z M 46.5 44.68 C 46.9 45.6 46.48 46.66 45.56 47 C 44.64 47.46 43.57 47 43.18 46.12 C 41.43 42 37.3 39.47 32.66 39.47 C 27.91 39.47 23.75 42 22 46.11 C 21.79 46.81 21.11 47.23 20.41 47.23 C 20.18 47.23 19.94 47.18 19.71 47 C 18.79 46.71 18.35 45.65 18.73 44.72 C 20.97 39.33 26.44 35.85 32.66 35.85 C 38.75 35.85 44.18 39.31 46.5 44.68 Z M 20 23.35 C 20 21.28 21.75 19.61 23.81 19.61 C 25.88 19.61 27.56 21.28 27.56 23.35 C 27.56 25.42 25.88 27 23.81 27 C 21.75 27 20 25.42 20 23.35 Z M 37 23.35 C 37 21.28 38.72 19.61 40.79 19.61 C 42.86 19.61 44.54 21.28 44.54 23.35 C 44.54 25.42 42.86 27 40.79 27 C 38.72 27 37 25.42 37 23.35 Z M 37 23.35' />
<path d='M 32 0 C 14.355469 0 0 14.355469 0 32 C 0 49.644531 14.355469 64 32 64 C 49.644531 64 64 49.644531 64 32 C 64 14.355469 49.644531 0 32 0 Z M 32 61.332031 C 15.824219 61.332031 2.667969 48.175781 2.667969 32 C 2.667969 15.824219 15.824219 2.667969 32 2.667969 C 48.175781 2.667969 61.332031 15.824219 61.332031 32 C 61.332031 48.175781 48.175781 61.332031 32 61.332031 Z M 32 61.332031' />
<path d='M 24 24 C 24 25.472656 22.804688 26.667969 21.332031 26.667969 C 19.859375 26.667969 18.667969 25.472656 18.667969 24 C 18.667969 22.527344 19.859375 21.332031 21.332031 21.332031 C 22.804688 21.332031 24 22.527344 24 24 Z M 24 24' />
<path d='M 45.332031 24 C 45.332031 25.472656 44.140625 26.667969 42.667969 26.667969 C 41.195312 26.667969 40 25.472656 40 24 C 40 22.527344 41.195312 21.332031 42.667969 21.332031 C 44.140625 21.332031 45.332031 22.527344 45.332031 24 Z M 45.332031 24' />
<path d='M 32 40 L 31.996094 40 C 20.792969 40 16.445312 45.628906 16.265625 45.867188 C 15.828125 46.453125 15.945312 47.28125 16.527344 47.722656 C 17.113281 48.167969 17.949219 48.054688 18.394531 47.472656 C 18.546875 47.277344 22.21875 42.667969 31.996094 42.667969 L 32 42.667969 C 41.777344 42.667969 45.453125 47.277344 45.601562 47.46875 C 45.863281 47.816406 46.261719 48 46.667969 48 C 46.945312 48 47.226562 47.914062 47.46875 47.734375 C 48.054688 47.292969 48.175781 46.457031 47.734375 45.867188 C 47.554688 45.628906 43.203125 40 32 40 Z M 32 40' />
</g>
</svg>
)

View File

@ -165,7 +165,7 @@ class Account extends ImmutablePureComponent {
<NavLink
title={account.get('acct')}
to={`/${account.get('acct')}`}
className={[_s.default, _s.alignItemsStart, _s.noUnderline, _s.px10, _s.flexGrow1].join(' ')}
className={[_s.default, _s.alignItemsStart, _s.noUnderline, _s.px10, _s.overflowHidden, _s.flexNormal].join(' ')}
>
<DisplayName account={account} isMultiline={compact} />
{!compact && actionButton}

View File

@ -164,13 +164,13 @@ export default class Button extends PureComponent {
underline_onHover: underlineOnHover,
backgroundColorSubtle2Dark_onHover: backgroundColor === COLORS.tertiary || backgroundColor === COLORS.secondary,
backgroundColorBlackOpaque_onHover: backgroundColor === COLORS.black,
backgroundColorBrandDark_onHover: backgroundColor === COLORS.brand,
backgroundColorDangerDark_onHover: backgroundColor === COLORS.danger,
backgroundColorSubtle2Dark_onHover: backgroundColor === COLORS.tertiary || backgroundColor === COLORS.secondary && !isDisabled,
backgroundColorBlackOpaque_onHover: backgroundColor === COLORS.black && !isDisabled,
backgroundColorBrandDark_onHover: backgroundColor === COLORS.brand && !isDisabled,
backgroundColorDangerDark_onHover: backgroundColor === COLORS.danger && !isDisabled,
backgroundColorBrand_onHover: color === COLORS.brand && isOutline,
colorWhite_onHover: !!children && color === COLORS.brand && isOutline,
backgroundColorBrand_onHover: color === COLORS.brand && isOutline && !isDisabled,
colorWhite_onHover: !!children && color === COLORS.brand && isOutline && !isDisabled,
fillColorSecondary: !!icon && color === COLORS.secondary,
fillColorWhite: !!icon && color === COLORS.white,

View File

@ -27,7 +27,7 @@ class ColumnIndicator extends PureComponent {
render() {
const { type, message, intl } = this.props
const title = type !== 'error' ? intl.formatMessage(messages[type]) : message
const title = type !== 'error' && !message ? intl.formatMessage(messages[type]) : message
return (
<div className={[_s.default, _s.width100PC, _s.justifyContentCenter, _s.alignItemsCenter, _s.py15].join(' ')}>

View File

@ -60,7 +60,7 @@ class Comment extends ImmutablePureComponent {
<Avatar account={status.get('account')} size={32} />
</NavLink>
<div className={[_s.default, _s.flexNormal].join(' ')}>
<div className={_s.default}>
<div className={[_s.default, _s.px10, _s.pt5, _s.pb10, _s.radiusSmall, _s.backgroundColorSubtle].join(' ')}>
<CommentHeader status={status} />
<StatusContent

View File

@ -161,7 +161,7 @@ class DisplayName extends ImmutablePureComponent {
/>
{
!noRelationship && account.get('locked') &&
<Icon id='lock-filled' size={iconSize} className={_s.ml5} />
<Icon id='lock-filled' size={iconSize} className={[_s.fillColorPrimary, _s.ml5].join(' ')} />
}
</bdi>
{

View File

@ -31,8 +31,6 @@ class FloatingActionButton extends PureComponent {
return (
<Button
onClick={onOpenCompose}
color='white'
backgroundColor='brand'
className={[_s.posFixed, _s.z4, _s.py15, _s.mb15, _s.mr15, _s.bottom0, _s.right0].join(' ')}
title={message}
aria-label={message}

View File

@ -0,0 +1,37 @@
import Text from './text'
export default class Form extends PureComponent {
static propTypes = {
children: PropTypes.any,
errorMessage: PropTypes.string,
onSubmit: PropTypes.func.isRequired,
}
componentDidUpdate (prevProps) {
}
render() {
const {
children,
errorMessage,
onSubmit,
} = this.props
return (
<form onSubmit={onSubmit} className={_s.default}>
{
!!errorMessage &&
<Text color='danger' className={_s.my10}>
{errorMessage}
</Text>
}
<div className={_s.default}>
{children}
</div>
</form>
)
}
}

View File

@ -4,6 +4,7 @@ import { Fragment } from 'react'
import { NavLink } from 'react-router-dom'
import { defineMessages, injectIntl } from 'react-intl'
import classNames from 'classnames/bind'
import { PLACEHOLDER_MISSING_HEADER_SRC } from '../constants'
import { shortNumberFormat } from '../utils/numbers'
import Button from './button'
import DotTextSeperator from './dot_text_seperator'
@ -34,10 +35,16 @@ class GroupCollectionItem extends ImmutablePureComponent {
static propTypes = {
group: ImmutablePropTypes.map,
relationships: ImmutablePropTypes.map,
isHidden: PropTypes.bool,
}
render() {
const { intl, group, relationships } = this.props
const {
intl,
group,
relationships,
isHidden,
} = this.props
if (!relationships) return null
@ -53,7 +60,20 @@ class GroupCollectionItem extends ImmutablePureComponent {
const isMember = relationships.get('member')
const isAdmin = relationships.get('admin')
const coverSrc = group.get('cover')
const coverSrc = group.get('cover_image_url') || ''
const coverMissing = coverSrc.indexOf(PLACEHOLDER_MISSING_HEADER_SRC) > -1 || !coverSrc
if (isHidden) {
return (
<Fragment>
{group.get('title')}
{subtitle}
{isMember && intl.formatMessage(messages.member)}
{isAdmin && intl.formatMessage(messages.admin)}
</Fragment>
)
}
const navLinkClasses = cx({
default: 1,
@ -77,7 +97,7 @@ class GroupCollectionItem extends ImmutablePureComponent {
className={navLinkClasses}
>
{
!!coverSrc &&
!!coverSrc && !coverMissing &&
<Image
src={coverSrc}
alt={group.get('title')}
@ -86,7 +106,7 @@ class GroupCollectionItem extends ImmutablePureComponent {
}
{
!coverSrc && (isMember || isAdmin) &&
(!coverSrc || coverMissing) && (isMember || isAdmin) &&
<div className={[_s.default, _s.height40PX, _s.backgroundColorSubtle, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')} />
}

View File

@ -65,7 +65,7 @@ class GroupHeader extends ImmutablePureComponent {
},
]
const coverSrc = !!group ? group.get('cover') : undefined
const coverSrc = !!group ? group.get('cover_image_url') : undefined
const title = !!group ? group.get('title') : undefined
return (

View File

@ -4,6 +4,7 @@ import { Fragment } from 'react'
import { NavLink } from 'react-router-dom'
import { defineMessages, injectIntl } from 'react-intl'
import classNames from 'classnames/bind'
import { PLACEHOLDER_MISSING_HEADER_SRC } from '../constants'
import { shortNumberFormat } from '../utils/numbers'
import Image from './image'
import Text from './text'
@ -31,6 +32,7 @@ class GroupListItem extends ImmutablePureComponent {
relationships: ImmutablePropTypes.map,
slim: PropTypes.bool,
isLast: PropTypes.bool,
isHidden: PropTypes.bool,
}
static defaultProps = {
@ -39,9 +41,16 @@ class GroupListItem extends ImmutablePureComponent {
}
render() {
const { intl, group, relationships, slim, isLast } = this.props
const {
intl,
group,
relationships,
slim,
isLast,
isHidden,
} = this.props
if (!relationships) return null
if (!relationships || !group) return null
const unreadCount = relationships.get('unread_count')
@ -53,6 +62,15 @@ class GroupListItem extends ImmutablePureComponent {
</Fragment>
) : intl.formatMessage(messages.no_recent_activity)
if (isHidden) {
return (
<Fragment>
{group.get('title')}
{subtitle}
</Fragment>
)
}
const containerClasses = cx({
default: 1,
noUnderline: 1,
@ -80,10 +98,12 @@ class GroupListItem extends ImmutablePureComponent {
default: 1,
px10: 1,
mt5: 1,
flexShrink1: slim,
mb10: !slim,
})
const coverSrc = group.get('cover')
const coverSrc = group.get('cover_image_url') || ''
const coverMissing = coverSrc.indexOf(PLACEHOLDER_MISSING_HEADER_SRC) > -1 || !coverSrc
return (
<NavLink
@ -92,7 +112,7 @@ class GroupListItem extends ImmutablePureComponent {
>
{
(!!coverSrc || slim) &&
(!!coverSrc || slim) && !coverMissing &&
<Image
src={coverSrc}
alt={group.get('title')}

View File

@ -11,6 +11,7 @@ import ChatIcon from '../assets/chat_icon'
import CircleIcon from '../assets/circle_icon'
import CloseIcon from '../assets/close_icon'
import CodeIcon from '../assets/code_icon'
import CogIcon from '../assets/cog_icon'
import CommentIcon from '../assets/comment_icon'
import CopyIcon from '../assets/copy_icon'
import DissenterIcon from '../assets/dissenter_icon'
@ -80,6 +81,7 @@ const ICONS = {
'chat': ChatIcon,
'close': CloseIcon,
'code': CodeIcon,
'cog': CogIcon,
'comment': CommentIcon,
'copy': CopyIcon,
'dissenter': DissenterIcon,

View File

@ -7,6 +7,7 @@ import Text from './text'
const cx = classNames.bind(_s)
export default class Input extends PureComponent {
static propTypes = {
placeholder: PropTypes.string,
prependIcon: PropTypes.string,
@ -25,6 +26,10 @@ export default class Input extends PureComponent {
hideLabel: PropTypes.bool,
}
handleOnChange = (e) => {
this.props.onChange(e.target.value)
}
render() {
const {
placeholder,
@ -55,6 +60,7 @@ export default class Input extends PureComponent {
py5: small,
backgroundTransparent: !readOnly,
backgroundColorSubtle2: readOnly,
colorPrimary: !readOnly,
colorSecondary: readOnly,
fontSize15PX: !small,
fontSize13PX: small,
@ -101,7 +107,7 @@ export default class Input extends PureComponent {
placeholder={placeholder}
ref={inputRef}
value={value}
onChange={onChange}
onChange={this.handleOnChange}
onKeyUp={onKeyUp}
onFocus={onFocus}
onBlur={onBlur}

View File

@ -1,3 +1,4 @@
import { Fragment } from 'react'
import classNames from 'classnames/bind'
import Button from './button'
import Icon from './icon'
@ -9,6 +10,7 @@ export default class ListItem extends PureComponent {
static propTypes = {
icon: PropTypes.string,
isLast: PropTypes.bool,
isHidden: PropTypes.bool,
to: PropTypes.string,
href: PropTypes.string,
title: PropTypes.string,
@ -29,8 +31,17 @@ export default class ListItem extends PureComponent {
size,
icon,
hideArrow,
isHidden,
} = this.props
if (isHidden) {
return (
<Fragment>
{title}
</Fragment>
)
}
const small = size === 'small'
const large = size === 'large'
@ -77,7 +88,7 @@ export default class ListItem extends PureComponent {
/>
}
<Text color='primary' size={textSize}>
<Text color='primary' size={textSize} className={[_s.overflowHidden, _s.flexNormal, _s.pr5, _s.textOverflowEllipsis].join(' ')}>
{title}
</Text>
@ -86,7 +97,7 @@ export default class ListItem extends PureComponent {
<Icon
id='angle-right'
size='10px'
className={[_s.mlAuto, _s.fillColorSecondary].join(' ')}
className={[_s.mlAuto, _s.fillColorSecondary, _s.flexShrink1].join(' ')}
/>
}
</Button>

View File

@ -1,6 +1,5 @@
import { injectIntl, defineMessages } from 'react-intl'
import Button from './button'
import Icon from './icon'
import Text from './text'
const messages = defineMessages({
@ -15,8 +14,6 @@ class LoadMore extends PureComponent {
onClick: PropTypes.func,
disabled: PropTypes.bool,
visible: PropTypes.bool,
maxId: PropTypes.string,
gap: PropTypes.bool,
intl: PropTypes.object.isRequired,
}
@ -24,13 +21,16 @@ class LoadMore extends PureComponent {
visible: true,
}
handleClick = () => {
const { gap, maxId } = this.props
this.props.onClick(gap ? maxId : undefined)
handleClick = (e) => {
this.props.onClick()
}
render() {
const { disabled, visible, gap, intl } = this.props
const {
disabled,
visible,
intl,
} = this.props
return (
<div className={[_s.default, _s.py10, _s.px10].join(' ')}>
@ -40,22 +40,15 @@ class LoadMore extends PureComponent {
backgroundColor='tertiary'
color='primary'
disabled={disabled || !visible}
style={{ visibility: visible ? 'visible' : 'hidden' }}
style={{
visibility: visible ? 'visible' : 'hidden',
}}
onClick={this.handleClick}
aria-label={intl.formatMessage(messages.load_more)}
>
{
!gap &&
<Text color='inherit' align='center'>
{intl.formatMessage(messages.load_more)}
</Text>
}
{
gap &&
<Text align='center'>
<Icon id='ellipsis' size='14px' />
</Text>
}
<Text color='inherit' align='center'>
{intl.formatMessage(messages.load_more)}
</Text>
</Button>
</div>
)

View File

@ -13,13 +13,14 @@ const cx = classNames.bind(_s)
export default class MediaItem extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
attachment: ImmutablePropTypes.map.isRequired,
small: PropTypes.bool
isSmall: PropTypes.bool,
}
state = {
visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
loaded: false,
visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
}
componentDidMount() {
@ -59,7 +60,11 @@ export default class MediaItem extends ImmutablePureComponent {
}
render() {
const { attachment, small } = this.props
const {
account,
attachment,
isSmall,
} = this.props
const { visible, loaded } = this.state
const status = attachment.get('status')
@ -81,8 +86,8 @@ export default class MediaItem extends ImmutablePureComponent {
top0: 1,
height100PC: 1,
width100PC: 1,
py5: !small,
px5: !small,
py2: !isSmall,
px2: !isSmall,
})
const linkClasses = cx({
@ -91,15 +96,16 @@ export default class MediaItem extends ImmutablePureComponent {
height100PC: 1,
overflowHidden: 1,
border1PX: 1,
borderColorPrimary: !small,
borderColorPrimary: small,
borderColorPrimary: 1,
})
const statusUrl = `/${account.getIn(['acct'])}/posts/${status.get('id')}`;
return (
<div className={[_s.default, _s.width25PC, _s.pt25PC].join(' ')}>
<div className={containerClasses}>
<NavLink
to={status.get('url')} /* : todo : */
to={statusUrl}
title={title}
className={linkClasses}
>

View File

@ -51,7 +51,7 @@ class CommunityTimelineSettingsModal extends ImmutablePureComponent {
return (
<ModalLayout
width='320'
width={320}
title={intl.formatMessage(messages.title)}
>

View File

@ -0,0 +1,58 @@
import { injectIntl, defineMessages } from 'react-intl'
import { muteAccount } from '../../actions/accounts'
const messages = defineMessages({
muteMessage: { id: 'confirmations.mute.message', defaultMessage: 'Are you sure you want to mute {name}?' },
cancel: { id: 'confirmation_modal.cancel', defaultMessage: 'Cancel' },
title: { id: 'display_options', defaultMessage: 'Display Options' },
})
const mapStateToProps = (state) => ({
})
const mapDispatchToProps = (dispatch) => ({
})
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class DisplayOptionsModal extends PureComponent {
static propTypes = {
isSubmitting: PropTypes.bool.isRequired,
account: PropTypes.object.isRequired,
onConfirm: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
componentDidMount() {
this.button.focus()
}
handleClick = () => {
this.props.onClose()
this.props.onConfirm(this.props.account, this.props.notifications)
}
handleCancel = () => {
this.props.onClose()
}
// document.documentElement.style.setProperty("--color-surface", "black");
render() {
const { account, intl } = this.props
return (
<ModalLayout
width={320}
title={intl.formatMessage(messages.title)}
>
</ModalLayout>
)
}
}

View File

@ -28,10 +28,11 @@ class EditProfileModal extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map,
intl: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired,
}
render() {
const { account, intl } = this.props
const { account, intl, onClose } = this.props
const headerSrc = !!account ? account.get('header') : ''
@ -40,6 +41,7 @@ class EditProfileModal extends ImmutablePureComponent {
title={intl.formatMessage(messages.edit_profile)}
noPadding
width={460}
onClose={onClose}
>
<div className={[_s.default, _s.py5, _s.px5, _s.width100PC, _s.overflowHidden].join(' ')}>
<Image

View File

@ -4,7 +4,7 @@ import ModalLayout from './modal_layout'
import GroupCreate from '../../features/group_create'
const messages = defineMessages({
title: { id: 'create_group', defaultMessage: 'Create Group' },
title: { id: 'create_group', defaultMessage: 'Create group' },
})
export default
@ -22,7 +22,7 @@ class GroupCreateModal extends ImmutablePureComponent {
return (
<ModalLayout
title={intl.formatMessage(messages.title)}
width='440'
width={440}
onClose={onClose}
>
<GroupCreate onCloseModal={onClose} />

View File

@ -54,7 +54,7 @@ class HashtagTimelineSettingsModal extends ImmutablePureComponent {
return (
<ModalLayout
width='320'
width={320}
title={intl.formatMessage(messages.title)}
onClose={onClose}
>

View File

@ -55,7 +55,7 @@ class HomeTimelineSettingsModal extends ImmutablePureComponent {
return (
<ModalLayout
width='320'
width={320}
title={intl.formatMessage(messages.title)}
onClose={onClose}
>

View File

@ -22,10 +22,10 @@ class ListCreateModal extends ImmutablePureComponent {
return (
<ModalLayout
title={intl.formatMessage(messages.title)}
width='500'
width={500}
onClose={onClose}
>
<ListCreate />
<ListCreate isModal />
</ModalLayout>
)
}

View File

@ -1,6 +1,5 @@
import { injectIntl, defineMessages } from 'react-intl'
import { makeGetAccount } from '../../selectors'
import { blockAccount } from '../../actions/accounts'
import { deleteList } from '../../actions/lists'
import ConfirmationModal from './confirmation_modal'
const messages = defineMessages({
@ -10,14 +9,14 @@ const messages = defineMessages({
})
const mapDispatchToProps = (dispatch) => ({
onConfirm(account) {
// dispatch(blockAccount(account.get('id')))
onConfirm(listId) {
dispatch(deleteList(listId))
},
})
export default
@connect(null, mapDispatchToProps)
@injectIntl
@connect(null, mapDispatchToProps)
class ListDeleteModal extends PureComponent {
static propTypes = {
@ -27,17 +26,17 @@ class ListDeleteModal extends PureComponent {
}
handleClick = () => {
this.props.onConfirm(this.props.account)
this.props.onConfirm(this.props.list.get('id'))
}
render() {
const { list, intl, onClose } = this.props
const title = intl.formatMessage(messages.title, {
list: !!list ? account.get('title') : '',
list: !!list ? list.get('title') : '',
})
const message = intl.formatMessage(messages.listMessage, {
list: !!list ? account.get('title') : '',
list: !!list ? list.get('title') : '',
})
return (

View File

@ -1,68 +1,33 @@
import { injectIntl, defineMessages } from 'react-intl'
import { muteAccount } from '../../actions/accounts'
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ModalLayout from './modal_layout'
import ListEdit from '../../features/list_edit'
const messages = defineMessages({
muteMessage: { id: 'confirmations.mute.message', defaultMessage: 'Are you sure you want to mute {name}?' },
cancel: { id: 'confirmation_modal.cancel', defaultMessage: 'Cancel' },
confirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
})
const mapStateToProps = (state) => ({
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: state.getIn(['mutes', 'new', 'account']),
})
const mapDispatchToProps = (dispatch) => ({
onConfirm(account, notifications) {
dispatch(muteAccount(account.get('id'), notifications))
},
title: { id: 'lists.edit', defaultMessage: 'Edit list' },
})
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class UnfollowModal extends PureComponent {
class ListEditorModal extends ImmutablePureComponent {
static propTypes = {
isSubmitting: PropTypes.bool.isRequired,
account: PropTypes.object.isRequired,
onConfirm: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
componentDidMount() {
this.button.focus()
}
handleClick = () => {
this.props.onClose()
this.props.onConfirm(this.props.account, this.props.notifications)
}
handleCancel = () => {
this.props.onClose()
onClose: PropTypes.func.isRequired,
listId: PropTypes.string.isRequired,
}
render() {
const { account, intl } = this.props
// , {
// message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
// confirm: intl.formatMessage(messages.unfollowConfirm),
// onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
// }));
const { intl, onClose, listId } = this.props
return (
<ConfirmationModal
title={`Mute @${account.get('acct')}`}
message={<FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute @{name}?' values={{ name: account.get('acct') }} />}
confirm={<FormattedMessage id='mute' defaultMessage='Mute' />}
onConfirm={() => {
// dispatch(blockDomain(domain))
// dispatch(blockDomain(domain))
}}
/>
<ModalLayout
title={intl.formatMessage(messages.title)}
width={500}
onClose={onClose}
>
<ListEdit listId={listId} />
</ModalLayout>
)
}
}

View File

@ -55,7 +55,7 @@ class ListTimelineSettingsModal extends ImmutablePureComponent {
return (
<ModalLayout
width='320'
width={320}
title={intl.formatMessage(messages.title)}
onClose={onClose}
>

View File

@ -17,7 +17,6 @@ class ModalLayout extends PureComponent {
title: PropTypes.string,
children: PropTypes.node,
onClose: PropTypes.func.isRequired,
handleCloseModal: PropTypes.func.isRequired,
width: PropTypes.number,
hideClose: PropTypes.bool,
noPadding: PropTypes.bool,

View File

@ -2,73 +2,101 @@ import { closeModal } from '../../actions/modal'
import { cancelReplyCompose } from '../../actions/compose'
import Bundle from '../../features/ui/util/bundle'
import {
MuteModal,
ReportModal,
MODAL_ACTIONS,
MODAL_BLOCK_ACCOUNT,
MODAL_BLOCK_DOMAIN,
MODAL_BOOST,
MODAL_COMMUNITY_TIMELINE_SETTINGS,
MODAL_COMPOSE,
MODAL_CONFIRM,
MODAL_DISPLAY_OPTIONS,
MODAL_EDIT_PROFILE,
MODAL_EMBED,
MODAL_GIF_PICKER,
MODAL_GROUP_CREATE,
MODAL_GROUP_DELETE,
MODAL_GROUP_EDITOR,
MODAL_HASHTAG_TIMELINE_SETTINGS,
MODAL_HOME_TIMELINE_SETTINGS,
MODAL_HOTKEYS,
MODAL_LIST_CREATE,
MODAL_LIST_DELETE,
MODAL_LIST_EDITOR,
MODAL_LIST_TIMELINE_SETTINGS,
MODAL_MEDIA,
MODAL_MUTE,
MODAL_PRO_UPGRADE,
MODAL_REPORT,
MODAL_STATUS_REVISIONS,
MODAL_UNAUTHORIZED,
MODAL_UNFOLLOW,
MODAL_VIDEO,
} from '../../constants'
import {
ActionsModal,
BlockAccountModal,
BlockDomainModal,
BoostModal,
CommunityTimelineSettingsModal,
ComposeModal,
ConfirmationModal,
DisplayOptionsModal,
EditProfileModal,
EmbedModal,
// ListEditor,
// ListAdder,
GifPickerModal,
GroupCreateModal,
GroupDeleteModal,
GroupEditorModal,
HashtagTimelineSettingsModal,
HomeTimelineSettingsModal,
HotkeysModal,
ListCreateModal,
ListDeleteModal,
ListEditorModal,
ListTimelineSettingsModal,
MediaModal,
MuteModal,
ProUpgradeModal,
ReportModal,
StatusRevisionsModal,
UnauthorizedModal,
UnfollowModal,
VideoModal,
} from '../../features/ui/util/async_components'
import ModalBase from './modal_base'
import BundleModalError from '../bundle_modal_error'
import ActionsModal from './actions_modal'
import BlockAccountModal from './block_account_modal'
import BlockDomainModal from './block_domain_modal'
import BoostModal from './boost_modal'
import CommunityTimelineSettingsModal from './community_timeline_settings_modal'
import ComposeModal from './compose_modal'
import ConfirmationModal from './confirmation_modal'
import EditProfileModal from './edit_profile_modal'
import GifPickerModal from './gif_picker_modal'
import GroupCreateModal from './group_create_modal'
import GroupDeleteModal from './group_delete_modal'
import GroupEditorModal from './group_editor_modal'
import HashtagTimelineSettingsModal from './hashtag_timeline_settings_modal'
import HomeTimelineSettingsModal from './home_timeline_settings_modal'
import HotkeysModal from './hotkeys_modal'
import ListCreateModal from './list_create_modal'
import ListDeleteModal from './list_delete_modal'
import ListEditorModal from './list_editor_modal'
import ListTimelineSettingsModal from './list_timeline_settings_modal'
import MediaModal from './media_modal'
import ModalLoading from './modal_loading'
import ProUpgradeModal from './pro_upgrade_modal'
import VideoModal from './video_modal'
import UnauthorizedModal from './unauthorized_modal'
import UnfollowModal from './unfollow_modal'
const MODAL_COMPONENTS = {
ACTIONS: () => Promise.resolve({ default: ActionsModal }),
BLOCK_ACCOUNT: () => Promise.resolve({ default: BlockAccountModal }),
BLOCK_DOMAIN: () => Promise.resolve({ default: BlockDomainModal }),
BOOST: () => Promise.resolve({ default: BoostModal }),
COMMUNITY_TIMELINE_SETTINGS: () => Promise.resolve({ default: CommunityTimelineSettingsModal }),
COMPOSE: () => Promise.resolve({ default: ComposeModal }),
CONFIRM: () => Promise.resolve({ default: ConfirmationModal }),
EDIT_PROFILE: () => Promise.resolve({ default: EditProfileModal }),
EMBED: () => Promise.resolve({ default: EmbedModal }),
GIF_PICKER: () => Promise.resolve({ default: GifPickerModal }),
GROUP_CREATE: () => Promise.resolve({ default: GroupCreateModal }),
GROUP_DELETE: () => Promise.resolve({ default: GroupDeleteModal }),
GROUP_EDITOR: () => Promise.resolve({ default: GroupEditorModal }),
HASHTAG_TIMELINE_SETTINGS: () => Promise.resolve({ default: HashtagTimelineSettingsModal }),
HOME_TIMELINE_SETTINGS: () => Promise.resolve({ default: HomeTimelineSettingsModal }),
HOTKEYS: () => Promise.resolve({ default: HotkeysModal }),
LIST_CREATE: () => Promise.resolve({ default: ListCreateModal }),
LIST_DELETE: () => Promise.resolve({ default: ListDeleteModal }),
LIST_EDITOR: () => Promise.resolve({ default: ListEditorModal }),
LIST_TIMELINE_SETTINGS: () => Promise.resolve({ default: ListTimelineSettingsModal }),
MEDIA: () => Promise.resolve({ default: MediaModal }),
MUTE: MuteModal,
PRO_UPGRADE: () => Promise.resolve({ default: ProUpgradeModal }),
REPORT: ReportModal,
STATUS_REVISIONS: StatusRevisionsModal,
UNAUTHORIZED: () => Promise.resolve({ default: UnauthorizedModal }),
UNFOLLOW: () => Promise.resolve({ default: UnfollowModal }),
VIDEO: () => Promise.resolve({ default: VideoModal }),
}
const MODAL_COMPONENTS = {}
MODAL_COMPONENTS[MODAL_ACTIONS] = ActionsModal
MODAL_COMPONENTS[MODAL_BLOCK_ACCOUNT] = BlockAccountModal
MODAL_COMPONENTS[MODAL_BLOCK_DOMAIN] = BlockDomainModal
MODAL_COMPONENTS[MODAL_BOOST] = BoostModal
MODAL_COMPONENTS[MODAL_COMMUNITY_TIMELINE_SETTINGS] = CommunityTimelineSettingsModal
MODAL_COMPONENTS[MODAL_COMPOSE] = ComposeModal
MODAL_COMPONENTS[MODAL_CONFIRM] = ConfirmationModal
MODAL_COMPONENTS[MODAL_DISPLAY_OPTIONS] = DisplayOptionsModal
MODAL_COMPONENTS[MODAL_EDIT_PROFILE] = EditProfileModal
MODAL_COMPONENTS[MODAL_EMBED] = EmbedModal
MODAL_COMPONENTS[MODAL_GIF_PICKER] = GifPickerModal
MODAL_COMPONENTS[MODAL_GROUP_CREATE] = GroupCreateModal
MODAL_COMPONENTS[MODAL_GROUP_DELETE] = GroupDeleteModal
MODAL_COMPONENTS[MODAL_GROUP_EDITOR] = GroupEditorModal
MODAL_COMPONENTS[MODAL_HASHTAG_TIMELINE_SETTINGS] = HashtagTimelineSettingsModal
MODAL_COMPONENTS[MODAL_HOME_TIMELINE_SETTINGS] = HomeTimelineSettingsModal
MODAL_COMPONENTS[MODAL_HOTKEYS] = HotkeysModal
MODAL_COMPONENTS[MODAL_LIST_CREATE] = ListCreateModal
MODAL_COMPONENTS[MODAL_LIST_DELETE] = ListDeleteModal
MODAL_COMPONENTS[MODAL_LIST_EDITOR] = ListEditorModal
MODAL_COMPONENTS[MODAL_LIST_TIMELINE_SETTINGS] = ListTimelineSettingsModal
MODAL_COMPONENTS[MODAL_MEDIA] = MediaModal
MODAL_COMPONENTS[MODAL_MUTE] = MuteModal
MODAL_COMPONENTS[MODAL_PRO_UPGRADE] = ProUpgradeModal
MODAL_COMPONENTS[MODAL_REPORT] = ReportModal
MODAL_COMPONENTS[MODAL_STATUS_REVISIONS] = StatusRevisionsModal
MODAL_COMPONENTS[MODAL_UNAUTHORIZED] = UnauthorizedModal
MODAL_COMPONENTS[MODAL_UNFOLLOW] = UnfollowModal
MODAL_COMPONENTS[MODAL_VIDEO] = VideoModal
const mapStateToProps = (state) => ({
type: state.getIn(['modal', 'modalType']),

View File

@ -25,7 +25,7 @@ class ProUpgradeModal extends ImmutablePureComponent {
return (
<ModalLayout
title={intl.formatMessage(messages.title)}
width='460'
width={460}
onClose={onClose}
>
<Text>

View File

@ -5,7 +5,6 @@ import classNames from 'classnames/bind'
import { loadStatusRevisions } from '../../actions/status_revisions'
import ModalLayout from './modal_layout'
import RelativeTimestamp from '../relative_timestamp'
import ScrollableList from '../scrollable_list'
import Text from '../text'
const cx = classNames.bind(_s)
@ -56,42 +55,40 @@ class StatusRevisionsModal extends ImmutablePureComponent {
return (
<ModalLayout
title={intl.formatMessage(messages.title)}
width='480'
width={480}
onClose={onClose}
>
<div className={[_s.default]}>
<ScrollableList>
{
revisions.map((revision, i) => {
const isFirst = i === 0
const isLast = i === revisions.size - 1
{
revisions.map((revision, i) => {
const isFirst = i === 0
const isLast = i === revisions.size - 1
const containerClasses = cx({
default: 1,
pt5: 1,
pb10: 1,
mt5: !isFirst,
borderColorSecondary: !isLast,
borderBottom1PX: !isLast,
})
return (
<div key={`status-revision-${i}`} className={containerClasses}>
<div className={[_s.default, _s.pb5].join(' ')}>
<Text size='medium'>
{revision.get('text')}
</Text>
</div>
<div className={[_s.default]}>
<Text size='small' color='secondary'>
Edited on <RelativeTimestamp timestamp={revision.get('created_at')} />
</Text>
</div>
</div>
)
const containerClasses = cx({
default: 1,
pt5: 1,
pb10: 1,
mt5: !isFirst,
borderColorSecondary: !isLast,
borderBottom1PX: !isLast,
})
}
</ScrollableList>
return (
<div key={`status-revision-${i}`} className={containerClasses}>
<div className={[_s.default, _s.pb5].join(' ')}>
<Text size='medium'>
{revision.get('text')}
</Text>
</div>
<div className={[_s.default]}>
<Text size='small' color='secondary'>
Edited on <RelativeTimestamp timestamp={revision.get('created_at')} />
</Text>
</div>
</div>
)
})
}
</div>
</ModalLayout>
)

View File

@ -43,10 +43,18 @@ class Notification extends ImmutablePureComponent {
createdAt: PropTypes.string,
statusId: PropTypes.string,
type: PropTypes.string.isRequired,
isHidden: PropTypes.bool,
}
render() {
const { intl, accounts, createdAt, type, statusId } = this.props
const {
intl,
accounts,
createdAt,
type,
statusId,
isHidden,
} = this.props
const count = !!accounts ? accounts.size : 0
@ -83,6 +91,10 @@ class Notification extends ImmutablePureComponent {
return null
}
if (isHidden) {
// : todo :
}
return (
<div className={[_s.default, _s.px10, _s.cursorPointer, _s.backgroundColorSubtle_onHover].join(' ')}>
<div className={[_s.default, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>

View File

@ -1,5 +1,5 @@
import { Fragment } from 'react'
import { defineMessages, injectIntl } from 'react-intl'
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { shortNumberFormat } from '../../utils/numbers'
@ -9,6 +9,7 @@ import Divider from '../divider'
import Heading from '../heading'
import Icon from '../icon'
import Text from '../text'
import RelativeTimestamp from '../relative_timestamp'
const messages = defineMessages({
title: { id: 'about', defaultMessage: 'About' },
@ -27,15 +28,19 @@ class GroupInfoPanel extends ImmutablePureComponent {
render() {
const { intl, group } = this.props
console.log("group:", group)
return (
<PanelLayout title={intl.formatMessage(messages.title)}>
{
!!group &&
<Fragment>
<Heading size='h2'>
{group.get('title')}
</Heading>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')}>
<Text weight='medium'>
{group.get('title')}
</Text>
</div>
<Divider isSmall />
@ -63,6 +68,23 @@ class GroupInfoPanel extends ImmutablePureComponent {
<Divider isSmall />
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')}>
<Icon id='calendar' size='12px' className={_s.fillColorSecondary} />
<Text
size='small'
color='secondary'
className={_s.ml5}
>
{
<FormattedMessage id='lists.panel_created' defaultMessage='Created: {date}' values={{
date: <RelativeTimestamp timestamp={group.get('created_at')} />,
}} />
}
</Text>
</div>
<Divider isSmall />
<Text>
{group.get('description')}
</Text>

View File

@ -1,9 +1,10 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl } from 'react-intl'
import { fetchGroups } from '../../actions/groups'
import PanelLayout from './panel_layout'
import GroupListItem from '../group_list_item'
import Button from '../button'
import ScrollableList from '../scrollable_list'
const messages = defineMessages({
title: { id: 'groups.sidebar-panel.title', defaultMessage: 'Groups you\'re in' },
@ -15,13 +16,41 @@ const mapStateToProps = (state) => ({
groupIds: state.getIn(['group_lists', 'member']),
})
const mapDispatchToProps = (dispatch) => ({
onFetchGroups: (type) => {
dispatch(fetchGroups(type))
}
})
export default
@connect(mapStateToProps)
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class GroupSidebarPanel extends ImmutablePureComponent {
static propTypes = {
groupIds: ImmutablePropTypes.list,
slim: PropTypes.bool,
isLazy: PropTypes.bool,
isSlim: PropTypes.bool,
onFetchGroups: PropTypes.func.isRequired,
}
state = {
fetched: false,
}
static getDerivedStateFromProps(nextProps, prevState) {
if (!nextProps.isHidden && nextProps.isIntersecting && !prevState.fetched) {
return {
fetched: true
}
}
return null
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (!prevState.fetched && this.state.fetched && this.props.isLazy) {
this.props.onFetchGroups('member')
}
}
render() {
@ -42,16 +71,20 @@ class GroupSidebarPanel extends ImmutablePureComponent {
noPadding={slim}
>
<div className={_s.default}>
{
groupIds.slice(0, maxCount).map((groupId, i) => (
<GroupListItem
key={`group-panel-item-${groupId}`}
id={groupId}
slim={slim}
isLast={groupIds.length - 1 === i}
/>
))
}
<ScrollableList
scrollKey='groups-panel'
>
{
groupIds.slice(0, maxCount).map((groupId, i) => (
<GroupListItem
key={`group-panel-item-${groupId}`}
id={groupId}
slim={slim}
isLast={groupIds.length - 1 === i}
/>
))
}
</ScrollableList>
</div>
</PanelLayout>
)

View File

@ -1,57 +1,48 @@
import { defineMessages, injectIntl } from 'react-intl'
import { fetchSuggestions, dismissSuggestion } from '../../actions/suggestions'
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import PanelLayout from './panel_layout'
import Avatar from '../avatar'
import Divider from '../divider'
import Icon from '../icon'
import Heading from '../heading'
import RelativeTimestamp from '../relative_timestamp'
import Text from '../text'
const messages = defineMessages({
dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
memberCount: { id: 'lists.panel_members', defaultMessage: 'Members: {count}' },
createdAt: { id: 'lists.panel_created', defaultMessage: 'Created: {date}' },
title: { id: 'lists_information', defaultMessage: 'List Information' },
edit: { id: 'edit', defaultMessage: 'Edit' },
})
const mapStateToProps = (state) => ({
// accountIds: state.getIn(['listEditor', 'accounts', 'items']),
})
const mapDispatchToProps = (dispatch) => ({
})
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class ListDetailsPanel extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
onEdit: PropTypes.func.isRequired,
list: ImmutablePropTypes.map,
}
handleShowAllLists() {
handleOnEdit = () => {
this.props.onEdit()
}
render() {
const { intl } = this.props
const { intl, list } = this.props
const title = !!list ? list.get('title') : ''
const createdAt = !!list ? list.get('created_at') : ''
return (
<PanelLayout
title={intl.formatMessage(messages.title)}
headerButtonTitle={intl.formatMessage(messages.edit)}
headerButtonAction={this.handleShowAllLists}
headerButtonAction={this.handleOnEdit}
>
<div className={_s.default}>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter,].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')}>
<Text weight='medium'>
Some List Title
{title}
</Text>
</div>
@ -65,42 +56,13 @@ class ListDetailsPanel extends ImmutablePureComponent {
className={_s.ml5}
>
{
intl.formatMessage(messages.createdAt, {
date: '12-25-2019'
})
<FormattedMessage id='lists.panel_created' defaultMessage='Created: {date}' values={{
date: <RelativeTimestamp timestamp={createdAt} />,
}} />
}
</Text>
</div>
<Divider isSmall />
<div className={[_s.default].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')}>
<Icon id='group' size='12px' className={_s.fillColorSecondary} />
<Text
size='small'
color='secondary'
className={_s.ml5}
>
{
intl.formatMessage(messages.memberCount, {
count: 10
})
}
</Text>
</div>
<div className={[_s.default, _s.flexRow, _s.flexWrap, _s.pt10].join(' ')}>
{
[1, 2, 3, 4, 5, 6, 7, 8, 9].map(item => (
<div className={[_s.default, _s.mr5].join(' ')}>
<Avatar size={26} />
</div>
))
}
</div>
</div>
</div>
</PanelLayout>
)

View File

@ -32,8 +32,28 @@ class ListsPanel extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
}
componentWillMount() {
this.props.onFetchLists()
state = {
fetched: false,
}
updateOnProps = [
'lists',
]
static getDerivedStateFromProps(nextProps, prevState) {
if (!nextProps.isHidden && nextProps.isIntersecting && !prevState.fetched) {
return {
fetched: true,
}
}
return null
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (!prevState.fetched && this.state.fetched) {
this.props.onFetchLists()
}
}
render() {

View File

@ -66,11 +66,12 @@ class MediaGalleryPanel extends ImmutablePureComponent {
>
<div className={[_s.default, _s.flexRow, _s.flexWrap, _s.px10, _s.py10].join(' ')}>
{
attachments.slice(0, 16).map((attachment) => (
attachments.slice(0, 16).map((attachment, i) => (
<MediaItem
small
isSmall
key={attachment.get('id')}
attachment={attachment}
account={account}
/>
))
}

View File

@ -9,7 +9,7 @@ import SettingSwitch from '../setting_switch'
const messages = defineMessages({
title: { id: 'notification_filters', defaultMessage: 'Notification Filters' },
onlyVerified: { id: 'notification_only_verified', defaultMessage: 'Only Verified Users' },
onlyFollowing: { id: 'notification_only_following', defaultMessage: 'Only People I Follow' },
// onlyFollowing: { id: 'notification_only_following', defaultMessage: 'Only People I Follow' },
})
const mapStateToProps = (state) => ({
@ -55,13 +55,14 @@ class NotificationFilterPanel extends ImmutablePureComponent {
label={intl.formatMessage(messages.onlyVerified)}
/>
<SettingSwitch
{ /* : todo :
<SettingSwitch
prefix='notification'
settings={settings}
settingPath={'onlyFollowing'}
onChange={onChange}
label={intl.formatMessage(messages.onlyFollowing)}
/>
/> */ }
</PanelLayout>
)
}

View File

@ -4,6 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
import { fetchGabTrends } from '../../actions/gab_trends'
import PanelLayout from './panel_layout'
import ColumnIndicator from '../column_indicator'
import ScrollableList from '../scrollable_list'
import TrendingItem from '../trends_item'
const messages = defineMessages({
@ -29,7 +30,7 @@ class TrendsPanel extends ImmutablePureComponent {
onFetchGabTrends: PropTypes.func.isRequired,
}
componentWillMount() {
componentDidMount() {
this.props.onFetchGabTrends()
}
@ -47,19 +48,26 @@ class TrendsPanel extends ImmutablePureComponent {
<ColumnIndicator type='loading' />
}
{
gabtrends && gabtrends.slice(0, 8).map((trend, i) => (
<TrendingItem
key={`gab-trend-${i}`}
index={i + 1}
isLast={i === 7}
url={trend.get('url')}
title={trend.get('title')}
description={trend.get('description')}
imageUrl={trend.get('image')}
publishDate={trend.get('date_published')}
author={trend.getIn(['author', 'name'], '')}
/>
))
!gabtrends.isEmpty() &&
<ScrollableList
scrollKey='trending-items'
>
{
gabtrends.slice(0, 8).map((trend, i) => (
<TrendingItem
key={`gab-trend-${i}`}
index={i + 1}
isLast={i === 7}
url={trend.get('url')}
title={trend.get('title')}
description={trend.get('description')}
imageUrl={trend.get('image')}
publishDate={trend.get('date_published')}
author={trend.getIn(['author', 'name'], '')}
/>
))
}
</ScrollableList>
}
</div>
</PanelLayout>

View File

@ -36,19 +36,34 @@ class WhoToFollowPanel extends ImmutablePureComponent {
'suggestions',
]
componentDidMount () {
this.props.fetchSuggestions()
state = {
fetched: false,
}
static getDerivedStateFromProps(nextProps, prevState) {
if (!nextProps.isHidden && nextProps.isIntersecting && !prevState.fetched) {
return {
fetched: true
}
}
return null
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (!prevState.fetched && this.state.fetched) {
this.props.fetchSuggestions()
}
}
render() {
const { intl, suggestions, dismissSuggestion } = this.props
if (suggestions.isEmpty()) {
return null
}
if (suggestions.isEmpty()) return null
return (
<PanelLayout
noPadding
title={intl.formatMessage(messages.title)}
footerButtonTitle={intl.formatMessage(messages.show_more)}
footerButtonTo='/explore'
@ -57,6 +72,7 @@ class WhoToFollowPanel extends ImmutablePureComponent {
{
suggestions.map(accountId => (
<Account
compact
showDismiss
key={accountId}
id={accountId}

View File

@ -0,0 +1,12 @@
import PopoverLayout from './popover_layout'
import Text from '../text'
export default class UserInfoPopover extends PureComponent {
render() {
return (
<PopoverLayout>
<Text>testing</Text>
</PopoverLayout>
)
}
}

View File

@ -2,8 +2,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { Manager, Reference, Popper } from 'react-popper'
import classnames from 'classnames/bind'
import spring from 'react-motion/lib/spring'
import Motion from '../../features/ui/util/optional_motion'
import { openPopover, closePopover } from '../../actions/popover'
import { openModal, closeModal } from '../../actions/modal'
import { isUserTouching } from '../../utils/is_mobile'
@ -117,7 +115,6 @@ class PopoverBase extends ImmutablePureComponent {
targetRef,
innerRef,
} = this.props
const open = this.state.id === openPopoverType
const containerClasses = cx({
default: 1,
@ -125,8 +122,6 @@ class PopoverBase extends ImmutablePureComponent {
displayNone: !visible,
})
console.log('targetRef:', targetRef)
return (
<Manager>
<Popper

View File

@ -1,37 +1,51 @@
import detectPassiveEvents from 'detect-passive-events'
import { closePopover } from '../../actions/popover'
import {
POPOVER_CONTENT_WARNING,
POPOVER_DATE_PICKER,
POPOVER_EMOJI_PICKER,
POPOVER_GROUP_INFO,
POPOVER_PROFILE_OPTIONS,
POPOVER_REPOST_OPTIONS,
POPOVER_SEARCH,
POPOVER_SIDEBAR_MORE,
POPOVER_STATUS_OPTIONS,
POPOVER_STATUS_SHARE,
POPOVER_STATUS_VISIBILITY,
POPOVER_USER_INFO,
} from '../../constants'
import {
ContentWarningPopover,
DatePickerPopover,
EmojiPickerPopover,
GroupInfoPopover,
ProfileOptionsPopover,
RepostOptionsPopover,
SearchPopover,
SidebarMorePopover,
StatusOptionsPopover,
StatusSharePopover,
StatusVisibilityPopover,
UserInfoPopover,
} from '../../features/ui/util/async_components'
import Bundle from '../../features/ui/util/bundle'
import BundleModalError from '../bundle_modal_error'
import PopoverBase from './popover_base'
import ContentWarningPopover from './content_warning_popover'
import DatePickerPopover from './date_picker_popover'
import EmojiPickerPopover from './emoji_picker_popover'
import GroupInfoPopover from './group_info_popover'
import ProfileOptionsPopover from './profile_options_popover'
import RepostOptionsPopover from './repost_options_popover'
import SearchPopover from './search_popover'
import SidebarMorePopover from './sidebar_more_popover'
import StatusOptionsPopover from './status_options_popover'
import StatusSharePopover from './status_share_popover'
import StatusVisibilityPopover from './status_visibility_popover'
import UserInfoPopover from './user_info_popover'
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false
const POPOVER_COMPONENTS = {
CONTENT_WARNING: () => Promise.resolve({ default: ContentWarningPopover }),
DATE_PICKER: () => Promise.resolve({ default: DatePickerPopover }),
EMOJI_PICKER: () => Promise.resolve({ default: EmojiPickerPopover }),
GROUP_INFO: () => GroupInfoPopover,
PROFILE_OPTIONS: () => Promise.resolve({ default: ProfileOptionsPopover }),
REPOST_OPTIONS: () => Promise.resolve({ default: RepostOptionsPopover }),
SEARCH: () => Promise.resolve({ default: SearchPopover }),
SIDEBAR_MORE: () => Promise.resolve({ default: SidebarMorePopover }),
STATUS_OPTIONS: () => Promise.resolve({ default: StatusOptionsPopover }),
STATUS_SHARE: () => Promise.resolve({ default: StatusSharePopover }),
STATUS_VISIBILITY: () => Promise.resolve({ default: StatusVisibilityPopover }),
USER_INFO: () => Promise.resolve({ default: UserInfoPopover }),
}
const POPOVER_COMPONENTS = {}
POPOVER_COMPONENTS[POPOVER_CONTENT_WARNING] = ContentWarningPopover
POPOVER_COMPONENTS[POPOVER_DATE_PICKER] = DatePickerPopover
POPOVER_COMPONENTS[POPOVER_EMOJI_PICKER] = EmojiPickerPopover
POPOVER_COMPONENTS[POPOVER_GROUP_INFO] = GroupInfoPopover
POPOVER_COMPONENTS[POPOVER_PROFILE_OPTIONS] = ProfileOptionsPopover
POPOVER_COMPONENTS[POPOVER_REPOST_OPTIONS] = RepostOptionsPopover
POPOVER_COMPONENTS[POPOVER_SEARCH] = SearchPopover
POPOVER_COMPONENTS[POPOVER_SIDEBAR_MORE] = SidebarMorePopover
POPOVER_COMPONENTS[POPOVER_STATUS_OPTIONS] = StatusOptionsPopover
POPOVER_COMPONENTS[POPOVER_STATUS_SHARE] = StatusSharePopover
POPOVER_COMPONENTS[POPOVER_STATUS_VISIBILITY] = StatusVisibilityPopover
POPOVER_COMPONENTS[POPOVER_USER_INFO] = UserInfoPopover
const mapStateToProps = (state) => ({
type: state.getIn(['popover', 'popoverType']),
@ -84,14 +98,10 @@ class PopoverRoot extends PureComponent {
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions)
}
setRef = c => {
setRef = (c) => {
this.node = c
}
setFocusRef = c => {
this.focusedItem = c
}
handleKeyDown = e => {
const items = Array.from(this.node.getElementsByTagName('a'))
const index = items.indexOf(document.activeElement)
@ -150,8 +160,6 @@ class PopoverRoot extends PureComponent {
const { type, props } = this.props
const visible = !!type
console.log("POPOVER_COMPONENTS[type]:", type, POPOVER_COMPONENTS[type]);
return (
<PopoverBase
visible={visible}
@ -162,12 +170,12 @@ class PopoverRoot extends PureComponent {
visible &&
<Bundle
fetchComponent={POPOVER_COMPONENTS[type]}
loading={this.renderLoading(type)}
loading={this.renderLoading()}
error={this.renderError}
renderDelay={200}
>
{
(SpecificComponent) => <SpecificComponent {...props} />
(Component) => <Component {...props} />
}
</Bundle>
}

View File

@ -1,8 +1,39 @@
import { defineMessages, injectIntl } from 'react-intl'
import { MODAL_DISPLAY_OPTIONS } from '../../constants'
import { openModal } from '../../actions/modal'
import PopoverLayout from './popover_layout'
import List from '../list'
export default class SidebarMorePopover extends PureComponent {
const messages = defineMessages({
display: { id: 'display_options', defaultMessage: 'Display Options' },
help: { id: 'getting_started.help', defaultMessage: 'Help' },
settings: { id: 'settings', defaultMessage: 'Settings' },
logout: { 'id': 'confirmations.logout.confirm', 'defaultMessage': 'Log out' },
})
const mapDispatchToProps = (dispatch) => ({
onOpenDisplayModal: () => {
dispatch(openModal(MODAL_DISPLAY_OPTIONS))
},
})
export default
@injectIntl
@connect(null, mapDispatchToProps)
class SidebarMorePopover extends PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
onOpenDisplayModal: PropTypes.func.isRequired,
}
handleOnOpenDisplayModal = () => {
this.props.onOpenDisplayModal()
}
render() {
const { intl } = this.props
return (
<PopoverLayout className={_s.width240PX}>
<List
@ -10,15 +41,19 @@ export default class SidebarMorePopover extends PureComponent {
scrollKey='profile_options'
items={[
{
title: 'Help',
title: intl.formatMessage(messages.help),
href: 'https://help.gab.com',
},
{
title: 'Settings',
title: intl.formatMessage(messages.display),
onClick: this.handleOnOpenDisplayModal,
},
{
title: intl.formatMessage(messages.settings),
href: '/settings',
},
{
title: 'Log Out',
title: intl.formatMessage(messages.logout),
href: '/auth/log_out',
},
]}

View File

@ -47,7 +47,7 @@ class StatusVisibilityDropdown extends PureComponent {
this.props.onChange(value)
}
componentWillMount () {
componentDidMount () {
const { intl } = this.props
this.options = [

View File

@ -6,7 +6,9 @@ import {
CX,
POPOVER_PROFILE_OPTIONS,
PLACEHOLDER_MISSING_HEADER_SRC,
MODAL_EDIT_PROFILE,
} from '../constants'
import { openModal } from '../actions/modal'
import { openPopover } from '../actions/popover'
import { me } from '../initial_state'
import AccountActionButton from './account_action_button'
@ -27,6 +29,7 @@ const messages = defineMessages({
comments: { id: 'comments', defaultMessage: 'Comments' },
media: { id: 'media', defaultMessage: 'Media' },
accountFollowsYou: { id: 'account.follows_you', defaultMessage: 'Follows you' },
editProfile: { id: "account.edit_profile", defaultMessage: "Edit profile" },
})
const mapDispatchToProps = (dispatch) => ({
@ -35,6 +38,10 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(openPopover(POPOVER_PROFILE_OPTIONS, props))
},
onEditProfile() {
dispatch(openModal(MODAL_EDIT_PROFILE))
},
});
export default
@ -45,6 +52,7 @@ class ProfileHeader extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map,
intl: PropTypes.object.isRequired,
onEditProfile: PropTypes.func.isRequired,
openProfileOptionsPopover: PropTypes.func.isRequired,
}
@ -52,6 +60,10 @@ class ProfileHeader extends ImmutablePureComponent {
stickied: false,
}
handleOnEditProfile = () => {
this.props.onEditProfile()
}
handleOpenMore = () => {
const { openProfileOptionsPopover, account } = this.props
@ -164,15 +176,10 @@ class ProfileHeader extends ImmutablePureComponent {
backgroundColor='none'
color='brand'
className={[_s.justifyContentCenter, _s.alignItemsCenter].join(' ')}
href=''
onClick={this.handleOnEditProfile}
>
<Text
color='inherit'
weight='bold'
size='medium'
className={[_s.px15].join(' ')}
>
Edit Profile
<Text color='inherit' weight='bold' size='medium' className={_s.px15}>
{intl.formatMessage(messages.editProfile)}
</Text>
</Button>
</div>

View File

@ -196,7 +196,7 @@ export default class ScrollableList extends PureComponent {
return firstChild && firstChild.key;
}
handleLoadMore = e => {
handleLoadMore = (e) => {
e.preventDefault();
this.props.onLoadMore();
}
@ -213,9 +213,7 @@ export default class ScrollableList extends PureComponent {
} = this.props
const childrenCount = React.Children.count(children);
const trackScroll = true; //placeholder
const loadMore = (hasMore && onLoadMore) ? <LoadMore visible={!isLoading} onClick={this.handleLoadMore} /> : null;
const loadMore = (hasMore && onLoadMore) ? <LoadMore visible={!isLoading} onClick={this.handleLoadMore} /> : null
if (showLoading) {
return <ColumnIndicator type='loading' />
@ -232,7 +230,7 @@ export default class ScrollableList extends PureComponent {
index={index}
listLength={childrenCount}
intersectionObserverWrapper={this.intersectionObserverWrapper}
saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null}
saveHeightKey={`${this.context.router.route.location.key}:${scrollKey}`}
>
{
React.cloneElement(child, {

View File

@ -57,8 +57,8 @@ class Search extends PureComponent {
}
}
handleChange = (e) => {
this.props.onChange(e.target.value)
handleChange = (value) => {
this.props.onChange(value)
}
handleFocus = () => {

View File

@ -43,8 +43,13 @@ const mapStateToProps = (state, { timelineId }) => {
const getStatusIds = makeGetStatusIds();
const promotion = promotions.length > 0 && sample(promotions.filter(p => p.timeline_id === timelineId));
const statusIds = getStatusIds(state, {
type: timelineId.substring(0,5) === 'group' ? 'group' : timelineId,
id: timelineId
})
return {
statusIds: getStatusIds(state, { type: timelineId.substring(0,5) === 'group' ? 'group' : timelineId, id: timelineId }),
statusIds,
isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
hasMore: state.getIn(['timelines', timelineId, 'hasMore']),

View File

@ -28,7 +28,7 @@ export default class TabBar extends PureComponent {
onClick={tab.onClick}
icon={tab.icon}
to={tab.to}
active={tab.active}
isActive={tab.active}
isLarge={isLarge}
/>
))

View File

@ -13,22 +13,23 @@ export default class Textarea extends PureComponent {
onKeyUp: PropTypes.func,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
onClear: PropTypes.func,
title: PropTypes.string,
}
handleOnChange = (e) => {
this.props.onChange(e.target.value)
}
render() {
const {
placeholder,
prependIcon,
value,
hasClear,
onChange,
onKeyUp,
onFocus,
onBlur,
onClear,
title
title,
} = this.props
const inputClasses = cx({
@ -64,7 +65,7 @@ export default class Textarea extends PureComponent {
type='text'
placeholder={placeholder}
value={value}
onChange={onChange}
onChange={this.handleOnChange}
onKeyUp={onKeyUp}
onFocus={onFocus}
onBlur={onBlur}

View File

@ -1,3 +1,4 @@
import { Fragment } from 'react'
import classNames from 'classnames/bind'
import { urlRegex } from '../features/compose/util/url_regex'
import Button from './button'
@ -19,6 +20,7 @@ export default class TrendingItem extends PureComponent {
author: PropTypes.string,
publishDate: PropTypes.string,
isLast: PropTypes.bool,
isHidden: PropTypes.bool,
wide: PropTypes.bool,
}
@ -51,9 +53,24 @@ export default class TrendingItem extends PureComponent {
publishDate,
isLast,
wide,
isHidden,
} = this.props
const { hovering } = this.state
const correctedAuthor = author.replace('www.', '')
const correctedDescription = description.length >= 120 ? `${description.substring(0, 120).trim()}...` : description
const descriptionHasLink = correctedDescription.match(urlRegex)
if (isHidden) {
return (
<Fragment>
{title}
{!descriptionHasLink && correctedDescription}
{correctedAuthor}
</Fragment>
)
}
const containerClasses = cx({
default: 1,
noUnderline: 1,
@ -70,10 +87,6 @@ export default class TrendingItem extends PureComponent {
underline: hovering,
})
const correctedAuthor = author.replace('www.', '')
const correctedDescription = description.length >= 120 ? `${description.substring(0, 120).trim()}...` : description
const descriptionHasLink = correctedDescription.match(urlRegex)
const image = (
<Image
nullable

View File

@ -12,7 +12,47 @@ export const BREAKPOINT_EXTRA_SMALL = 992
export const ALLOWED_AROUND_SHORT_CODE = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d'
export const MAX_POST_CHARACTER_COUNT = 3000
export const PLACEHOLDER_MISSING_HEADER_SRC = '/headers/original/missing.png'
export const PLACEHOLDER_MISSING_HEADER_SRC = '/original/missing.png'
export const POPOVER_CONTENT_WARNING = 'CONTENT_WARNING'
export const POPOVER_DATE_PICKER = 'DATE_PICKER'
export const POPOVER_EMOJI_PICKER = 'EMOJI_PICKER'
export const POPOVER_GROUP_INFO = 'GROUP_INFO'
export const POPOVER_PROFILE_OPTIONS = 'PROFILE_OPTIONS'
export const POPOVER_USER_INFO = 'USER_INFO'
export const POPOVER_REPOST_OPTIONS = 'REPOST_OPTIONS'
export const POPOVER_SEARCH = 'SEARCH'
export const POPOVER_SIDEBAR_MORE = 'SIDEBAR_MORE'
export const POPOVER_STATUS_OPTIONS = 'STATUS_OPTIONS'
export const POPOVER_STATUS_SHARE = 'STATUS_SHARE'
export const POPOVER_STATUS_VISIBILITY = 'STATUS_VISIBILITY'
export const POPOVER_USER_INFO = 'USER_INFO'
export const MODAL_ACTIONS = 'ACTIONS'
export const MODAL_BLOCK_ACCOUNT = 'BLOCK_ACCOUNT'
export const MODAL_BLOCK_DOMAIN = 'BLOCK_DOMAIN'
export const MODAL_BOOST = 'BOOST'
export const MODAL_COMMUNITY_TIMELINE_SETTINGS = 'COMMUNITY_TIMELINE_SETTINGS'
export const MODAL_COMPOSE = 'COMPOSE'
export const MODAL_CONFIRM = 'CONFIRM'
export const MODAL_DISPLAY_OPTIONS = 'DISPLAY_OPTIONS'
export const MODAL_EDIT_PROFILE = 'EDIT_PROFILE'
export const MODAL_EMBED = 'EMBED'
export const MODAL_GIF_PICKER = 'GIF_PICKER'
export const MODAL_GROUP_CREATE = 'GROUP_CREATE'
export const MODAL_GROUP_DELETE = 'GROUP_DELETE'
export const MODAL_GROUP_EDITOR = 'GROUP_EDITOR'
export const MODAL_HASHTAG_TIMELINE_SETTINGS = 'HASHTAG_TIMELINE_SETTINGS'
export const MODAL_HOME_TIMELINE_SETTINGS = 'HOME_TIMELINE_SETTINGS'
export const MODAL_HOTKEYS = 'HOTKEYS'
export const MODAL_LIST_CREATE = 'LIST_CREATE'
export const MODAL_LIST_DELETE = 'LIST_DELETE'
export const MODAL_LIST_EDITOR = 'LIST_EDITOR'
export const MODAL_LIST_TIMELINE_SETTINGS = 'LIST_TIMELINE_SETTINGS'
export const MODAL_MEDIA = 'MEDIA'
export const MODAL_MUTE = 'MUTE'
export const MODAL_PRO_UPGRADE = 'PRO_UPGRADE'
export const MODAL_REPORT = 'REPORT'
export const MODAL_STATUS_REVISIONS = 'STATUS_REVISIONS'
export const MODAL_UNAUTHORIZED = 'UNAUTHORIZED'
export const MODAL_UNFOLLOW = 'UNFOLLOW'
export const MODAL_VIDEO = 'VIDEO'

View File

@ -98,8 +98,12 @@ class AccountGallery extends ImmutablePureComponent {
>
{
attachments.map((attachment) => (
<MediaItem key={attachment.get('id')} attachment={attachment} />
attachments.map((attachment, i) => (
<MediaItem
key={attachment.get('id')}
attachment={attachment}
account={account}
/>
))
}
@ -108,16 +112,16 @@ class AccountGallery extends ImmutablePureComponent {
<ColumnIndicator type='loading' />
}
{ /*
attachments.size === 0 &&
<ColumnIndicator type='empty' message={intl.formatMessage(messages.none)} />
*/ }
{
hasMore && !(isLoading && attachments.size === 0) &&
<LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
!isLoading && attachments.size === 0 &&
<ColumnIndicator type='error' message={intl.formatMessage(messages.none)} />
}
</div>
{
hasMore && !(isLoading && attachments.size === 0) &&
<LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
}
</Block>
)
}

View File

@ -1,70 +0,0 @@
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import { fetchReposts } from '../actions/interactions';
import { fetchStatus } from '../actions/statuses';
import { makeGetStatus } from '../selectors';
import Account from '../components/account';
import ColumnIndicator from '../components/column_indicator';
import ScrollableList from '../components/scrollable_list';
const mapStateToProps = (state, props) => {
const getStatus = makeGetStatus();
const status = getStatus(state, {
id: props.params.statusId,
username: props.params.username,
});
return {
status,
accountIds: state.getIn(['user_lists', 'favorites', props.params.statusId]),
};
};
export default
@connect(mapStateToProps)
class Favorites extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
status: ImmutablePropTypes.map,
};
componentWillMount() {
this.props.dispatch(fetchReposts(this.props.params.statusId));
this.props.dispatch(fetchStatus(this.props.params.statusId));
}
componentWillReceiveProps(nextProps) {
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
this.props.dispatch(fetchReposts(nextProps.params.statusId));
this.props.dispatch(fetchStatus(nextProps.params.statusId));
}
}
render() {
const { accountIds, status } = this.props;
if (!accountIds) {
return <ColumnIndicator type='loading' />
} else if (!status) {
return <ColumnIndicator type='missing' />
}
return (
<ScrollableList
scrollKey='reposts'
emptyMessage={<FormattedMessage id='status.reposts.empty' defaultMessage='No one has reposted this gab yet. When someone does, they will show up here.' />}
>
{
accountIds.map(id =>
<Account key={id} id={id} />
)
}
</ScrollableList>
);
}
}

View File

@ -41,7 +41,7 @@ class Following extends ImmutablePureComponent {
hasMore: PropTypes.bool,
}
componentWillMount() {
componentDidMount() {
const { accountId } = this.props
if (!!accountId && accountId !== -1) {
@ -67,7 +67,7 @@ class Following extends ImmutablePureComponent {
account,
accountIds,
hasMore,
intl
intl,
} = this.props
if (!account) return null

View File

@ -2,12 +2,20 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl } from 'react-intl'
import isObject from 'lodash.isobject'
import { changeValue, submit, setUp, reset } from '../actions/group_editor'
import ColumnIndicator from '../components/column_indicator';
import {
changeGroupTitle,
changeGroupDescription,
changeGroupCoverImage,
submit,
setGroup,
resetEditor,
} from '../actions/group_editor'
import ColumnIndicator from '../components/column_indicator'
import Button from '../components/button'
import Divider from '../components/divider'
import Input from '../components/input'
import Text from '../components/text'
import Form from '../components/form'
import Textarea from '../components/textarea'
import FileInput from '../components/file_input'
@ -18,6 +26,8 @@ const messages = defineMessages({
coverImageChange: { id: 'groups.form.coverImageChange', defaultMessage: 'Banner image selected' },
create: { id: 'groups.form.create', defaultMessage: 'Create group' },
update: { id: 'groups.form.update', defaultMessage: 'Update group' },
titlePlaceholder: { id: 'groups.form.title_placeholder', defaultMessage: 'New group title...' },
descriptionPlaceholder: { id: 'groups.form.description_placeholder', defaultMessage: 'Some group description...' },
})
const mapStateToProps = (state, { params }) => {
@ -27,26 +37,37 @@ const mapStateToProps = (state, { params }) => {
return {
group,
error: groupId && !group,
title: state.getIn(['group_editor', 'title']),
description: state.getIn(['group_editor', 'description']),
titleValue: state.getIn(['group_editor', 'title']),
descriptionValue: state.getIn(['group_editor', 'description']),
coverImage: state.getIn(['group_editor', 'coverImage']),
disabled: state.getIn(['group_editor', 'isSubmitting']),
isSubmitting: state.getIn(['group_editor', 'isSubmitting']),
}
}
const mapDispatchToProps = (dispatch) => ({
onTitleChange: value => dispatch(changeValue('title', value)),
onDescriptionChange: value => dispatch(changeValue('description', value)),
onCoverImageChange: value => dispatch(changeValue('coverImage', value)),
onSubmit: routerHistory => dispatch(submit(routerHistory)),
reset: () => dispatch(reset()),
setUp: group => dispatch(setUp(group)),
onTitleChange: (value) => {
dispatch(changeGroupTitle(value))
},
onDescriptionChange: (value) => {
dispatch(changeGroupDescription(value))
},
onCoverImageChange: (imageData) => {
console.log("imageData:", imageData)
dispatch(changeGroupCoverImage(imageData))
},
onResetEditor: () => {
dispatch(resetEditor())
},
onSetGroup: (group) => {
dispatch(setGroup(group))
},
onSubmit: (routerHistory) => dispatch(submit(routerHistory)),
})
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class Create extends ImmutablePureComponent {
@connect(mapStateToProps, mapDispatchToProps)
class GroupCreate extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object
@ -54,43 +75,45 @@ class Create extends ImmutablePureComponent {
static propTypes = {
group: ImmutablePropTypes.map,
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
titleValue: PropTypes.string.isRequired,
descriptionValue: PropTypes.string.isRequired,
coverImage: PropTypes.object,
disabled: PropTypes.bool,
intl: PropTypes.object.isRequired,
onTitleChange: PropTypes.func.isRequired,
onDescriptionChange: PropTypes.func.isRequired,
onResetEditor: PropTypes.func.isRequired,
onSetGroup: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
onCloseModal: PropTypes.func,
isSubmitting: PropTypes.bool,
}
componentWillMount() {
updateOnProps = [
'group',
'titleValue',
'descriptionValue',
'coverImage',
'isSubmitting',
]
componentDidMount() {
if (!this.props.group) {
this.props.reset()
this.props.onResetEditor()
} else {
this.props.setUp(this.props.group)
this.props.onSetGroup(this.props.group)
}
}
componentWillReceiveProps(nextProps) {
if (this.props.group !== nextProps.group && !!nextProps.group) {
this.props.setUp(nextProps.group)
this.props.onSetGroup(nextProps.group)
}
}
handleTitleChange = e => {
this.props.onTitleChange(e.target.value)
}
handleDescriptionChange = e => {
this.props.onDescriptionChange(e.target.value)
}
handleCoverImageChange = e => {
handleCoverImageChange = (e) => {
this.props.onCoverImageChange(e.target.files[0])
}
handleSubmit = e => {
handleSubmit = (e) => {
e.preventDefault()
this.props.onSubmit(this.context.router.history)
}
@ -99,11 +122,14 @@ class Create extends ImmutablePureComponent {
const {
group,
error,
title,
description,
titleValue,
descriptionValue,
coverImage,
disabled,
intl
intl,
onTitleChange,
onDescriptionChange,
isSubmitting,
onSubmit,
} = this.props
if (!group && error) {
@ -111,30 +137,30 @@ class Create extends ImmutablePureComponent {
}
return (
<form onSubmit={this.handleSubmit}>
<Form onSubmit={onSubmit}>
<Input
title={intl.formatMessage(messages.title)}
value={title}
disabled={disabled}
onChange={this.handleTitleChange}
placeholder={'New group title...'}
value={titleValue}
onChange={onTitleChange}
disabled={isSubmitting}
placeholder={intl.formatMessage(messages.titlePlaceholder)}
/>
<Divider isInvisible />
<Textarea
title={intl.formatMessage(messages.description)}
value={description}
disabled={disabled}
onChange={this.handleDescriptionChange}
placeholder={'Some group description...'}
value={descriptionValue}
onChange={onDescriptionChange}
placeholder={intl.formatMessage(messages.descriptionPlaceholder)}
disabled={isSubmitting}
/>
<Divider isInvisible />
<FileInput
disabled={isSubmitting}
title={intl.formatMessage(coverImage === null ? messages.coverImage : messages.coverImageChange)}
disabled={disabled}
onChange={this.handleCoverImageChange}
width='340px'
height='145px'
@ -142,13 +168,16 @@ class Create extends ImmutablePureComponent {
<Divider isInvisible />
<Button className={_s.ml10}>
<Text color='white'>
<Button
isDisabled={!titleValue || !descriptionValue && !isSubmitting}
onClick={this.handleSubmit}
>
<Text color='inherit' align='center'>
{intl.formatMessage(!!group ? messages.update : messages.create)}
</Text>
</Button>
</form>
</Form>
)
}

View File

@ -85,7 +85,7 @@ class GroupTimeline extends ImmutablePureComponent {
}
return (
<StatusListContainer
<StatusList
alwaysPrepend
scrollKey={`group_timeline-${columnId}`}
timelineId={`group:${id}`}

View File

@ -1,12 +1,17 @@
import { defineMessages, injectIntl } from 'react-intl'
import { changeListEditorTitle, submitListEditor } from '../actions/lists'
import { closeModal } from '../actions/modal'
import { MODAL_LIST_CREATE } from '../constants'
import Button from '../components/button'
import Input from '../components/input'
import Form from '../components/form'
import Text from '../components/text'
const messages = defineMessages({
label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' },
create: { id: 'lists.new.create_title', defaultMessage: 'Create' },
create: { id: 'lists.new.create_title', defaultMessage: 'Create list' },
list_description: { id: 'list.description', defaultMessage: 'Lists are private and only you can see who is on a list.\nNo one else can view your lists. No one knows that they are on your list.' },
new_list_placeholder: { id: 'list.title_placeholder', defaultMessage: 'My new list...', },
})
const mapStateToProps = (state) => ({
@ -14,9 +19,12 @@ const mapStateToProps = (state) => ({
disabled: state.getIn(['listEditor', 'isSubmitting']),
})
const mapDispatchToProps = (dispatch) => ({
onChange: value => dispatch(changeListEditorTitle(value)),
onSubmit: () => dispatch(submitListEditor(true)),
const mapDispatchToProps = (dispatch, { isModal }) => ({
onChange: (value) => dispatch(changeListEditorTitle(value)),
onSubmit: () => {
if (isModal) dispatch(closeModal(MODAL_LIST_CREATE))
dispatch(submitListEditor(true))
},
})
export default
@ -25,49 +33,47 @@ export default
class ListCreate extends PureComponent {
static propTypes = {
value: PropTypes.string.isRequired,
disabled: PropTypes.bool,
value: PropTypes.string,
intl: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
isModal: PropTypes.bool,
}
render() {
const {
value,
disabled,
intl,
onSubmit,
onChange
onChange,
} = this.props
const isDisabled = !value
return (
<form onSubmit={onSubmit}>
<Form>
<Input
title={intl.formatMessage(messages.label)}
placeholder='My new list...'
placeholder={intl.formatMessage(messages.new_list_placeholder)}
value={value}
onChange={onChange}
onSubmit={onSubmit}
disabled={disabled}
/>
<div className={[_s.default, _s.my10, _s.py5, _s.ml10].join(' ')}>
<Text color='secondary' size='small'>
Lists are private and only you can see who is on a list.<br/>
No one else can view your lists. No one knows that they are on your list.
{intl.formatMessage(messages.list_description)}
</Text>
</div>
<Button
className={_s.ml10}
type='submit'
isDisabled={isDisabled}
onClick={onSubmit}
>
<Text color='white'>
<Text color='inherit' align='center'>
{intl.formatMessage(messages.create)}
</Text>
</Button>
</form>
</Form>
)
}

View File

@ -12,9 +12,9 @@ const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) => ({
onSubmit: value => dispatch(fetchListSuggestions(value)),
onSubmit: (value) => dispatch(fetchListSuggestions(value)),
onClear: () => dispatch(clearListSuggestions()),
onChange: value => dispatch(changeListSuggestions(value)),
onChange: (value) => dispatch(changeListSuggestions(value)),
});
export default

View File

@ -7,6 +7,7 @@ import Account from './components/account'
import ListEditorSearch from './components/list_editor_search'
import EditListForm from './components/edit_list_form/edit_list_form'
import Button from '../../components/button'
import Form from '../../components/form'
import Input from '../../components/input'
const mapStateToProps = (state, { params }) => {
@ -24,7 +25,7 @@ const mapStateToProps = (state, { params }) => {
}
const mapDispatchToProps = (dispatch) => ({
onInitialize: listId => dispatch(setupListEditor(listId)),
onInitialize: (listId) => dispatch(setupListEditor(listId)),
onReset: () => dispatch(resetListEditor()),
})
@ -80,7 +81,7 @@ class ListEdit extends ImmutablePureComponent {
console.log("title:", title)
return (
<div>
<Form>
<Input
title={intl.formatMessage(messages.editListTitle)}
placeholder='My new list title...'
@ -90,8 +91,6 @@ class ListEdit extends ImmutablePureComponent {
// disabled={disabled}
/>
{
/*
<div className='compose-modal__header'>
<h3 className='compose-modal__header__title'>
{intl.formatMessage(messages.editList)}
@ -119,9 +118,8 @@ class ListEdit extends ImmutablePureComponent {
</div>
</div>
</div>
*/ }
</div>
</Form>
)
}

View File

@ -1,26 +1,22 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { connectListStream } from '../actions/streaming';
import { expandListTimeline } from '../actions/timelines';
import { fetchList, deleteList } from '../actions/lists';
import { openModal } from '../actions/modal';
import StatusList from '../components/status_list';
import ColumnIndicator from '../components/column_indicator';
import Button from '../components/button';
const messages = defineMessages({
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
deleteConfirm: { id: 'confirmations.delete_list.confirm', defaultMessage: 'Delete' },
});
import { Fragment } from 'react'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { FormattedMessage } from 'react-intl'
import { connectListStream } from '../actions/streaming'
import { expandListTimeline } from '../actions/timelines'
import { fetchList, deleteList } from '../actions/lists'
import { openModal } from '../actions/modal'
import StatusList from '../components/status_list'
import ColumnIndicator from '../components/column_indicator'
import Button from '../components/button'
import Text from '../components/text'
const mapStateToProps = (state, props) => ({
list: state.getIn(['lists', props.params.id]),
});
})
export default
@connect(mapStateToProps)
@injectIntl
class ListTimeline extends ImmutablePureComponent {
static contextTypes = {
@ -35,84 +31,76 @@ class ListTimeline extends ImmutablePureComponent {
PropTypes.bool,
]),
intl: PropTypes.object.isRequired,
};
}
componentDidMount() {
this.handleConnect(this.props.params.id);
this.handleConnect(this.props.params.id)
}
componentWillUnmount() {
this.handleDisconnect();
this.handleDisconnect()
}
componentWillReceiveProps(nextProps) {
if (nextProps.params.id !== this.props.params.id) {
this.handleDisconnect();
this.handleConnect(nextProps.params.id);
this.handleDisconnect()
this.handleConnect(nextProps.params.id)
}
}
handleConnect(id) {
const { dispatch } = this.props;
const { dispatch } = this.props
dispatch(fetchList(id));
dispatch(expandListTimeline(id));
dispatch(fetchList(id))
dispatch(expandListTimeline(id))
this.disconnect = dispatch(connectListStream(id));
this.disconnect = dispatch(connectListStream(id))
}
handleDisconnect() {
if (this.disconnect) {
this.disconnect();
this.disconnect = null;
this.disconnect()
this.disconnect = null
}
}
handleLoadMore = maxId => {
const { id } = this.props.params;
this.props.dispatch(expandListTimeline(id, { maxId }));
handleLoadMore = (maxId) => {
const { id } = this.props.params
this.props.dispatch(expandListTimeline(id, { maxId }))
}
handleEditClick = () => {
this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id }));
}
handleDeleteClick = () => {
const { dispatch, intl } = this.props;
const { id } = this.props.params;
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.deleteMessage),
confirm: intl.formatMessage(messages.deleteConfirm),
onConfirm: () => {
dispatch(deleteList(id));
this.context.router.history.push('/lists');
},
}));
this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id }))
}
render() {
const { list } = this.props;
const { id } = this.props.params;
const title = list ? list.get('title') : id;
const { list } = this.props
const { id } = this.props.params
const title = list ? list.get('title') : id
if (typeof list === 'undefined') {
return (<ColumnIndicator type='loading' />);
return <ColumnIndicator type='loading' />
} else if (list === false) {
return (<ColumnIndicator type='missing' />);
return <ColumnIndicator type='missing' />
}
const emptyMessage = (
<div>
<div className={[_s.default].join(' ')}>
<FormattedMessage
id='empty_column.list'
defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />
<br/><br/>
<Button onClick={this.handleEditClick}>
<FormattedMessage id='list.click_to_add' defaultMessage='Click here to add people' />
defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.'
/>
<Button
onClick={this.handleEditClick}
className={[_s.mt10]}
>
<Text color='inherit'>
<FormattedMessage id='list.click_to_add' defaultMessage='Click here to add people' />
</Text>
</Button>
</div>
);
)
return (
<StatusList
@ -121,7 +109,7 @@ class ListTimeline extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore}
emptyMessage={emptyMessage}
/>
);
)
}
}

View File

@ -53,6 +53,7 @@ class Notifications extends ImmutablePureComponent {
}
handleLoadGap = (maxId) => {
// maxId={index > 0 ? notifications.getIn([index - 1, 'id']) : null}
this.props.dispatch(expandNotifications({ maxId }))
}
@ -122,13 +123,7 @@ class Notifications extends ImmutablePureComponent {
scrollableContent = this.scrollableContent
} else if (notifications.size > 0 || hasMore) {
scrollableContent = notifications.map((item, index) => item === null ? (
<LoadMore
gap
key={'gap:' + notifications.getIn([index + 1, 'id'])}
disabled={isLoading}
maxId={index > 0 ? notifications.getIn([index - 1, 'id']) : null}
onClick={this.handleLoadGap}
/>
<LoadMore disabled={isLoading} onClick={this.handleLoadGap} />
) : (
<NotificationContainer
key={`notification-${index}`}

View File

@ -0,0 +1,72 @@
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ColumnIndicator from '../components/column_indicator'
import { fetchLikes } from '../actions/interactions'
import Account from '../components/account'
import ScrollableList from '../components/scrollable_list'
const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
});
const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
});
export default
@injectIntl
@connect(mapStateToProps)
class StatusLikes extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list,
multiColumn: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
componentWillMount () {
if (!this.props.accountIds) {
this.props.dispatch(fetchLikes(this.props.params.statusId));
}
}
componentWillReceiveProps (nextProps) {
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
this.props.dispatch(fetchLikes(nextProps.params.statusId));
}
}
handleRefresh = () => {
this.props.dispatch(fetchLikes(this.props.params.statusId));
}
render () {
const { intl, shouldUpdateScroll, accountIds, multiColumn } = this.props;
if (!accountIds) {
return <ColumnIndicator type='loading' />
}
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favourited this toot yet. When someone does, they will show up here.' />;
return (
<div>
<ScrollableList
scrollKey='favourites'
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
>
{accountIds.map(id =>
<Account key={id} id={id} withNote={false} />,
)}
</ScrollableList>
</div>
);
}
}

View File

@ -23,7 +23,7 @@ const mapStateToProps = (state, props) => {
export default
@connect(mapStateToProps)
class Reposts extends ImmutablePureComponent {
class StatusReposts extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,

View File

@ -6,6 +6,7 @@ import { Switch, Redirect, withRouter } from 'react-router-dom'
import debounce from 'lodash.debounce'
import { uploadCompose, resetCompose } from '../../actions/compose'
import { expandHomeTimeline } from '../../actions/timelines'
import { fetchGroups } from '../../actions/groups'
import {
initializeNotifications,
expandNotifications,
@ -40,7 +41,6 @@ import {
BlockedAccounts,
BlockedDomains,
CommunityTimeline,
// Favorites,
// Filters,
Followers,
Following,
@ -61,17 +61,17 @@ import {
ListTimeline,
Mutes,
Notifications,
Reposts,
Search,
// Shortcuts,
Status,
StatusLikes,
StatusReposts,
} from './util/async_components'
import { me, meUsername } from '../../initial_state'
// Dummy import, to make sure that <Status /> ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles.
import '../../components/status'
import { fetchGroups } from '../../actions/groups'
const messages = defineMessages({
beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Gab Social.' },
@ -116,7 +116,7 @@ class SwitchingArea extends PureComponent {
onLayoutChange: PropTypes.func.isRequired,
}
componentWillMount() {
componentDidMount() {
window.addEventListener('resize', this.handleResize, {
passive: true
})
@ -209,10 +209,10 @@ class SwitchingArea extends PureComponent {
<WrappedRoute path='/:username/posts/:statusId' publicRoute exact page={BasicPage} component={Status} content={children} componentParams={{ title: 'Status' }} />
<Redirect from='/@:username/posts/:statusId/reposts' to='/:username/posts/:statusId/reposts' />
<WrappedRoute path='/:username/posts/:statusId/reposts' page={BasicPage} component={Reposts} content={children} componentParams={{ title: 'Reposts' }} />
<WrappedRoute path='/:username/posts/:statusId/reposts' page={BasicPage} component={StatusReposts} content={children} componentParams={{ title: 'Reposts' }} />
{ /* <Redirect from='/@:username/posts/:statusId/favorites' to='/:username/posts/:statusId/favorites' />
<WrappedRoute path='/:username/posts/:statusId/favorites' page={BasicPage} component={Favorites} content={children} componentParams={{ title: 'Favorites' }} /> */ }
<Redirect from='/@:username/posts/:statusId/likes' to='/:username/posts/:statusId/likes' />
<WrappedRoute path='/:username/posts/:statusId/likes' page={BasicPage} component={StatusLikes} content={children} componentParams={{ title: 'Likes' }} />
<WrappedRoute page={ErrorPage} component={GenericNotFound} content={children} />
</Switch>
@ -349,7 +349,13 @@ class UI extends PureComponent {
}
}
componentWillMount() {
// componentWillMount() {
// }
componentDidMount() {
// if (!me) return
window.addEventListener('beforeunload', this.handleBeforeUnload, false)
document.addEventListener('dragenter', this.handleDragEnter, false)
@ -370,15 +376,9 @@ class UI extends PureComponent {
this.props.dispatch(expandHomeTimeline())
this.props.dispatch(expandNotifications())
this.props.dispatch(initializeNotifications())
this.props.dispatch(fetchGroups('member'))
setTimeout(() => this.props.dispatch(fetchFilters()), 500)
}
}
componentDidMount() {
if (!me) return
// this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
// return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName)
// }
@ -435,7 +435,7 @@ class UI extends PureComponent {
}
handleHotkeyToggleHelp = () => {
this.props.dispatch(openModal("HOTKEYS"))
this.props.dispatch(openModal('HOTKEYS'))
}
handleHotkeyGoToHome = () => {
@ -471,7 +471,7 @@ class UI extends PureComponent {
}
handleOpenComposeModal = () => {
this.props.dispatch(openModal("COMPOSE"))
this.props.dispatch(openModal('COMPOSE'))
}
render() {

View File

@ -1,34 +1,74 @@
export function AccountTimeline() { return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline') }
export function AccountGallery() { return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery') }
export function ActionsModal() { return import(/* webpackChunkName: "components/actions_modal" */'../../../components/modal/actions_modal') }
export function BlockAccountModal() { return import(/* webpackChunkName: "components/block_account_modal" */'../../../components/modal/block_account_modal') }
export function BlockDomainModal() { return import(/* webpackChunkName: "components/block_domain_modal" */'../../../components/modal/block_domain_modal') }
export function BlockedAccounts() { return import(/* webpackChunkName: "features/blocked_accounts" */'../../blocked_accounts') }
export function BlockedDomains() { return import(/* webpackChunkName: "features/blocked_domains" */'../../blocked_domains') }
export function BoostModal() { return import(/* webpackChunkName: "components/boost_modal" */'../../../components/modal/boost_modal') }
export function CommunityTimeline() { return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline') }
export function CommunityTimelineSettingsModal() { return import(/* webpackChunkName: "components/community_timeline_settings_modal" */'../../../components/modal/community_timeline_settings_modal') }
export function Compose() { return import(/* webpackChunkName: "features/compose" */'../../compose') }
export function ComposeForm() { return import(/* webpackChunkName: "components/compose_form" */'../../compose/components/compose_form') }
export function ComposeModal() { return import(/* webpackChunkName: "components/compose_modal" */'../../../components/modal/compose_modal') }
export function ConfirmationModal() { return import(/* webpackChunkName: "components/confirmation_modal" */'../../../components/modal/confirmation_modal') }
export function ContentWarningPopover() { return import(/* webpackChunkName: "components/content_warning_popover" */'../../../components/popover/content_warning_popover') }
export function DatePickerPopover() { return import(/* webpackChunkName: "components/date_picker_popover" */'../../../components/popover/date_picker_popover') }
export function DisplayOptionsModal() { return import(/* webpackChunkName: "components/display_options_modal" */'../../../components/modal/display_options_modal') }
export function EditProfileModal() { return import(/* webpackChunkName: "components/edit_profile_modal" */'../../../components/modal/edit_profile_modal') }
export function EmbedModal() { return import(/* webpackChunkName: "modals/embed_modal" */'../../../components/modal/embed_modal') }
export function EmojiPicker() { return import(/* webpackChunkName: "emoji_picker" */'../../../components/emoji/emoji_picker') }
export function EmojiPickerPopover() { return import(/* webpackChunkName: "components/emoji_picker_popover" */'../../../components/popover/emoji_picker_popover') }
export function Followers() { return import(/* webpackChunkName: "features/followers" */'../../followers') }
export function Following() { return import(/* webpackChunkName: "features/following" */'../../following') }
export function FollowRequests() { return import(/* webpackChunkName: "features/follow_requests" */'../../follow_requests') }
export function LikedStatuses() { return import(/* webpackChunkName: "features/liked_statuses" */'../../liked_statuses') }
export function GenericNotFound() { return import(/* webpackChunkName: "features/generic_not_found" */'../../generic_not_found') }
export function GifPickerModal() { return import(/* webpackChunkName: "components/gif_picker_modal" */'../../../components/modal/gif_picker_modal') }
export function GroupsCollection() { return import(/* webpackChunkName: "features/groups_collection" */'../../groups_collection') }
export function GroupCreate() { return import(/* webpackChunkName: "features/group_create" */'../../group_create') }
export function GroupCreateModal() { return import(/* webpackChunkName: "components/group_create_modal" */'../../../components/modal/group_create_modal') }
export function GroupDeleteModal() { return import(/* webpackChunkName: "components/group_delete_modal" */'../../../components/modal/group_delete_modal') }
export function GroupEditorModal() { return import(/* webpackChunkName: "components/group_editor_modal" */'../../../components/modal/group_editor_modal') }
export function GroupInfoPopover() { return import(/* webpackChunkName: "components/group_info_popover" */'../../../components/popover/group_info_popover') }
export function GroupMembers() { return import(/* webpackChunkName: "features/group_members" */'../../group_members') }
export function GroupRemovedAccounts() { return import(/* webpackChunkName: "features/group_removed_accounts" */'../../group_removed_accounts') }
export function GroupTimeline() { return import(/* webpackChunkName: "features/group_timeline" */'../../group_timeline') }
export function HashtagTimeline() { return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline') }
export function HashtagTimelineSettingsModal() { return import(/* webpackChunkName: "components/hashtag_timeline_settings_modal" */'../../../components/modal/hashtag_timeline_settings_modal') }
export function HomeTimeline() { return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline') }
export function HomeTimelineSettingsModal() { return import(/* webpackChunkName: "components/home_timeline_settings_modal" */'../../../components/modal/home_timeline_settings_modal') }
export function HotkeysModal() { return import(/* webpackChunkName: "components/hotkeys_modal" */'../../../components/modal/hotkeys_modal') }
export function ListCreate() { return import(/* webpackChunkName: "features/list_create" */'../../list_create') }
export function ListCreateModal() { return import(/* webpackChunkName: "components/list_create_modal" */'../../../components/modal/list_create_modal') }
export function ListDeleteModal() { return import(/* webpackChunkName: "components/list_delete_modal" */'../../../components/modal/list_delete_modal') }
export function ListsDirectory() { return import(/* webpackChunkName: "features/lists_directory" */'../../lists_directory') }
export function ListEdit() { return import(/* webpackChunkName: "features/list_editor" */'../../list_edit') }
export function ListEditorModal() { return import(/* webpackChunkName: "components/list_editor_modal" */'../../../components/modal/list_editor_modal') }
export function ListTimeline() { return import(/* webpackChunkName: "features/list_timeline" */'../../list_timeline') }
export function ListTimelineSettingsModal() { return import(/* webpackChunkName: "components/list_timeline_settings_modal" */'../../../components/modal/list_timeline_settings_modal') }
export function MediaGallery() { return import(/* webpackChunkName: "components/media_gallery" */'../../../components/media_gallery') }
export function MediaModal() { return import(/* webpackChunkName: "components/media_modal" */'../../../components/modal/media_modal') }
export function ModalLoading() { return import(/* webpackChunkName: "components/modal_loading" */'../../../components/modal/modal_loading') }
export function Mutes() { return import(/* webpackChunkName: "features/mutes" */'../../mutes') }
export function MuteModal() { return import(/* webpackChunkName: "modals/mute_modal" */'../../../components/modal/mute_modal') }
export function Notifications() { return import(/* webpackChunkName: "features/notifications" */'../../notifications') }
export function Reposts() { return import(/* webpackChunkName: "features/reposts" */'../../reposts') }
export function ProfileOptionsPopover() { return import(/* webpackChunkName: "components/profile_options_popover" */'../../../components/popover/profile_options_popover') }
export function ProUpgradeModal() { return import(/* webpackChunkName: "components/pro_upgrade_modal" */'../../../components/modal/pro_upgrade_modal') }
export function ReportModal() { return import(/* webpackChunkName: "modals/report_modal" */'../../../components/modal/report_modal') }
export function RepostOptionsPopover() { return import(/* webpackChunkName: "components/repost_options_popover" */'../../../components/popover/repost_options_popover') }
export function Search() { return import(/*webpackChunkName: "features/search" */'../../search') }
export function Status() { return import(/* webpackChunkName: "components/status" */'../../../components/status') }
export function SearchPopover() { return import(/* webpackChunkName: "components/search_popover" */'../../../components/popover/search_popover') }
export function SidebarMorePopover() { return import(/* webpackChunkName: "components/sidebar_more_popover" */'../../../components/popover/sidebar_more_popover') }
export function StatusLikes() { return import(/* webpackChunkName: "features/status_likes" */'../../status_likes') }
export function StatusOptionsPopover() { return import(/* webpackChunkName: "components/status_options_popover" */'../../../components/popover/status_options_popover') }
export function StatusReposts() { return import(/* webpackChunkName: "features/status_reposts" */'../../status_reposts') }
export function StatusRevisionsModal() { return import(/* webpackChunkName: "modals/status_revisions_modal" */'../../../components/modal/status_revisions_modal') }
export function StatusSharePopover() { return import(/* webpackChunkName: "components/status_share_popover" */'../../../components/popover/status_share_popover') }
export function StatusVisibilityPopover() { return import(/* webpackChunkName: "components/status_visibility_popover" */'../../../components/popover/status_visibility_popover') }
export function UnauthorizedModal() { return import(/* webpackChunkName: "components/unauthorized_modal" */'../../../components/modal/unauthorized_modal') }
export function UnfollowModal() { return import(/* webpackChunkName: "components/unfollow_modal" */'../../../components/modal/unfollow_modal') }
export function UserInfoPopover() { return import(/* webpackChunkName: "components/user_info_popover" */'../../../components/popover/user_info_popover') }
export function Video() { return import(/* webpackChunkName: "components/video" */'../../../components/video') }
export function VideoModal() { return import(/* webpackChunkName: "components/video_modal" */'../../../components/modal/video_modal') }

View File

@ -1,7 +1,7 @@
import {
fetchBundleRequest,
fetchBundleSuccess,
fetchBundleFail
fetchBundleFail,
} from '../../../actions/bundles'
const mapDispatchToProps = (dispatch) => ({

View File

@ -1,3 +1,4 @@
import api from '../../../api'
import { Route } from 'react-router-dom'
import BundleColumnError from '../../../components/bundle_column_error'
import Bundle from './bundle'
@ -51,6 +52,15 @@ export default class WrappedRoute extends PureComponent {
...rest
} = this.props
// : todo :
// api().get('/api/v1/accounts/verify_credentials')
// .then((res) => {
// console.log("res:", res)
// })
// .catch((err) => {
// console.log("err:", err)
// })
if (!publicRoute && !me) {
const actualUrl = encodeURIComponent(this.props.computedMatch.url)
return <Route path={this.props.path} component={() => {

View File

@ -32,7 +32,7 @@ export default class GroupLayout extends ImmutablePureComponent {
actions={actions}
showBackBtn={showBackBtn}
>
<div className={[_s.default, _s.width1015PX, _s.pl15, _s.py15].join(' ')}>
<div className={[_s.default, _s.width100PC].join(' ')}>
<GroupHeader group={group} relationships={relationships} />

View File

@ -7,7 +7,6 @@ import LinkFooter from '../components/link_footer'
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
import ProgressPanel from '../components/panel/progress_panel'
import TrendsPanel from '../components/panel/trends_panel'
import HashtagsPanel from '../components/panel/hashtags_panel'
import DefaultLayout from '../layouts/default_layout'
import TimelineComposeBlock from '../components/timeline_compose_block'
import Divider from '../components/divider'
@ -51,7 +50,6 @@ class CommunityPage extends PureComponent {
<Fragment>
<ProgressPanel />
<TrendsPanel />
<HashtagsPanel />
<WhoToFollowPanel />
<GroupSidebarPanel />
<LinkFooter />

View File

@ -92,7 +92,7 @@ class GroupPage extends ImmutablePureComponent {
<Fragment>
<GroupInfoPanel group={group} />
<WhoToFollowPanel />
<GroupSidebarPanel slim />
<GroupSidebarPanel isSlim />
<LinkFooter />
</Fragment>
)}

View File

@ -2,6 +2,7 @@ import { Fragment } from 'react'
import { me } from '../initial_state'
import { defineMessages, injectIntl } from 'react-intl'
import { openModal } from '../actions/modal'
import { MODAL_GROUP_CREATE } from '../constants'
import PageTitle from '../features/ui/util/page_title'
import LinkFooter from '../components/link_footer'
import GroupsPanel from '../components/panel/groups_panel'
@ -22,7 +23,7 @@ const mapStateToProps = (state) => ({
const mapDispatchToProps = (dispatch) => ({
onOpenGroupCreateModal() {
dispatch(openModal('GROUP_CREATE'))
dispatch(openModal(MODAL_GROUP_CREATE))
},
})
@ -81,6 +82,7 @@ class GroupsPage extends PureComponent {
showBackBtn
title={title}
actions={actions}
tabs={tabs}
layout={(
<Fragment>
<WhoToFollowPanel />
@ -88,7 +90,6 @@ class GroupsPage extends PureComponent {
<LinkFooter />
</Fragment>
)}
tabs={tabs}
>
<PageTitle path={title} />
{ children }

View File

@ -6,7 +6,6 @@ import LinkFooter from '../components/link_footer'
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
import ProgressPanel from '../components/panel/progress_panel'
import TrendsPanel from '../components/panel/trends_panel'
import HashtagsPanel from '../components/panel/hashtags_panel'
import DefaultLayout from '../layouts/default_layout'
const messages = defineMessages({
@ -53,7 +52,6 @@ class HashtagPage extends PureComponent {
<Fragment>
<ProgressPanel />
<TrendsPanel />
<HashtagsPanel />
<WhoToFollowPanel />
<LinkFooter />
</Fragment>

View File

@ -1,6 +1,8 @@
import { Fragment } from 'react'
import { openModal } from '../actions/modal'
import { defineMessages, injectIntl } from 'react-intl'
import IntersectionObserverArticle from '../components/intersection_observer_article'
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper'
import PageTitle from '../features/ui/util/page_title'
import GroupsPanel from '../components/panel/groups_panel'
import ListsPanel from '../components/panel/lists_panel'
@ -9,7 +11,6 @@ import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
import ProgressPanel from '../components/panel/progress_panel'
import UserPanel from '../components/panel/user_panel'
import TrendsPanel from '../components/panel/trends_panel'
import HashtagsPanel from '../components/panel/hashtags_panel'
import DefaultLayout from '../layouts/default_layout'
import TimelineComposeBlock from '../components/timeline_compose_block'
import Divider from '../components/divider'
@ -40,6 +41,25 @@ class HomePage extends PureComponent {
onOpenHomePageSettingsModal: PropTypes.func.isRequired,
}
intersectionObserverWrapper = new IntersectionObserverWrapper()
componentDidMount() {
this.attachIntersectionObserver()
}
componentWillUnmount() {
this.detachIntersectionObserver()
}
attachIntersectionObserver() {
this.intersectionObserverWrapper.connect()
}
detachIntersectionObserver() {
this.intersectionObserverWrapper.disconnect()
}
render() {
const {
intl,
@ -62,10 +82,30 @@ class HomePage extends PureComponent {
<UserPanel />
<ProgressPanel />
<TrendsPanel />
<ListsPanel />
<HashtagsPanel />
<WhoToFollowPanel />
<GroupsPanel />
<IntersectionObserverArticle
id={'home-sidebar-lists-panel'}
listLength={7}
index={4}
intersectionObserverWrapper={this.intersectionObserverWrapper}
>
<ListsPanel />
</IntersectionObserverArticle>
<IntersectionObserverArticle
id={'home-sidebar-wtf-panel'}
listLength={7}
index={5}
intersectionObserverWrapper={this.intersectionObserverWrapper}
>
<WhoToFollowPanel />
</IntersectionObserverArticle>
<IntersectionObserverArticle
id={'home-sidebar-groups-panel'}
listLength={7}
index={6}
intersectionObserverWrapper={this.intersectionObserverWrapper}
>
<GroupsPanel isLazy />
</IntersectionObserverArticle>
<LinkFooter />
</Fragment>
)}

View File

@ -3,6 +3,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl } from 'react-intl'
import { openModal } from '../actions/modal'
import {
MODAL_LIST_EDITOR,
// MODAL_LIST_TIMELINE_SETTINGS,
} from '../constants'
import PageTitle from '../features/ui/util/page_title'
import LinkFooter from '../components/link_footer'
import DefaultLayout from '../layouts/default_layout'
@ -18,15 +22,16 @@ const mapStateToProps = (state, props) => ({
list: state.getIn(['lists', props.params.id]),
})
const mapDispatchToProps = (dispatch, { list }) => ({
onOpenListEditModal() {
dispatch(openModal('LIST_EDIT', {
list,
}))
},
onOpenListTimelineSettingsModal() {
dispatch(openModal('LIST_TIMELINE_SETTINGS'))
const mapDispatchToProps = (dispatch) => ({
onOpenListEditModal(list) {
if (!list) return
const listId = list.get('id')
dispatch(openModal(MODAL_LIST_EDITOR, { listId }))
},
// : todo :
// onOpenListTimelineSettingsModal() {
// dispatch(openModal(MODAL_LIST_TIMELINE_SETTINGS))
// },
})
export default
@ -39,42 +44,46 @@ class ListPage extends ImmutablePureComponent {
list: ImmutablePropTypes.map,
children: PropTypes.node.isRequired,
onOpenListEditModal: PropTypes.func.isRequired,
onOpenListTimelineSettingsModal: PropTypes.func.isRequired,
// onOpenListTimelineSettingsModal: PropTypes.func.isRequired,
}
handleOnOpenListEditModal = () => {
this.props.onOpenListEditModal(this.props.list)
}
render() {
const {
intl,
children,
list,
onOpenListEditModal,
onOpenListTimelineSettingsModal
// onOpenListTimelineSettingsModal
} = this.props
const title = !!list ? list.get('title') : ''
return (
<DefaultLayout
showBackBtn
title={title}
actions={[
{
icon: 'list-edit',
onClick: onOpenListEditModal,
},
{
icon: 'ellipsis',
onClick: onOpenListTimelineSettingsModal,
icon: 'cog',
onClick: this.handleOnOpenListEditModal,
},
// {
// icon: 'ellipsis',
// onClick: onOpenListTimelineSettingsModal,
// },
]}
layout={(
<Fragment>
<ListDetailsPanel />
<ListDetailsPanel list={list} onEdit={this.handleOnOpenListEditModal} />
<TrendsPanel />
<WhoToFollowPanel />
<LinkFooter />
</Fragment>
)}
showBackBtn
>
<PageTitle path={[title, intl.formatMessage(messages.list)]} />
{ children }

View File

@ -93,9 +93,11 @@ class NotificationsPage extends PureComponent {
const tabs = filters.map((filter) => ({
title: intl.formatMessage(messages[filter]),
onClick: () => this.onChangeActiveFilter(filter),
active: selectedFilter === filter,
active: selectedFilter.toLowerCase() === filter.toLowerCase(),
}))
console.log("selectedFilter, filter: ", tabs, selectedFilter)
return (
<DefaultLayout
title={intl.formatMessage(messages.notifications)}
@ -117,4 +119,4 @@ class NotificationsPage extends PureComponent {
</DefaultLayout>
)
}
}
}{}

View File

@ -6,7 +6,6 @@ import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
import ProgressPanel from '../components/panel/progress_panel'
import UserPanel from '../components/panel/user_panel'
import TrendsPanel from '../components/panel/trends_panel'
import HashtagsPanel from '../components/panel/hashtags_panel'
import DefaultLayout from '../layouts/default_layout'
export default
@ -24,7 +23,6 @@ class ShortcutsPage extends PureComponent {
<UserPanel />
<ProgressPanel />
<TrendsPanel />
<HashtagsPanel />
<WhoToFollowPanel />
<GroupSidebarPanel />
<LinkFooter />

View File

@ -1,57 +1,69 @@
import { Map as ImmutableMap } from 'immutable';
import { Map as ImmutableMap } from 'immutable'
import {
GROUP_CREATE_REQUEST,
GROUP_CREATE_FAIL,
GROUP_CREATE_SUCCESS,
GROUP_UPDATE_REQUEST,
GROUP_UPDATE_FAIL,
GROUP_UPDATE_SUCCESS,
GROUP_EDITOR_RESET,
GROUP_EDITOR_SETUP,
GROUP_EDITOR_VALUE_CHANGE,
} from '../actions/group_editor';
GROUP_CREATE_REQUEST,
GROUP_CREATE_FAIL,
GROUP_CREATE_SUCCESS,
GROUP_UPDATE_REQUEST,
GROUP_UPDATE_FAIL,
GROUP_UPDATE_SUCCESS,
GROUP_EDITOR_RESET,
GROUP_EDITOR_SETUP,
GROUP_EDITOR_TITLE_CHANGE,
GROUP_EDITOR_DESCRIPTION_CHANGE,
GROUP_EDITOR_COVER_IMAGE_CHANGE,
} from '../actions/group_editor'
const initialState = ImmutableMap({
groupId: null,
isSubmitting: false,
isChanged: false,
title: '',
description: '',
coverImage: null,
});
groupId: null,
isSubmitting: false,
isChanged: false,
title: '',
description: '',
coverImage: null,
})
export default function groupEditorReducer(state = initialState, action) {
switch(action.type) {
case GROUP_EDITOR_RESET:
return initialState;
case GROUP_EDITOR_SETUP:
return state.withMutations(map => {
map.set('groupId', action.group.get('id'));
map.set('title', action.group.get('title'));
map.set('description', action.group.get('description'));
map.set('isSubmitting', false);
});
case GROUP_EDITOR_VALUE_CHANGE:
return state.withMutations(map => {
map.set(action.field, action.value);
map.set('isChanged', true);
});
case GROUP_CREATE_REQUEST:
case GROUP_UPDATE_REQUEST:
return state.withMutations(map => {
map.set('isSubmitting', true);
map.set('isChanged', false);
});
case GROUP_CREATE_FAIL:
case GROUP_UPDATE_FAIL:
return state.set('isSubmitting', false);
case GROUP_CREATE_SUCCESS:
case GROUP_UPDATE_SUCCESS:
return state.withMutations(map => {
map.set('isSubmitting', false);
map.set('groupId', action.group.id);
});
default:
return state;
}
};
switch(action.type) {
case GROUP_EDITOR_RESET:
return initialState
case GROUP_EDITOR_SETUP:
return state.withMutations(map => {
map.set('groupId', action.group.get('id'))
map.set('title', action.group.get('title'))
map.set('description', action.group.get('description'))
map.set('isSubmitting', false)
})
case GROUP_EDITOR_TITLE_CHANGE:
return state.withMutations(map => {
map.set('title', action.title)
map.set('isChanged', true)
})
case GROUP_EDITOR_DESCRIPTION_CHANGE:
return state.withMutations(map => {
map.set('description', action.description)
map.set('isChanged', true)
})
case GROUP_EDITOR_COVER_IMAGE_CHANGE:
return state.withMutations(map => {
map.set('coverImage', action.value)
map.set('isChanged', true)
})
case GROUP_CREATE_REQUEST:
case GROUP_UPDATE_REQUEST:
return state.withMutations(map => {
map.set('isSubmitting', true)
map.set('isChanged', false)
})
case GROUP_CREATE_FAIL:
case GROUP_UPDATE_FAIL:
return state.set('isSubmitting', false)
case GROUP_CREATE_SUCCESS:
case GROUP_UPDATE_SUCCESS:
return state.withMutations(map => {
map.set('isSubmitting', false)
map.set('groupId', action.group.id)
})
default:
return state
}
}

View File

@ -1,5 +1,16 @@
:root {
--radius: 8px;
--radius-small: 8px;
--radius-circle: 9999px;
/* Default Font Sizes */
--font_size_extra_small: 12px;
--font_size_small: 13px;
--font_size_normal: 14px;
--font_size_medium: 15px;
--font_size_large: 16px;
--font_size_extra_large: 19px;
--font_size_extra_extra_large: 24px;
--color_brand-dark: #38a16b;
--color_brand-light: #36e991;
@ -9,13 +20,13 @@
--color_white: #fff;
--color_black: #2d3436;
--color_black-opaque: rgba(0, 0, 0, .8);
--color_black-opaquer: rgba(0, 0, 0, .4);
--color_black-opaquer: rgba(0, 0, 0, .5);
--color_gold: #ffd700;
--color_red: #de2960;
--color_red-dark: #c72c5b;
/* LIGHT THEME */
/* --solid_color_primary: #fff;
--solid_color_primary: #fff;
--solid_color_primary-opaque:rgba(255, 255, 255,.6);
--solid_color_secondary: #e2e8ec;
--solid_color_secondary-dark: #d9e0e5;
@ -26,7 +37,7 @@
--text_color_secondary: #4b4f55;
--text_color_tertiary: #777;
--border_color_secondary: #ececed; */
--border_color_secondary: #ececed;
/* MUTED THEME */
/* --solid_color_primary: #222;
@ -43,7 +54,7 @@
--border_color_secondary: #424141;*/
/* BLACK THEME */
--solid_color_primary: #13171b;
/* --solid_color_primary: #13171b;
--solid_color_primary-opaque:rgba(19, 23, 27, .6);
--solid_color_secondary: #4f5050;
--solid_color_secondary-dark: #424343;
@ -54,7 +65,7 @@
--text_color_secondary: #61686E;
--text_color_tertiary: #656565;
--border_color_secondary: #212020;
--border_color_secondary: #212020; */
}
html,
@ -62,6 +73,7 @@ body {
height: 100%;
margin: 0;
padding: 0;
background-color: var(--solid_color_tertiary);
}
body {
@ -78,7 +90,7 @@ body {
.statusContent * {
margin-top: 0;
margin-bottom: 0;
font-size: 15px;
font-size: var(--font_size_medium);
overflow-wrap: break-word;
color: var(--text_color_primary);
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif;
@ -102,7 +114,7 @@ body {
}
.statusContent h1 {
font-size: 1.5rem;
font-size: var(--font_size_extra_large);
font-weight: 700;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
@ -119,7 +131,7 @@ body {
.dangerousContent * {
margin-top: 0;
margin-bottom: 0;
font-size: 14px;
font-size: var(--font_size_normal);
overflow-wrap: break-word;
color: var(--text_color_primary);
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif;
@ -224,8 +236,12 @@ body {
.resizeNone { resize: none; }
.circle { border-radius: 9999px; }
.radiusSmall { border-radius: var(--radius); }
.circle { border-radius: var(--radius-circle); }
.radiusSmall { border-radius: var(--radius-small); }
.topLeftRadiusSmall { border-top-left-radius: var(--radius-small); }
.topRightRadiusSmall { border-top-right-radius: var(--radius-small); }
.bottomRightRadiusSmall { border-bottom-right-radius: var(--radius-small); }
.bottomLeftRadiusSmall { border-bottom-left-radius: var(--radius-small); }
.borderColorPrimary { border-color: var(--solid_color_primary); }
.borderColorSecondary { border-color: var(--border_color_secondary); }
@ -509,13 +525,13 @@ body {
.textAlignLeft { text-align: left; }
.textAlignCenter { text-align: center; }
.fontSize24PX { font-size: 24px; }
.fontSize19PX { font-size: 19px; }
.fontSize16PX { font-size: 16px; }
.fontSize15PX { font-size: 15px; }
.fontSize14PX { font-size: 14px; }
.fontSize13PX { font-size: 13px; }
.fontSize12PX { font-size: 12px; }
.fontSize24PX { font-size: var(--font_size_extra_extra_large); }
.fontSize19PX { font-size: var(--font_size_extra_large); }
.fontSize16PX { font-size: var(--font_size_large); }
.fontSize15PX { font-size: var(--font_size_medium); }
.fontSize14PX { font-size: var(--font_size_normal); }
.fontSize13PX { font-size: var(--font_size_small); }
.fontSize12PX { font-size: var(--font_size_extra_small); }
.fontSize0 { font-size: 0; }
.fontWeightNormal { font-weight: 400; }
@ -588,6 +604,7 @@ body {
.pl0 { padding-left: 0; }
.pr15 { padding-right: 15px; }
.pr5 { padding-right: 5px; }
.pr0 { padding-right: 0; }
.px15 {
@ -677,7 +694,7 @@ body {
.select {
height: 42px;
line-height: 42px;
font-size: 18px;
font-size: var(--font_size_extra_large);
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
@ -708,7 +725,7 @@ body {
/* :global(.public-DraftEditorPlaceholder-inner) {
font-weight: 400;
font-size: 16px;
font-size: var(--font_size_large);
} */
:global(.RichEditor-blockquote) {
@ -722,14 +739,14 @@ body {
:global(.public-DraftStyleDefault-pre) {
background-color: rgba(0,0,0,.05);
font-family: 'Inconsolata', 'Menlo', 'Consolas', monospace;
font-size: 16px;
font-size: var(--font_size_large);
padding: 10px 20px;
}
/* */
:global(.emoji-mart-search input) {
border-radius: 9999px !important;
border-radius: var(--radius-circle) !important;
}
/* */

View File

@ -3,7 +3,7 @@
class REST::GroupSerializer < ActiveModel::Serializer
include RoutingHelper
attributes :id, :title, :description, :cover_image_url, :is_archived, :member_count
attributes :id, :title, :description, :cover_image_url, :is_archived, :member_count, :created_at
def id
object.id.to_s

View File

@ -24,6 +24,9 @@
= render 'stream_entries/meta', stream_entry: @stream_entry, account: @account
- elsif @account && @account.local?
= render 'accounts/meta', account: @account, older_url: nil, newer_url: nil
- else
- description = strip_tags(t('about.about_gabsocial_html'))
%meta{ name: 'description', content: description }/
%title= content_for?(:page_title) ? safe_join([yield(:page_title).chomp.html_safe, title], ' - ') : title