Progress
This commit is contained in:
parent
e9f01c0b16
commit
196a906cec
|
@ -9,7 +9,7 @@ class Api::V1::GifsController < Api::BaseController
|
||||||
|
|
||||||
def categories
|
def categories
|
||||||
uri = URI('https://api.tenor.com/v1/categories')
|
uri = URI('https://api.tenor.com/v1/categories')
|
||||||
theOptions = { :key => "QHFJ0C5EWGBH" }
|
theOptions = { :key => "TENOR_KEY" }
|
||||||
uri.query = URI.encode_www_form(theOptions)
|
uri.query = URI.encode_www_form(theOptions)
|
||||||
|
|
||||||
res = Net::HTTP.get_response(uri)
|
res = Net::HTTP.get_response(uri)
|
||||||
|
@ -19,7 +19,7 @@ class Api::V1::GifsController < Api::BaseController
|
||||||
def search
|
def search
|
||||||
uri = URI('https://api.tenor.com/v1/search')
|
uri = URI('https://api.tenor.com/v1/search')
|
||||||
theOptions = {
|
theOptions = {
|
||||||
:key => "QHFJ0C5EWGBH",
|
:key => "TENOR_KEY",
|
||||||
:media_filter => "minimal",
|
:media_filter => "minimal",
|
||||||
:limit => 30,
|
:limit => 30,
|
||||||
:q => params[:search],
|
:q => params[:search],
|
||||||
|
|
|
@ -22,8 +22,10 @@ class Api::V1::StatusesController < Api::BaseController
|
||||||
render json: @status, serializer: REST::StatusSerializer
|
render json: @status, serializer: REST::StatusSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# direct descendants only
|
||||||
def comments
|
def comments
|
||||||
descendants_results = @status.descendants(CONTEXT_LIMIT, current_account)
|
descendants_results = @status.descendants(CONTEXT_LIMIT, current_account, nil, nil, 1)
|
||||||
|
puts "descendants_results: " + descendants_results.inspect
|
||||||
loaded_descendants = cache_collection(descendants_results, Status)
|
loaded_descendants = cache_collection(descendants_results, Status)
|
||||||
|
|
||||||
@context = Context.new(descendants: loaded_descendants)
|
@context = Context.new(descendants: loaded_descendants)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
import { CancelToken, isCancel } from 'axios';
|
import { CancelToken, isCancel } from 'axios';
|
||||||
import throttle from 'lodash.throttle'
|
import throttle from 'lodash.throttle'
|
||||||
|
import moment from 'moment-mini'
|
||||||
import { search as emojiSearch } from '../components/emoji/emoji_mart_search_light';
|
import { search as emojiSearch } from '../components/emoji/emoji_mart_search_light';
|
||||||
import { urlRegex } from '../features/ui/util/url_regex'
|
import { urlRegex } from '../features/ui/util/url_regex'
|
||||||
import { tagHistory } from '../settings';
|
import { tagHistory } from '../settings';
|
||||||
|
@ -77,23 +78,26 @@ export const ensureComposeIsVisible = (getState, routerHistory) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function changeCompose(text, markdown) {
|
export function changeCompose(text, markdown, replyId) {
|
||||||
console.log("changeCompose:", markdown)
|
console.log("changeCompose:", text)
|
||||||
return {
|
return {
|
||||||
type: COMPOSE_CHANGE,
|
type: COMPOSE_CHANGE,
|
||||||
text: text,
|
text: text,
|
||||||
markdown: markdown,
|
markdown: markdown,
|
||||||
|
replyId: replyId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function replyCompose(status) {
|
export function replyCompose(status, router, showModal) {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: COMPOSE_REPLY,
|
type: COMPOSE_REPLY,
|
||||||
status: status,
|
status: status,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (showModal) {
|
||||||
dispatch(openModal('COMPOSE'));
|
dispatch(openModal('COMPOSE'));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -169,7 +173,7 @@ export function handleComposeSubmit(dispatch, getState, response, status) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function submitCompose(group, replyToId=null) {
|
export function submitCompose(group, replyToId = null) {
|
||||||
return function (dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
if (!me) return;
|
if (!me) return;
|
||||||
|
|
||||||
|
@ -202,8 +206,7 @@ export function submitCompose(group, replyToId=null) {
|
||||||
const method = id === null ? 'post' : 'put';
|
const method = id === null ? 'post' : 'put';
|
||||||
|
|
||||||
const scheduled_at = getState().getIn(['compose', 'scheduled_at'], null);
|
const scheduled_at = getState().getIn(['compose', 'scheduled_at'], null);
|
||||||
// : todo :
|
if (scheduled_at !== null) scheduled_at = moment.utc(scheduled_at).toDate();
|
||||||
// if (scheduled_at !== null) scheduled_at = moment.utc(scheduled_at).toDate();
|
|
||||||
|
|
||||||
api(getState)[method](endpoint, {
|
api(getState)[method](endpoint, {
|
||||||
status,
|
status,
|
||||||
|
|
|
@ -52,9 +52,14 @@ export const clearGifResults = () => ({
|
||||||
type: GIFS_CLEAR_RESULTS,
|
type: GIFS_CLEAR_RESULTS,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const setSelectedGif = (url) => ({
|
export const clearSelectedGif = () => ({
|
||||||
|
type: GIF_CLEAR_SELECTED,
|
||||||
|
result,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const setSelectedGif = (result) => ({
|
||||||
type: GIF_SET_SELECTED,
|
type: GIF_SET_SELECTED,
|
||||||
url,
|
result,
|
||||||
})
|
})
|
||||||
|
|
||||||
export function changeGifSearchText(text) {
|
export function changeGifSearchText(text) {
|
||||||
|
|
|
@ -16,8 +16,8 @@ const GlobeIcon = ({
|
||||||
aria-label={title}
|
aria-label={title}
|
||||||
>
|
>
|
||||||
<g>
|
<g>
|
||||||
<circle fill='none' stroke='#616770' strokeWidth='1px' cx='14' cy='14' r='13.5' />
|
<circle fill='none' stroke='#4B4F55' strokeWidth='1px' cx='14' cy='14' r='13.5' />
|
||||||
<path fill='#616770' d='M 16 5 L 18 4 L 20 5 L 22 5 L 23 4 C 24 5 25 6 25 7 L 25 7 L 22 8 L 20 7 L 19 6 L 16 6 L 14 7 L 15 11 L 18 12 L 20 12 L 21 13 L 21 14 L 22 16 L 23 18 L 23 20 L 26.4 21 C 20 27 12 29 6 25 C 1 21 0 14 1 8 L 2 11 L 3 12 L 5 13 L 4 14 L 4 15 L 5 17 L 7 17 L 7 22 L 8 24 L 9 25 L 9 22 L 11 21 L 11 20 L 13 18 L 14 15 L 12 15 L 10 13 L 7 13 L 6 11 L 5 13 L 5 11 L 4 10 L 4 8 L 7 8 L 9 7 L 11 4 L 12 4 L 13 2 L 10 2 L 10 1 C 12 0 16 0 18 1 L 18 2 L 16 2 L 15 4 Z M 16 5' />
|
<path fill='#4B4F55' d='M 16 5 L 18 4 L 20 5 L 22 5 L 23 4 C 24 5 25 6 25 7 L 25 7 L 22 8 L 20 7 L 19 6 L 16 6 L 14 7 L 15 11 L 18 12 L 20 12 L 21 13 L 21 14 L 22 16 L 23 18 L 23 20 L 26.4 21 C 20 27 12 29 6 25 C 1 21 0 14 1 8 L 2 11 L 3 12 L 5 13 L 4 14 L 4 15 L 5 17 L 7 17 L 7 22 L 8 24 L 9 25 L 9 22 L 11 21 L 11 20 L 13 18 L 14 15 L 12 15 L 10 13 L 7 13 L 6 11 L 5 13 L 5 11 L 4 10 L 4 8 L 7 8 L 9 7 L 11 4 L 12 4 L 13 2 L 10 2 L 10 1 C 12 0 16 0 18 1 L 18 2 L 16 2 L 15 4 Z M 16 5' />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,12 +11,12 @@ const VerifiedIcon = ({
|
||||||
y='0px'
|
y='0px'
|
||||||
width={size}
|
width={size}
|
||||||
height={size}
|
height={size}
|
||||||
viewBox='0 0 24 24'
|
viewBox='0 0 32 32'
|
||||||
xmlSpace='preserve'
|
xmlSpace='preserve'
|
||||||
aria-label={title}
|
aria-label={title}
|
||||||
>
|
>
|
||||||
<g>
|
<g>
|
||||||
<path fill='#3E99ED' d='M22.5 12.5c0-1.58-.875-2.95-2.148-3.6.154-.435.238-.905.238-1.4 0-2.21-1.71-3.998-3.818-3.998-.47 0-.92.084-1.336.25C14.818 2.415 13.51 1.5 12 1.5s-2.816.917-3.437 2.25c-.415-.165-.866-.25-1.336-.25-2.11 0-3.818 1.79-3.818 4 0 .494.083.964.237 1.4-1.272.65-2.147 2.018-2.147 3.6 0 1.495.782 2.798 1.942 3.486-.02.17-.032.34-.032.514 0 2.21 1.708 4 3.818 4 .47 0 .92-.086 1.335-.25.62 1.334 1.926 2.25 3.437 2.25 1.512 0 2.818-.916 3.437-2.25.415.163.865.248 1.336.248 2.11 0 3.818-1.79 3.818-4 0-.174-.012-.344-.033-.513 1.158-.687 1.943-1.99 1.943-3.484zm-6.616-3.334l-4.334 6.5c-.145.217-.382.334-.625.334-.143 0-.288-.04-.416-.126l-.115-.094-2.415-2.415c-.293-.293-.293-.768 0-1.06s.768-.294 1.06 0l1.77 1.767 3.825-5.74c.23-.345.696-.436 1.04-.207.346.23.44.696.21 1.04z' />
|
<path fill='#3E99ED' d='M 27.3125 4.6875 C 24.292969 1.664062 20.273438 0 16 0 C 11.726562 0 7.707031 1.664062 4.6875 4.6875 C 1.664062 7.707031 0 11.726562 0 16 C 0 20.273438 1.664062 24.292969 4.6875 27.3125 C 7.707031 30.335938 11.726562 32 16 32 C 20.273438 32 24.292969 30.335938 27.3125 27.3125 C 30.335938 24.292969 32 20.273438 32 16 C 32 11.726562 30.335938 7.707031 27.3125 4.6875 Z M 23.644531 12.191406 L 14.703125 21.132812 C 14.519531 21.316406 14.28125 21.410156 14.039062 21.410156 C 13.800781 21.410156 13.558594 21.316406 13.375 21.132812 L 8.355469 16.113281 C 7.988281 15.746094 7.988281 15.152344 8.355469 14.785156 C 8.722656 14.421875 9.316406 14.421875 9.683594 14.785156 L 14.039062 19.144531 L 22.316406 10.867188 C 22.683594 10.5 23.277344 10.5 23.644531 10.867188 C 24.011719 11.230469 24.011719 11.824219 23.644531 12.191406 Z M 23.644531 12.191406' />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import isObject from 'lodash.isobject'
|
import isObject from 'lodash.isobject'
|
||||||
import classNames from 'classnames/bind'
|
import classNames from 'classnames/bind'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
|
import Textarea from 'react-textarea-autosize'
|
||||||
import { isRtl } from '../utils/rtl'
|
import { isRtl } from '../utils/rtl'
|
||||||
import { textAtCursorMatchesToken } from '../utils/cursor_token_match'
|
import { textAtCursorMatchesToken } from '../utils/cursor_token_match'
|
||||||
import AutosuggestAccount from './autosuggest_account'
|
import AutosuggestAccount from './autosuggest_account'
|
||||||
|
@ -226,27 +227,56 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
||||||
direction: isRtl(value) ? 'rtl' : 'ltr',
|
direction: isRtl(value) ? 'rtl' : 'ltr',
|
||||||
}
|
}
|
||||||
|
|
||||||
const textClasses = cx({
|
const textareaClasses = cx({
|
||||||
default: 1,
|
default: 1,
|
||||||
lineHeight125: 1,
|
font: 1,
|
||||||
|
wrap: 1,
|
||||||
resizeNone: 1,
|
resizeNone: 1,
|
||||||
text: 1,
|
bgTransparent: 1,
|
||||||
displayBlock: 1,
|
|
||||||
outlineNone: 1,
|
outlineNone: 1,
|
||||||
bgPrimary: !small,
|
lineHeight125: 1,
|
||||||
bgSubtle: small,
|
height100PC: small,
|
||||||
py15: !small,
|
width100PC: !small,
|
||||||
py10: small,
|
pt15: !small,
|
||||||
|
px15: !small,
|
||||||
|
px10: small,
|
||||||
|
pb10: !small,
|
||||||
fs16PX: !small,
|
fs16PX: !small,
|
||||||
fs14PX: small,
|
fs14PX: small,
|
||||||
mr5: small,
|
heightMax200PX: small,
|
||||||
|
heightMax80VH: !small,
|
||||||
|
heightMin100PX: !small,
|
||||||
|
})
|
||||||
|
|
||||||
|
const textareaContainerClasses = cx({
|
||||||
|
default: 1,
|
||||||
|
maxWidth100PC: 1,
|
||||||
|
flexGrow1: small,
|
||||||
|
height100PC: small,
|
||||||
|
justifyContentCenter: small,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (textarea) {
|
if (textarea) {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className={[_s.default, _s.flexGrow1, _s.maxWidth100PC].join(' ')}>
|
<div className={textareaContainerClasses}>
|
||||||
<Composer
|
<Textarea
|
||||||
|
inputRef={this.setTextbox}
|
||||||
|
className={textareaClasses}
|
||||||
|
disabled={disabled}
|
||||||
|
placeholder={placeholder}
|
||||||
|
autoFocus={false}
|
||||||
|
value={value}
|
||||||
|
onChange={this.onChange}
|
||||||
|
// onKeyDown={this.onKeyDown}
|
||||||
|
// onKeyUp={onKeyUp}
|
||||||
|
// onFocus={this.onFocus}
|
||||||
|
// onBlur={this.onBlur}
|
||||||
|
// onPaste={this.onPaste}
|
||||||
|
aria-autocomplete='list'
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/*<Composer
|
||||||
inputRef={this.setTextbox}
|
inputRef={this.setTextbox}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
@ -259,7 +289,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
onPaste={this.onPaste}
|
onPaste={this.onPaste}
|
||||||
small={small}
|
small={small}
|
||||||
/>
|
/>*/}
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,52 +1,125 @@
|
||||||
import { NavLink } from 'react-router-dom'
|
import { NavLink } from 'react-router-dom'
|
||||||
import { defineMessages, injectIntl } from 'react-intl'
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
import { makeGetStatus } from '../selectors';
|
import {
|
||||||
|
favorite,
|
||||||
|
unfavorite,
|
||||||
|
} from '../actions/interactions'
|
||||||
|
import { replyCompose } from '../actions/compose'
|
||||||
|
import { openModal } from '../actions/modal'
|
||||||
|
import { openPopover } from '../actions/popover'
|
||||||
|
import { makeGetStatus } from '../selectors'
|
||||||
|
import { me } from '../initial_state'
|
||||||
import Avatar from './avatar'
|
import Avatar from './avatar'
|
||||||
import Button from './button'
|
import Button from './button'
|
||||||
import CommentHeader from './comment_header'
|
import CommentHeader from './comment_header'
|
||||||
import StatusContent from './status_content'
|
import StatusContent from './status_content'
|
||||||
|
import StatusMedia from './status_media'
|
||||||
|
import { defaultMediaVisibility } from './status'
|
||||||
import Text from './text'
|
import Text from './text'
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
||||||
like: { id: 'status.like', defaultMessage: 'Like' },
|
like: { id: 'status.like', defaultMessage: 'Like' },
|
||||||
|
unlike: { id: 'status.unlike', defaultMessage: 'Unlike' },
|
||||||
})
|
})
|
||||||
|
|
||||||
const makeMapStateToProps = (state, props) => ({
|
const makeMapStateToProps = (state, props) => ({
|
||||||
status: makeGetStatus()(state, props)
|
status: makeGetStatus()(state, props)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
onReply (status, router) {
|
||||||
|
if (!me) return dispatch(openModal('UNAUTHORIZED'))
|
||||||
|
|
||||||
|
dispatch((_, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
if (state.getIn(['compose', 'text']).trim().length !== 0) {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: <FormattedMessage id='confirmations.reply.message' defaultMessage='Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' />,
|
||||||
|
confirm: <FormattedMessage id='confirmations.reply.confirm' defaultMessage='Reply' />,
|
||||||
|
onConfirm: () => dispatch(replyCompose(status, router)),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
dispatch(replyCompose(status, router, true))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onFavorite (status) {
|
||||||
|
if (!me) return dispatch(openModal('UNAUTHORIZED'))
|
||||||
|
|
||||||
|
if (status.get('favourited')) {
|
||||||
|
dispatch(unfavorite(status))
|
||||||
|
} else {
|
||||||
|
dispatch(favorite(status))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onOpenStatusOptions(status) {
|
||||||
|
dispatch(openPopover('STATUS_OPTOINS', { status }))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export default
|
export default
|
||||||
@injectIntl
|
@injectIntl
|
||||||
@connect(makeMapStateToProps)
|
@connect(makeMapStateToProps, mapDispatchToProps)
|
||||||
class Comment extends ImmutablePureComponent {
|
class Comment extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
indent: PropTypes.number,
|
indent: PropTypes.number,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
status: ImmutablePropTypes.map.isRequired,
|
status: ImmutablePropTypes.map.isRequired,
|
||||||
|
isHidden: PropTypes.bool,
|
||||||
|
isIntersecting: PropTypes.bool,
|
||||||
|
onReply: PropTypes.func.isRequired,
|
||||||
|
onFavorite: PropTypes.func.isRequired,
|
||||||
|
onOpenStatusOptions: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOnProps = [
|
updateOnProps = [
|
||||||
'status',
|
'status',
|
||||||
'indent',
|
'indent',
|
||||||
|
'isHidden',
|
||||||
|
'isIntersecting',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
state = {
|
||||||
|
showMedia: defaultMediaVisibility(this.props.status),
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnReply = () => {
|
||||||
|
this.props.onReply(this.props.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnFavorite = () => {
|
||||||
|
this.props.onFavorite(this.props.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnOpenStatusOptions = () => {
|
||||||
|
this.props.onOpenStatusOptions(this.props.status)
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
indent,
|
indent,
|
||||||
intl,
|
intl,
|
||||||
status,
|
status,
|
||||||
|
isHidden,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
|
if (isHidden) {
|
||||||
|
return (
|
||||||
|
<div tabIndex='0'>
|
||||||
|
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
|
||||||
|
{status.get('content')}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
paddingLeft: `${indent * 40}px`,
|
paddingLeft: `${indent * 40}px`,
|
||||||
}
|
}
|
||||||
|
|
||||||
// : todo : add media
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[_s.default, _s.px15, _s.mb10, _s.py5].join(' ')} data-comment={status.get('id')}>
|
<div className={[_s.default, _s.px15, _s.mb10, _s.py5].join(' ')} data-comment={status.get('id')}>
|
||||||
<div className={[_s.default].join(' ')} style={style}>
|
<div className={[_s.default].join(' ')} style={style}>
|
||||||
|
@ -69,12 +142,34 @@ class Comment extends ImmutablePureComponent {
|
||||||
isComment
|
isComment
|
||||||
collapsable
|
collapsable
|
||||||
/>
|
/>
|
||||||
|
<div className={[_s.default].join(' ')}>
|
||||||
|
<StatusMedia
|
||||||
|
isComment
|
||||||
|
status={status}
|
||||||
|
onOpenMedia={this.props.onOpenMedia}
|
||||||
|
cacheWidth={this.props.cacheMediaWidth}
|
||||||
|
defaultWidth={this.props.cachedMediaWidth}
|
||||||
|
visible={this.state.showMedia}
|
||||||
|
onToggleVisibility={this.handleToggleMediaVisibility}
|
||||||
|
width={this.props.cachedMediaWidth}
|
||||||
|
onOpenVideo={this.handleOpenVideo}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={[_s.default, _s.flexRow, _s.mt5].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.mt5].join(' ')}>
|
||||||
<CommentButton title={intl.formatMessage(messages.like)} />
|
<CommentButton
|
||||||
<CommentButton title={intl.formatMessage(messages.reply)} />
|
title={intl.formatMessage(status.get('favourited') ? messages.unlike: messages.like)}
|
||||||
<CommentButton title='···' />
|
onClick={this.handleOnFavorite}
|
||||||
|
/>
|
||||||
|
<CommentButton
|
||||||
|
title={intl.formatMessage(messages.reply)}
|
||||||
|
onClick={this.handleOnReply}
|
||||||
|
/>
|
||||||
|
<CommentButton
|
||||||
|
title='···'
|
||||||
|
onClick={this.handleOnOpenStatusOptions}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
import Button from './button'
|
import Button from './button'
|
||||||
import Comment from './comment'
|
import Comment from './comment'
|
||||||
|
import ScrollableList from './scrollable_list'
|
||||||
import Text from './text'
|
import Text from './text'
|
||||||
|
import { PureComponent } from 'react';
|
||||||
|
|
||||||
export default class CommentList extends ImmutablePureComponent {
|
export default class CommentList extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
@ -11,18 +13,26 @@ export default class CommentList extends ImmutablePureComponent {
|
||||||
descendants: ImmutablePropTypes.list,
|
descendants: ImmutablePropTypes.list,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleLoadMore = () => {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
descendants,
|
descendants,
|
||||||
commentsLimited,
|
commentsLimited,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
|
const upperLimit = 6
|
||||||
const size = descendants.size
|
const size = descendants.size
|
||||||
const max = Math.min(commentsLimited ? 2 : 6, size)
|
const max = Math.min(commentsLimited ? 2 : upperLimit, size)
|
||||||
console.log("max:", size, max)
|
|
||||||
|
const Wrapper = !commentsLimited ? ScrollableList : DummyContainer
|
||||||
|
console.log("Wrapper:", Wrapper)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<Wrapper scrollKey='comments'>
|
||||||
{
|
{
|
||||||
descendants.slice(0, max).map((descendant, i) => (
|
descendants.slice(0, max).map((descendant, i) => (
|
||||||
<Comment
|
<Comment
|
||||||
|
@ -32,13 +42,15 @@ export default class CommentList extends ImmutablePureComponent {
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
</Wrapper>
|
||||||
{
|
{
|
||||||
size > 0 && size > max &&
|
size > 0 && size > max && commentsLimited &&
|
||||||
<div className={[_s.default, _s.flexRow, _s.px15, _s.pb5, _s.mb10, _s.alignItemsCenter].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.px15, _s.pb5, _s.mb10, _s.alignItemsCenter].join(' ')}>
|
||||||
<Button
|
<Button
|
||||||
isText
|
isText
|
||||||
backgroundColor='none'
|
backgroundColor='none'
|
||||||
color='tertiary'
|
color='tertiary'
|
||||||
|
onClick={this.handleLoadMore}
|
||||||
>
|
>
|
||||||
<Text weight='bold' color='inherit'>
|
<Text weight='bold' color='inherit'>
|
||||||
View more comments
|
View more comments
|
||||||
|
@ -58,3 +70,9 @@ export default class CommentList extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DummyContainer extends PureComponent {
|
||||||
|
render() {
|
||||||
|
return <div>{this.props.children}</div>
|
||||||
|
}
|
||||||
|
}
|
|
@ -100,9 +100,23 @@ class Composer extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
markdownText: '',
|
||||||
|
plainText: '',
|
||||||
editorState: EditorState.createEmpty(compositeDecorator),
|
editorState: EditorState.createEmpty(compositeDecorator),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(nextProps, prevState) {
|
||||||
|
// if (!nextProps.isHidden && nextProps.isIntersecting && !prevState.fetched) {
|
||||||
|
// return {
|
||||||
|
// fetched: true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditorState.createWithContent(ContentState.createFromText('Hello'))
|
||||||
|
|
||||||
onChange = (editorState) => {
|
onChange = (editorState) => {
|
||||||
this.setState({ editorState })
|
this.setState({ editorState })
|
||||||
const content = this.state.editorState.getCurrentContent();
|
const content = this.state.editorState.getCurrentContent();
|
||||||
|
@ -123,15 +137,14 @@ class Composer extends PureComponent {
|
||||||
this.props.onChange(null, text, selectionStart, markdownString)
|
this.props.onChange(null, text, selectionStart, markdownString)
|
||||||
}
|
}
|
||||||
|
|
||||||
// **bold**
|
// **bold**
|
||||||
// *italic*
|
// *italic*
|
||||||
// __underline__
|
// __underline__
|
||||||
// ~strikethrough~
|
// ~strikethrough~
|
||||||
// # title
|
// # title
|
||||||
// > quote
|
// > quote
|
||||||
// `code`
|
// `code`
|
||||||
// ```code```
|
// ```code```
|
||||||
|
|
||||||
|
|
||||||
focus = () => {
|
focus = () => {
|
||||||
this.textbox.editor.focus()
|
this.textbox.editor.focus()
|
||||||
|
@ -168,7 +181,7 @@ class Composer extends PureComponent {
|
||||||
disabled,
|
disabled,
|
||||||
placeholder,
|
placeholder,
|
||||||
autoFocus,
|
autoFocus,
|
||||||
// value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
onKeyUp,
|
onKeyUp,
|
||||||
|
|
|
@ -122,7 +122,7 @@ class DisplayName extends ImmutablePureComponent {
|
||||||
|
|
||||||
const iconSize =
|
const iconSize =
|
||||||
!!isLarge ? '19px' :
|
!!isLarge ? '19px' :
|
||||||
!!isSmall ? '14px' : '16px'
|
!!isSmall ? '14px' : '15px'
|
||||||
|
|
||||||
const domain = account.get('acct').split('@')[1]
|
const domain = account.get('acct').split('@')[1]
|
||||||
const isRemoteUser = !!domain
|
const isRemoteUser = !!domain
|
||||||
|
|
|
@ -29,7 +29,7 @@ const emojify = (str, customEmojis = {}) => {
|
||||||
// if you want additional emoji handler, add statements below which set replacement and return true.
|
// if you want additional emoji handler, add statements below which set replacement and return true.
|
||||||
if (shortname in customEmojis) {
|
if (shortname in customEmojis) {
|
||||||
const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
|
const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
|
||||||
replacement = `<img draggable="false" style="height:20px;width:20px;margin:-3px 0 0;" alt="${shortname}" title="${shortname}" src="${filename}" />`;
|
replacement = `<img draggable="false" style="height:16px;width:16px;margin:-3px 0 0;font-family: 'object-fit:contain',inherit;vertical-align: middle;-o-object-fit: contain;object-fit: contain;" alt="${shortname}" title="${shortname}" src="${filename}" />`;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -60,7 +60,7 @@ const emojify = (str, customEmojis = {}) => {
|
||||||
} else { // matched to unicode emoji
|
} else { // matched to unicode emoji
|
||||||
const { filename, shortCode } = unicodeMapping[match];
|
const { filename, shortCode } = unicodeMapping[match];
|
||||||
const title = shortCode ? `:${shortCode}:` : '';
|
const title = shortCode ? `:${shortCode}:` : '';
|
||||||
replacement = `<img draggable="false" style="height:20px;width:20px;margin: -3px 0 0;" alt="${match}" title="${title}" src="${assetHost}/emoji/${filename}.svg" />`;
|
replacement = `<img draggable="false" style="height:16px;width:16px;margin:-3px 0 0;font-family: 'object-fit:contain',inherit;vertical-align: middle;-o-object-fit: contain;object-fit: contain;" alt="${match}" title="${title}" src="${assetHost}/emoji/${filename}.svg" />`;
|
||||||
rend = i + match.length;
|
rend = i + match.length;
|
||||||
// If the matched character was followed by VS15 (for selecting text presentation), skip it.
|
// If the matched character was followed by VS15 (for selecting text presentation), skip it.
|
||||||
if (str.codePointAt(rend) === 65038) {
|
if (str.codePointAt(rend) === 65038) {
|
||||||
|
|
|
@ -9,48 +9,39 @@ export default class ExtendedVideoPlayer extends PureComponent {
|
||||||
controls: PropTypes.bool.isRequired,
|
controls: PropTypes.bool.isRequired,
|
||||||
muted: PropTypes.bool.isRequired,
|
muted: PropTypes.bool.isRequired,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
};
|
}
|
||||||
|
|
||||||
handleLoadedData = () => {
|
handleLoadedData = () => {
|
||||||
if (this.props.time) {
|
if (this.props.time) {
|
||||||
this.video.currentTime = this.props.time;
|
this.video.currentTime = this.props.time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.video.addEventListener('loadeddata', this.handleLoadedData);
|
this.video.addEventListener('loadeddata', this.handleLoadedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this.video.removeEventListener('loadeddata', this.handleLoadedData);
|
this.video.removeEventListener('loadeddata', this.handleLoadedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
setRef = (c) => {
|
setRef = (c) => {
|
||||||
this.video = c;
|
this.video = c
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick = e => {
|
handleClick = e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation()
|
||||||
const handler = this.props.onClick;
|
const handler = this.props.onClick
|
||||||
if (handler) handler();
|
if (handler) handler()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { src, muted, controls, alt } = this.props;
|
const { src, muted, controls, alt } = this.props
|
||||||
|
|
||||||
// .extended-video-player {
|
|
||||||
// @include size(100%);
|
|
||||||
// @include flex(center, center);
|
|
||||||
|
|
||||||
// video {
|
|
||||||
// @include max-size($media-modal-media-max-width, $media-modal-media-max-height);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='extended-video-player'>
|
<div className={[_s.default, _s.width100PC, _s.height100PC, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
|
||||||
<video
|
<video
|
||||||
|
className={[_s.default, _s.maxWidth100PC, _s.heightMax100PC].join(' ')}
|
||||||
playsInline
|
playsInline
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
src={src}
|
src={src}
|
||||||
|
@ -64,7 +55,7 @@ export default class ExtendedVideoPlayer extends PureComponent {
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ const messages = defineMessages({
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
|
|
||||||
toggleMembership(group, relationships) {
|
onToggleMembership(group, relationships) {
|
||||||
if (relationships.get('member')) {
|
if (relationships.get('member')) {
|
||||||
dispatch(leaveGroup(group.get('id')));
|
dispatch(leaveGroup(group.get('id')));
|
||||||
} else {
|
} else {
|
||||||
|
@ -116,6 +116,7 @@ class GroupHeader extends ImmutablePureComponent {
|
||||||
<Button
|
<Button
|
||||||
radiusSmall
|
radiusSmall
|
||||||
className={_s.mr5}
|
className={_s.mr5}
|
||||||
|
onClick={this.handleOnToggleMembership}
|
||||||
{...actionButtonOptions}
|
{...actionButtonOptions}
|
||||||
>
|
>
|
||||||
<Text color='inherit' size='small'>
|
<Text color='inherit' size='small'>
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default class Input extends PureComponent {
|
||||||
inputRef: PropTypes.func,
|
inputRef: PropTypes.func,
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
hideLabel: PropTypes.bool,
|
hideLabel: PropTypes.bool,
|
||||||
|
maxLength: PropTypes.number,
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOnChange = (e) => {
|
handleOnChange = (e) => {
|
||||||
|
@ -46,7 +47,8 @@ export default class Input extends PureComponent {
|
||||||
readOnly,
|
readOnly,
|
||||||
inputRef,
|
inputRef,
|
||||||
id,
|
id,
|
||||||
hideLabel
|
hideLabel,
|
||||||
|
maxLength,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const inputClasses = cx({
|
const inputClasses = cx({
|
||||||
|
@ -87,7 +89,7 @@ export default class Input extends PureComponent {
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div className={[_s.default, _s.bgPrimary, _s.border1PX, _s.borderColorSecondary, _s.flexRow, _s.circle, _s.alignItemsCenter].join(' ')}>
|
<div className={[_s.default, _s.flexGrow1, _s.bgPrimary, _s.border1PX, _s.borderColorSecondary, _s.flexRow, _s.circle, _s.alignItemsCenter].join(' ')}>
|
||||||
{
|
{
|
||||||
!!prependIcon &&
|
!!prependIcon &&
|
||||||
<Icon id={prependIcon} size='16px' className={[_s.fillPrimary, _s.ml15, _s.mr5].join(' ')} />
|
<Icon id={prependIcon} size='16px' className={[_s.fillPrimary, _s.ml15, _s.mr5].join(' ')} />
|
||||||
|
@ -110,6 +112,7 @@ export default class Input extends PureComponent {
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
|
maxLength={maxLength}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,7 +22,7 @@ class LoadMore extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick = (e) => {
|
handleClick = (e) => {
|
||||||
this.props.onClick()
|
this.props.onClick(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -309,10 +309,11 @@ class MediaGallery extends PureComponent {
|
||||||
} = this.props
|
} = this.props
|
||||||
const { visible } = this.state
|
const { visible } = this.state
|
||||||
|
|
||||||
const width = this.state.width || defaultWidth;
|
let width = this.state.width || defaultWidth
|
||||||
|
if (reduced) width = width / 2
|
||||||
|
|
||||||
const style = {};
|
const style = {}
|
||||||
const size = media.take(4).size;
|
const size = media.take(4).size
|
||||||
|
|
||||||
const standard169 = width / (16 / 9);
|
const standard169 = width / (16 / 9);
|
||||||
const standard169_percent = 100 / (16 / 9);
|
const standard169_percent = 100 / (16 / 9);
|
||||||
|
@ -517,9 +518,9 @@ class MediaGallery extends PureComponent {
|
||||||
|
|
||||||
//If reduced (i.e. like in a quoted post)
|
//If reduced (i.e. like in a quoted post)
|
||||||
//then we need to make media smaller
|
//then we need to make media smaller
|
||||||
if (reduced) {
|
// if (reduced) {
|
||||||
style.height = width / 2 || '50%'
|
// style.height = width / 2 || '50%'
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
style.height = 'auto'
|
style.height = 'auto'
|
||||||
|
@ -573,7 +574,7 @@ class MediaGallery extends PureComponent {
|
||||||
<Button
|
<Button
|
||||||
title={intl.formatMessage(messages.toggle_visible)}
|
title={intl.formatMessage(messages.toggle_visible)}
|
||||||
icon='hidden'
|
icon='hidden'
|
||||||
backgroundColor='none'
|
backgroundColor='black'
|
||||||
className={[_s.px10, _s.bgBlackOpaque_onHover].join(' ')}
|
className={[_s.px10, _s.bgBlackOpaque_onHover].join(' ')}
|
||||||
onClick={this.handleOpen}
|
onClick={this.handleOpen}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -9,9 +9,9 @@ export default
|
||||||
class ConfirmationModal extends PureComponent {
|
class ConfirmationModal extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.node.isRequired,
|
title: PropTypes.any.isRequired,
|
||||||
message: PropTypes.node.isRequired,
|
message: PropTypes.any.isRequired,
|
||||||
confirm: PropTypes.string.isRequired,
|
confirm: PropTypes.any.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
onConfirm: PropTypes.func.isRequired,
|
onConfirm: PropTypes.func.isRequired,
|
||||||
secondary: PropTypes.string,
|
secondary: PropTypes.string,
|
||||||
|
|
|
@ -49,8 +49,9 @@ export const mapDispatchToProps = (dispatch, { onClose }) => ({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSelectResult: (resultId) => {
|
handleSelectResult: (result) => {
|
||||||
|
dispatch(setSelectedGif(result))
|
||||||
|
onClose()
|
||||||
},
|
},
|
||||||
|
|
||||||
// dispatchSubmit: (e) => {
|
// dispatchSubmit: (e) => {
|
||||||
|
@ -70,11 +71,11 @@ class GifPickerModal extends PureComponent {
|
||||||
handleCloseModal: PropTypes.func.isRequired,
|
handleCloseModal: PropTypes.func.isRequired,
|
||||||
handleFetchCategories: PropTypes.func.isRequired,
|
handleFetchCategories: PropTypes.func.isRequired,
|
||||||
handleOnChange: PropTypes.func.isRequired,
|
handleOnChange: PropTypes.func.isRequired,
|
||||||
|
handleSelectResult: PropTypes.func.isRequired,
|
||||||
categories: PropTypes.array.isRequired,
|
categories: PropTypes.array.isRequired,
|
||||||
results: PropTypes.array.isRequired,
|
results: PropTypes.array.isRequired,
|
||||||
loading: PropTypes.bool,
|
loading: PropTypes.bool,
|
||||||
error: PropTypes.bool,
|
error: PropTypes.bool,
|
||||||
chosenUrl: PropTypes.string,
|
|
||||||
searchText: PropTypes.string,
|
searchText: PropTypes.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,8 +99,8 @@ class GifPickerModal extends PureComponent {
|
||||||
this.props.handleOnChange(category)
|
this.props.handleOnChange(category)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelectGifResult = (resultId) => {
|
handleSelectGifResult = (resultBlock) => {
|
||||||
console.log("handleSelectGifResult:", resultId)
|
this.props.handleSelectResult(resultBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -109,7 +110,7 @@ class GifPickerModal extends PureComponent {
|
||||||
results,
|
results,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
searchText
|
searchText,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -165,6 +166,7 @@ class GifPickerModal extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
class GifResultsCollectionColumn extends PureComponent {
|
class GifResultsCollectionColumn extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
results: PropTypes.array.isRequired,
|
results: PropTypes.array.isRequired,
|
||||||
handleSelectGifResult: PropTypes.func.isRequired,
|
handleSelectGifResult: PropTypes.func.isRequired,
|
||||||
|
@ -183,8 +185,8 @@ class GifResultsCollectionColumn extends PureComponent {
|
||||||
results.map((result, i) => (
|
results.map((result, i) => (
|
||||||
<button
|
<button
|
||||||
key={`gif-result-item-${i}`}
|
key={`gif-result-item-${i}`}
|
||||||
onClick={() => this.onClick(result.id)}
|
onClick={() => this.onClick(result)}
|
||||||
className={[_s.default, _s.cursorPointer, _s.px2, _s.py2].join(' ')}
|
className={[_s.default, _s.outlineNone, _s.bgTransparent, _s.cursorPointer, _s.px2, _s.py2].join(' ')}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
height={result.media[0].tinygif.dims[1]}
|
height={result.media[0].tinygif.dims[1]}
|
||||||
|
@ -196,9 +198,11 @@ class GifResultsCollectionColumn extends PureComponent {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class GifResultsCollection extends PureComponent {
|
class GifResultsCollection extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
results: PropTypes.array.isRequired,
|
results: PropTypes.array.isRequired,
|
||||||
handleSelectGifResult: PropTypes.func.isRequired,
|
handleSelectGifResult: PropTypes.func.isRequired,
|
||||||
|
@ -250,7 +254,7 @@ class GifCategoriesCollection extends PureComponent {
|
||||||
<button
|
<button
|
||||||
key={`gif-category-${i}`}
|
key={`gif-category-${i}`}
|
||||||
onClick={() => this.onClick(category.searchterm)}
|
onClick={() => this.onClick(category.searchterm)}
|
||||||
className={[_s.default, _s.px2, _s.py2, _s.width50PC].join(' ')}
|
className={[_s.default, _s.outlineNone, _s.bgTransparent, _s.px2, _s.py2, _s.width50PC].join(' ')}
|
||||||
>
|
>
|
||||||
<div className={[_s.default, _s.cursorPointer].join(' ')}>
|
<div className={[_s.default, _s.cursorPointer].join(' ')}>
|
||||||
<Image
|
<Image
|
||||||
|
@ -269,4 +273,5 @@ class GifCategoriesCollection extends PureComponent {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -158,19 +158,23 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
pagination = media.map((item, i) => {
|
pagination = media.map((item, i) => {
|
||||||
const btnClasses = CX({
|
const btnClasses = CX({
|
||||||
default: 1,
|
default: 1,
|
||||||
px5: 1,
|
width10PX: 1,
|
||||||
py5: 1,
|
height10PX: 1,
|
||||||
outlineNone: 1,
|
outlineNone: 1,
|
||||||
colorPrimary: 1,
|
|
||||||
circle: 1,
|
circle: 1,
|
||||||
cursorPointer: 1,
|
cursorPointer: 1,
|
||||||
|
colorPrimary: i === index,
|
||||||
|
lineHeight0825: i === index,
|
||||||
bgPrimaryOpaque: i !== index,
|
bgPrimaryOpaque: i !== index,
|
||||||
bgPrimary: i === index,
|
bgPrimary: i === index,
|
||||||
})
|
})
|
||||||
|
const activeText = i === index ? '•' : ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={[_s.default, _s.px5].join(' ')} key={`media-pagination-${i}`}>
|
<li className={[_s.default, _s.px5].join(' ')} key={`media-pagination-${i}`}>
|
||||||
<button tabIndex='0' className={btnClasses} onClick={this.handleChangeIndex} data-index={i} />
|
<button tabIndex='0' className={btnClasses} onClick={this.handleChangeIndex} data-index={i}>
|
||||||
|
{activeText}
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -234,6 +238,9 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
const swipeableViewsStyle = {
|
const swipeableViewsStyle = {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigationClasses = CX({
|
const navigationClasses = CX({
|
||||||
|
@ -244,7 +251,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
return (
|
return (
|
||||||
<div className={[_s.default, _s.width100PC, _s.height100PC, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
|
<div className={[_s.default, _s.width100PC, _s.height100PC, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
|
||||||
<div
|
<div
|
||||||
className={[_s.default, _s.top0, _s.right0, _s.bottom0, _s.left0].join(' ')}
|
className={[_s.default, _s.posAbs, _s.top0, _s.right0, _s.bottom0, _s.left0].join(' ')}
|
||||||
role='presentation'
|
role='presentation'
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
|
@ -252,6 +259,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
style={swipeableViewsStyle}
|
style={swipeableViewsStyle}
|
||||||
containerStyle={{
|
containerStyle={{
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
onChangeIndex={this.handleSwipe}
|
onChangeIndex={this.handleSwipe}
|
||||||
onSwitching={this.handleSwitching}
|
onSwitching={this.handleSwitching}
|
||||||
|
@ -286,7 +294,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul className={[_s.default, _s.posAbsolute, _s.bottom0, _s.mb15, _s.flexRow, _s.bgBlackOpaque, _s.circle, _s.py10, _s.px15, _s.listStyleNone].join(' ')}>
|
<ul className={[_s.default, _s.posAbs, _s.bottom0, _s.mb15, _s.flexRow, _s.bgBlackOpaque, _s.circle, _s.py10, _s.px15, _s.listStyleNone].join(' ')}>
|
||||||
{pagination}
|
{pagination}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -135,7 +135,7 @@ class ModalRoot extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLoading = () => {
|
renderLoading = () => {
|
||||||
return <ModalLoading />
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
renderError = () => {
|
renderError = () => {
|
||||||
|
|
|
@ -45,12 +45,7 @@ class ProUpgradeModal extends ImmutablePureComponent {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
backgroundColor='brand'
|
|
||||||
color='white'
|
|
||||||
icon='pro'
|
|
||||||
href='https://pro.gab.com'
|
href='https://pro.gab.com'
|
||||||
className={_s.justifyContentCenter}
|
|
||||||
iconClassName={[_s.mr5, _s.fillWhite].join(' ')}
|
|
||||||
>
|
>
|
||||||
<Text color='inherit' weight='bold' align='center'>
|
<Text color='inherit' weight='bold' align='center'>
|
||||||
{intl.formatMessage(messages.title)}
|
{intl.formatMessage(messages.title)}
|
||||||
|
|
|
@ -56,8 +56,8 @@ class ReportModal extends ImmutablePureComponent {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCommentChange = e => {
|
handleCommentChange = (e) => {
|
||||||
this.props.dispatch(changeReportComment(e.target.value))
|
this.props.dispatch(changeReportComment(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
handleForwardChange = e => {
|
handleForwardChange = e => {
|
||||||
|
|
|
@ -75,9 +75,11 @@ class Poll extends ImmutablePureComponent {
|
||||||
const { selected } = this.state
|
const { selected } = this.state
|
||||||
const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100
|
const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100
|
||||||
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'))
|
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'))
|
||||||
|
const optionHasNoVotes = option.get('votes_count') === 0
|
||||||
const active = !!selected[`${optionIndex}`]
|
const active = !!selected[`${optionIndex}`]
|
||||||
const showResults = poll.get('voted') || poll.get('expired')
|
const showResults = poll.get('voted') || poll.get('expired')
|
||||||
const multiple = poll.get('multiple')
|
const multiple = poll.get('multiple')
|
||||||
|
const correctedWidthPercent = optionHasNoVotes ? 100 : percent
|
||||||
|
|
||||||
let titleEmojified = option.get('title_emojified')
|
let titleEmojified = option.get('title_emojified')
|
||||||
if (!titleEmojified) {
|
if (!titleEmojified) {
|
||||||
|
@ -92,10 +94,12 @@ class Poll extends ImmutablePureComponent {
|
||||||
left0: 1,
|
left0: 1,
|
||||||
radiusSmall: 1,
|
radiusSmall: 1,
|
||||||
height100PC: 1,
|
height100PC: 1,
|
||||||
bgSecondary: !leading,
|
bgSecondary: !leading && !optionHasNoVotes,
|
||||||
|
bgTertiary: !leading && optionHasNoVotes,
|
||||||
bgBrandLight: leading,
|
bgBrandLight: leading,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// : todo :
|
||||||
const inputClasses = cx('poll__input', {
|
const inputClasses = cx('poll__input', {
|
||||||
'poll__input--checkbox': multiple,
|
'poll__input--checkbox': multiple,
|
||||||
'poll__input--active': active,
|
'poll__input--active': active,
|
||||||
|
@ -107,7 +111,7 @@ class Poll extends ImmutablePureComponent {
|
||||||
py10: showResults,
|
py10: showResults,
|
||||||
mb10: 1,
|
mb10: 1,
|
||||||
border1PX: !showResults,
|
border1PX: !showResults,
|
||||||
fillSecondary: !showResults,
|
borderColorSecondary: !showResults,
|
||||||
circle: !showResults,
|
circle: !showResults,
|
||||||
cursorPointer: !showResults,
|
cursorPointer: !showResults,
|
||||||
bgSubtle_onHover: !showResults,
|
bgSubtle_onHover: !showResults,
|
||||||
|
@ -127,7 +131,7 @@ class Poll extends ImmutablePureComponent {
|
||||||
<li className={listItemClasses} key={option.get('title')}>
|
<li className={listItemClasses} key={option.get('title')}>
|
||||||
{
|
{
|
||||||
showResults && (
|
showResults && (
|
||||||
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
|
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(correctedWidthPercent, { stiffness: 180, damping: 24 }) }}>
|
||||||
{({ width }) =>
|
{({ width }) =>
|
||||||
<span className={chartClasses} style={{ width: `${width}%` }} />
|
<span className={chartClasses} style={{ width: `${width}%` }} />
|
||||||
}
|
}
|
||||||
|
@ -139,7 +143,7 @@ class Poll extends ImmutablePureComponent {
|
||||||
<Text
|
<Text
|
||||||
size='medium'
|
size='medium'
|
||||||
color='primary'
|
color='primary'
|
||||||
weight={leading ? 'bold' : 'normal'}
|
weight={(leading && showResults) ? 'bold' : 'normal'}
|
||||||
className={[_s.displayFlex, _s.flexRow, _s.width100PC, _s.alignItemsCenter].join(' ')}
|
className={[_s.displayFlex, _s.flexRow, _s.width100PC, _s.alignItemsCenter].join(' ')}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
import DatePicker from 'react-datepicker'
|
import DatePicker from 'react-datepicker'
|
||||||
|
import { FormattedMessage } from 'react-intl'
|
||||||
|
import moment from 'moment-mini'
|
||||||
import { changeScheduledAt } from '../../actions/compose'
|
import { changeScheduledAt } from '../../actions/compose'
|
||||||
|
import { openModal } from '../../actions/modal'
|
||||||
|
import { closePopover } from '../../actions/popover'
|
||||||
import { me } from '../../initial_state'
|
import { me } from '../../initial_state'
|
||||||
|
import {
|
||||||
|
MODAL_PRO_UPGRADE,
|
||||||
|
} from '../../constants'
|
||||||
import { isMobile } from '../../utils/is_mobile'
|
import { isMobile } from '../../utils/is_mobile'
|
||||||
import PopoverLayout from './popover_layout'
|
import PopoverLayout from './popover_layout'
|
||||||
|
import Button from '../button'
|
||||||
|
import Text from '../text'
|
||||||
|
|
||||||
import '!style-loader!css-loader!react-datepicker/dist/react-datepicker.css'
|
import '!style-loader!css-loader!react-datepicker/dist/react-datepicker.css'
|
||||||
|
|
||||||
|
@ -11,9 +20,18 @@ const mapStateToProps = (state) => ({
|
||||||
isPro: state.getIn(['accounts', me, 'is_pro']),
|
isPro: state.getIn(['accounts', me, 'is_pro']),
|
||||||
})
|
})
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch, { isPro }) => ({
|
||||||
setScheduledAt (date) {
|
setScheduledAt (date) {
|
||||||
|
if (!isPro) {
|
||||||
|
dispatch(closePopover())
|
||||||
|
return dispatch(openModal(MODAL_PRO_UPGRADE))
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(changeScheduledAt(date))
|
dispatch(changeScheduledAt(date))
|
||||||
|
|
||||||
|
if (!date) {
|
||||||
|
dispatch(closePopover())
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -32,15 +50,19 @@ class DatePickerPopover extends PureComponent {
|
||||||
this.props.setScheduledAt(date)
|
this.props.setScheduledAt(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
handleRemoveDate = () => {
|
||||||
const { date, isPro, position } = this.props
|
this.props.setScheduledAt(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { date, isPro } = this.props
|
||||||
|
|
||||||
const open = !!date
|
|
||||||
const datePickerDisabled = !isPro
|
const datePickerDisabled = !isPro
|
||||||
const withPortal = isMobile(window.innerWidth)
|
const withPortal = isMobile(window.innerWidth)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopoverLayout width={331}>
|
<PopoverLayout width={331}>
|
||||||
|
<div className={[_s.default].join(' ')}>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
inline
|
inline
|
||||||
target={this}
|
target={this}
|
||||||
|
@ -67,6 +89,30 @@ class DatePickerPopover extends PureComponent {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
date &&
|
||||||
|
<div className={[_s.default, _s.alignItemsCenter, _s.flexRow, _s.px10, _s.py10, _s.borderTop1PX, _s.borderColorSecondary].join(' ')}>
|
||||||
|
<Text size='extraSmall' color='secondary'>
|
||||||
|
<FormattedMessage id='scheduled_for_datetime' defaultMessage='Scheduled for {datetime}' values={{
|
||||||
|
datetime: moment.utc(date).format('lll'),
|
||||||
|
}}/>
|
||||||
|
</Text>
|
||||||
|
<div className={_s.mlAuto}>
|
||||||
|
<Button
|
||||||
|
isNarrow
|
||||||
|
radiusSmall
|
||||||
|
color='primary'
|
||||||
|
backgroundColor='tertiary'
|
||||||
|
onClick={this.handleRemoveDate}
|
||||||
|
>
|
||||||
|
<Text color='inherit' size='small'>
|
||||||
|
<FormattedMessage id='remove' defaultMessage='Remove' />
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</PopoverLayout>
|
</PopoverLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import detectPassiveEvents from 'detect-passive-events'
|
||||||
import { changeSetting } from '../../actions/settings'
|
import { changeSetting } from '../../actions/settings'
|
||||||
import { useEmoji } from '../../actions/emojis'
|
import { useEmoji } from '../../actions/emojis'
|
||||||
import { closePopover } from '../../actions/popover'
|
import { closePopover } from '../../actions/popover'
|
||||||
|
import { insertEmojiCompose } from '../../actions/compose'
|
||||||
import { EmojiPicker as EmojiPickerAsync } from '../../features/ui/util/async_components'
|
import { EmojiPicker as EmojiPickerAsync } from '../../features/ui/util/async_components'
|
||||||
import { buildCustomEmojis } from '../emoji/emoji'
|
import { buildCustomEmojis } from '../emoji/emoji'
|
||||||
import PopoverLayout from './popover_layout'
|
import PopoverLayout from './popover_layout'
|
||||||
|
@ -209,21 +210,19 @@ const mapStateToProps = (state) => ({
|
||||||
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
|
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
|
||||||
})
|
})
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
onClosePopover() {
|
onClosePopover() {
|
||||||
dispatch(closePopover())
|
dispatch(closePopover())
|
||||||
},
|
},
|
||||||
|
|
||||||
onSkinTone: skinTone => {
|
onSkinTone: (skinTone) => {
|
||||||
dispatch(changeSetting(['skinTone'], skinTone))
|
dispatch(changeSetting(['skinTone'], skinTone))
|
||||||
},
|
},
|
||||||
|
|
||||||
onPickEmoji: emoji => {
|
onPickEmoji: (emoji) => {
|
||||||
dispatch(useEmoji(emoji))
|
dispatch(useEmoji(emoji))
|
||||||
|
console.log("emoji:", emoji)
|
||||||
if (onPickEmoji) {
|
dispatch(insertEmojiCompose(0, emoji, false))
|
||||||
onPickEmoji(emoji)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -57,12 +57,6 @@ class PopoverRoot extends PureComponent {
|
||||||
props: PropTypes.object,
|
props: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
getSnapshotBeforeUpdate() {
|
|
||||||
return {
|
|
||||||
visible: !!this.props.type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderEmpty = () => {
|
renderEmpty = () => {
|
||||||
return <div />
|
return <div />
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,7 +197,7 @@ export default class ScrollableList extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoadMore = (e) => {
|
handleLoadMore = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
this.props.onLoadMore();
|
this.props.onLoadMore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,10 @@ export default
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class Sidebar extends ImmutablePureComponent {
|
class Sidebar extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
router: PropTypes.object,
|
||||||
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
|
@ -94,6 +98,18 @@ class Sidebar extends ImmutablePureComponent {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
historyBack = () => {
|
||||||
|
if (window.history && window.history.length === 1) {
|
||||||
|
this.context.router.history.push('/home')
|
||||||
|
} else {
|
||||||
|
this.context.router.history.goBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBackClick = () => {
|
||||||
|
this.historyBack()
|
||||||
|
}
|
||||||
|
|
||||||
setMoreButtonRef = n => {
|
setMoreButtonRef = n => {
|
||||||
this.moreBtnRef = n
|
this.moreBtnRef = n
|
||||||
}
|
}
|
||||||
|
@ -213,7 +229,17 @@ class Sidebar extends ImmutablePureComponent {
|
||||||
<div className={_s.default}>
|
<div className={_s.default}>
|
||||||
{
|
{
|
||||||
!!title &&
|
!!title &&
|
||||||
<div className={[_s.default, _s.px5, _s.py10].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.px5, _s.py10].join(' ')}>
|
||||||
|
<Button
|
||||||
|
noClasses
|
||||||
|
color='primary'
|
||||||
|
backgroundColor='none'
|
||||||
|
className={[_s.alignItemsCenter, _s.bgTransparent, _s.mr5, _s.cursorPointer, _s.outlineNone, _s.default, _s.justifyContentCenter].join(' ')}
|
||||||
|
icon='back'
|
||||||
|
iconSize='20px'
|
||||||
|
iconClassName={[_s.mr5, _s.fillPrimary].join(' ')}
|
||||||
|
onClick={this.handleBackClick}
|
||||||
|
/>
|
||||||
<Heading size='h1'>
|
<Heading size='h1'>
|
||||||
{title}
|
{title}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import ComposeFormContainer from '../features/compose/containers/compose_form_co
|
||||||
import StatusContent from './status_content'
|
import StatusContent from './status_content'
|
||||||
import StatusPrepend from './status_prepend'
|
import StatusPrepend from './status_prepend'
|
||||||
import StatusActionBar from './status_action_bar'
|
import StatusActionBar from './status_action_bar'
|
||||||
|
import StatusMedia from './status_media'
|
||||||
import Poll from './poll'
|
import Poll from './poll'
|
||||||
import StatusHeader from './status_header'
|
import StatusHeader from './status_header'
|
||||||
import CommentList from './comment_list'
|
import CommentList from './comment_list'
|
||||||
|
@ -87,6 +88,7 @@ class Status extends ImmutablePureComponent {
|
||||||
cacheMediaWidth: PropTypes.func,
|
cacheMediaWidth: PropTypes.func,
|
||||||
cachedMediaWidth: PropTypes.number,
|
cachedMediaWidth: PropTypes.number,
|
||||||
contextType: PropTypes.string,
|
contextType: PropTypes.string,
|
||||||
|
commentsLimited: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid checking props that are functions (and whose equality will always
|
// Avoid checking props that are functions (and whose equality will always
|
||||||
|
@ -124,7 +126,7 @@ class Status extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromProps(nextProps, prevState) {
|
static getDerivedStateFromProps(nextProps, prevState) {
|
||||||
if (!nextProps.isHidden && nextProps.isIntersecting && !prevState.loadedComments) {
|
if (!nextProps.isHidden && (nextProps.isIntersecting || !nextProps.commentsLimited) && !prevState.loadedComments) {
|
||||||
return {
|
return {
|
||||||
loadedComments: true
|
loadedComments: true
|
||||||
}
|
}
|
||||||
|
@ -132,6 +134,7 @@ class Status extends ImmutablePureComponent {
|
||||||
|
|
||||||
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
|
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
|
||||||
return {
|
return {
|
||||||
|
loadedComments: false,
|
||||||
showMedia: defaultMediaVisibility(nextProps.status),
|
showMedia: defaultMediaVisibility(nextProps.status),
|
||||||
statusId: nextProps.status.get('id'),
|
statusId: nextProps.status.get('id'),
|
||||||
}
|
}
|
||||||
|
@ -142,6 +145,7 @@ class Status extends ImmutablePureComponent {
|
||||||
|
|
||||||
// Compensate height changes
|
// Compensate height changes
|
||||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
|
// timeline lazy loading comments
|
||||||
if (!prevState.loadedComments && this.state.loadedComments && this.props.status) {
|
if (!prevState.loadedComments && this.state.loadedComments && this.props.status) {
|
||||||
const commentCount = this.props.status.get('replies_count')
|
const commentCount = this.props.status.get('replies_count')
|
||||||
if (commentCount > 0) {
|
if (commentCount > 0) {
|
||||||
|
@ -163,7 +167,7 @@ class Status extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMoveUp = id => {
|
handleMoveUp = (id) => {
|
||||||
const { status, ancestorsIds, descendantsIds } = this.props
|
const { status, ancestorsIds, descendantsIds } = this.props
|
||||||
|
|
||||||
if (id === status.get('id')) {
|
if (id === status.get('id')) {
|
||||||
|
@ -347,7 +351,7 @@ class Status extends ImmutablePureComponent {
|
||||||
|
|
||||||
if (!status) return null
|
if (!status) return null
|
||||||
|
|
||||||
let media, reblogContent, rebloggedByText = null
|
let reblogContent, rebloggedByText = null
|
||||||
|
|
||||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||||
rebloggedByText = intl.formatMessage(
|
rebloggedByText = intl.formatMessage(
|
||||||
|
@ -386,62 +390,6 @@ class Status extends ImmutablePureComponent {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.get('poll')) {
|
|
||||||
media = <Poll pollId={status.get('poll')} />
|
|
||||||
} else if (status.get('media_attachments').size > 0) {
|
|
||||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
|
||||||
const video = status.getIn(['media_attachments', 0])
|
|
||||||
|
|
||||||
media = (
|
|
||||||
<Bundle fetchComponent={Video} loading={this.renderLoadingMedia}>
|
|
||||||
{Component => (
|
|
||||||
<Component
|
|
||||||
inline
|
|
||||||
preview={video.get('preview_url')}
|
|
||||||
blurhash={video.get('blurhash')}
|
|
||||||
src={video.get('url')}
|
|
||||||
alt={video.get('description')}
|
|
||||||
aspectRatio={video.getIn(['meta', 'small', 'aspect'])}
|
|
||||||
width={this.props.cachedMediaWidth}
|
|
||||||
height={110}
|
|
||||||
sensitive={status.get('sensitive')}
|
|
||||||
onOpenVideo={this.handleOpenVideo}
|
|
||||||
cacheWidth={this.props.cacheMediaWidth}
|
|
||||||
visible={this.state.showMedia}
|
|
||||||
onToggleVisibility={this.handleToggleMediaVisibility}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Bundle>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
media = (
|
|
||||||
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMedia}>
|
|
||||||
{Component => (
|
|
||||||
<Component
|
|
||||||
reduced={isChild}
|
|
||||||
media={status.get('media_attachments')}
|
|
||||||
sensitive={status.get('sensitive')}
|
|
||||||
onOpenMedia={this.props.onOpenMedia}
|
|
||||||
cacheWidth={this.props.cacheMediaWidth}
|
|
||||||
defaultWidth={this.props.cachedMediaWidth}
|
|
||||||
visible={this.state.showMedia}
|
|
||||||
onToggleVisibility={this.handleToggleMediaVisibility}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Bundle>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
|
|
||||||
media = (
|
|
||||||
<StatusCard
|
|
||||||
onOpenMedia={this.props.onOpenMedia}
|
|
||||||
card={status.get('card')}
|
|
||||||
cacheWidth={this.props.cacheMediaWidth}
|
|
||||||
defaultWidth={this.props.cachedMediaWidth}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const containerClasses = cx({
|
const containerClasses = cx({
|
||||||
default: 1,
|
default: 1,
|
||||||
pb15: isFeatured,
|
pb15: isFeatured,
|
||||||
|
@ -476,7 +424,7 @@ class Status extends ImmutablePureComponent {
|
||||||
data-featured={isFeatured ? 'true' : null}
|
data-featured={isFeatured ? 'true' : null}
|
||||||
aria-label={textForScreenReader(intl, status, rebloggedByText)}
|
aria-label={textForScreenReader(intl, status, rebloggedByText)}
|
||||||
ref={this.handleRef}
|
ref={this.handleRef}
|
||||||
// onClick={this.handleClick}
|
onClick={isChild ? this.handleClick : undefined}
|
||||||
>
|
>
|
||||||
<div className={innerContainerClasses}>
|
<div className={innerContainerClasses}>
|
||||||
|
|
||||||
|
@ -497,7 +445,17 @@ class Status extends ImmutablePureComponent {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{media}
|
<StatusMedia
|
||||||
|
isChild={isChild}
|
||||||
|
status={status}
|
||||||
|
onOpenMedia={this.props.onOpenMedia}
|
||||||
|
cacheWidth={this.props.cacheMediaWidth}
|
||||||
|
defaultWidth={this.props.cachedMediaWidth}
|
||||||
|
visible={this.state.showMedia}
|
||||||
|
onToggleVisibility={this.handleToggleMediaVisibility}
|
||||||
|
width={this.props.cachedMediaWidth}
|
||||||
|
onOpenVideo={this.handleOpenVideo}
|
||||||
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!status.get('quote') && !isChild &&
|
!!status.get('quote') && !isChild &&
|
||||||
|
|
|
@ -42,7 +42,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
updateOnProps = ['status']
|
updateOnProps = ['status']
|
||||||
|
|
||||||
handleReplyClick = () => {
|
handleReplyClick = () => {
|
||||||
this.props.onReply(this.props.status)
|
this.props.onReply(this.props.status, null, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFavoriteClick = () => {
|
handleFavoriteClick = () => {
|
||||||
|
|
|
@ -10,8 +10,8 @@ import Text from './text'
|
||||||
const MAX_HEIGHT = 200
|
const MAX_HEIGHT = 200
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
showMore: { id: 'status.show_more', defaultMessage: 'Show more' },
|
show: { id: 'status.show_more', defaultMessage: 'Show' },
|
||||||
showLess: { id: 'status.show_less', defaultMessage: 'Show less' },
|
hide: { id: 'status.show_less', defaultMessage: 'Hide' },
|
||||||
readMore: { id: 'status.read_more', defaultMessage: 'Read more' },
|
readMore: { id: 'status.read_more', defaultMessage: 'Read more' },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@ class StatusContent extends ImmutablePureComponent {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleText = intl.formatMessage(hidden ? messages.showMore : messages.showLess)
|
const toggleText = intl.formatMessage(hidden ? messages.show : messages.hide)
|
||||||
|
|
||||||
const spoilerContainerClasses = cx({
|
const spoilerContainerClasses = cx({
|
||||||
default: 1,
|
default: 1,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Fragment } from 'react'
|
import { Fragment } from 'react'
|
||||||
|
import { injectIntl, defineMessages } from 'react-intl'
|
||||||
import { NavLink } from 'react-router-dom'
|
import { NavLink } from 'react-router-dom'
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
|
@ -14,6 +15,14 @@ import Icon from './icon'
|
||||||
import Button from './button'
|
import Button from './button'
|
||||||
import Avatar from './avatar'
|
import Avatar from './avatar'
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||||
|
public_long: { id: 'privacy.public.long', defaultMessage: 'Visible for anyone on or off Gab' },
|
||||||
|
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||||
|
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Do not show in public timelines' },
|
||||||
|
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for your followers only' },
|
||||||
|
})
|
||||||
|
|
||||||
const cx = classNames.bind(_s)
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
@ -33,10 +42,12 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
})
|
})
|
||||||
|
|
||||||
export default
|
export default
|
||||||
|
@injectIntl
|
||||||
@connect(null, mapDispatchToProps)
|
@connect(null, mapDispatchToProps)
|
||||||
class StatusHeader extends ImmutablePureComponent {
|
class StatusHeader extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
onOpenStatusRevisionsPopover: PropTypes.func.isRequired,
|
onOpenStatusRevisionsPopover: PropTypes.func.isRequired,
|
||||||
onOpenStatusOptionsPopover: PropTypes.func.isRequired,
|
onOpenStatusOptionsPopover: PropTypes.func.isRequired,
|
||||||
|
@ -56,7 +67,11 @@ class StatusHeader extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { status, reduced } = this.props
|
const {
|
||||||
|
intl,
|
||||||
|
reduced,
|
||||||
|
status,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
const statusUrl = `/${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`
|
const statusUrl = `/${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`
|
||||||
|
|
||||||
|
@ -68,7 +83,22 @@ class StatusHeader extends ImmutablePureComponent {
|
||||||
})
|
})
|
||||||
|
|
||||||
const avatarSize = reduced ? 20 : 46
|
const avatarSize = reduced ? 20 : 46
|
||||||
const visibilityIcon = 'globe'
|
|
||||||
|
const visibility = status.get('visibility')
|
||||||
|
|
||||||
|
let visibilityIcon
|
||||||
|
let visibilityText
|
||||||
|
|
||||||
|
if (visibility === 'private') {
|
||||||
|
visibilityIcon = 'lock-filled'
|
||||||
|
visibilityText = intl.formatMessage(messages.private_long)
|
||||||
|
} else if (visibility === 'unlisted') {
|
||||||
|
visibilityIcon = 'unlock-filled'
|
||||||
|
visibilityText = `${intl.formatMessage(messages.unlisted_short)} - ${intl.formatMessage(messages.unlisted_long)}`
|
||||||
|
} else {
|
||||||
|
visibilityIcon = 'globe'
|
||||||
|
visibilityText = `${intl.formatMessage(messages.public_short)} - ${intl.formatMessage(messages.public_long)}`
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
|
@ -127,7 +157,9 @@ class StatusHeader extends ImmutablePureComponent {
|
||||||
|
|
||||||
<DotTextSeperator />
|
<DotTextSeperator />
|
||||||
|
|
||||||
<Icon id={visibilityIcon} size='12px' className={[_s.default, _s.displayInline, _s.ml5, _s.fillSecondary].join(' ')} />
|
<span title={visibilityText} className={[_s.default, _s.displayInline, _s.ml5].join(' ')}>
|
||||||
|
<Icon id={visibilityIcon} size='12px' className={[_s.default, _s.fillSecondary].join(' ')} />
|
||||||
|
</span>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!status.get('group') &&
|
!!status.get('group') &&
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
import { injectIntl } from 'react-intl'
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
|
import StatusCard from './status_card'
|
||||||
|
import { MediaGallery, Video } from '../features/ui/util/async_components'
|
||||||
|
import Poll from './poll'
|
||||||
|
|
||||||
|
// We use the component (and not the container) since we do not want
|
||||||
|
// to use the progress bar to show download progress
|
||||||
|
import Bundle from '../features/ui/util/bundle'
|
||||||
|
|
||||||
|
export default class StatusMedia extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
status: ImmutablePropTypes.map,
|
||||||
|
isChild: PropTypes.bool,
|
||||||
|
isComment: PropTypes.bool,
|
||||||
|
onOpenMedia: PropTypes.func,
|
||||||
|
onOpenVideo: PropTypes.func,
|
||||||
|
width: PropTypes.number,
|
||||||
|
onToggleVisibility: PropTypes.func,
|
||||||
|
visible: PropTypes.bool,
|
||||||
|
defaultWidth: PropTypes.number,
|
||||||
|
cacheWidth: PropTypes.number,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid checking props that are functions (and whose equality will always
|
||||||
|
// evaluate to false. See react-immutable-pure-component for usage.
|
||||||
|
updateOnProps = [
|
||||||
|
'status',
|
||||||
|
'isChild',
|
||||||
|
'isComment',
|
||||||
|
'cacheWidth',
|
||||||
|
'defaultWidth',
|
||||||
|
'visible',
|
||||||
|
'width',
|
||||||
|
]
|
||||||
|
|
||||||
|
renderLoadingMedia() {
|
||||||
|
return <div className={_s.backgroundColorPanel} style={{ height: '110px' }} />
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
status,
|
||||||
|
isChild,
|
||||||
|
isComment,
|
||||||
|
onOpenMedia,
|
||||||
|
onOpenVideo,
|
||||||
|
width,
|
||||||
|
onToggleVisibility,
|
||||||
|
visible,
|
||||||
|
defaultWidth,
|
||||||
|
cacheWidth,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
if (!status) return null
|
||||||
|
|
||||||
|
let media = null
|
||||||
|
|
||||||
|
if (status.get('poll')) {
|
||||||
|
media = <Poll pollId={status.get('poll')} />
|
||||||
|
} else if (status.get('media_attachments').size > 0) {
|
||||||
|
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||||
|
const video = status.getIn(['media_attachments', 0])
|
||||||
|
|
||||||
|
media = (
|
||||||
|
<Bundle fetchComponent={Video} loading={this.renderLoadingMedia}>
|
||||||
|
{Component => (
|
||||||
|
<Component
|
||||||
|
inline
|
||||||
|
preview={video.get('preview_url')}
|
||||||
|
blurhash={video.get('blurhash')}
|
||||||
|
src={video.get('url')}
|
||||||
|
alt={video.get('description')}
|
||||||
|
aspectRatio={video.getIn(['meta', 'small', 'aspect'])}
|
||||||
|
sensitive={status.get('sensitive')}
|
||||||
|
height={110}
|
||||||
|
width={width}
|
||||||
|
onOpenVideo={onOpenVideo}
|
||||||
|
cacheWidth={cacheWidth}
|
||||||
|
visible={visible}
|
||||||
|
onToggleVisibility={onToggleVisibility}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Bundle>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
media = (
|
||||||
|
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMedia}>
|
||||||
|
{Component => (
|
||||||
|
<Component
|
||||||
|
reduced={isChild}
|
||||||
|
media={status.get('media_attachments')}
|
||||||
|
sensitive={status.get('sensitive')}
|
||||||
|
onOpenMedia={onOpenMedia}
|
||||||
|
cacheWidth={cacheWidth}
|
||||||
|
defaultWidth={defaultWidth}
|
||||||
|
visible={visible}
|
||||||
|
onToggleVisibility={onToggleVisibility}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Bundle>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
|
||||||
|
media = (
|
||||||
|
<StatusCard
|
||||||
|
card={status.get('card')}
|
||||||
|
onOpenMedia={onOpenMedia}
|
||||||
|
cacheWidth={cacheWidth}
|
||||||
|
defaultWidth={defaultWidth}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return media
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,30 +1,18 @@
|
||||||
const MIN_SCALE = 1;
|
const MIN_SCALE = 1
|
||||||
const MAX_SCALE = 4;
|
const MAX_SCALE = 4
|
||||||
|
|
||||||
const getMidpoint = (p1, p2) => ({
|
const getMidpoint = (p1, p2) => ({
|
||||||
x: (p1.clientX + p2.clientX) / 2,
|
x: (p1.clientX + p2.clientX) / 2,
|
||||||
y: (p1.clientY + p2.clientY) / 2,
|
y: (p1.clientY + p2.clientY) / 2,
|
||||||
});
|
})
|
||||||
|
|
||||||
const getDistance = (p1, p2) =>
|
const getDistance = (p1, p2) => {
|
||||||
Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2));
|
return Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2))
|
||||||
|
}
|
||||||
|
|
||||||
const clamp = (min, max, value) => Math.min(max, Math.max(min, value));
|
const clamp = (min, max, value) => {
|
||||||
|
return Math.min(max, Math.max(min, value))
|
||||||
// .zoomable-image {
|
}
|
||||||
// position: relative;
|
|
||||||
|
|
||||||
// @include flex(center, center);
|
|
||||||
// @include size(100%);
|
|
||||||
|
|
||||||
// img {
|
|
||||||
// object-fit: contain;
|
|
||||||
|
|
||||||
// @include size(auto);
|
|
||||||
// @include max-size($media-modal-media-max-width, $media-modal-media-max-height);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// : todo :
|
|
||||||
|
|
||||||
export default class ZoomableImage extends PureComponent {
|
export default class ZoomableImage extends PureComponent {
|
||||||
|
|
||||||
|
@ -40,69 +28,69 @@ export default class ZoomableImage extends PureComponent {
|
||||||
alt: '',
|
alt: '',
|
||||||
width: null,
|
width: null,
|
||||||
height: null,
|
height: null,
|
||||||
};
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
scale: MIN_SCALE,
|
scale: MIN_SCALE,
|
||||||
}
|
}
|
||||||
|
|
||||||
removers = [];
|
removers = []
|
||||||
container = null;
|
container = null
|
||||||
image = null;
|
image = null
|
||||||
lastTouchEndTime = 0;
|
lastTouchEndTime = 0
|
||||||
lastDistance = 0;
|
lastDistance = 0
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
let handler = this.handleTouchStart;
|
let handler = this.handleTouchStart
|
||||||
this.container.addEventListener('touchstart', handler);
|
this.container.addEventListener('touchstart', handler)
|
||||||
this.removers.push(() => this.container.removeEventListener('touchstart', handler));
|
this.removers.push(() => this.container.removeEventListener('touchstart', handler))
|
||||||
handler = this.handleTouchMove;
|
handler = this.handleTouchMove
|
||||||
// on Chrome 56+, touch event listeners will default to passive
|
// on Chrome 56+, touch event listeners will default to passive
|
||||||
// https://www.chromestatus.com/features/5093566007214080
|
// https://www.chromestatus.com/features/5093566007214080
|
||||||
this.container.addEventListener('touchmove', handler, { passive: false });
|
this.container.addEventListener('touchmove', handler, { passive: false })
|
||||||
this.removers.push(() => this.container.removeEventListener('touchend', handler));
|
this.removers.push(() => this.container.removeEventListener('touchend', handler))
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this.removeEventListeners();
|
this.removeEventListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
removeEventListeners () {
|
removeEventListeners () {
|
||||||
this.removers.forEach(listeners => listeners());
|
this.removers.forEach(listeners => listeners())
|
||||||
this.removers = [];
|
this.removers = []
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTouchStart = e => {
|
handleTouchStart = e => {
|
||||||
if (e.touches.length !== 2) return;
|
if (e.touches.length !== 2) return
|
||||||
|
|
||||||
this.lastDistance = getDistance(...e.touches);
|
this.lastDistance = getDistance(...e.touches)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTouchMove = e => {
|
handleTouchMove = e => {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = this.container;
|
const { scrollTop, scrollHeight, clientHeight } = this.container
|
||||||
if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) {
|
if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) {
|
||||||
// prevent propagating event to MediaModal
|
// prevent propagating event to MediaModal
|
||||||
e.stopPropagation();
|
e.stopPropagation()
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
if (e.touches.length !== 2) return;
|
if (e.touches.length !== 2) return
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
e.stopPropagation();
|
e.stopPropagation()
|
||||||
|
|
||||||
const distance = getDistance(...e.touches);
|
const distance = getDistance(...e.touches)
|
||||||
const midpoint = getMidpoint(...e.touches);
|
const midpoint = getMidpoint(...e.touches)
|
||||||
const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance);
|
const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance)
|
||||||
|
|
||||||
this.zoom(scale, midpoint);
|
this.zoom(scale, midpoint)
|
||||||
|
|
||||||
this.lastMidpoint = midpoint;
|
this.lastMidpoint = midpoint
|
||||||
this.lastDistance = distance;
|
this.lastDistance = distance
|
||||||
}
|
}
|
||||||
|
|
||||||
zoom(nextScale, midpoint) {
|
zoom(nextScale, midpoint) {
|
||||||
const { scale } = this.state;
|
const { scale } = this.state
|
||||||
const { scrollLeft, scrollTop } = this.container;
|
const { scrollLeft, scrollTop } = this.container
|
||||||
|
|
||||||
// math memo:
|
// math memo:
|
||||||
// x = (scrollLeft + midpoint.x) / scrollWidth
|
// x = (scrollLeft + midpoint.x) / scrollWidth
|
||||||
|
@ -110,38 +98,44 @@ export default class ZoomableImage extends PureComponent {
|
||||||
// scrollWidth = clientWidth * scale
|
// scrollWidth = clientWidth * scale
|
||||||
// scrollWidth' = clientWidth * nextScale
|
// scrollWidth' = clientWidth * nextScale
|
||||||
// Solve x = x' for nextScrollLeft
|
// Solve x = x' for nextScrollLeft
|
||||||
const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x;
|
const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x
|
||||||
const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y;
|
const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y
|
||||||
|
|
||||||
this.setState({ scale: nextScale }, () => {
|
this.setState({ scale: nextScale }, () => {
|
||||||
this.container.scrollLeft = nextScrollLeft;
|
this.container.scrollLeft = nextScrollLeft
|
||||||
this.container.scrollTop = nextScrollTop;
|
this.container.scrollTop = nextScrollTop
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick = e => {
|
handleClick = e => {
|
||||||
// don't propagate event to MediaModal
|
// don't propagate event to MediaModal
|
||||||
e.stopPropagation();
|
e.stopPropagation()
|
||||||
const handler = this.props.onClick;
|
const handler = this.props.onClick
|
||||||
if (handler) handler();
|
if (handler) handler()
|
||||||
}
|
}
|
||||||
|
|
||||||
setContainerRef = c => {
|
setContainerRef = c => {
|
||||||
this.container = c;
|
this.container = c
|
||||||
}
|
}
|
||||||
|
|
||||||
setImageRef = c => {
|
setImageRef = c => {
|
||||||
this.image = c;
|
this.image = c
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { alt, src } = this.props;
|
const { alt, src } = this.props
|
||||||
const { scale } = this.state;
|
const { scale } = this.state
|
||||||
const overflow = scale === 1 ? 'hidden' : 'scroll';
|
|
||||||
|
const overflow = scale === 1 ? 'hidden' : 'scroll'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='zoomable-image' ref={this.setContainerRef} style={{ overflow }}>
|
<div
|
||||||
|
className={[_s.default, _s.width100PC, _s.height100PC, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}
|
||||||
|
ref={this.setContainerRef}
|
||||||
|
style={{ overflow }}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
|
className={[_s.default, _s.objectFitContain, _s.heightAuto, _s.widthAuto, _s.maxWidth100PC, _s.heightMax100PC].join(' ')}
|
||||||
role='presentation'
|
role='presentation'
|
||||||
ref={this.setImageRef}
|
ref={this.setImageRef}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
|
@ -154,7 +148,7 @@ export default class ZoomableImage extends PureComponent {
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { defineMessages, injectIntl } from 'react-intl'
|
import { FormattedMessage } from 'react-intl'
|
||||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||||
import {
|
import {
|
||||||
replyCompose,
|
replyCompose,
|
||||||
|
@ -34,16 +34,8 @@ import {
|
||||||
import { makeGetStatus } from '../selectors';
|
import { makeGetStatus } from '../selectors';
|
||||||
import Status from '../components/status';
|
import Status from '../components/status';
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
|
||||||
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
|
||||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
|
||||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
|
||||||
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
const getStatus = makeGetStatus();
|
const getStatus = makeGetStatus()
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const statusId = props.id || props.params.statusId
|
const statusId = props.id || props.params.statusId
|
||||||
|
@ -63,8 +55,11 @@ const makeMapStateToProps = () => {
|
||||||
descendantsIds = descendantsIds.withMutations(mutable => {
|
descendantsIds = descendantsIds.withMutations(mutable => {
|
||||||
const ids = [status.get('id')]
|
const ids = [status.get('id')]
|
||||||
|
|
||||||
|
const r = state.getIn(['contexts', 'replies', ids[0]])
|
||||||
|
console.log("r:", r)
|
||||||
|
|
||||||
while (ids.length > 0) {
|
while (ids.length > 0) {
|
||||||
let id = ids.shift();
|
let id = ids.shift()
|
||||||
const replies = state.getIn(['contexts', 'replies', id])
|
const replies = state.getIn(['contexts', 'replies', id])
|
||||||
|
|
||||||
if (status.get('id') !== id) {
|
if (status.get('id') !== id) {
|
||||||
|
@ -94,22 +89,22 @@ const makeMapStateToProps = () => {
|
||||||
return mapStateToProps
|
return mapStateToProps
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
onReply (status, router) {
|
onReply (status, router, showModal) {
|
||||||
if (!me) return dispatch(openModal('UNAUTHORIZED'))
|
if (!me) return dispatch(openModal('UNAUTHORIZED'))
|
||||||
|
|
||||||
dispatch((_, getState) => {
|
dispatch((_, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
if (state.getIn(['compose', 'text']).trim().length !== 0) {
|
if (state.getIn(['compose', 'text']).trim().length !== 0) {
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
message: intl.formatMessage(messages.replyMessage),
|
message: <FormattedMessage id='confirmations.reply.message' defaultMessage='Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' />,
|
||||||
confirm: intl.formatMessage(messages.replyConfirm),
|
confirm: <FormattedMessage id='confirmations.reply.confirm' defaultMessage='Reply' />,
|
||||||
onConfirm: () => dispatch(replyCompose(status, router)),
|
onConfirm: () => dispatch(replyCompose(status, router)),
|
||||||
}));
|
}))
|
||||||
} else {
|
} else {
|
||||||
dispatch(replyCompose(status, router));
|
dispatch(replyCompose(status, router, showModal));
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
onRepost (targetRef, status, e) {
|
onRepost (targetRef, status, e) {
|
||||||
|
@ -177,8 +172,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
dispatch(deleteStatus(status.get('id'), history));
|
dispatch(deleteStatus(status.get('id'), history));
|
||||||
} else {
|
} else {
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
message: intl.formatMessage(messages.deleteMessage),
|
message: <FormattedMessage id='confirmations.delete.message' defaultMessage='Are you sure you want to delete this status?' />,
|
||||||
confirm: intl.formatMessage(messages.deleteConfirm),
|
confirm: <FormattedMessage id='confirmations.delete.confirm' defaultMessage='Delete' />,
|
||||||
onConfirm: () => dispatch(deleteStatus(status.get('id'), history)),
|
onConfirm: () => dispatch(deleteStatus(status.get('id'), history)),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ import StatusContainer from '../../../containers/status_container'
|
||||||
import StatusVisibilityButton from './status_visibility_button'
|
import StatusVisibilityButton from './status_visibility_button'
|
||||||
import UploadButton from './media_upload_button'
|
import UploadButton from './media_upload_button'
|
||||||
import UploadForm from './upload_form'
|
import UploadForm from './upload_form'
|
||||||
|
import GifForm from './gif_form'
|
||||||
|
import Input from '../../../components/input'
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
placeholder: { id: 'compose_form.placeholder', defaultMessage: "What's on your mind?" },
|
placeholder: { id: 'compose_form.placeholder', defaultMessage: "What's on your mind?" },
|
||||||
|
@ -78,6 +80,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
replyToId: PropTypes.string,
|
replyToId: PropTypes.string,
|
||||||
reduxReplyToId: PropTypes.string,
|
reduxReplyToId: PropTypes.string,
|
||||||
hasPoll: PropTypes.bool,
|
hasPoll: PropTypes.bool,
|
||||||
|
selectedGifSrc: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -85,7 +88,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleChange = (e, markdown) => {
|
handleChange = (e, markdown) => {
|
||||||
this.props.onChange(e.target.value, markdown);
|
this.props.onChange(e.target.value, markdown, this.props.replyToId)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleComposeFocus = () => {
|
handleComposeFocus = () => {
|
||||||
|
@ -156,8 +159,8 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']);
|
this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChangeSpoilerText = (e) => {
|
handleChangeSpoilerText = (value) => {
|
||||||
this.props.onChangeSpoilerText(e.target.value);
|
this.props.onChangeSpoilerText(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -169,7 +172,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (!this.autosuggestTextarea) return;
|
if (!this.autosuggestTextarea) return
|
||||||
|
|
||||||
// This statement does several things:
|
// This statement does several things:
|
||||||
// - If we're beginning a reply, and,
|
// - If we're beginning a reply, and,
|
||||||
|
@ -196,17 +199,13 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
setAutosuggestTextarea = (c) => {
|
setAutosuggestTextarea = (c) => {
|
||||||
this.autosuggestTextarea = c;
|
this.autosuggestTextarea = c
|
||||||
}
|
}
|
||||||
|
|
||||||
setForm = (c) => {
|
setForm = (c) => {
|
||||||
this.form = c
|
this.form = c
|
||||||
}
|
}
|
||||||
|
|
||||||
setSpoilerText = (c) => {
|
|
||||||
this.spoilerText = c
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEmojiPick = (data) => {
|
handleEmojiPick = (data) => {
|
||||||
const { text } = this.props
|
const { text } = this.props
|
||||||
const position = this.autosuggestTextarea.textbox.selectionStart
|
const position = this.autosuggestTextarea.textbox.selectionStart
|
||||||
|
@ -236,6 +235,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
isMatch,
|
isMatch,
|
||||||
isChangingUpload,
|
isChangingUpload,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
|
selectedGifSrc,
|
||||||
} = this.props
|
} = this.props
|
||||||
const disabled = isSubmitting
|
const disabled = isSubmitting
|
||||||
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
|
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
|
||||||
|
@ -294,7 +294,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
>
|
>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!reduxReplyToId && !shouldCondense &&
|
!!reduxReplyToId && !shouldCondense && isModalOpen &&
|
||||||
<div className={[_s.default, _s.px15, _s.py10, _s.mt5].join(' ')}>
|
<div className={[_s.default, _s.px15, _s.py10, _s.mt5].join(' ')}>
|
||||||
<StatusContainer
|
<StatusContainer
|
||||||
id={reduxReplyToId}
|
id={reduxReplyToId}
|
||||||
|
@ -306,19 +306,13 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
{
|
{
|
||||||
!!spoiler &&
|
!!spoiler &&
|
||||||
<div className={[_s.default, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
|
<div className={[_s.default, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
|
||||||
<AutosuggestTextbox
|
<Input
|
||||||
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
||||||
value={this.props.spoilerText}
|
value={this.props.spoilerText}
|
||||||
onChange={this.handleChangeSpoilerText}
|
onChange={this.handleChangeSpoilerText}
|
||||||
onKeyDown={this.handleKeyDown}
|
|
||||||
disabled={!this.props.spoiler}
|
disabled={!this.props.spoiler}
|
||||||
ref={this.setSpoilerText}
|
|
||||||
suggestions={this.props.suggestions}
|
|
||||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
|
||||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
|
||||||
onSuggestionSelected={this.onSpoilerSuggestionSelected}
|
|
||||||
searchTokens={[':']}
|
|
||||||
prependIcon='warning'
|
prependIcon='warning'
|
||||||
|
maxLength={256}
|
||||||
id='cw-spoiler-input'
|
id='cw-spoiler-input'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -349,10 +343,15 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
{ /* : todo : for gif
|
{
|
||||||
(isUploading || hasGif) &&
|
/*
|
||||||
|
!!selectedGifSrc && !anyMedia &&
|
||||||
<div className={[_s.default, _s.px15].join(' ')}>
|
<div className={[_s.default, _s.px15].join(' ')}>
|
||||||
<UploadForm replyToId={replyToId} />
|
<GifForm
|
||||||
|
replyToId={replyToId}
|
||||||
|
small={shouldCondense}
|
||||||
|
selectedGifSrc={selectedGifSrc}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
@ -365,7 +364,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!quoteOfId &&
|
!!quoteOfId && isModalOpen &&
|
||||||
<div className={[_s.default, _s.px15, _s.py10, _s.mt5].join(' ')}>
|
<div className={[_s.default, _s.px15, _s.py10, _s.mt5].join(' ')}>
|
||||||
<StatusContainer
|
<StatusContainer
|
||||||
id={quoteOfId}
|
id={quoteOfId}
|
||||||
|
@ -376,29 +375,21 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
|
|
||||||
<div className={actionsContainerClasses}>
|
<div className={actionsContainerClasses}>
|
||||||
<div className={[_s.default, _s.flexRow, _s.mrAuto].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.mrAuto].join(' ')}>
|
||||||
{
|
|
||||||
!shouldCondense &&
|
<EmojiPickerButton small={shouldCondense} isMatch={isMatch} />
|
||||||
<RichTextEditorButton />
|
|
||||||
}
|
|
||||||
<UploadButton small={shouldCondense} />
|
<UploadButton small={shouldCondense} />
|
||||||
|
{ /* <GifSelectorButton small={shouldCondense} /> */ }
|
||||||
|
|
||||||
{
|
{
|
||||||
!edit && !shouldCondense &&
|
!edit && !shouldCondense &&
|
||||||
<PollButton />
|
<PollButton />
|
||||||
}
|
}
|
||||||
{
|
|
||||||
!shouldCondense &&
|
{ !shouldCondense && <StatusVisibilityButton /> }
|
||||||
<StatusVisibilityButton />
|
{ !shouldCondense && <SpoilerButton /> }
|
||||||
}
|
{ !shouldCondense && <SchedulePostButton /> }
|
||||||
{
|
{ /* !shouldCondense && <RichTextEditorButton /> */ }
|
||||||
!shouldCondense &&
|
|
||||||
<SpoilerButton />
|
|
||||||
}
|
|
||||||
{
|
|
||||||
!shouldCondense &&
|
|
||||||
<SchedulePostButton />
|
|
||||||
}
|
|
||||||
<GifSelectorButton small={shouldCondense} />
|
|
||||||
<EmojiPickerButton small={shouldCondense} isMatch={isMatch} />
|
|
||||||
|
|
||||||
{
|
{
|
||||||
shouldCondense &&
|
shouldCondense &&
|
||||||
|
|
|
@ -1,36 +1,39 @@
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
import { clearSelectedGif } from '../../../actions/tenor'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
import Image from '../../../components/image'
|
||||||
import ProgressBar from '../../../../components/progress_bar'
|
|
||||||
import Upload from '../media_upload_item'
|
|
||||||
import SensitiveMediaButton from '../sensitive_media_button'
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')),
|
onClearSelectedGif() {
|
||||||
isUploading: state.getIn(['compose', 'is_uploading']),
|
dispatch(clearSelectedGif())
|
||||||
uploadProgress: state.getIn(['compose', 'progress']),
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
export default
|
export default
|
||||||
@connect(mapStateToProps)
|
@connect(null, mapDispatchToProps)
|
||||||
class GifForm extends ImmutablePureComponent {
|
class GifForm extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
mediaIds: ImmutablePropTypes.list.isRequired,
|
onClearSelectedGif: PropTypes.func.isRequired,
|
||||||
isUploading: PropTypes.bool,
|
replyToId: PropTypes.string,
|
||||||
uploadProgress: PropTypes.number,
|
small: PropTypes.bool,
|
||||||
};
|
selectedGifSrc: PropTypes.string.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
mediaIds,
|
selectedGifSrc,
|
||||||
isUploading,
|
small,
|
||||||
uploadProgress,
|
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
|
if (!selectedGifSrc) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={_s.default}>
|
<div className={_s.default}>
|
||||||
<div className={[_s.default, _s.flexRow, _s.flexWrap].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.flexWrap].join(' ')}>
|
||||||
<Upload id={id} key={id} />
|
<Image
|
||||||
|
width='auto'
|
||||||
|
src={selectedGifSrc}
|
||||||
|
className={[_s.maxWidth100PC, _s.radiusSmall, _s.height260PX].join(' ')}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,7 +12,7 @@ const messages = defineMessages({
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
|
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
|
||||||
disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size + state.getIn(['compose', 'pending_media_attachments']) > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')),
|
disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size + state.getIn(['compose', 'pending_media_attachments']) > 3 || state.getIn(['compose', 'media_attachments']).some(m => ['video', 'audio', 'gifv'].includes(m.get('type')))),
|
||||||
unavailable: state.getIn(['compose', 'poll']) !== null,
|
unavailable: state.getIn(['compose', 'poll']) !== null,
|
||||||
resetFileKey: state.getIn(['compose', 'resetFileKey']),
|
resetFileKey: state.getIn(['compose', 'resetFileKey']),
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { submitCompose } from '../../../actions/compose';
|
||||||
import Button from '../../../components/button'
|
import Button from '../../../components/button'
|
||||||
import Image from '../../../components/image'
|
import Image from '../../../components/image'
|
||||||
import Input from '../../../components/input'
|
import Input from '../../../components/input'
|
||||||
|
import Text from '../../../components/text'
|
||||||
|
|
||||||
const cx = classNames.bind(_s)
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
|
@ -15,27 +16,20 @@ const messages = defineMessages({
|
||||||
delete: { id: 'upload_form.undo', defaultMessage: 'Delete' },
|
delete: { id: 'upload_form.undo', defaultMessage: 'Delete' },
|
||||||
})
|
})
|
||||||
|
|
||||||
const mapStateToProps = (state, { id, otherProps }) => {
|
const mapStateToProps = (state, { id }) => ({
|
||||||
console.log("otherProps:", otherProps)
|
|
||||||
return {
|
|
||||||
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
|
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
onUndo: (id) => {
|
||||||
onUndo: id => {
|
dispatch(undoUploadCompose(id))
|
||||||
dispatch(undoUploadCompose(id));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onDescriptionChange: (id, description) => {
|
onDescriptionChange: (id, description) => {
|
||||||
dispatch(changeUploadCompose(id, { description }));
|
dispatch(changeUploadCompose(id, { description }))
|
||||||
},
|
},
|
||||||
|
|
||||||
onSubmit () {
|
onSubmit () {
|
||||||
dispatch(submitCompose());
|
dispatch(submitCompose())
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default
|
export default
|
||||||
|
@ -72,13 +66,13 @@ class Upload extends ImmutablePureComponent {
|
||||||
this.props.onSubmit()
|
this.props.onSubmit()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUndoClick = e => {
|
handleUndoClick = (e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
this.props.onUndo(this.props.media.get('id'))
|
this.props.onUndo(this.props.media.get('id'))
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInputChange = e => {
|
handleInputChange = (value) => {
|
||||||
this.setState({ dirtyDescription: e.target.value })
|
this.setState({ dirtyDescription: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseEnter = () => {
|
handleMouseEnter = () => {
|
||||||
|
@ -128,8 +122,6 @@ class Upload extends ImmutablePureComponent {
|
||||||
displayNone: !active,
|
displayNone: !active,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log("media:", media)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
|
@ -144,6 +136,12 @@ class Upload extends ImmutablePureComponent {
|
||||||
className={[_s.default, _s.height158PX].join(' ')}
|
className={[_s.default, _s.height158PX].join(' ')}
|
||||||
src={media.get('preview_url')}
|
src={media.get('preview_url')}
|
||||||
/>
|
/>
|
||||||
|
{
|
||||||
|
media.get('type') === 'gifv' &&
|
||||||
|
<div className={[_s.default, _s.posAbs, _s.z2, _s.radiusSmall, _s.bgBlackOpaque, _s.px5, _s.py5, _s.ml10, _s.mt10, _s.top0, _s.left0].join(' ')}>
|
||||||
|
<Text size='extraSmall' color='white' weight='medium'>GIF</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<Button
|
<Button
|
||||||
backgroundColor='black'
|
backgroundColor='black'
|
||||||
color='white'
|
color='white'
|
||||||
|
|
|
@ -7,14 +7,11 @@ import {
|
||||||
removePollOption,
|
removePollOption,
|
||||||
changePollOption,
|
changePollOption,
|
||||||
changePollSettings,
|
changePollSettings,
|
||||||
clearComposeSuggestions,
|
|
||||||
fetchComposeSuggestions,
|
|
||||||
selectComposeSuggestion,
|
|
||||||
} from '../../../actions/compose'
|
} from '../../../actions/compose'
|
||||||
import Button from '../../../components/button'
|
import Button from '../../../components/button'
|
||||||
import Text from '../../../components/text'
|
import Text from '../../../components/text'
|
||||||
import Select from '../../../components/select'
|
import Select from '../../../components/select'
|
||||||
import AutosuggestTextbox from '../../../components/autosuggest_textbox'
|
import Input from '../../../components/input'
|
||||||
|
|
||||||
const cx = classNames.bind(_s)
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
|
@ -29,7 +26,6 @@ const messages = defineMessages({
|
||||||
})
|
})
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
suggestions: state.getIn(['compose', 'suggestions']),
|
|
||||||
options: state.getIn(['compose', 'poll', 'options']),
|
options: state.getIn(['compose', 'poll', 'options']),
|
||||||
expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
|
expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
|
||||||
isMultiple: state.getIn(['compose', 'poll', 'multiple']),
|
isMultiple: state.getIn(['compose', 'poll', 'multiple']),
|
||||||
|
@ -52,18 +48,6 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
dispatch(changePollSettings(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
|
export default
|
||||||
|
@ -79,10 +63,6 @@ class PollForm extends ImmutablePureComponent {
|
||||||
onAddOption: PropTypes.func.isRequired,
|
onAddOption: PropTypes.func.isRequired,
|
||||||
onRemoveOption: PropTypes.func.isRequired,
|
onRemoveOption: PropTypes.func.isRequired,
|
||||||
onChangeSettings: PropTypes.func.isRequired,
|
onChangeSettings: PropTypes.func.isRequired,
|
||||||
suggestions: ImmutablePropTypes.list,
|
|
||||||
onClearSuggestions: PropTypes.func.isRequired,
|
|
||||||
onFetchSuggestions: PropTypes.func.isRequired,
|
|
||||||
onSuggestionSelected: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,15 +180,11 @@ class PollFormOption extends ImmutablePureComponent {
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
onRemove: PropTypes.func.isRequired,
|
onRemove: PropTypes.func.isRequired,
|
||||||
onToggleMultiple: 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,
|
intl: PropTypes.object.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOptionTitleChange = e => {
|
handleOptionTitleChange = (value) => {
|
||||||
this.props.onChange(this.props.index, e.target.value)
|
this.props.onChange(this.props.index, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOptionRemove = () => {
|
handleOptionRemove = () => {
|
||||||
|
@ -221,18 +197,6 @@ class PollFormOption extends ImmutablePureComponent {
|
||||||
e.stopPropagation()
|
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() {
|
render() {
|
||||||
const { isPollMultiple, title, index, intl } = this.props
|
const { isPollMultiple, title, index, intl } = this.props
|
||||||
|
|
||||||
|
@ -257,16 +221,11 @@ class PollFormOption extends ImmutablePureComponent {
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AutosuggestTextbox
|
<Input
|
||||||
placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
|
placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
|
||||||
maxLength={25}
|
maxLength={64}
|
||||||
value={title}
|
value={title}
|
||||||
onChange={this.handleOptionTitleChange}
|
onChange={this.handleOptionTitleChange}
|
||||||
suggestions={this.props.suggestions}
|
|
||||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
|
||||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
|
||||||
onSuggestionSelected={this.onSuggestionSelected}
|
|
||||||
searchTokens={[':']}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
fetchComposeSuggestions,
|
fetchComposeSuggestions,
|
||||||
selectComposeSuggestion,
|
selectComposeSuggestion,
|
||||||
changeComposeSpoilerText,
|
changeComposeSpoilerText,
|
||||||
insertEmojiCompose,
|
|
||||||
uploadCompose,
|
uploadCompose,
|
||||||
changeScheduledAt,
|
changeScheduledAt,
|
||||||
} from '../../../actions/compose'
|
} from '../../../actions/compose'
|
||||||
|
@ -18,35 +17,66 @@ const mapStateToProps = (state, { replyToId }) => {
|
||||||
const reduxReplyToId = state.getIn(['compose', 'in_reply_to'])
|
const reduxReplyToId = state.getIn(['compose', 'in_reply_to'])
|
||||||
const isMatch = reduxReplyToId || replyToId ? reduxReplyToId === replyToId : true
|
const isMatch = reduxReplyToId || replyToId ? reduxReplyToId === replyToId : true
|
||||||
|
|
||||||
|
if (!isMatch) {
|
||||||
return {
|
return {
|
||||||
isMatch,
|
isMatch,
|
||||||
edit: !isMatch ? null : state.getIn(['compose', 'id']) !== null,
|
edit: null,
|
||||||
text: !isMatch ? '' : state.getIn(['compose', 'text']),
|
text: '',
|
||||||
suggestions: !isMatch ? ImmutableList() : state.getIn(['compose', 'suggestions']),
|
suggestions: ImmutableList(),
|
||||||
spoiler: !isMatch ? false : state.getIn(['compose', 'spoiler']),
|
spoiler: false,
|
||||||
spoilerText: !isMatch ? '' : state.getIn(['compose', 'spoiler_text']),
|
spoilerText: '',
|
||||||
privacy: !isMatch ? null : state.getIn(['compose', 'privacy']),
|
privacy: null,
|
||||||
focusDate: !isMatch ? null : state.getIn(['compose', 'focusDate']),
|
focusDate: null,
|
||||||
caretPosition: !isMatch ? null : state.getIn(['compose', 'caretPosition']),
|
caretPosition: null,
|
||||||
preselectDate: !isMatch ? null : state.getIn(['compose', 'preselectDate']),
|
preselectDate: null,
|
||||||
isSubmitting: !isMatch ? false : state.getIn(['compose', 'is_submitting']),
|
isSubmitting: false,
|
||||||
isChangingUpload: !isMatch ? false : state.getIn(['compose', 'is_changing_upload']),
|
isChangingUpload: false,
|
||||||
isUploading: !isMatch ? false : state.getIn(['compose', 'is_uploading']),
|
isUploading: false,
|
||||||
showSearch: !isMatch ? false : state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
showSearch: false,
|
||||||
anyMedia: !isMatch ? false : state.getIn(['compose', 'media_attachments']).size > 0,
|
anyMedia: false,
|
||||||
isModalOpen: !isMatch ? false : state.getIn(['modal', 'modalType']) === 'COMPOSE',
|
isModalOpen: false,
|
||||||
quoteOfId: !isMatch ? null : state.getIn(['compose', 'quote_of_id']),
|
quoteOfId: null,
|
||||||
scheduledAt: !isMatch ? null : state.getIn(['compose', 'scheduled_at']),
|
scheduledAt: null,
|
||||||
account: state.getIn(['accounts', me]),
|
account: state.getIn(['accounts', me]),
|
||||||
hasPoll: !isMatch ? false : state.getIn(['compose', 'poll']),
|
hasPoll: false,
|
||||||
|
selectedGifSrc: null,
|
||||||
|
reduxReplyToId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log("isMatch:", isMatch, reduxReplyToId, replyToId, state.getIn(['compose', 'text']))
|
||||||
|
|
||||||
|
return {
|
||||||
|
isMatch,
|
||||||
|
edit: state.getIn(['compose', 'id']) !== null,
|
||||||
|
text: state.getIn(['compose', 'text']),
|
||||||
|
suggestions: state.getIn(['compose', 'suggestions']),
|
||||||
|
spoiler: state.getIn(['compose', 'spoiler']),
|
||||||
|
spoilerText: state.getIn(['compose', 'spoiler_text']),
|
||||||
|
privacy: state.getIn(['compose', 'privacy']),
|
||||||
|
focusDate: state.getIn(['compose', 'focusDate']),
|
||||||
|
caretPosition: state.getIn(['compose', 'caretPosition']),
|
||||||
|
preselectDate: state.getIn(['compose', 'preselectDate']),
|
||||||
|
isSubmitting: state.getIn(['compose', 'is_submitting']),
|
||||||
|
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
|
||||||
|
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||||
|
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
||||||
|
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||||
|
isModalOpen: state.getIn(['modal', 'modalType']) === 'COMPOSE',
|
||||||
|
quoteOfId: state.getIn(['compose', 'quote_of_id']),
|
||||||
|
scheduledAt: state.getIn(['compose', 'scheduled_at']),
|
||||||
|
account: state.getIn(['accounts', me]),
|
||||||
|
hasPoll: state.getIn(['compose', 'poll']),
|
||||||
|
selectedGifSrc: state.getIn(['tenor', 'selectedGif', 'src']),
|
||||||
reduxReplyToId,
|
reduxReplyToId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch, { reduxReplyToId, replyToId }) => ({
|
||||||
|
|
||||||
onChange(text, markdown) {
|
onChange(text, markdown, newReplyToId) {
|
||||||
dispatch(changeCompose(text, markdown))
|
console.log("text:", text, newReplyToId, replyToId, reduxReplyToId)
|
||||||
|
dispatch(changeCompose(text, markdown, newReplyToId))
|
||||||
},
|
},
|
||||||
|
|
||||||
onSubmit(group, replyToId) {
|
onSubmit(group, replyToId) {
|
||||||
|
@ -73,10 +103,6 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
dispatch(uploadCompose(files))
|
dispatch(uploadCompose(files))
|
||||||
},
|
},
|
||||||
|
|
||||||
onPickEmoji(position, data, needsSpace) {
|
|
||||||
dispatch(insertEmojiCompose(position, data, needsSpace))
|
|
||||||
},
|
|
||||||
|
|
||||||
setScheduledAt(date) {
|
setScheduledAt(date) {
|
||||||
dispatch(changeScheduledAt(date))
|
dispatch(changeScheduledAt(date))
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,8 +30,8 @@ class ListEditorSearch extends PureComponent {
|
||||||
onClear: PropTypes.func.isRequired,
|
onClear: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleChange = e => {
|
handleChange = (value) => {
|
||||||
this.props.onChange(e.target.value);
|
this.props.onChange(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyUp = e => {
|
handleKeyUp = e => {
|
||||||
|
|
|
@ -54,7 +54,7 @@ class Search extends ImmutablePureComponent {
|
||||||
const showHashtags = pathname === '/search/hashtags'
|
const showHashtags = pathname === '/search/hashtags'
|
||||||
const showGroups = pathname === '/search/groups'
|
const showGroups = pathname === '/search/groups'
|
||||||
const isTop = !showPeople && !showHashtags && !showGroups
|
const isTop = !showPeople && !showHashtags && !showGroups
|
||||||
const theLimit = 10
|
const theLimit = 4
|
||||||
|
|
||||||
let accounts, statuses, hashtags, groups
|
let accounts, statuses, hashtags, groups
|
||||||
|
|
||||||
|
|
|
@ -345,7 +345,7 @@
|
||||||
"status.report": "Report @{name}",
|
"status.report": "Report @{name}",
|
||||||
"status.sensitive_warning": "Деликатно съдържание",
|
"status.sensitive_warning": "Деликатно съдържание",
|
||||||
"status.share": "Share",
|
"status.share": "Share",
|
||||||
"status.show_less": "Show less",
|
"status.show_less": "Hide",
|
||||||
"status.show_less_all": "Show less for all",
|
"status.show_less_all": "Show less for all",
|
||||||
"status.show_more": "Show more",
|
"status.show_more": "Show more",
|
||||||
"status.show_more_all": "Show more for all",
|
"status.show_more_all": "Show more for all",
|
||||||
|
|
|
@ -379,7 +379,7 @@
|
||||||
"id": "status.show_more"
|
"id": "status.show_more"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Show less",
|
"defaultMessage": "Hide",
|
||||||
"id": "status.show_less"
|
"id": "status.show_less"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -348,7 +348,7 @@
|
||||||
"status.report": "Report @{name}",
|
"status.report": "Report @{name}",
|
||||||
"status.sensitive_warning": "Sensitive content",
|
"status.sensitive_warning": "Sensitive content",
|
||||||
"status.share": "Share",
|
"status.share": "Share",
|
||||||
"status.show_less": "Show less",
|
"status.show_less": "Hide",
|
||||||
"status.show_less_all": "Show less for all",
|
"status.show_less_all": "Show less for all",
|
||||||
"status.show_more": "Show more",
|
"status.show_more": "Show more",
|
||||||
"status.show_more_all": "Show more for all",
|
"status.show_more_all": "Show more for all",
|
||||||
|
|
|
@ -345,7 +345,7 @@
|
||||||
"status.report": "Report @{name}",
|
"status.report": "Report @{name}",
|
||||||
"status.sensitive_warning": "Sensitive content",
|
"status.sensitive_warning": "Sensitive content",
|
||||||
"status.share": "Share",
|
"status.share": "Share",
|
||||||
"status.show_less": "Show less",
|
"status.show_less": "Hide",
|
||||||
"status.show_less_all": "Show less for all",
|
"status.show_less_all": "Show less for all",
|
||||||
"status.show_more": "Show more",
|
"status.show_more": "Show more",
|
||||||
"status.show_more_all": "Show more for all",
|
"status.show_more_all": "Show more for all",
|
||||||
|
|
|
@ -345,7 +345,7 @@
|
||||||
"status.report": "Report @{name}",
|
"status.report": "Report @{name}",
|
||||||
"status.sensitive_warning": "Sensitive content",
|
"status.sensitive_warning": "Sensitive content",
|
||||||
"status.share": "Share",
|
"status.share": "Share",
|
||||||
"status.show_less": "Show less",
|
"status.show_less": "Hide",
|
||||||
"status.show_less_all": "Show less for all",
|
"status.show_less_all": "Show less for all",
|
||||||
"status.show_more": "Show more",
|
"status.show_more": "Show more",
|
||||||
"status.show_more_all": "Show more for all",
|
"status.show_more_all": "Show more for all",
|
||||||
|
|
|
@ -345,7 +345,7 @@
|
||||||
"status.report": "Report @{name}",
|
"status.report": "Report @{name}",
|
||||||
"status.sensitive_warning": "Sensitive content",
|
"status.sensitive_warning": "Sensitive content",
|
||||||
"status.share": "Share",
|
"status.share": "Share",
|
||||||
"status.show_less": "Show less",
|
"status.show_less": "Hide",
|
||||||
"status.show_less_all": "Show less for all",
|
"status.show_less_all": "Show less for all",
|
||||||
"status.show_more": "Show more",
|
"status.show_more": "Show more",
|
||||||
"status.show_more_all": "Show more for all",
|
"status.show_more_all": "Show more for all",
|
||||||
|
|
|
@ -345,7 +345,7 @@
|
||||||
"status.report": "Report @{name}",
|
"status.report": "Report @{name}",
|
||||||
"status.sensitive_warning": "Sensitive content",
|
"status.sensitive_warning": "Sensitive content",
|
||||||
"status.share": "Share",
|
"status.share": "Share",
|
||||||
"status.show_less": "Show less",
|
"status.show_less": "Hide",
|
||||||
"status.show_less_all": "Show less for all",
|
"status.show_less_all": "Show less for all",
|
||||||
"status.show_more": "Show more",
|
"status.show_more": "Show more",
|
||||||
"status.show_more_all": "Show more for all",
|
"status.show_more_all": "Show more for all",
|
||||||
|
|
|
@ -170,11 +170,13 @@ const updateSuggestionTags = (state, token) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const insertEmoji = (state, position, emojiData, needsSpace) => {
|
const insertEmoji = (state, position, emojiData, needsSpace) => {
|
||||||
const oldText = state.get('text');
|
const oldText = state.get('text')
|
||||||
const emoji = needsSpace ? ' ' + emojiData.native : emojiData.native;
|
const emoji = needsSpace ? ' ' + emojiData.native : emojiData.native
|
||||||
|
const text = `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`
|
||||||
|
console.log("insertEmoji reducer:", emoji, position, emojiData, needsSpace, text)
|
||||||
|
|
||||||
return state.merge({
|
return state.merge({
|
||||||
text: `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`,
|
text,
|
||||||
focusDate: new Date(),
|
focusDate: new Date(),
|
||||||
caretPosition: position + emoji.length + 1,
|
caretPosition: position + emoji.length + 1,
|
||||||
idempotencyKey: uuid(),
|
idempotencyKey: uuid(),
|
||||||
|
@ -248,10 +250,14 @@ export default function compose(state = initialState, action) {
|
||||||
.set('privacy', action.value)
|
.set('privacy', action.value)
|
||||||
.set('idempotencyKey', uuid());
|
.set('idempotencyKey', uuid());
|
||||||
case COMPOSE_CHANGE:
|
case COMPOSE_CHANGE:
|
||||||
return state
|
return state.withMutations(map => {
|
||||||
.set('text', action.text)
|
map.set('text', action.text)
|
||||||
.set('markdown', action.markdown)
|
map.set('markdown', action.markdown)
|
||||||
.set('idempotencyKey', uuid());
|
map.set('idempotencyKey', uuid())
|
||||||
|
if (action.replyId) {
|
||||||
|
map.set('in_reply_to', action.replyId)
|
||||||
|
}
|
||||||
|
})
|
||||||
case COMPOSE_COMPOSING_CHANGE:
|
case COMPOSE_COMPOSING_CHANGE:
|
||||||
return state.set('is_composing', action.value);
|
return state.set('is_composing', action.value);
|
||||||
case COMPOSE_REPLY:
|
case COMPOSE_REPLY:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
GIFS_CLEAR_RESULTS,
|
GIFS_CLEAR_RESULTS,
|
||||||
GIF_SET_SELECTED,
|
GIF_SET_SELECTED,
|
||||||
|
GIF_CLEAR_SELECTED,
|
||||||
GIF_CHANGE_SEARCH_TEXT,
|
GIF_CHANGE_SEARCH_TEXT,
|
||||||
GIF_RESULTS_FETCH_REQUEST,
|
GIF_RESULTS_FETCH_REQUEST,
|
||||||
GIF_RESULTS_FETCH_SUCCESS,
|
GIF_RESULTS_FETCH_SUCCESS,
|
||||||
|
@ -14,11 +15,15 @@ import { Map as ImmutableMap } from 'immutable'
|
||||||
const initialState = ImmutableMap({
|
const initialState = ImmutableMap({
|
||||||
categories: [],
|
categories: [],
|
||||||
results: [],
|
results: [],
|
||||||
chosenUrl: '',
|
|
||||||
searchText: '',
|
searchText: '',
|
||||||
next: 0,
|
next: 0,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: false,
|
error: false,
|
||||||
|
selectedGif: ImmutableMap({
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
src: '',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function (state = initialState, action) {
|
export default function (state = initialState, action) {
|
||||||
|
@ -48,7 +53,13 @@ export default function (state = initialState, action) {
|
||||||
case GIFS_CLEAR_RESULTS:
|
case GIFS_CLEAR_RESULTS:
|
||||||
return state.set('results', [])
|
return state.set('results', [])
|
||||||
case GIF_SET_SELECTED:
|
case GIF_SET_SELECTED:
|
||||||
return state.set('chosenUrl', action.url)
|
return state.set('selectedGif', ImmutableMap({
|
||||||
|
id: action.result.id,
|
||||||
|
title: action.result.title,
|
||||||
|
src: action.result.media[0].gif.url,
|
||||||
|
}))
|
||||||
|
case GIF_CLEAR_SELECTED:
|
||||||
|
return state.set('selectedGif', ImmutableMap())
|
||||||
case GIF_CHANGE_SEARCH_TEXT:
|
case GIF_CHANGE_SEARCH_TEXT:
|
||||||
return state.set('searchText', action.text.trim());
|
return state.set('searchText', action.text.trim());
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
--color_white: #fff;
|
--color_white: #fff;
|
||||||
--color_black: #2d3436;
|
--color_black: #2d3436;
|
||||||
--color_black-opaque: rgba(0, 0, 0, .8);
|
--color_black-opaque: rgba(0, 0, 0, .8);
|
||||||
--color_black-opaquer: rgba(0, 0, 0, .5);
|
--color_black-opaquer: rgba(0, 0, 0, .65);
|
||||||
--color_gold: #ffd700;
|
--color_gold: #ffd700;
|
||||||
--color_red: #de2960;
|
--color_red: #de2960;
|
||||||
--color_red-dark: #c72c5b;
|
--color_red-dark: #c72c5b;
|
||||||
|
@ -99,6 +99,7 @@ body {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-size: var(--fs_m);
|
font-size: var(--fs_m);
|
||||||
|
line-height: 1.3125;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
color: var(--text_color_primary);
|
color: var(--text_color_primary);
|
||||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, Roboto, Ubuntu, "Helvetica Neue", sans-serif;
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, Roboto, Ubuntu, "Helvetica Neue", sans-serif;
|
||||||
|
@ -140,6 +141,7 @@ body {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-size: var(--fs_n);
|
font-size: var(--fs_n);
|
||||||
|
line-height: 1.3125;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
color: var(--text_color_primary);
|
color: var(--text_color_primary);
|
||||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif;
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif;
|
||||||
|
@ -384,6 +386,7 @@ body {
|
||||||
|
|
||||||
/* */
|
/* */
|
||||||
|
|
||||||
|
.lineHeight0825 { line-height: 0.825em; }
|
||||||
.lineHeight125 { line-height: 1.25em; }
|
.lineHeight125 { line-height: 1.25em; }
|
||||||
.lineHeight15 { line-height: 1.5em; }
|
.lineHeight15 { line-height: 1.5em; }
|
||||||
.lineHeight2 { line-height: 2em; }
|
.lineHeight2 { line-height: 2em; }
|
||||||
|
@ -402,12 +405,15 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.heightMax100VH { max-height: 100vh; }
|
.heightMax100VH { max-height: 100vh; }
|
||||||
|
.heightMax100PC { max-height: 100%; }
|
||||||
.heightMax80VH { max-height: 80vh; }
|
.heightMax80VH { max-height: 80vh; }
|
||||||
|
.heightMax200PX { max-height: 200px; }
|
||||||
.heightMax56PX { max-height: 56px; }
|
.heightMax56PX { max-height: 56px; }
|
||||||
.heightCalc53PX { height: calc(100vh - 53px); }
|
.heightCalc53PX { height: calc(100vh - 53px); }
|
||||||
|
|
||||||
.heightMin100VH { min-height: 100vh; }
|
.heightMin100VH { min-height: 100vh; }
|
||||||
.heightMin50VH { min-height: 50vh; }
|
.heightMin50VH { min-height: 50vh; }
|
||||||
|
.heightMin100PX { min-height: 100px; }
|
||||||
.heightMin50PX { min-height: 50px; }
|
.heightMin50PX { min-height: 50px; }
|
||||||
|
|
||||||
.height100PC { height: 100%; }
|
.height100PC { height: 100%; }
|
||||||
|
@ -422,8 +428,10 @@ body {
|
||||||
.height24PX { height: 24px; }
|
.height24PX { height: 24px; }
|
||||||
.height22PX { height: 22px; }
|
.height22PX { height: 22px; }
|
||||||
.height20PX { height: 20px; }
|
.height20PX { height: 20px; }
|
||||||
|
.height10PX { height: 10px; }
|
||||||
.height4PX { height: 4px; }
|
.height4PX { height: 4px; }
|
||||||
.height1PX { height: 1px; }
|
.height1PX { height: 1px; }
|
||||||
|
.heightAuto { height: auto; }
|
||||||
|
|
||||||
.maxWidth100PC { max-width: 100%; }
|
.maxWidth100PC { max-width: 100%; }
|
||||||
.maxWidth640PX { max-width: 640px; }
|
.maxWidth640PX { max-width: 640px; }
|
||||||
|
@ -442,9 +450,10 @@ body {
|
||||||
.width72PX { width: 72px; }
|
.width72PX { width: 72px; }
|
||||||
.width50PX { width: 50px; }
|
.width50PX { width: 50px; }
|
||||||
.width20PX { width: 20px; }
|
.width20PX { width: 20px; }
|
||||||
|
.width10PX { width: 10px; }
|
||||||
.width4PX { width: 4px; }
|
.width4PX { width: 4px; }
|
||||||
.width1PX { width: 1px; }
|
.width1PX { width: 1px; }
|
||||||
|
.widthAuto { width: auto; }
|
||||||
|
|
||||||
@media (min-width: 1480px) {
|
@media (min-width: 1480px) {
|
||||||
.width1015PX {
|
.width1015PX {
|
||||||
|
@ -561,9 +570,8 @@ body {
|
||||||
.underline { text-decoration: underline; }
|
.underline { text-decoration: underline; }
|
||||||
.underline_onHover:hover { text-decoration: underline; }
|
.underline_onHover:hover { text-decoration: underline; }
|
||||||
|
|
||||||
.objectFitCover {
|
.objectFitCover { object-fit: cover; }
|
||||||
object-fit: cover;
|
.objectFitContain { object-fit: contain; }
|
||||||
}
|
|
||||||
|
|
||||||
.textShadow {
|
.textShadow {
|
||||||
text-shadow: 0 0 5px var(--color_black);
|
text-shadow: 0 0 5px var(--color_black);
|
||||||
|
|
|
@ -83,7 +83,8 @@ class EditStatusService < BaseService
|
||||||
|
|
||||||
@media = @account.media_attachments.where(id: @options[:media_ids].take(4).map(&:to_i))
|
@media = @account.media_attachments.where(id: @options[:media_ids].take(4).map(&:to_i))
|
||||||
|
|
||||||
raise GabSocial::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:video?)
|
hasVideoOrGif = @media.find(&:video?) || @media.find(&:gifv?)
|
||||||
|
raise GabSocial::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && hasVideoOrGif
|
||||||
end
|
end
|
||||||
|
|
||||||
def language_from_option(str)
|
def language_from_option(str)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class HashtagQueryService < BaseService
|
class HashtagQueryService < BaseService
|
||||||
LIMIT_PER_MODE = 1
|
LIMIT_PER_MODE = 1
|
||||||
|
|
||||||
def call(tag, params, account = nil, local = false)
|
def call(tag, params, account = nil, local = true)
|
||||||
tags = tags_for(Array(tag.name) | Array(params[:any])).pluck(:id)
|
tags = tags_for(Array(tag.name) | Array(params[:any])).pluck(:id)
|
||||||
all = tags_for(params[:all])
|
all = tags_for(params[:all])
|
||||||
none = tags_for(params[:none])
|
none = tags_for(params[:none])
|
||||||
|
|
|
@ -115,7 +115,8 @@ class PostStatusService < BaseService
|
||||||
|
|
||||||
@media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i))
|
@media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i))
|
||||||
|
|
||||||
raise GabSocial::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:video?)
|
hasVideoOrGif = @media.find(&:video?) || @media.find(&:gifv?)
|
||||||
|
raise GabSocial::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && hasVideoOrGif
|
||||||
end
|
end
|
||||||
|
|
||||||
def language_from_option(str)
|
def language_from_option(str)
|
||||||
|
|
|
@ -724,7 +724,7 @@ en:
|
||||||
limit: You have reached the maximum amount of lists
|
limit: You have reached the maximum amount of lists
|
||||||
media_attachments:
|
media_attachments:
|
||||||
validations:
|
validations:
|
||||||
images_and_video: Cannot attach a video to a status that already contains images
|
images_and_video: Cannot attach a video or gif to a status that already contains images
|
||||||
too_many: Cannot attach more than 4 files
|
too_many: Cannot attach more than 4 files
|
||||||
migrations:
|
migrations:
|
||||||
acct: username@domain of the new account
|
acct: username@domain of the new account
|
||||||
|
|
|
@ -685,7 +685,7 @@ en_GB:
|
||||||
limit: You have reached the maximum amount of lists
|
limit: You have reached the maximum amount of lists
|
||||||
media_attachments:
|
media_attachments:
|
||||||
validations:
|
validations:
|
||||||
images_and_video: Cannot attach a video to a status that already contains images
|
images_and_video: Cannot attach a video or gif to a status that already contains images
|
||||||
too_many: Cannot attach more than 4 files
|
too_many: Cannot attach more than 4 files
|
||||||
migrations:
|
migrations:
|
||||||
acct: username@domain of the new account
|
acct: username@domain of the new account
|
||||||
|
|
|
@ -181,7 +181,7 @@ io:
|
||||||
upload: Kargar
|
upload: Kargar
|
||||||
media_attachments:
|
media_attachments:
|
||||||
validations:
|
validations:
|
||||||
images_and_video: Cannot attach a video to a status that already contains images
|
images_and_video: Cannot attach a video or gif to a status that already contains images
|
||||||
too_many: Cannot attach more than 4 files
|
too_many: Cannot attach more than 4 files
|
||||||
notification_mailer:
|
notification_mailer:
|
||||||
digest:
|
digest:
|
||||||
|
|
|
@ -118,6 +118,7 @@
|
||||||
"markdown-draft-js": "^2.2.0",
|
"markdown-draft-js": "^2.2.0",
|
||||||
"mini-css-extract-plugin": "^0.9.0",
|
"mini-css-extract-plugin": "^0.9.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
|
"moment-mini": "^2.24.0",
|
||||||
"npmlog": "^4.1.2",
|
"npmlog": "^4.1.2",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
"object-fit-images": "^3.2.3",
|
"object-fit-images": "^3.2.3",
|
||||||
|
@ -145,6 +146,7 @@
|
||||||
"react-router-scroll-4": "^1.0.0-beta.1",
|
"react-router-scroll-4": "^1.0.0-beta.1",
|
||||||
"react-stickynode": "^2.1.1",
|
"react-stickynode": "^2.1.1",
|
||||||
"react-swipeable-views": "^0.13.0",
|
"react-swipeable-views": "^0.13.0",
|
||||||
|
"react-textarea-autosize": "^7.1.2",
|
||||||
"redis": "^2.7.1",
|
"redis": "^2.7.1",
|
||||||
"redux": "^4.0.1",
|
"redux": "^4.0.1",
|
||||||
"redux-immutable": "^4.0.0",
|
"redux-immutable": "^4.0.0",
|
||||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -6296,6 +6296,11 @@ mkdirp@^0.5, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "^1.2.5"
|
minimist "^1.2.5"
|
||||||
|
|
||||||
|
moment-mini@^2.24.0:
|
||||||
|
version "2.24.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.24.0.tgz#fa68d98f7fe93ae65bf1262f6abb5fb6983d8d18"
|
||||||
|
integrity sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==
|
||||||
|
|
||||||
moment-timezone@^0.5.x:
|
moment-timezone@^0.5.x:
|
||||||
version "0.5.28"
|
version "0.5.28"
|
||||||
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.28.tgz#f093d789d091ed7b055d82aa81a82467f72e4338"
|
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.28.tgz#f093d789d091ed7b055d82aa81a82467f72e4338"
|
||||||
|
@ -8019,6 +8024,14 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.7.0:
|
||||||
react-is "^16.8.6"
|
react-is "^16.8.6"
|
||||||
scheduler "^0.19.1"
|
scheduler "^0.19.1"
|
||||||
|
|
||||||
|
react-textarea-autosize@^7.1.2:
|
||||||
|
version "7.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-7.1.2.tgz#70fdb333ef86bcca72717e25e623e90c336e2cda"
|
||||||
|
integrity sha512-uH3ORCsCa3C6LHxExExhF4jHoXYCQwE5oECmrRsunlspaDAbS4mGKNlWZqjLfInWtFQcf0o1n1jC/NGXFdUBCg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.1.2"
|
||||||
|
prop-types "^15.6.0"
|
||||||
|
|
||||||
react@^16.13.1:
|
react@^16.13.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
||||||
|
|
Loading…
Reference in New Issue