This commit is contained in:
mgabdev
2020-04-23 23:17:27 -04:00
parent e2e7e8c0af
commit 01c8041a6a
73 changed files with 905 additions and 1002 deletions

View File

@@ -1,28 +1,29 @@
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import classNames from 'classnames/bind'
import { length } from 'stringz'
import CharacterCounter from '../../../../components/character_counter'
import UploadForm from '../upload_form'
import AutosuggestTextbox from '../../../../components/autosuggest_textbox'
import PollButton from '../../components/poll_button'
import UploadButton from '../media_upload_button'
import SpoilerButton from '../../components/spoiler_button'
import RichTextEditorButton from '../../components/rich_text_editor_button'
import GifSelectorButton from '../../components/gif_selector_button'
import StatusVisibilityButton from '../../components/status_visibility_button'
import EmojiPickerButton from '../../components/emoji_picker_button'
import PollFormContainer from '../../containers/poll_form_container'
import SchedulePostButton from '../schedule_post_button'
import StatusContainer from '../../../../containers/status_container'
import Button from '../../../../components/button'
import Avatar from '../../../../components/avatar'
import { isMobile } from '../../../../utils/is_mobile'
import { countableText } from '../../util/counter'
const allowedAroundShortCode = '><\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'
const maxPostCharacterCount = 3000
import { isMobile } from '../../../utils/is_mobile'
import { countableText } from '../util/counter'
import {
CX,
MAX_POST_CHARACTER_COUNT,
ALLOWED_AROUND_SHORT_CODE,
} from '../../../constants'
import AutosuggestTextbox from '../../../components/autosuggest_textbox'
import Avatar from '../../../components/avatar'
import Button from '../../../components/button'
import CharacterCounter from '../../../components/character_counter'
import EmojiPickerButton from './emoji_picker_button'
import GifSelectorButton from './gif_selector_button'
import PollButton from './poll_button'
import PollForm from './poll_form'
import RichTextEditorButton from './rich_text_editor_button'
import SchedulePostButton from './schedule_post_button'
import SpoilerButton from './spoiler_button'
import StatusContainer from '../../../containers/status_container'
import StatusVisibilityButton from './status_visibility_button'
import UploadButton from './media_upload_button'
import UploadForm from './upload_form'
const messages = defineMessages({
placeholder: { id: 'compose_form.placeholder', defaultMessage: "What's on your mind?" },
@@ -33,8 +34,6 @@ const messages = defineMessages({
schedulePost: { id: 'compose_form.schedule_post', defaultMessage: 'Schedule Post' },
});
const cx = classNames.bind(_s)
export default
@injectIntl
class ComposeForm extends ImmutablePureComponent {
@@ -134,7 +133,7 @@ class ComposeForm extends ImmutablePureComponent {
const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('');
if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > maxPostCharacterCount || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > MAX_POST_CHARACTER_COUNT || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
return;
}
@@ -211,7 +210,7 @@ class ComposeForm extends ImmutablePureComponent {
handleEmojiPick = (data) => {
const { text } = this.props
const position = this.autosuggestTextarea.textbox.selectionStart
const needsSpace = data.custom && position > 0 && !allowedAroundShortCode.includes(text[position - 1])
const needsSpace = data.custom && position > 0 && !ALLOWED_AROUND_SHORT_CODE.includes(text[position - 1])
this.props.onPickEmoji(position, data, needsSpace)
}
@@ -240,17 +239,17 @@ class ComposeForm extends ImmutablePureComponent {
} = this.props
const disabled = isSubmitting
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
const disabledButton = disabled || isUploading || isChangingUpload || length(text) > maxPostCharacterCount || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
const disabledButton = disabled || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
const shouldAutoFocus = autoFocus && !showSearch && !isMobile(window.innerWidth)
const parentContainerClasses = cx({
const parentContainerClasses = CX({
default: 1,
width100PC: 1,
flexRow: !shouldCondense,
pb10: !shouldCondense,
})
const childContainerClasses = cx({
const childContainerClasses = CX({
default: 1,
flexWrap: 1,
overflowHidden: 1,
@@ -262,17 +261,17 @@ class ComposeForm extends ImmutablePureComponent {
px5: shouldCondense,
})
const actionsContainerClasses = cx({
const actionsContainerClasses = CX({
default: 1,
flexRow: 1,
alignItemsCenter: !shouldCondense,
alignItemsStart: shouldCondense,
mt10: !shouldCondense,
px15: !shouldCondense,
marginLeftAuto: shouldCondense,
mlAuto: shouldCondense,
})
const commentPublishBtnClasses = cx({
const commentPublishBtnClasses = CX({
default: 1,
justifyContentCenter: 1,
displayNone: length(this.props.text) === 0 || anyMedia,
@@ -284,7 +283,7 @@ class ComposeForm extends ImmutablePureComponent {
{
shouldCondense &&
<div className={[_s.default, _s.mr10, _s.mt5].join(' ')}>
<Avatar account={account} size={28} />
<Avatar account={account} size={28} noHover />
</div>
}
@@ -361,7 +360,7 @@ class ComposeForm extends ImmutablePureComponent {
{
!edit && hasPoll &&
<div className={[_s.default, _s.px15, _s.mt5].join(' ')}>
<PollFormContainer replyToId={replyToId} />
<PollForm replyToId={replyToId} />
</div>
}
@@ -376,7 +375,7 @@ class ComposeForm extends ImmutablePureComponent {
}
<div className={actionsContainerClasses}>
<div className={[_s.default, _s.flexRow, _s.marginRightAuto].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.mrAuto].join(' ')}>
{
!shouldCondense &&
<RichTextEditorButton />
@@ -418,7 +417,7 @@ class ComposeForm extends ImmutablePureComponent {
{
!shouldCondense &&
<CharacterCounter max={maxPostCharacterCount} text={text} />
<CharacterCounter max={MAX_POST_CHARACTER_COUNT} text={text} />
}
{

View File

@@ -1,144 +0,0 @@
.compose-form {
padding: 10px;
&__sensitive-button {
padding: 0 10px 10px 10px;
@include text-sizing(14px, 500);
&.active {
color: $highlight-text-color;
}
input[type=checkbox] {
display: none;
}
.checkbox {
display: inline-block;
position: relative;
box-sizing: border-box;
flex: 0 0 auto;
margin-right: 10px;
top: -1px;
vertical-align: middle;
@include size(18px);
@include border-design($ui-primary-color, 1px, 4px);
&.active {
border-color: $highlight-text-color;
background: $highlight-text-color;
}
}
}
.emoji-picker-dropdown {
position: absolute;
top: 5px;
right: 5px;
z-index: 1;
}
&.condensed {
.autosuggest-textarea__textarea {
min-height: 46px;
border-radius: 5px;
}
}
.emoji-picker-wrapper {
position: relative;
height: 0;
}
&__modifiers {
color: $inverted-text-color;
font-family: inherit;
font-size: 14px;
background: $simple-background-color;
}
&__buttons-wrapper {
padding: 10px;
background: darken($simple-background-color, 8%);
border-radius: 0 0 4px 4px;
flex: 0 0 auto;
@include flex(space-between);
.compose-form__buttons {
display: flex;
.compose-form__sensitive-button {
display: none;
&.compose-form__sensitive-button--visible {
display: block;
}
.compose-form__sensitive-button__icon {
line-height: 27px;
}
}
}
.icon-button {
box-sizing: content-box;
padding: 0 3px;
}
}
&__publish {
min-width: 0;
flex: 0 0 auto;
@include flex(flex-end);
}
&__publish-button-wrapper {
overflow: hidden;
padding-top: 10px;
}
}
.spoiler-input {
height: 0;
transform-origin: bottom;
opacity: 0.0;
position: relative;
&--visible {
height: 36px;
margin-bottom: 11px;
opacity: 1.0;
}
&__input {
display: block;
box-sizing: border-box;
width: 100%;
margin: 0;
color: $inverted-text-color;
background: $simple-background-color;
padding: 10px;
font-family: inherit;
font-size: 14px;
resize: vertical;
border: 0;
outline: 0;
border-radius: 4px;
&:focus {
outline: 0;
}
@include breakpoint(sm) {
font-size: 16px;
}
}
}
.no-reduce-motion .spoiler-input {
transition: height 0.4s ease, opacity 0.4s ease;
}

View File

@@ -1 +0,0 @@
export { default } from './compose_form'

View File

@@ -1,117 +0,0 @@
.emoji-picker-dropdown__menu {
background: $simple-background-color;
position: absolute;
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
border-radius: 4px;
margin-top: 5px;
z-index: 20000;
.emoji-mart-scroll {
transition: opacity 200ms ease;
}
&.selecting .emoji-mart-scroll {
opacity: 0.5;
}
}
.emoji-picker-dropdown__modifiers {
position: absolute;
top: 60px;
right: 11px;
cursor: pointer;
}
.emoji-picker-dropdown__modifiers__menu {
position: absolute;
z-index: 4;
top: -4px;
left: -8px;
background: $simple-background-color;
border-radius: 4px;
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
overflow: hidden;
button {
display: block;
cursor: pointer;
border: 0;
padding: 4px 8px;
background: transparent;
&:hover,
&:focus,
&:active {
background: rgba($ui-secondary-color, 0.4);
}
}
.emoji-mart-emoji {
height: 22px;
}
}
.emoji-button {
display: block;
margin-left: 2px;
width: 24px;
outline: 0;
cursor: pointer;
@include text-sizing(24px, 400, 24px);
&:active,
&:focus {
outline: 0 !important;
}
img {
filter: grayscale(100%);
opacity: 0.8;
display: block;
margin: 2px 0 0 0;
@include size(22px);
}
&:hover,
&:active,
&:focus {
img {
opacity: 1;
filter: none;
}
}
}
.no-reduce-motion .pulse-loading {
transform-origin: center center;
animation: heartbeat 1.5s ease-in-out infinite both;
}
@keyframes heartbeat {
from {
transform: scale(1);
animation-timing-function: ease-out;
}
10% {
transform: scale(0.91);
animation-timing-function: ease-in;
}
17% {
transform: scale(0.98);
animation-timing-function: ease-out;
}
33% {
transform: scale(0.87);
animation-timing-function: ease-in;
}
45% {
transform: scale(1);
animation-timing-function: ease-out;
}
}

View File

@@ -1 +0,0 @@
export { default } from './emoji_picker_dropdown'

View File

@@ -2,6 +2,15 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import classNames from 'classnames/bind'
import { defineMessages, injectIntl } from 'react-intl'
import {
addPollOption,
removePollOption,
changePollOption,
changePollSettings,
clearComposeSuggestions,
fetchComposeSuggestions,
selectComposeSuggestion,
} from '../../../actions/compose'
import Button from '../../../components/button'
import Text from '../../../components/text'
import Select from '../../../components/select'
@@ -19,108 +28,47 @@ const messages = defineMessages({
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
})
@injectIntl
class PollFormOption extends ImmutablePureComponent {
const mapStateToProps = (state) => ({
suggestions: state.getIn(['compose', 'suggestions']),
options: state.getIn(['compose', 'poll', 'options']),
expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
isMultiple: state.getIn(['compose', 'poll', 'multiple']),
})
static propTypes = {
title: PropTypes.string.isRequired,
index: PropTypes.number.isRequired,
isPollMultiple: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
onToggleMultiple: PropTypes.func.isRequired,
suggestions: ImmutablePropTypes.list,
onClearSuggestions: PropTypes.func.isRequired,
onFetchSuggestions: PropTypes.func.isRequired,
onSuggestionSelected: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
const mapDispatchToProps = (dispatch) => ({
onAddOption() {
dispatch(addPollOption(''))
},
handleOptionTitleChange = e => {
this.props.onChange(this.props.index, e.target.value);
}
onRemoveOption(index) {
dispatch(removePollOption(index))
},
handleOptionRemove = () => {
this.props.onRemove(this.props.index);
}
onChangeOption(index, title) {
dispatch(changePollOption(index, title))
},
handleToggleMultiple = e => {
this.props.onToggleMultiple();
e.preventDefault();
e.stopPropagation();
};
onChangeSettings(expiresIn, isMultiple) {
dispatch(changePollSettings(expiresIn, isMultiple))
},
onSuggestionsClearRequested = () => {
this.props.onClearSuggestions();
}
onClearSuggestions () {
dispatch(clearComposeSuggestions())
},
onSuggestionsFetchRequested = (token) => {
this.props.onFetchSuggestions(token);
}
onFetchSuggestions (token) {
dispatch(fetchComposeSuggestions(token))
},
onSuggestionSelected = (tokenStart, token, value) => {
this.props.onSuggestionSelected(tokenStart, token, value, ['poll', 'options', this.props.index]);
}
onSuggestionSelected (position, token, accountId, path) {
dispatch(selectComposeSuggestion(position, token, accountId, path))
},
render() {
const { isPollMultiple, title, index, intl } = this.props;
const toggleClasses = cx({
default: 1,
px10: 1,
py10: 1,
borderColorSecondary: 1,
border1PX: 1,
outlineNone: 1,
mr10: 1,
circle: !isPollMultiple,
})
return (
<li className={[_s.default, _s.flexRow, _s.mb10].join(' ')}>
<label className={[_s.default, _s.flexRow, _s.flexGrow1, _s.alignItemsCenter].join(' ')}>
<span
className={toggleClasses}
onClick={this.handleToggleMultiple}
role='button'
tabIndex='0'
/>
<AutosuggestTextbox
placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
maxLength={25}
value={title}
onChange={this.handleOptionTitleChange}
suggestions={this.props.suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
searchTokens={[':']}
/>
</label>
{
index > 1 &&
<Button
isNarrow
backgroundColor='none'
className={[_s.ml5, _s.justifyContentCenter].join(' ')}
icon='close'
iconSize='8px'
iconClassName={_s.fillColorSecondary}
disabled={index <= 1}
title={intl.formatMessage(messages.remove_option)}
onClick={this.handleOptionRemove}
/>
}
</li>
)
}
}
})
export default
@injectIntl
@connect(mapStateToProps, mapDispatchToProps)
class PollForm extends ImmutablePureComponent {
static propTypes = {
@@ -166,6 +114,7 @@ class PollForm extends ImmutablePureComponent {
{
options.map((title, i) => (
<PollFormOption
intl={intl}
title={title}
key={`poll-form-option-${i}`}
index={i}
@@ -241,3 +190,102 @@ class PollForm extends ImmutablePureComponent {
}
}
class PollFormOption extends ImmutablePureComponent {
static propTypes = {
title: PropTypes.string.isRequired,
index: PropTypes.number.isRequired,
isPollMultiple: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
onToggleMultiple: PropTypes.func.isRequired,
suggestions: ImmutablePropTypes.list,
onClearSuggestions: PropTypes.func.isRequired,
onFetchSuggestions: PropTypes.func.isRequired,
onSuggestionSelected: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
handleOptionTitleChange = e => {
this.props.onChange(this.props.index, e.target.value)
}
handleOptionRemove = () => {
this.props.onRemove(this.props.index)
}
handleToggleMultiple = e => {
this.props.onToggleMultiple()
e.preventDefault()
e.stopPropagation()
}
onSuggestionsClearRequested = () => {
this.props.onClearSuggestions()
}
onSuggestionsFetchRequested = (token) => {
this.props.onFetchSuggestions(token)
}
onSuggestionSelected = (tokenStart, token, value) => {
this.props.onSuggestionSelected(tokenStart, token, value, ['poll', 'options', this.props.index])
}
render() {
const { isPollMultiple, title, index, intl } = this.props
const toggleClasses = cx({
default: 1,
px10: 1,
py10: 1,
borderColorSecondary: 1,
border1PX: 1,
outlineNone: 1,
mr10: 1,
circle: !isPollMultiple,
})
return (
<li className={[_s.default, _s.flexRow, _s.mb10].join(' ')}>
<label className={[_s.default, _s.flexRow, _s.flexGrow1, _s.alignItemsCenter].join(' ')}>
<span
className={toggleClasses}
onClick={this.handleToggleMultiple}
role='button'
tabIndex='0'
/>
<AutosuggestTextbox
placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
maxLength={25}
value={title}
onChange={this.handleOptionTitleChange}
suggestions={this.props.suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
searchTokens={[':']}
/>
</label>
{
index > 1 &&
<Button
isNarrow
backgroundColor='none'
className={[_s.ml5, _s.justifyContentCenter].join(' ')}
icon='close'
iconSize='8px'
iconClassName={_s.fillColorSecondary}
disabled={index <= 1}
title={intl.formatMessage(messages.remove_option)}
onClick={this.handleOptionRemove}
/>
}
</li>
)
}
}

View File

@@ -1,20 +1,33 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { FormattedMessage } from 'react-intl'
import HashtagItem from '../../../../components/hashtag_item'
import Icon from '../../../../components/icon'
import { WhoToFollowPanel } from '../../../../components/panel'
import { withRouter } from 'react-router-dom';
import { fetchSuggestions, dismissSuggestion } from '../../../actions/suggestions';
import HashtagItem from '../../../components/hashtag_item'
import Icon from '../../../components/icon'
import { WhoToFollowPanel } from '../../../components/panel'
// import TrendsPanel from '../../ui/components/trends_panel'
import GroupListItem from '../../../../components/group_list_item'
import Block from '../../../../components/block'
import Heading from '../../../../components/heading'
import Button from '../../../../components/button'
import Text from '../../../../components/text'
import Account from '../../../../components/account'
import GroupListItem from '../../../components/group_list_item'
import Block from '../../../components/block'
import Heading from '../../../components/heading'
import Button from '../../../components/button'
import Text from '../../../components/text'
import Account from '../../../components/account'
// : todo :
const mapStateToProps = (state) => ({
results: state.getIn(['search', 'results']),
suggestions: state.getIn(['suggestions', 'items']),
});
export default class SearchResults extends ImmutablePureComponent {
const mapDispatchToProps = (dispatch) => ({
fetchSuggestions: () => dispatch(fetchSuggestions()),
dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))),
});
export default
@withRouter
@connect(mapStateToProps, mapDispatchToProps)
class SearchResults extends ImmutablePureComponent {
static propTypes = {
results: ImmutablePropTypes.map.isRequired,
@@ -46,12 +59,12 @@ export default class SearchResults extends ImmutablePureComponent {
if (results.get('accounts') && results.get('accounts').size > 0 && (isTop || showPeople)) {
const size = isTop ? Math.min(results.get('accounts').size, 5) : results.get('accounts').size;
accounts = (
<div className={[_s.default, _s.py15, _s.px15].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.mb15].join(' ')}>
<Heading size='h3'>
<div className={[_s.default, _s.py15].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.mb15, _s.px15].join(' ')}>
<Heading size='h2'>
People
</Heading>
<div className={[_s.default, _s.marginLeftAuto].join(' ')}>
<div className={[_s.default, _s.mlAuto].join(' ')}>
<Button
isText
backgroundColor='none'
@@ -66,7 +79,7 @@ export default class SearchResults extends ImmutablePureComponent {
</div>
{
results.get('accounts').slice(0, size).map(accountId => <Account expanded key={accountId} id={accountId} />)
results.get('accounts').slice(0, size).map(accountId => <Account compact key={accountId} id={accountId} />)
}
</div>
);

View File

@@ -1 +0,0 @@
export { default } from './search_results'

View File

@@ -1,40 +0,0 @@
.search-results {
&__header {
color: $dark-text-color;
background: lighten($ui-base-color, 2%);
padding: 15px;
cursor: default;
@include text-sizing(16px, 500);
.fa {
display: inline-block;
margin-right: 5px;
}
}
&__section {
margin-bottom: 5px;
h5 {
display: flex;
background: darken($ui-base-color, 4%);
border-bottom: 1px solid lighten($ui-base-color, 8%);
color: $dark-text-color;
padding: 15px;
cursor: default;
@include text-sizing(16px, 500);
.fa {
display: inline-block;
margin-right: 5px;
}
}
.account:last-child,
&>div:last-child .status {
border-bottom: 0;
}
}
}

View File

@@ -11,7 +11,7 @@ import { mascot } from '../../initial_state';
import Motion from '../ui/util/optional_motion';
import ComposeFormContainer from './containers/compose_form_container';
// import SearchContainer from './containers/search_container';
import SearchResultsContainer from './containers/search_results_container';
import SearchResults from './components/search_results';
import NavigationBar from './components/navigation_bar';
import elephantUIPlane from '../../../images/logo_ui_column_footer.png';
@@ -93,7 +93,7 @@ class Compose extends ImmutablePureComponent {
<Motion defaultStyle={{ x: isSearchPage ? 0 : -100 }} style={{ x: spring(showSearch || isSearchPage ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
{({ x }) => (
<div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
<SearchResultsContainer />
<SearchResults />
</div>
)}
</Motion>

View File

@@ -1,107 +0,0 @@
.drawer {
width: 300px;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow-y: hidden;
}
.drawer__tab {
display: block;
flex: 1 1 auto;
padding: 15px 5px 13px;
color: $darker-text-color;
text-decoration: none;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
}
.drawer {
flex: 1 1 100%;
overflow: hidden;
}
.drawer__pager {
box-sizing: border-box;
padding: 0;
flex-grow: 1;
position: relative;
overflow: hidden;
display: flex;
}
.drawer__inner {
top: 0;
left: 0;
background: lighten($ui-base-color, 13%);
box-sizing: border-box;
padding: 0;
display: flex;
flex-direction: column;
overflow: hidden;
overflow-y: auto;
@include size(100%);
&.darker {
background: $ui-base-color;
}
}
.drawer__inner__gabsocial {
background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto;
flex: 1;
min-height: 47px;
display: none;
>img {
display: block;
object-fit: contain;
object-position: bottom left;
pointer-events: none;
user-drag: none;
@include unselectable;
@include size(100%);
}
@media screen and (min-height: 640px) {
display: block;
}
}
.drawer__header {
flex: 0 0 auto;
font-size: 16px;
background: lighten($ui-base-color, 8%);
margin-bottom: 10px;
display: flex;
flex-direction: row;
a {
transition: background 100ms ease-in;
&:hover {
background: lighten($ui-base-color, 3%);
transition: background 200ms ease-out;
}
}
}
@media screen and (min-width: 631px) {
.drawer {
flex: 0 0 auto;
padding: 10px;
@include horizontal-padding(5px);
&:first-child {
padding-left: 10px;
}
&:last-child {
padding-right: 10px;
}
}
}

View File

@@ -1,47 +0,0 @@
import PollForm from '../components/poll_form';
import { addPollOption, removePollOption, changePollOption, changePollSettings } from '../../../actions/compose';
import {
clearComposeSuggestions,
fetchComposeSuggestions,
selectComposeSuggestion,
} from '../../../actions/compose';
const mapStateToProps = (state) => ({
suggestions: state.getIn(['compose', 'suggestions']),
options: state.getIn(['compose', 'poll', 'options']),
expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
isMultiple: state.getIn(['compose', 'poll', 'multiple']),
});
const mapDispatchToProps = (dispatch) => ({
onAddOption() {
dispatch(addPollOption(''))
},
onRemoveOption(index) {
dispatch(removePollOption(index));
},
onChangeOption(index, title) {
dispatch(changePollOption(index, title));
},
onChangeSettings(expiresIn, isMultiple) {
dispatch(changePollSettings(expiresIn, isMultiple));
},
onClearSuggestions () {
dispatch(clearComposeSuggestions());
},
onFetchSuggestions (token) {
dispatch(fetchComposeSuggestions(token));
},
onSuggestionSelected (position, token, accountId, path) {
dispatch(selectComposeSuggestion(position, token, accountId, path));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(PollForm);

View File

@@ -1,15 +0,0 @@
import SearchResults from '../components/search_results';
import { fetchSuggestions, dismissSuggestion } from '../../../actions/suggestions';
import { withRouter } from 'react-router-dom';
const mapStateToProps = (state) => ({
results: state.getIn(['search', 'results']),
suggestions: state.getIn(['suggestions', 'items']),
});
const mapDispatchToProps = (dispatch) => ({
fetchSuggestions: () => dispatch(fetchSuggestions()),
dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))),
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(SearchResults));

View File

@@ -74,12 +74,12 @@ class Followers extends ImmutablePureComponent {
return (
<Block>
<div className={[_s.default, _s.px15, _s.py10, _s.justifyContentCenter, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}>
<Heading size='h3'>
<div className={[_s.default, _s.px15, _s.py10, _s.justifyContentCenter].join(' ')}>
<Heading size='h2'>
{intl.formatMessage(messages.followers)}
</Heading>
</div>
<div className={[_s.default, _s.px15, _s.py10].join(' ')}>
<div className={[_s.default, _s.py10].join(' ')}>
<ScrollableList
scrollKey='followers'
hasMore={hasMore}

View File

@@ -74,12 +74,12 @@ class Following extends ImmutablePureComponent {
return (
<Block>
<div className={[_s.default, _s.px15, _s.py10, _s.justifyContentCenter, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}>
<Heading size='h3'>
<div className={[_s.default, _s.px15, _s.py10, _s.justifyContentCenter].join(' ')}>
<Heading size='h2'>
{intl.formatMessage(messages.follows)}
</Heading>
</div>
<div className={[_s.default, _s.px15, _s.py10].join(' ')}>
<div className={[_s.default, _s.py10].join(' ')}>
<ScrollableList
scrollKey='following'
hasMore={hasMore}

View File

@@ -8,13 +8,13 @@ import {
expandNotifications,
scrollTopNotifications,
dequeueNotifications,
} from '../../actions/notifications'
import NotificationContainer from './containers/notification_container'
} from '../actions/notifications'
import NotificationContainer from '../containers/notification_container'
// import ColumnSettingsContainer from './containers/column_settings_container'
import ScrollableList from '../../components/scrollable_list'
import LoadMore from '../../components/load_more'
import TimelineQueueButtonHeader from '../../components/timeline_queue_button_header'
import Block from '../../components/block'
import ScrollableList from '../components/scrollable_list'
import LoadMore from '../components/load_more'
import TimelineQueueButtonHeader from '../components/timeline_queue_button_header'
import Block from '../components/block'
const mapStateToProps = (state) => ({
notifications: state.getIn(['notifications', 'items']),

View File

@@ -1,139 +0,0 @@
import { NavLink } from 'react-router-dom'
import { injectIntl, defineMessages } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { HotKeys } from 'react-hotkeys'
import ImmutablePropTypes from 'react-immutable-proptypes'
import StatusContainer from '../../../containers/status_container'
import Avatar from '../../../components/avatar'
import Icon from '../../../components/icon'
import Text from '../../../components/text'
import DisplayName from '../../../components/display_name'
const messages = defineMessages({
poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' },
mentionedInPost: { id: 'mentioned_in_post', defaultMessage: 'mentioned you in their post' },
mentionedInComment: { id: 'mentioned_in_comment', defaultMessage: 'mentioned you in their comment' },
followedYouOne: { id: 'followed_you_one', defaultMessage: 'followed you' },
followedYouMultiple: { id: 'followed_you_multiple', defaultMessage: 'and {count} others followed you' },
likedStatusOne: { id: 'liked_status_one', defaultMessage: 'liked your status' },
likedStatusMultiple: { id: 'liked_status_multiple', defaultMessage: 'and {count} others liked your status' },
repostedStatusOne: { id: 'reposted_status_one', defaultMessage: 'reposted your status' },
repostedStatusMultiple: { id: 'reposted_status_multiple', defaultMessage: 'and {count} others reposted your status' },
})
const notificationForScreenReader = (intl, message, timestamp) => {
const output = [message]
output.push(intl.formatDate(timestamp, { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }))
return output.join(', ')
}
export default
@injectIntl
class Notification extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
}
static propTypes = {
intl: PropTypes.object.isRequired,
accounts: ImmutablePropTypes.list.isRequired,
createdAt: PropTypes.string,
statusId: PropTypes.string,
type: PropTypes.string.isRequired,
}
render() {
const { intl, accounts, createdAt, type, statusId } = this.props
const count = !!accounts ? accounts.size : 0
let message
let icon
switch (type) {
case 'follow':
icon = 'group'
message = intl.formatMessage(count > 1 ? messages.followedYouMultiple : messages.followedYouOne, {
count: count - 1,
})
break
case 'mention':
icon = 'comment'
message = intl.formatMessage(messages.mentionedInPost)
break
case 'like':
icon = 'like'
message = intl.formatMessage(count > 1 ? messages.likedStatusMultiple : messages.likedStatusOne, {
count: count - 1,
})
break
case 'repost':
icon = 'repost'
message = intl.formatMessage(count > 1 ? messages.repostedStatusMultiple : messages.repostedStatusOne, {
count: count - 1,
})
break
case 'poll':
icon = 'poll'
message = intl.formatMessage(messages.poll)
break
default:
return null
}
return (
<div className={[_s.default, _s.px10, _s.cursorPointer, _s.backgroundSubtle_onHover].join(' ')}>
<div className={[_s.default, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.my10, _s.py10, _s.px10].join(' ')}>
<Icon id={icon} size='20px' className={_s.mt5} />
<div className={[_s.default, _s.ml15, _s.flexNormal].join(' ')}>
<div className={[_s.default, _s.flexRow].join(' ')}>
{
accounts.slice(0, 8).map((account, i) => (
<NavLink
to={`/${account.get('acct')}`}
key={`fav-avatar-${i}`}
className={_s.mr5}
>
<Avatar size={30} account={account} />
</NavLink>
))
}
</div>
<div className={[_s.default, _s.pt10].join(' ')}>
<div className={[_s.default, _s.flexRow].join(' ')}>
<div className={_s.text}>
{
accounts.slice(0, 1).map((account, i) => (
<DisplayName key={i} account={account} noUsername />
))
}
</div>
<Text size='medium'>
{' '}
{message}
</Text>
</div>
</div>
{
!!statusId &&
<div className={[_s.default, _s.pt10, _s.mt5].join(' ')}>
<StatusContainer
id={statusId}
isChild
/>
</div>
}
</div>
</div>
</div>
</div>
)
}
}

View File

@@ -1,120 +0,0 @@
import { List as ImmutableList } from 'immutable'
import { openModal } from '../../../actions/modal'
import { mentionCompose } from '../../../actions/compose'
import {
reblog,
favorite,
unreblog,
unfavorite,
} from '../../../actions/interactions'
import {
hideStatus,
revealStatus,
} from '../../../actions/statuses'
import { boostModal } from '../../../initial_state'
import { makeGetNotification } from '../../../selectors'
import Notification from '../components/notification'
const getAccountFromState = (state, accountId) => {
return state.getIn(['accounts', accountId])
}
const makeMapStateToProps = () => {
const getNotification = makeGetNotification()
const mapStateToProps = (state, props) => {
const isFollows = !!props.notification.get('follow')
const isLikes = !!props.notification.get('like')
const isReposts = !!props.notification.get('repost')
const isGrouped = isFollows || isLikes || isReposts
if (isFollows) {
const list = props.notification.get('follow')
let accounts = ImmutableList()
list.forEach((item) => {
const account = getAccountFromState(state, item.get('account'))
accounts = accounts.set(accounts.size, account)
})
return {
type: 'follow',
accounts: accounts,
createdAt: undefined,
statusId: undefined,
}
} else if (isLikes || isReposts) {
const theType = isLikes ? 'like' : 'repost'
const list = props.notification.get(theType)
let accounts = ImmutableList()
const accountIdArr = list.get('accounts')
for (let i = 0; i < accountIdArr.length; i++) {
const accountId = accountIdArr[i];
const account = getAccountFromState(state, accountId)
accounts = accounts.set(accounts.size, account)
}
return {
type: theType,
accounts: accounts,
createdAt: undefined,
statusId: list.get('status'),
}
} else if (!isGrouped) {
const notification = getNotification(state, props.notification, props.notification.get('account'))
const account = notification.get('account')
const statusId = notification.get('status')
return {
accounts: !!account ? ImmutableList([account]) : ImmutableList(),
type: notification.get('type'),
createdAt: notification.get('created_at'),
statusId: statusId || undefined,
}
}
}
return mapStateToProps
}
const mapDispatchToProps = (dispatch) => ({
onMention: (account, router) => {
dispatch(mentionCompose(account, router))
},
onModalRepost (status) {
dispatch(repost(status))
},
onRepost (status, e) {
if (status.get('reblogged')) {
dispatch(unrepost(status))
} else {
if (e.shiftKey || !boostModal) {
this.onModalRepost(status)
} else {
dispatch(openModal('BOOST', { status, onRepost: this.onModalRepost }))
}
}
},
onFavorite (status) {
if (status.get('favourited')) {
dispatch(unfavorite(status))
} else {
dispatch(favorite(status))
}
},
onToggleHidden (status) {
if (status.get('hidden')) {
dispatch(revealStatus(status.get('id')))
} else {
dispatch(hideStatus(status.get('id')))
}
},
})
export default connect(makeMapStateToProps, mapDispatchToProps)(Notification)

View File

@@ -1 +0,0 @@
export { default } from './notifications'

View File

@@ -1,5 +1,5 @@
// import SearchContainer from '../compose/containers/search_container';
import SearchResultsContainer from './compose/containers/search_results_container';
import SearchResults from './compose/components/search_results';
export default class Search extends PureComponent {
@@ -10,7 +10,7 @@ export default class Search extends PureComponent {
<div className='drawer__pager'>
<div className='drawer__inner darker'>
<SearchResultsContainer />
<SearchResults />
</div>
</div>
</div>