Progress
This commit is contained in:
parent
e9f01c0b16
commit
196a906cec
|
@ -9,7 +9,7 @@ class Api::V1::GifsController < Api::BaseController
|
|||
|
||||
def categories
|
||||
uri = URI('https://api.tenor.com/v1/categories')
|
||||
theOptions = { :key => "QHFJ0C5EWGBH" }
|
||||
theOptions = { :key => "TENOR_KEY" }
|
||||
uri.query = URI.encode_www_form(theOptions)
|
||||
|
||||
res = Net::HTTP.get_response(uri)
|
||||
|
@ -19,7 +19,7 @@ class Api::V1::GifsController < Api::BaseController
|
|||
def search
|
||||
uri = URI('https://api.tenor.com/v1/search')
|
||||
theOptions = {
|
||||
:key => "QHFJ0C5EWGBH",
|
||||
:key => "TENOR_KEY",
|
||||
:media_filter => "minimal",
|
||||
:limit => 30,
|
||||
:q => params[:search],
|
||||
|
|
|
@ -22,8 +22,10 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
render json: @status, serializer: REST::StatusSerializer
|
||||
end
|
||||
|
||||
# direct descendants only
|
||||
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)
|
||||
|
||||
@context = Context.new(descendants: loaded_descendants)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import api from '../api';
|
||||
import { CancelToken, isCancel } from 'axios';
|
||||
import throttle from 'lodash.throttle'
|
||||
import moment from 'moment-mini'
|
||||
import { search as emojiSearch } from '../components/emoji/emoji_mart_search_light';
|
||||
import { urlRegex } from '../features/ui/util/url_regex'
|
||||
import { tagHistory } from '../settings';
|
||||
|
@ -77,23 +78,26 @@ export const ensureComposeIsVisible = (getState, routerHistory) => {
|
|||
}
|
||||
};
|
||||
|
||||
export function changeCompose(text, markdown) {
|
||||
console.log("changeCompose:", markdown)
|
||||
export function changeCompose(text, markdown, replyId) {
|
||||
console.log("changeCompose:", text)
|
||||
return {
|
||||
type: COMPOSE_CHANGE,
|
||||
text: text,
|
||||
markdown: markdown,
|
||||
replyId: replyId,
|
||||
};
|
||||
};
|
||||
|
||||
export function replyCompose(status) {
|
||||
export function replyCompose(status, router, showModal) {
|
||||
return (dispatch) => {
|
||||
dispatch({
|
||||
type: COMPOSE_REPLY,
|
||||
status: status,
|
||||
});
|
||||
|
||||
dispatch(openModal('COMPOSE'));
|
||||
if (showModal) {
|
||||
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) {
|
||||
if (!me) return;
|
||||
|
||||
|
@ -202,8 +206,7 @@ export function submitCompose(group, replyToId=null) {
|
|||
const method = id === null ? 'post' : 'put';
|
||||
|
||||
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, {
|
||||
status,
|
||||
|
|
|
@ -52,9 +52,14 @@ export const clearGifResults = () => ({
|
|||
type: GIFS_CLEAR_RESULTS,
|
||||
})
|
||||
|
||||
export const setSelectedGif = (url) => ({
|
||||
export const clearSelectedGif = () => ({
|
||||
type: GIF_CLEAR_SELECTED,
|
||||
result,
|
||||
})
|
||||
|
||||
export const setSelectedGif = (result) => ({
|
||||
type: GIF_SET_SELECTED,
|
||||
url,
|
||||
result,
|
||||
})
|
||||
|
||||
export function changeGifSearchText(text) {
|
||||
|
|
|
@ -16,8 +16,8 @@ const GlobeIcon = ({
|
|||
aria-label={title}
|
||||
>
|
||||
<g>
|
||||
<circle fill='none' stroke='#616770' 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' />
|
||||
<circle fill='none' stroke='#4B4F55' strokeWidth='1px' cx='14' cy='14' r='13.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>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -11,12 +11,12 @@ const VerifiedIcon = ({
|
|||
y='0px'
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox='0 0 24 24'
|
||||
viewBox='0 0 32 32'
|
||||
xmlSpace='preserve'
|
||||
aria-label={title}
|
||||
>
|
||||
<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>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
|
|||
import isObject from 'lodash.isobject'
|
||||
import classNames from 'classnames/bind'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import Textarea from 'react-textarea-autosize'
|
||||
import { isRtl } from '../utils/rtl'
|
||||
import { textAtCursorMatchesToken } from '../utils/cursor_token_match'
|
||||
import AutosuggestAccount from './autosuggest_account'
|
||||
|
@ -226,27 +227,56 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||
direction: isRtl(value) ? 'rtl' : 'ltr',
|
||||
}
|
||||
|
||||
const textClasses = cx({
|
||||
const textareaClasses = cx({
|
||||
default: 1,
|
||||
lineHeight125: 1,
|
||||
font: 1,
|
||||
wrap: 1,
|
||||
resizeNone: 1,
|
||||
text: 1,
|
||||
displayBlock: 1,
|
||||
bgTransparent: 1,
|
||||
outlineNone: 1,
|
||||
bgPrimary: !small,
|
||||
bgSubtle: small,
|
||||
py15: !small,
|
||||
py10: small,
|
||||
lineHeight125: 1,
|
||||
height100PC: small,
|
||||
width100PC: !small,
|
||||
pt15: !small,
|
||||
px15: !small,
|
||||
px10: small,
|
||||
pb10: !small,
|
||||
fs16PX: !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) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={[_s.default, _s.flexGrow1, _s.maxWidth100PC].join(' ')}>
|
||||
<Composer
|
||||
<div className={textareaContainerClasses}>
|
||||
<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}
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
|
@ -259,7 +289,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||
onBlur={this.onBlur}
|
||||
onPaste={this.onPaste}
|
||||
small={small}
|
||||
/>
|
||||
/>*/}
|
||||
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -1,52 +1,125 @@
|
|||
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 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 Button from './button'
|
||||
import CommentHeader from './comment_header'
|
||||
import StatusContent from './status_content'
|
||||
import StatusMedia from './status_media'
|
||||
import { defaultMediaVisibility } from './status'
|
||||
import Text from './text'
|
||||
|
||||
const messages = defineMessages({
|
||||
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
||||
like: { id: 'status.like', defaultMessage: 'Like' },
|
||||
unlike: { id: 'status.unlike', defaultMessage: 'Unlike' },
|
||||
})
|
||||
|
||||
const makeMapStateToProps = (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
|
||||
@injectIntl
|
||||
@connect(makeMapStateToProps)
|
||||
@connect(makeMapStateToProps, mapDispatchToProps)
|
||||
class Comment extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
indent: PropTypes.number,
|
||||
intl: PropTypes.object.isRequired,
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
isHidden: PropTypes.bool,
|
||||
isIntersecting: PropTypes.bool,
|
||||
onReply: PropTypes.func.isRequired,
|
||||
onFavorite: PropTypes.func.isRequired,
|
||||
onOpenStatusOptions: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
updateOnProps = [
|
||||
'status',
|
||||
'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() {
|
||||
const {
|
||||
indent,
|
||||
intl,
|
||||
status,
|
||||
isHidden,
|
||||
} = this.props
|
||||
|
||||
if (isHidden) {
|
||||
return (
|
||||
<div tabIndex='0'>
|
||||
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
|
||||
{status.get('content')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const style = {
|
||||
paddingLeft: `${indent * 40}px`,
|
||||
}
|
||||
|
||||
// : todo : add media
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.px15, _s.mb10, _s.py5].join(' ')} data-comment={status.get('id')}>
|
||||
<div className={[_s.default].join(' ')} style={style}>
|
||||
|
@ -69,12 +142,34 @@ class Comment extends ImmutablePureComponent {
|
|||
isComment
|
||||
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 className={[_s.default, _s.flexRow, _s.mt5].join(' ')}>
|
||||
<CommentButton title={intl.formatMessage(messages.like)} />
|
||||
<CommentButton title={intl.formatMessage(messages.reply)} />
|
||||
<CommentButton title='···' />
|
||||
<CommentButton
|
||||
title={intl.formatMessage(status.get('favourited') ? messages.unlike: messages.like)}
|
||||
onClick={this.handleOnFavorite}
|
||||
/>
|
||||
<CommentButton
|
||||
title={intl.formatMessage(messages.reply)}
|
||||
onClick={this.handleOnReply}
|
||||
/>
|
||||
<CommentButton
|
||||
title='···'
|
||||
onClick={this.handleOnOpenStatusOptions}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import Button from './button'
|
||||
import Comment from './comment'
|
||||
import ScrollableList from './scrollable_list'
|
||||
import Text from './text'
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
export default class CommentList extends ImmutablePureComponent {
|
||||
|
||||
|
@ -11,34 +13,44 @@ export default class CommentList extends ImmutablePureComponent {
|
|||
descendants: ImmutablePropTypes.list,
|
||||
}
|
||||
|
||||
handleLoadMore = () => {
|
||||
//
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
descendants,
|
||||
commentsLimited,
|
||||
} = this.props
|
||||
|
||||
const upperLimit = 6
|
||||
const size = descendants.size
|
||||
const max = Math.min(commentsLimited ? 2 : 6, size)
|
||||
console.log("max:", size, max)
|
||||
const max = Math.min(commentsLimited ? 2 : upperLimit, size)
|
||||
|
||||
const Wrapper = !commentsLimited ? ScrollableList : DummyContainer
|
||||
console.log("Wrapper:", Wrapper)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Wrapper scrollKey='comments'>
|
||||
{
|
||||
descendants.slice(0, max).map((descendant, i) => (
|
||||
<Comment
|
||||
key={`comment-${descendant.get('statusId')}-${i}`}
|
||||
id={descendant.get('statusId')}
|
||||
indent={descendant.get('indent')}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Wrapper>
|
||||
{
|
||||
descendants.slice(0, max).map((descendant, i) => (
|
||||
<Comment
|
||||
key={`comment-${descendant.get('statusId')}-${i}`}
|
||||
id={descendant.get('statusId')}
|
||||
indent={descendant.get('indent')}
|
||||
/>
|
||||
))
|
||||
}
|
||||
{
|
||||
size > 0 && size > max &&
|
||||
size > 0 && size > max && commentsLimited &&
|
||||
<div className={[_s.default, _s.flexRow, _s.px15, _s.pb5, _s.mb10, _s.alignItemsCenter].join(' ')}>
|
||||
<Button
|
||||
isText
|
||||
backgroundColor='none'
|
||||
color='tertiary'
|
||||
onClick={this.handleLoadMore}
|
||||
>
|
||||
<Text weight='bold' color='inherit'>
|
||||
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,8 +100,22 @@ class Composer extends PureComponent {
|
|||
}
|
||||
|
||||
state = {
|
||||
markdownText: '',
|
||||
plainText: '',
|
||||
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) => {
|
||||
this.setState({ editorState })
|
||||
|
@ -123,15 +137,14 @@ class Composer extends PureComponent {
|
|||
this.props.onChange(null, text, selectionStart, markdownString)
|
||||
}
|
||||
|
||||
// **bold**
|
||||
// *italic*
|
||||
// __underline__
|
||||
// ~strikethrough~
|
||||
// # title
|
||||
// > quote
|
||||
// `code`
|
||||
// ```code```
|
||||
|
||||
// **bold**
|
||||
// *italic*
|
||||
// __underline__
|
||||
// ~strikethrough~
|
||||
// # title
|
||||
// > quote
|
||||
// `code`
|
||||
// ```code```
|
||||
|
||||
focus = () => {
|
||||
this.textbox.editor.focus()
|
||||
|
@ -168,7 +181,7 @@ class Composer extends PureComponent {
|
|||
disabled,
|
||||
placeholder,
|
||||
autoFocus,
|
||||
// value,
|
||||
value,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
onKeyUp,
|
||||
|
|
|
@ -122,7 +122,7 @@ class DisplayName extends ImmutablePureComponent {
|
|||
|
||||
const iconSize =
|
||||
!!isLarge ? '19px' :
|
||||
!!isSmall ? '14px' : '16px'
|
||||
!!isSmall ? '14px' : '15px'
|
||||
|
||||
const domain = account.get('acct').split('@')[1]
|
||||
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 (shortname in customEmojis) {
|
||||
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 false;
|
||||
|
@ -60,7 +60,7 @@ const emojify = (str, customEmojis = {}) => {
|
|||
} else { // matched to unicode emoji
|
||||
const { filename, shortCode } = unicodeMapping[match];
|
||||
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;
|
||||
// If the matched character was followed by VS15 (for selecting text presentation), skip it.
|
||||
if (str.codePointAt(rend) === 65038) {
|
||||
|
|
|
@ -9,48 +9,39 @@ export default class ExtendedVideoPlayer extends PureComponent {
|
|||
controls: PropTypes.bool.isRequired,
|
||||
muted: PropTypes.bool.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
handleLoadedData = () => {
|
||||
if (this.props.time) {
|
||||
this.video.currentTime = this.props.time;
|
||||
this.video.currentTime = this.props.time
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.video.addEventListener('loadeddata', this.handleLoadedData);
|
||||
this.video.addEventListener('loadeddata', this.handleLoadedData)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.video.removeEventListener('loadeddata', this.handleLoadedData);
|
||||
this.video.removeEventListener('loadeddata', this.handleLoadedData)
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.video = c;
|
||||
this.video = c
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
e.stopPropagation();
|
||||
const handler = this.props.onClick;
|
||||
if (handler) handler();
|
||||
e.stopPropagation()
|
||||
const handler = this.props.onClick
|
||||
if (handler) handler()
|
||||
}
|
||||
|
||||
render () {
|
||||
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);
|
||||
// }
|
||||
// }
|
||||
|
||||
const { src, muted, controls, alt } = this.props
|
||||
|
||||
return (
|
||||
<div className='extended-video-player'>
|
||||
<div className={[_s.default, _s.width100PC, _s.height100PC, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
|
||||
<video
|
||||
className={[_s.default, _s.maxWidth100PC, _s.heightMax100PC].join(' ')}
|
||||
playsInline
|
||||
ref={this.setRef}
|
||||
src={src}
|
||||
|
@ -64,7 +55,7 @@ export default class ExtendedVideoPlayer extends PureComponent {
|
|||
onClick={this.handleClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ const messages = defineMessages({
|
|||
|
||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
|
||||
toggleMembership(group, relationships) {
|
||||
onToggleMembership(group, relationships) {
|
||||
if (relationships.get('member')) {
|
||||
dispatch(leaveGroup(group.get('id')));
|
||||
} else {
|
||||
|
@ -116,6 +116,7 @@ class GroupHeader extends ImmutablePureComponent {
|
|||
<Button
|
||||
radiusSmall
|
||||
className={_s.mr5}
|
||||
onClick={this.handleOnToggleMembership}
|
||||
{...actionButtonOptions}
|
||||
>
|
||||
<Text color='inherit' size='small'>
|
||||
|
|
|
@ -24,6 +24,7 @@ export default class Input extends PureComponent {
|
|||
inputRef: PropTypes.func,
|
||||
id: PropTypes.string.isRequired,
|
||||
hideLabel: PropTypes.bool,
|
||||
maxLength: PropTypes.number,
|
||||
}
|
||||
|
||||
handleOnChange = (e) => {
|
||||
|
@ -46,7 +47,8 @@ export default class Input extends PureComponent {
|
|||
readOnly,
|
||||
inputRef,
|
||||
id,
|
||||
hideLabel
|
||||
hideLabel,
|
||||
maxLength,
|
||||
} = this.props
|
||||
|
||||
const inputClasses = cx({
|
||||
|
@ -87,7 +89,7 @@ export default class Input extends PureComponent {
|
|||
</Text>
|
||||
</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 &&
|
||||
<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}
|
||||
onBlur={onBlur}
|
||||
readOnly={readOnly}
|
||||
maxLength={maxLength}
|
||||
/>
|
||||
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ class LoadMore extends PureComponent {
|
|||
}
|
||||
|
||||
handleClick = (e) => {
|
||||
this.props.onClick()
|
||||
this.props.onClick(e)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -309,10 +309,11 @@ class MediaGallery extends PureComponent {
|
|||
} = this.props
|
||||
const { visible } = this.state
|
||||
|
||||
const width = this.state.width || defaultWidth;
|
||||
let width = this.state.width || defaultWidth
|
||||
if (reduced) width = width / 2
|
||||
|
||||
const style = {};
|
||||
const size = media.take(4).size;
|
||||
const style = {}
|
||||
const size = media.take(4).size
|
||||
|
||||
const standard169 = width / (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)
|
||||
//then we need to make media smaller
|
||||
if (reduced) {
|
||||
style.height = width / 2 || '50%'
|
||||
}
|
||||
// if (reduced) {
|
||||
// style.height = width / 2 || '50%'
|
||||
// }
|
||||
|
||||
if (!visible) {
|
||||
style.height = 'auto'
|
||||
|
@ -573,7 +574,7 @@ class MediaGallery extends PureComponent {
|
|||
<Button
|
||||
title={intl.formatMessage(messages.toggle_visible)}
|
||||
icon='hidden'
|
||||
backgroundColor='none'
|
||||
backgroundColor='black'
|
||||
className={[_s.px10, _s.bgBlackOpaque_onHover].join(' ')}
|
||||
onClick={this.handleOpen}
|
||||
/>
|
||||
|
|
|
@ -9,9 +9,9 @@ export default
|
|||
class ConfirmationModal extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
title: PropTypes.node.isRequired,
|
||||
message: PropTypes.node.isRequired,
|
||||
confirm: PropTypes.string.isRequired,
|
||||
title: PropTypes.any.isRequired,
|
||||
message: PropTypes.any.isRequired,
|
||||
confirm: PropTypes.any.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
secondary: PropTypes.string,
|
||||
|
|
|
@ -49,8 +49,9 @@ export const mapDispatchToProps = (dispatch, { onClose }) => ({
|
|||
}
|
||||
},
|
||||
|
||||
handleSelectResult: (resultId) => {
|
||||
|
||||
handleSelectResult: (result) => {
|
||||
dispatch(setSelectedGif(result))
|
||||
onClose()
|
||||
},
|
||||
|
||||
// dispatchSubmit: (e) => {
|
||||
|
@ -70,11 +71,11 @@ class GifPickerModal extends PureComponent {
|
|||
handleCloseModal: PropTypes.func.isRequired,
|
||||
handleFetchCategories: PropTypes.func.isRequired,
|
||||
handleOnChange: PropTypes.func.isRequired,
|
||||
handleSelectResult: PropTypes.func.isRequired,
|
||||
categories: PropTypes.array.isRequired,
|
||||
results: PropTypes.array.isRequired,
|
||||
loading: PropTypes.bool,
|
||||
error: PropTypes.bool,
|
||||
chosenUrl: PropTypes.string,
|
||||
searchText: PropTypes.string,
|
||||
}
|
||||
|
||||
|
@ -98,8 +99,8 @@ class GifPickerModal extends PureComponent {
|
|||
this.props.handleOnChange(category)
|
||||
}
|
||||
|
||||
handleSelectGifResult = (resultId) => {
|
||||
console.log("handleSelectGifResult:", resultId)
|
||||
handleSelectGifResult = (resultBlock) => {
|
||||
this.props.handleSelectResult(resultBlock)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -109,7 +110,7 @@ class GifPickerModal extends PureComponent {
|
|||
results,
|
||||
loading,
|
||||
error,
|
||||
searchText
|
||||
searchText,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
|
@ -165,6 +166,7 @@ class GifPickerModal extends PureComponent {
|
|||
}
|
||||
|
||||
class GifResultsCollectionColumn extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
results: PropTypes.array.isRequired,
|
||||
handleSelectGifResult: PropTypes.func.isRequired,
|
||||
|
@ -183,8 +185,8 @@ class GifResultsCollectionColumn extends PureComponent {
|
|||
results.map((result, i) => (
|
||||
<button
|
||||
key={`gif-result-item-${i}`}
|
||||
onClick={() => this.onClick(result.id)}
|
||||
className={[_s.default, _s.cursorPointer, _s.px2, _s.py2].join(' ')}
|
||||
onClick={() => this.onClick(result)}
|
||||
className={[_s.default, _s.outlineNone, _s.bgTransparent, _s.cursorPointer, _s.px2, _s.py2].join(' ')}
|
||||
>
|
||||
<Image
|
||||
height={result.media[0].tinygif.dims[1]}
|
||||
|
@ -196,9 +198,11 @@ class GifResultsCollectionColumn extends PureComponent {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GifResultsCollection extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
results: PropTypes.array.isRequired,
|
||||
handleSelectGifResult: PropTypes.func.isRequired,
|
||||
|
@ -250,7 +254,7 @@ class GifCategoriesCollection extends PureComponent {
|
|||
<button
|
||||
key={`gif-category-${i}`}
|
||||
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(' ')}>
|
||||
<Image
|
||||
|
@ -269,4 +273,5 @@ class GifCategoriesCollection extends PureComponent {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -158,19 +158,23 @@ class MediaModal extends ImmutablePureComponent {
|
|||
pagination = media.map((item, i) => {
|
||||
const btnClasses = CX({
|
||||
default: 1,
|
||||
px5: 1,
|
||||
py5: 1,
|
||||
width10PX: 1,
|
||||
height10PX: 1,
|
||||
outlineNone: 1,
|
||||
colorPrimary: 1,
|
||||
circle: 1,
|
||||
cursorPointer: 1,
|
||||
colorPrimary: i === index,
|
||||
lineHeight0825: i === index,
|
||||
bgPrimaryOpaque: i !== index,
|
||||
bgPrimary: i === index,
|
||||
})
|
||||
const activeText = i === index ? '•' : ''
|
||||
|
||||
return (
|
||||
<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>
|
||||
)
|
||||
})
|
||||
|
@ -234,6 +238,9 @@ class MediaModal extends ImmutablePureComponent {
|
|||
const swipeableViewsStyle = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}
|
||||
|
||||
const navigationClasses = CX({
|
||||
|
@ -244,7 +251,7 @@ class MediaModal extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className={[_s.default, _s.width100PC, _s.height100PC, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
|
||||
<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'
|
||||
onClick={onClose}
|
||||
>
|
||||
|
@ -252,6 +259,7 @@ class MediaModal extends ImmutablePureComponent {
|
|||
style={swipeableViewsStyle}
|
||||
containerStyle={{
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
onChangeIndex={this.handleSwipe}
|
||||
onSwitching={this.handleSwitching}
|
||||
|
@ -286,7 +294,7 @@ class MediaModal extends ImmutablePureComponent {
|
|||
|
||||
</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}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -135,7 +135,7 @@ class ModalRoot extends PureComponent {
|
|||
}
|
||||
|
||||
renderLoading = () => {
|
||||
return <ModalLoading />
|
||||
return null
|
||||
}
|
||||
|
||||
renderError = () => {
|
||||
|
|
|
@ -45,12 +45,7 @@ class ProUpgradeModal extends ImmutablePureComponent {
|
|||
</div>
|
||||
|
||||
<Button
|
||||
backgroundColor='brand'
|
||||
color='white'
|
||||
icon='pro'
|
||||
href='https://pro.gab.com'
|
||||
className={_s.justifyContentCenter}
|
||||
iconClassName={[_s.mr5, _s.fillWhite].join(' ')}
|
||||
>
|
||||
<Text color='inherit' weight='bold' align='center'>
|
||||
{intl.formatMessage(messages.title)}
|
||||
|
|
|
@ -56,8 +56,8 @@ class ReportModal extends ImmutablePureComponent {
|
|||
onClose: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
handleCommentChange = e => {
|
||||
this.props.dispatch(changeReportComment(e.target.value))
|
||||
handleCommentChange = (e) => {
|
||||
this.props.dispatch(changeReportComment(value))
|
||||
}
|
||||
|
||||
handleForwardChange = e => {
|
||||
|
|
|
@ -75,9 +75,11 @@ class Poll extends ImmutablePureComponent {
|
|||
const { selected } = this.state
|
||||
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 optionHasNoVotes = option.get('votes_count') === 0
|
||||
const active = !!selected[`${optionIndex}`]
|
||||
const showResults = poll.get('voted') || poll.get('expired')
|
||||
const multiple = poll.get('multiple')
|
||||
const correctedWidthPercent = optionHasNoVotes ? 100 : percent
|
||||
|
||||
let titleEmojified = option.get('title_emojified')
|
||||
if (!titleEmojified) {
|
||||
|
@ -92,10 +94,12 @@ class Poll extends ImmutablePureComponent {
|
|||
left0: 1,
|
||||
radiusSmall: 1,
|
||||
height100PC: 1,
|
||||
bgSecondary: !leading,
|
||||
bgSecondary: !leading && !optionHasNoVotes,
|
||||
bgTertiary: !leading && optionHasNoVotes,
|
||||
bgBrandLight: leading,
|
||||
})
|
||||
|
||||
// : todo :
|
||||
const inputClasses = cx('poll__input', {
|
||||
'poll__input--checkbox': multiple,
|
||||
'poll__input--active': active,
|
||||
|
@ -107,7 +111,7 @@ class Poll extends ImmutablePureComponent {
|
|||
py10: showResults,
|
||||
mb10: 1,
|
||||
border1PX: !showResults,
|
||||
fillSecondary: !showResults,
|
||||
borderColorSecondary: !showResults,
|
||||
circle: !showResults,
|
||||
cursorPointer: !showResults,
|
||||
bgSubtle_onHover: !showResults,
|
||||
|
@ -127,7 +131,7 @@ class Poll extends ImmutablePureComponent {
|
|||
<li className={listItemClasses} key={option.get('title')}>
|
||||
{
|
||||
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 }) =>
|
||||
<span className={chartClasses} style={{ width: `${width}%` }} />
|
||||
}
|
||||
|
@ -139,7 +143,7 @@ class Poll extends ImmutablePureComponent {
|
|||
<Text
|
||||
size='medium'
|
||||
color='primary'
|
||||
weight={leading ? 'bold' : 'normal'}
|
||||
weight={(leading && showResults) ? 'bold' : 'normal'}
|
||||
className={[_s.displayFlex, _s.flexRow, _s.width100PC, _s.alignItemsCenter].join(' ')}
|
||||
>
|
||||
{
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
import DatePicker from 'react-datepicker'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
import moment from 'moment-mini'
|
||||
import { changeScheduledAt } from '../../actions/compose'
|
||||
import { openModal } from '../../actions/modal'
|
||||
import { closePopover } from '../../actions/popover'
|
||||
import { me } from '../../initial_state'
|
||||
import {
|
||||
MODAL_PRO_UPGRADE,
|
||||
} from '../../constants'
|
||||
import { isMobile } from '../../utils/is_mobile'
|
||||
import PopoverLayout from './popover_layout'
|
||||
import Button from '../button'
|
||||
import Text from '../text'
|
||||
|
||||
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']),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = (dispatch, { isPro }) => ({
|
||||
setScheduledAt (date) {
|
||||
if (!isPro) {
|
||||
dispatch(closePopover())
|
||||
return dispatch(openModal(MODAL_PRO_UPGRADE))
|
||||
}
|
||||
|
||||
dispatch(changeScheduledAt(date))
|
||||
|
||||
if (!date) {
|
||||
dispatch(closePopover())
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -31,42 +49,70 @@ class DatePickerPopover extends PureComponent {
|
|||
handleSetDate = (date) => {
|
||||
this.props.setScheduledAt(date)
|
||||
}
|
||||
|
||||
handleRemoveDate = () => {
|
||||
this.props.setScheduledAt(null)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { date, isPro, position } = this.props
|
||||
const { date, isPro } = this.props
|
||||
|
||||
const open = !!date
|
||||
const datePickerDisabled = !isPro
|
||||
const withPortal = isMobile(window.innerWidth)
|
||||
|
||||
return (
|
||||
<PopoverLayout width={331}>
|
||||
<DatePicker
|
||||
inline
|
||||
target={this}
|
||||
className='schedule-post-dropdown__datepicker'
|
||||
minDate={new Date()}
|
||||
selected={date}
|
||||
onChange={date => this.handleSetDate(date)}
|
||||
timeFormat='p'
|
||||
timeIntervals={15}
|
||||
timeCaption='Time'
|
||||
dateFormat='MMM d, yyyy h:mm aa'
|
||||
disabled={datePickerDisabled}
|
||||
showTimeSelect
|
||||
withPortal={withPortal}
|
||||
popperModifiers={{
|
||||
offset: {
|
||||
enabled: true,
|
||||
offset: '0px, 5px'
|
||||
},
|
||||
preventOverflow: {
|
||||
enabled: true,
|
||||
escapeWithReference: false,
|
||||
boundariesElement: 'viewport'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className={[_s.default].join(' ')}>
|
||||
<DatePicker
|
||||
inline
|
||||
target={this}
|
||||
className='schedule-post-dropdown__datepicker'
|
||||
minDate={new Date()}
|
||||
selected={date}
|
||||
onChange={date => this.handleSetDate(date)}
|
||||
timeFormat='p'
|
||||
timeIntervals={15}
|
||||
timeCaption='Time'
|
||||
dateFormat='MMM d, yyyy h:mm aa'
|
||||
disabled={datePickerDisabled}
|
||||
showTimeSelect
|
||||
withPortal={withPortal}
|
||||
popperModifiers={{
|
||||
offset: {
|
||||
enabled: true,
|
||||
offset: '0px, 5px'
|
||||
},
|
||||
preventOverflow: {
|
||||
enabled: true,
|
||||
escapeWithReference: false,
|
||||
boundariesElement: 'viewport'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import detectPassiveEvents from 'detect-passive-events'
|
|||
import { changeSetting } from '../../actions/settings'
|
||||
import { useEmoji } from '../../actions/emojis'
|
||||
import { closePopover } from '../../actions/popover'
|
||||
import { insertEmojiCompose } from '../../actions/compose'
|
||||
import { EmojiPicker as EmojiPickerAsync } from '../../features/ui/util/async_components'
|
||||
import { buildCustomEmojis } from '../emoji/emoji'
|
||||
import PopoverLayout from './popover_layout'
|
||||
|
@ -209,21 +210,19 @@ const mapStateToProps = (state) => ({
|
|||
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onClosePopover() {
|
||||
dispatch(closePopover())
|
||||
},
|
||||
|
||||
onSkinTone: skinTone => {
|
||||
onSkinTone: (skinTone) => {
|
||||
dispatch(changeSetting(['skinTone'], skinTone))
|
||||
},
|
||||
|
||||
onPickEmoji: emoji => {
|
||||
onPickEmoji: (emoji) => {
|
||||
dispatch(useEmoji(emoji))
|
||||
|
||||
if (onPickEmoji) {
|
||||
onPickEmoji(emoji)
|
||||
}
|
||||
console.log("emoji:", emoji)
|
||||
dispatch(insertEmojiCompose(0, emoji, false))
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -274,7 +273,7 @@ class EmojiPickerPopover extends ImmutablePureComponent {
|
|||
} = this.props
|
||||
|
||||
const { loading } = this.state
|
||||
|
||||
|
||||
return (
|
||||
<PopoverLayout width={340}>
|
||||
<EmojiPickerMenu
|
||||
|
|
|
@ -57,12 +57,6 @@ class PopoverRoot extends PureComponent {
|
|||
props: PropTypes.object,
|
||||
}
|
||||
|
||||
getSnapshotBeforeUpdate() {
|
||||
return {
|
||||
visible: !!this.props.type
|
||||
}
|
||||
}
|
||||
|
||||
renderEmpty = () => {
|
||||
return <div />
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ export default class ScrollableList extends PureComponent {
|
|||
}
|
||||
|
||||
handleLoadMore = (e) => {
|
||||
e.preventDefault();
|
||||
e.preventDefault()
|
||||
this.props.onLoadMore();
|
||||
}
|
||||
|
||||
|
|
|
@ -67,6 +67,10 @@ export default
|
|||
@injectIntl
|
||||
class Sidebar extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
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 => {
|
||||
this.moreBtnRef = n
|
||||
}
|
||||
|
@ -213,7 +229,17 @@ class Sidebar extends ImmutablePureComponent {
|
|||
<div className={_s.default}>
|
||||
{
|
||||
!!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'>
|
||||
{title}
|
||||
</Heading>
|
||||
|
|
|
@ -10,6 +10,7 @@ import ComposeFormContainer from '../features/compose/containers/compose_form_co
|
|||
import StatusContent from './status_content'
|
||||
import StatusPrepend from './status_prepend'
|
||||
import StatusActionBar from './status_action_bar'
|
||||
import StatusMedia from './status_media'
|
||||
import Poll from './poll'
|
||||
import StatusHeader from './status_header'
|
||||
import CommentList from './comment_list'
|
||||
|
@ -87,6 +88,7 @@ class Status extends ImmutablePureComponent {
|
|||
cacheMediaWidth: PropTypes.func,
|
||||
cachedMediaWidth: PropTypes.number,
|
||||
contextType: PropTypes.string,
|
||||
commentsLimited: PropTypes.bool,
|
||||
}
|
||||
|
||||
// Avoid checking props that are functions (and whose equality will always
|
||||
|
@ -124,7 +126,7 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (!nextProps.isHidden && nextProps.isIntersecting && !prevState.loadedComments) {
|
||||
if (!nextProps.isHidden && (nextProps.isIntersecting || !nextProps.commentsLimited) && !prevState.loadedComments) {
|
||||
return {
|
||||
loadedComments: true
|
||||
}
|
||||
|
@ -132,6 +134,7 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
|
||||
return {
|
||||
loadedComments: false,
|
||||
showMedia: defaultMediaVisibility(nextProps.status),
|
||||
statusId: nextProps.status.get('id'),
|
||||
}
|
||||
|
@ -142,6 +145,7 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
// Compensate height changes
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
// timeline lazy loading comments
|
||||
if (!prevState.loadedComments && this.state.loadedComments && this.props.status) {
|
||||
const commentCount = this.props.status.get('replies_count')
|
||||
if (commentCount > 0) {
|
||||
|
@ -163,7 +167,7 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleMoveUp = id => {
|
||||
handleMoveUp = (id) => {
|
||||
const { status, ancestorsIds, descendantsIds } = this.props
|
||||
|
||||
if (id === status.get('id')) {
|
||||
|
@ -347,7 +351,7 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
if (!status) return null
|
||||
|
||||
let media, reblogContent, rebloggedByText = null
|
||||
let reblogContent, rebloggedByText = null
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
rebloggedByText = intl.formatMessage(
|
||||
|
@ -386,62 +390,6 @@ class Status extends ImmutablePureComponent {
|
|||
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({
|
||||
default: 1,
|
||||
pb15: isFeatured,
|
||||
|
@ -476,7 +424,7 @@ class Status extends ImmutablePureComponent {
|
|||
data-featured={isFeatured ? 'true' : null}
|
||||
aria-label={textForScreenReader(intl, status, rebloggedByText)}
|
||||
ref={this.handleRef}
|
||||
// onClick={this.handleClick}
|
||||
onClick={isChild ? this.handleClick : undefined}
|
||||
>
|
||||
<div className={innerContainerClasses}>
|
||||
|
||||
|
@ -497,7 +445,17 @@ class Status extends ImmutablePureComponent {
|
|||
/>
|
||||
</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 &&
|
||||
|
|
|
@ -42,7 +42,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
updateOnProps = ['status']
|
||||
|
||||
handleReplyClick = () => {
|
||||
this.props.onReply(this.props.status)
|
||||
this.props.onReply(this.props.status, null, true)
|
||||
}
|
||||
|
||||
handleFavoriteClick = () => {
|
||||
|
|
|
@ -10,8 +10,8 @@ import Text from './text'
|
|||
const MAX_HEIGHT = 200
|
||||
|
||||
const messages = defineMessages({
|
||||
showMore: { id: 'status.show_more', defaultMessage: 'Show more' },
|
||||
showLess: { id: 'status.show_less', defaultMessage: 'Show less' },
|
||||
show: { id: 'status.show_more', defaultMessage: 'Show' },
|
||||
hide: { id: 'status.show_less', defaultMessage: 'Hide' },
|
||||
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({
|
||||
default: 1,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Fragment } from 'react'
|
||||
import { injectIntl, defineMessages } from 'react-intl'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
|
@ -14,6 +15,14 @@ import Icon from './icon'
|
|||
import Button from './button'
|
||||
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 mapDispatchToProps = (dispatch) => ({
|
||||
|
@ -33,10 +42,12 @@ const mapDispatchToProps = (dispatch) => ({
|
|||
})
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
@connect(null, mapDispatchToProps)
|
||||
class StatusHeader extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
status: ImmutablePropTypes.map,
|
||||
onOpenStatusRevisionsPopover: PropTypes.func.isRequired,
|
||||
onOpenStatusOptionsPopover: PropTypes.func.isRequired,
|
||||
|
@ -56,7 +67,11 @@ class StatusHeader extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { status, reduced } = this.props
|
||||
const {
|
||||
intl,
|
||||
reduced,
|
||||
status,
|
||||
} = this.props
|
||||
|
||||
const statusUrl = `/${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`
|
||||
|
||||
|
@ -68,7 +83,22 @@ class StatusHeader extends ImmutablePureComponent {
|
|||
})
|
||||
|
||||
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 (
|
||||
<div className={containerClasses}>
|
||||
|
@ -126,8 +156,10 @@ class StatusHeader extends ImmutablePureComponent {
|
|||
</Button>
|
||||
|
||||
<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') &&
|
||||
|
|
|
@ -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 MAX_SCALE = 4;
|
||||
const MIN_SCALE = 1
|
||||
const MAX_SCALE = 4
|
||||
|
||||
const getMidpoint = (p1, p2) => ({
|
||||
x: (p1.clientX + p2.clientX) / 2,
|
||||
y: (p1.clientY + p2.clientY) / 2,
|
||||
});
|
||||
})
|
||||
|
||||
const getDistance = (p1, p2) =>
|
||||
Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2));
|
||||
const getDistance = (p1, p2) => {
|
||||
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));
|
||||
|
||||
// .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 :
|
||||
const clamp = (min, max, value) => {
|
||||
return Math.min(max, Math.max(min, value))
|
||||
}
|
||||
|
||||
export default class ZoomableImage extends PureComponent {
|
||||
|
||||
|
@ -40,69 +28,69 @@ export default class ZoomableImage extends PureComponent {
|
|||
alt: '',
|
||||
width: null,
|
||||
height: null,
|
||||
};
|
||||
}
|
||||
|
||||
state = {
|
||||
scale: MIN_SCALE,
|
||||
}
|
||||
|
||||
removers = [];
|
||||
container = null;
|
||||
image = null;
|
||||
lastTouchEndTime = 0;
|
||||
lastDistance = 0;
|
||||
removers = []
|
||||
container = null
|
||||
image = null
|
||||
lastTouchEndTime = 0
|
||||
lastDistance = 0
|
||||
|
||||
componentDidMount () {
|
||||
let handler = this.handleTouchStart;
|
||||
this.container.addEventListener('touchstart', handler);
|
||||
this.removers.push(() => this.container.removeEventListener('touchstart', handler));
|
||||
handler = this.handleTouchMove;
|
||||
let handler = this.handleTouchStart
|
||||
this.container.addEventListener('touchstart', handler)
|
||||
this.removers.push(() => this.container.removeEventListener('touchstart', handler))
|
||||
handler = this.handleTouchMove
|
||||
// on Chrome 56+, touch event listeners will default to passive
|
||||
// https://www.chromestatus.com/features/5093566007214080
|
||||
this.container.addEventListener('touchmove', handler, { passive: false });
|
||||
this.removers.push(() => this.container.removeEventListener('touchend', handler));
|
||||
this.container.addEventListener('touchmove', handler, { passive: false })
|
||||
this.removers.push(() => this.container.removeEventListener('touchend', handler))
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.removeEventListeners();
|
||||
this.removeEventListeners()
|
||||
}
|
||||
|
||||
removeEventListeners () {
|
||||
this.removers.forEach(listeners => listeners());
|
||||
this.removers = [];
|
||||
this.removers.forEach(listeners => listeners())
|
||||
this.removers = []
|
||||
}
|
||||
|
||||
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 => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = this.container;
|
||||
const { scrollTop, scrollHeight, clientHeight } = this.container
|
||||
if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) {
|
||||
// prevent propagating event to MediaModal
|
||||
e.stopPropagation();
|
||||
return;
|
||||
e.stopPropagation()
|
||||
return
|
||||
}
|
||||
if (e.touches.length !== 2) return;
|
||||
if (e.touches.length !== 2) return
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const distance = getDistance(...e.touches);
|
||||
const midpoint = getMidpoint(...e.touches);
|
||||
const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance);
|
||||
const distance = getDistance(...e.touches)
|
||||
const midpoint = getMidpoint(...e.touches)
|
||||
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.lastDistance = distance;
|
||||
this.lastMidpoint = midpoint
|
||||
this.lastDistance = distance
|
||||
}
|
||||
|
||||
zoom(nextScale, midpoint) {
|
||||
const { scale } = this.state;
|
||||
const { scrollLeft, scrollTop } = this.container;
|
||||
const { scale } = this.state
|
||||
const { scrollLeft, scrollTop } = this.container
|
||||
|
||||
// math memo:
|
||||
// x = (scrollLeft + midpoint.x) / scrollWidth
|
||||
|
@ -110,38 +98,44 @@ export default class ZoomableImage extends PureComponent {
|
|||
// scrollWidth = clientWidth * scale
|
||||
// scrollWidth' = clientWidth * nextScale
|
||||
// Solve x = x' for nextScrollLeft
|
||||
const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x;
|
||||
const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y;
|
||||
const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x
|
||||
const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y
|
||||
|
||||
this.setState({ scale: nextScale }, () => {
|
||||
this.container.scrollLeft = nextScrollLeft;
|
||||
this.container.scrollTop = nextScrollTop;
|
||||
});
|
||||
this.container.scrollLeft = nextScrollLeft
|
||||
this.container.scrollTop = nextScrollTop
|
||||
})
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
// don't propagate event to MediaModal
|
||||
e.stopPropagation();
|
||||
const handler = this.props.onClick;
|
||||
if (handler) handler();
|
||||
e.stopPropagation()
|
||||
const handler = this.props.onClick
|
||||
if (handler) handler()
|
||||
}
|
||||
|
||||
setContainerRef = c => {
|
||||
this.container = c;
|
||||
this.container = c
|
||||
}
|
||||
|
||||
setImageRef = c => {
|
||||
this.image = c;
|
||||
this.image = c
|
||||
}
|
||||
|
||||
render () {
|
||||
const { alt, src } = this.props;
|
||||
const { scale } = this.state;
|
||||
const overflow = scale === 1 ? 'hidden' : 'scroll';
|
||||
const { alt, src } = this.props
|
||||
const { scale } = this.state
|
||||
|
||||
const overflow = scale === 1 ? 'hidden' : 'scroll'
|
||||
|
||||
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
|
||||
className={[_s.default, _s.objectFitContain, _s.heightAuto, _s.widthAuto, _s.maxWidth100PC, _s.heightMax100PC].join(' ')}
|
||||
role='presentation'
|
||||
ref={this.setImageRef}
|
||||
alt={alt}
|
||||
|
@ -154,7 +148,7 @@ export default class ZoomableImage extends PureComponent {
|
|||
onClick={this.handleClick}
|
||||
/>
|
||||
</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 {
|
||||
replyCompose,
|
||||
|
@ -34,16 +34,8 @@ import {
|
|||
import { makeGetStatus } from '../selectors';
|
||||
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 getStatus = makeGetStatus();
|
||||
const getStatus = makeGetStatus()
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const statusId = props.id || props.params.statusId
|
||||
|
@ -62,9 +54,12 @@ const makeMapStateToProps = () => {
|
|||
let indent = -1
|
||||
descendantsIds = descendantsIds.withMutations(mutable => {
|
||||
const ids = [status.get('id')]
|
||||
|
||||
|
||||
const r = state.getIn(['contexts', 'replies', ids[0]])
|
||||
console.log("r:", r)
|
||||
|
||||
while (ids.length > 0) {
|
||||
let id = ids.shift();
|
||||
let id = ids.shift()
|
||||
const replies = state.getIn(['contexts', 'replies', id])
|
||||
|
||||
if (status.get('id') !== id) {
|
||||
|
@ -94,22 +89,22 @@ const makeMapStateToProps = () => {
|
|||
return mapStateToProps
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
onReply (status, router) {
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onReply (status, router, showModal) {
|
||||
if (!me) return dispatch(openModal('UNAUTHORIZED'))
|
||||
|
||||
dispatch((_, getState) => {
|
||||
const state = getState();
|
||||
if (state.getIn(['compose', 'text']).trim().length !== 0) {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: intl.formatMessage(messages.replyMessage),
|
||||
confirm: intl.formatMessage(messages.replyConfirm),
|
||||
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));
|
||||
dispatch(replyCompose(status, router, showModal));
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
onRepost (targetRef, status, e) {
|
||||
|
@ -177,8 +172,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
dispatch(deleteStatus(status.get('id'), history));
|
||||
} else {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: intl.formatMessage(messages.deleteMessage),
|
||||
confirm: intl.formatMessage(messages.deleteConfirm),
|
||||
message: <FormattedMessage id='confirmations.delete.message' defaultMessage='Are you sure you want to delete this status?' />,
|
||||
confirm: <FormattedMessage id='confirmations.delete.confirm' defaultMessage='Delete' />,
|
||||
onConfirm: () => dispatch(deleteStatus(status.get('id'), history)),
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ import StatusContainer from '../../../containers/status_container'
|
|||
import StatusVisibilityButton from './status_visibility_button'
|
||||
import UploadButton from './media_upload_button'
|
||||
import UploadForm from './upload_form'
|
||||
import GifForm from './gif_form'
|
||||
import Input from '../../../components/input'
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: { id: 'compose_form.placeholder', defaultMessage: "What's on your mind?" },
|
||||
|
@ -78,6 +80,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
replyToId: PropTypes.string,
|
||||
reduxReplyToId: PropTypes.string,
|
||||
hasPoll: PropTypes.bool,
|
||||
selectedGifSrc: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -85,7 +88,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleChange = (e, markdown) => {
|
||||
this.props.onChange(e.target.value, markdown);
|
||||
this.props.onChange(e.target.value, markdown, this.props.replyToId)
|
||||
}
|
||||
|
||||
handleComposeFocus = () => {
|
||||
|
@ -156,8 +159,8 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']);
|
||||
}
|
||||
|
||||
handleChangeSpoilerText = (e) => {
|
||||
this.props.onChangeSpoilerText(e.target.value);
|
||||
handleChangeSpoilerText = (value) => {
|
||||
this.props.onChangeSpoilerText(value)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -169,7 +172,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!this.autosuggestTextarea) return;
|
||||
if (!this.autosuggestTextarea) return
|
||||
|
||||
// This statement does several things:
|
||||
// - If we're beginning a reply, and,
|
||||
|
@ -196,17 +199,13 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
setAutosuggestTextarea = (c) => {
|
||||
this.autosuggestTextarea = c;
|
||||
this.autosuggestTextarea = c
|
||||
}
|
||||
|
||||
setForm = (c) => {
|
||||
this.form = c
|
||||
}
|
||||
|
||||
setSpoilerText = (c) => {
|
||||
this.spoilerText = c
|
||||
}
|
||||
|
||||
handleEmojiPick = (data) => {
|
||||
const { text } = this.props
|
||||
const position = this.autosuggestTextarea.textbox.selectionStart
|
||||
|
@ -236,12 +235,13 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
isMatch,
|
||||
isChangingUpload,
|
||||
isSubmitting,
|
||||
selectedGifSrc,
|
||||
} = this.props
|
||||
const disabled = isSubmitting
|
||||
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
|
||||
const disabledButton = disabled || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text) !== 0 && length(text.trim()) === 0 && !anyMedia);
|
||||
const shouldAutoFocus = autoFocus && !showSearch && !isMobile(window.innerWidth)
|
||||
|
||||
|
||||
const parentContainerClasses = CX({
|
||||
default: 1,
|
||||
width100PC: 1,
|
||||
|
@ -294,7 +294,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
>
|
||||
|
||||
{
|
||||
!!reduxReplyToId && !shouldCondense &&
|
||||
!!reduxReplyToId && !shouldCondense && isModalOpen &&
|
||||
<div className={[_s.default, _s.px15, _s.py10, _s.mt5].join(' ')}>
|
||||
<StatusContainer
|
||||
id={reduxReplyToId}
|
||||
|
@ -306,19 +306,13 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
{
|
||||
!!spoiler &&
|
||||
<div className={[_s.default, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
|
||||
<AutosuggestTextbox
|
||||
<Input
|
||||
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
||||
value={this.props.spoilerText}
|
||||
onChange={this.handleChangeSpoilerText}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
disabled={!this.props.spoiler}
|
||||
ref={this.setSpoilerText}
|
||||
suggestions={this.props.suggestions}
|
||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSpoilerSuggestionSelected}
|
||||
searchTokens={[':']}
|
||||
prependIcon='warning'
|
||||
maxLength={256}
|
||||
id='cw-spoiler-input'
|
||||
/>
|
||||
</div>
|
||||
|
@ -349,12 +343,17 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
</div>
|
||||
}
|
||||
|
||||
{ /* : todo : for gif
|
||||
(isUploading || hasGif) &&
|
||||
{
|
||||
/*
|
||||
!!selectedGifSrc && !anyMedia &&
|
||||
<div className={[_s.default, _s.px15].join(' ')}>
|
||||
<UploadForm replyToId={replyToId} />
|
||||
<GifForm
|
||||
replyToId={replyToId}
|
||||
small={shouldCondense}
|
||||
selectedGifSrc={selectedGifSrc}
|
||||
/>
|
||||
</div>
|
||||
*/
|
||||
*/
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -365,7 +364,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
{
|
||||
!!quoteOfId &&
|
||||
!!quoteOfId && isModalOpen &&
|
||||
<div className={[_s.default, _s.px15, _s.py10, _s.mt5].join(' ')}>
|
||||
<StatusContainer
|
||||
id={quoteOfId}
|
||||
|
@ -376,29 +375,21 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
|
||||
<div className={actionsContainerClasses}>
|
||||
<div className={[_s.default, _s.flexRow, _s.mrAuto].join(' ')}>
|
||||
{
|
||||
!shouldCondense &&
|
||||
<RichTextEditorButton />
|
||||
}
|
||||
|
||||
<EmojiPickerButton small={shouldCondense} isMatch={isMatch} />
|
||||
|
||||
<UploadButton small={shouldCondense} />
|
||||
{ /* <GifSelectorButton small={shouldCondense} /> */ }
|
||||
|
||||
{
|
||||
!edit && !shouldCondense &&
|
||||
<PollButton />
|
||||
}
|
||||
{
|
||||
!shouldCondense &&
|
||||
<StatusVisibilityButton />
|
||||
}
|
||||
{
|
||||
!shouldCondense &&
|
||||
<SpoilerButton />
|
||||
}
|
||||
{
|
||||
!shouldCondense &&
|
||||
<SchedulePostButton />
|
||||
}
|
||||
<GifSelectorButton small={shouldCondense} />
|
||||
<EmojiPickerButton small={shouldCondense} isMatch={isMatch} />
|
||||
|
||||
{ !shouldCondense && <StatusVisibilityButton /> }
|
||||
{ !shouldCondense && <SpoilerButton /> }
|
||||
{ !shouldCondense && <SchedulePostButton /> }
|
||||
{ /* !shouldCondense && <RichTextEditorButton /> */ }
|
||||
|
||||
{
|
||||
shouldCondense &&
|
||||
|
|
|
@ -1,36 +1,39 @@
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ProgressBar from '../../../../components/progress_bar'
|
||||
import Upload from '../media_upload_item'
|
||||
import SensitiveMediaButton from '../sensitive_media_button'
|
||||
import { clearSelectedGif } from '../../../actions/tenor'
|
||||
import Image from '../../../components/image'
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')),
|
||||
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||
uploadProgress: state.getIn(['compose', 'progress']),
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onClearSelectedGif() {
|
||||
dispatch(clearSelectedGif())
|
||||
},
|
||||
})
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps)
|
||||
class GifForm extends ImmutablePureComponent {
|
||||
@connect(null, mapDispatchToProps)
|
||||
class GifForm extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
mediaIds: ImmutablePropTypes.list.isRequired,
|
||||
isUploading: PropTypes.bool,
|
||||
uploadProgress: PropTypes.number,
|
||||
};
|
||||
onClearSelectedGif: PropTypes.func.isRequired,
|
||||
replyToId: PropTypes.string,
|
||||
small: PropTypes.bool,
|
||||
selectedGifSrc: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
mediaIds,
|
||||
isUploading,
|
||||
uploadProgress,
|
||||
selectedGifSrc,
|
||||
small,
|
||||
} = this.props
|
||||
|
||||
if (!selectedGifSrc) return null
|
||||
|
||||
return (
|
||||
<div className={_s.default}>
|
||||
<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>
|
||||
)
|
||||
|
|
|
@ -12,7 +12,7 @@ const messages = defineMessages({
|
|||
const makeMapStateToProps = () => {
|
||||
const mapStateToProps = (state) => ({
|
||||
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,
|
||||
resetFileKey: state.getIn(['compose', 'resetFileKey']),
|
||||
})
|
||||
|
|
|
@ -7,6 +7,7 @@ import { submitCompose } from '../../../actions/compose';
|
|||
import Button from '../../../components/button'
|
||||
import Image from '../../../components/image'
|
||||
import Input from '../../../components/input'
|
||||
import Text from '../../../components/text'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
|
@ -15,27 +16,20 @@ const messages = defineMessages({
|
|||
delete: { id: 'upload_form.undo', defaultMessage: 'Delete' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state, { id, otherProps }) => {
|
||||
console.log("otherProps:", otherProps)
|
||||
return {
|
||||
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
|
||||
}
|
||||
}
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
onUndo: id => {
|
||||
dispatch(undoUploadCompose(id));
|
||||
onUndo: (id) => {
|
||||
dispatch(undoUploadCompose(id))
|
||||
},
|
||||
|
||||
onDescriptionChange: (id, description) => {
|
||||
dispatch(changeUploadCompose(id, { description }));
|
||||
dispatch(changeUploadCompose(id, { description }))
|
||||
},
|
||||
|
||||
onSubmit () {
|
||||
dispatch(submitCompose());
|
||||
dispatch(submitCompose())
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default
|
||||
|
@ -72,13 +66,13 @@ class Upload extends ImmutablePureComponent {
|
|||
this.props.onSubmit()
|
||||
}
|
||||
|
||||
handleUndoClick = e => {
|
||||
handleUndoClick = (e) => {
|
||||
e.stopPropagation()
|
||||
this.props.onUndo(this.props.media.get('id'))
|
||||
}
|
||||
|
||||
handleInputChange = e => {
|
||||
this.setState({ dirtyDescription: e.target.value })
|
||||
handleInputChange = (value) => {
|
||||
this.setState({ dirtyDescription: value })
|
||||
}
|
||||
|
||||
handleMouseEnter = () => {
|
||||
|
@ -128,8 +122,6 @@ class Upload extends ImmutablePureComponent {
|
|||
displayNone: !active,
|
||||
})
|
||||
|
||||
console.log("media:", media)
|
||||
|
||||
return (
|
||||
<div
|
||||
tabIndex='0'
|
||||
|
@ -144,6 +136,12 @@ class Upload extends ImmutablePureComponent {
|
|||
className={[_s.default, _s.height158PX].join(' ')}
|
||||
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
|
||||
backgroundColor='black'
|
||||
color='white'
|
||||
|
|
|
@ -7,14 +7,11 @@ import {
|
|||
removePollOption,
|
||||
changePollOption,
|
||||
changePollSettings,
|
||||
clearComposeSuggestions,
|
||||
fetchComposeSuggestions,
|
||||
selectComposeSuggestion,
|
||||
} from '../../../actions/compose'
|
||||
import Button from '../../../components/button'
|
||||
import Text from '../../../components/text'
|
||||
import Select from '../../../components/select'
|
||||
import AutosuggestTextbox from '../../../components/autosuggest_textbox'
|
||||
import Input from '../../../components/input'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
|
@ -29,7 +26,6 @@ const messages = defineMessages({
|
|||
})
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
suggestions: state.getIn(['compose', 'suggestions']),
|
||||
options: state.getIn(['compose', 'poll', 'options']),
|
||||
expiresIn: state.getIn(['compose', 'poll', 'expires_in']),
|
||||
isMultiple: state.getIn(['compose', 'poll', 'multiple']),
|
||||
|
@ -52,18 +48,6 @@ const mapDispatchToProps = (dispatch) => ({
|
|||
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
|
||||
|
@ -79,10 +63,6 @@ class PollForm extends ImmutablePureComponent {
|
|||
onAddOption: PropTypes.func.isRequired,
|
||||
onRemoveOption: 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,
|
||||
}
|
||||
|
||||
|
@ -200,15 +180,11 @@ class PollFormOption extends ImmutablePureComponent {
|
|||
onChange: PropTypes.func.isRequired,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
onToggleMultiple: PropTypes.func.isRequired,
|
||||
suggestions: ImmutablePropTypes.list,
|
||||
onClearSuggestions: PropTypes.func.isRequired,
|
||||
onFetchSuggestions: PropTypes.func.isRequired,
|
||||
onSuggestionSelected: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
handleOptionTitleChange = e => {
|
||||
this.props.onChange(this.props.index, e.target.value)
|
||||
handleOptionTitleChange = (value) => {
|
||||
this.props.onChange(this.props.index, value)
|
||||
}
|
||||
|
||||
handleOptionRemove = () => {
|
||||
|
@ -221,18 +197,6 @@ class PollFormOption extends ImmutablePureComponent {
|
|||
e.stopPropagation()
|
||||
}
|
||||
|
||||
onSuggestionsClearRequested = () => {
|
||||
this.props.onClearSuggestions()
|
||||
}
|
||||
|
||||
onSuggestionsFetchRequested = (token) => {
|
||||
this.props.onFetchSuggestions(token)
|
||||
}
|
||||
|
||||
onSuggestionSelected = (tokenStart, token, value) => {
|
||||
this.props.onSuggestionSelected(tokenStart, token, value, ['poll', 'options', this.props.index])
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isPollMultiple, title, index, intl } = this.props
|
||||
|
||||
|
@ -257,16 +221,11 @@ class PollFormOption extends ImmutablePureComponent {
|
|||
tabIndex='0'
|
||||
/>
|
||||
|
||||
<AutosuggestTextbox
|
||||
<Input
|
||||
placeholder={intl.formatMessage(messages.option_placeholder, { number: index + 1 })}
|
||||
maxLength={25}
|
||||
maxLength={64}
|
||||
value={title}
|
||||
onChange={this.handleOptionTitleChange}
|
||||
suggestions={this.props.suggestions}
|
||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSuggestionSelected}
|
||||
searchTokens={[':']}
|
||||
/>
|
||||
</label>
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
fetchComposeSuggestions,
|
||||
selectComposeSuggestion,
|
||||
changeComposeSpoilerText,
|
||||
insertEmojiCompose,
|
||||
uploadCompose,
|
||||
changeScheduledAt,
|
||||
} from '../../../actions/compose'
|
||||
|
@ -18,35 +17,66 @@ const mapStateToProps = (state, { replyToId }) => {
|
|||
const reduxReplyToId = state.getIn(['compose', 'in_reply_to'])
|
||||
const isMatch = reduxReplyToId || replyToId ? reduxReplyToId === replyToId : true
|
||||
|
||||
if (!isMatch) {
|
||||
return {
|
||||
isMatch,
|
||||
edit: null,
|
||||
text: '',
|
||||
suggestions: ImmutableList(),
|
||||
spoiler: false,
|
||||
spoilerText: '',
|
||||
privacy: null,
|
||||
focusDate: null,
|
||||
caretPosition: null,
|
||||
preselectDate: null,
|
||||
isSubmitting: false,
|
||||
isChangingUpload: false,
|
||||
isUploading: false,
|
||||
showSearch: false,
|
||||
anyMedia: false,
|
||||
isModalOpen: false,
|
||||
quoteOfId: null,
|
||||
scheduledAt: null,
|
||||
account: state.getIn(['accounts', me]),
|
||||
hasPoll: false,
|
||||
selectedGifSrc: null,
|
||||
reduxReplyToId
|
||||
}
|
||||
}
|
||||
|
||||
// console.log("isMatch:", isMatch, reduxReplyToId, replyToId, state.getIn(['compose', 'text']))
|
||||
|
||||
return {
|
||||
isMatch,
|
||||
edit: !isMatch ? null : state.getIn(['compose', 'id']) !== null,
|
||||
text: !isMatch ? '' : state.getIn(['compose', 'text']),
|
||||
suggestions: !isMatch ? ImmutableList() : state.getIn(['compose', 'suggestions']),
|
||||
spoiler: !isMatch ? false : state.getIn(['compose', 'spoiler']),
|
||||
spoilerText: !isMatch ? '' : state.getIn(['compose', 'spoiler_text']),
|
||||
privacy: !isMatch ? null : state.getIn(['compose', 'privacy']),
|
||||
focusDate: !isMatch ? null : state.getIn(['compose', 'focusDate']),
|
||||
caretPosition: !isMatch ? null : state.getIn(['compose', 'caretPosition']),
|
||||
preselectDate: !isMatch ? null : state.getIn(['compose', 'preselectDate']),
|
||||
isSubmitting: !isMatch ? false : state.getIn(['compose', 'is_submitting']),
|
||||
isChangingUpload: !isMatch ? false : state.getIn(['compose', 'is_changing_upload']),
|
||||
isUploading: !isMatch ? false : state.getIn(['compose', 'is_uploading']),
|
||||
showSearch: !isMatch ? false : state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
||||
anyMedia: !isMatch ? false : state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
isModalOpen: !isMatch ? false : state.getIn(['modal', 'modalType']) === 'COMPOSE',
|
||||
quoteOfId: !isMatch ? null : state.getIn(['compose', 'quote_of_id']),
|
||||
scheduledAt: !isMatch ? null : state.getIn(['compose', 'scheduled_at']),
|
||||
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: !isMatch ? false : state.getIn(['compose', 'poll']),
|
||||
hasPoll: state.getIn(['compose', 'poll']),
|
||||
selectedGifSrc: state.getIn(['tenor', 'selectedGif', 'src']),
|
||||
reduxReplyToId,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = (dispatch, { reduxReplyToId, replyToId }) => ({
|
||||
|
||||
onChange(text, markdown) {
|
||||
dispatch(changeCompose(text, markdown))
|
||||
onChange(text, markdown, newReplyToId) {
|
||||
console.log("text:", text, newReplyToId, replyToId, reduxReplyToId)
|
||||
dispatch(changeCompose(text, markdown, newReplyToId))
|
||||
},
|
||||
|
||||
onSubmit(group, replyToId) {
|
||||
|
@ -73,10 +103,6 @@ const mapDispatchToProps = (dispatch) => ({
|
|||
dispatch(uploadCompose(files))
|
||||
},
|
||||
|
||||
onPickEmoji(position, data, needsSpace) {
|
||||
dispatch(insertEmojiCompose(position, data, needsSpace))
|
||||
},
|
||||
|
||||
setScheduledAt(date) {
|
||||
dispatch(changeScheduledAt(date))
|
||||
},
|
||||
|
|
|
@ -30,8 +30,8 @@ class ListEditorSearch extends PureComponent {
|
|||
onClear: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
handleChange = e => {
|
||||
this.props.onChange(e.target.value);
|
||||
handleChange = (value) => {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
|
||||
handleKeyUp = e => {
|
||||
|
|
|
@ -54,7 +54,7 @@ class Search extends ImmutablePureComponent {
|
|||
const showHashtags = pathname === '/search/hashtags'
|
||||
const showGroups = pathname === '/search/groups'
|
||||
const isTop = !showPeople && !showHashtags && !showGroups
|
||||
const theLimit = 10
|
||||
const theLimit = 4
|
||||
|
||||
let accounts, statuses, hashtags, groups
|
||||
|
||||
|
|
|
@ -345,7 +345,7 @@
|
|||
"status.report": "Report @{name}",
|
||||
"status.sensitive_warning": "Деликатно съдържание",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_less": "Hide",
|
||||
"status.show_less_all": "Show less for all",
|
||||
"status.show_more": "Show more",
|
||||
"status.show_more_all": "Show more for all",
|
||||
|
|
|
@ -379,7 +379,7 @@
|
|||
"id": "status.show_more"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Show less",
|
||||
"defaultMessage": "Hide",
|
||||
"id": "status.show_less"
|
||||
}
|
||||
],
|
||||
|
|
|
@ -348,7 +348,7 @@
|
|||
"status.report": "Report @{name}",
|
||||
"status.sensitive_warning": "Sensitive content",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_less": "Hide",
|
||||
"status.show_less_all": "Show less for all",
|
||||
"status.show_more": "Show more",
|
||||
"status.show_more_all": "Show more for all",
|
||||
|
|
|
@ -345,7 +345,7 @@
|
|||
"status.report": "Report @{name}",
|
||||
"status.sensitive_warning": "Sensitive content",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_less": "Hide",
|
||||
"status.show_less_all": "Show less for all",
|
||||
"status.show_more": "Show more",
|
||||
"status.show_more_all": "Show more for all",
|
||||
|
|
|
@ -345,7 +345,7 @@
|
|||
"status.report": "Report @{name}",
|
||||
"status.sensitive_warning": "Sensitive content",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_less": "Hide",
|
||||
"status.show_less_all": "Show less for all",
|
||||
"status.show_more": "Show more",
|
||||
"status.show_more_all": "Show more for all",
|
||||
|
|
|
@ -345,7 +345,7 @@
|
|||
"status.report": "Report @{name}",
|
||||
"status.sensitive_warning": "Sensitive content",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_less": "Hide",
|
||||
"status.show_less_all": "Show less for all",
|
||||
"status.show_more": "Show more",
|
||||
"status.show_more_all": "Show more for all",
|
||||
|
|
|
@ -345,7 +345,7 @@
|
|||
"status.report": "Report @{name}",
|
||||
"status.sensitive_warning": "Sensitive content",
|
||||
"status.share": "Share",
|
||||
"status.show_less": "Show less",
|
||||
"status.show_less": "Hide",
|
||||
"status.show_less_all": "Show less for all",
|
||||
"status.show_more": "Show more",
|
||||
"status.show_more_all": "Show more for all",
|
||||
|
|
|
@ -170,11 +170,13 @@ const updateSuggestionTags = (state, token) => {
|
|||
};
|
||||
|
||||
const insertEmoji = (state, position, emojiData, needsSpace) => {
|
||||
const oldText = state.get('text');
|
||||
const emoji = needsSpace ? ' ' + emojiData.native : emojiData.native;
|
||||
const oldText = state.get('text')
|
||||
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({
|
||||
text: `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`,
|
||||
text,
|
||||
focusDate: new Date(),
|
||||
caretPosition: position + emoji.length + 1,
|
||||
idempotencyKey: uuid(),
|
||||
|
@ -248,10 +250,14 @@ export default function compose(state = initialState, action) {
|
|||
.set('privacy', action.value)
|
||||
.set('idempotencyKey', uuid());
|
||||
case COMPOSE_CHANGE:
|
||||
return state
|
||||
.set('text', action.text)
|
||||
.set('markdown', action.markdown)
|
||||
.set('idempotencyKey', uuid());
|
||||
return state.withMutations(map => {
|
||||
map.set('text', action.text)
|
||||
map.set('markdown', action.markdown)
|
||||
map.set('idempotencyKey', uuid())
|
||||
if (action.replyId) {
|
||||
map.set('in_reply_to', action.replyId)
|
||||
}
|
||||
})
|
||||
case COMPOSE_COMPOSING_CHANGE:
|
||||
return state.set('is_composing', action.value);
|
||||
case COMPOSE_REPLY:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
GIFS_CLEAR_RESULTS,
|
||||
GIF_SET_SELECTED,
|
||||
GIF_CLEAR_SELECTED,
|
||||
GIF_CHANGE_SEARCH_TEXT,
|
||||
GIF_RESULTS_FETCH_REQUEST,
|
||||
GIF_RESULTS_FETCH_SUCCESS,
|
||||
|
@ -14,11 +15,15 @@ import { Map as ImmutableMap } from 'immutable'
|
|||
const initialState = ImmutableMap({
|
||||
categories: [],
|
||||
results: [],
|
||||
chosenUrl: '',
|
||||
searchText: '',
|
||||
next: 0,
|
||||
loading: false,
|
||||
error: false,
|
||||
selectedGif: ImmutableMap({
|
||||
id: '',
|
||||
title: '',
|
||||
src: '',
|
||||
})
|
||||
})
|
||||
|
||||
export default function (state = initialState, action) {
|
||||
|
@ -48,7 +53,13 @@ export default function (state = initialState, action) {
|
|||
case GIFS_CLEAR_RESULTS:
|
||||
return state.set('results', [])
|
||||
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:
|
||||
return state.set('searchText', action.text.trim());
|
||||
default:
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
--color_white: #fff;
|
||||
--color_black: #2d3436;
|
||||
--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_red: #de2960;
|
||||
--color_red-dark: #c72c5b;
|
||||
|
@ -99,6 +99,7 @@ body {
|
|||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-size: var(--fs_m);
|
||||
line-height: 1.3125;
|
||||
overflow-wrap: break-word;
|
||||
color: var(--text_color_primary);
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, Roboto, Ubuntu, "Helvetica Neue", sans-serif;
|
||||
|
@ -140,6 +141,7 @@ body {
|
|||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-size: var(--fs_n);
|
||||
line-height: 1.3125;
|
||||
overflow-wrap: break-word;
|
||||
color: var(--text_color_primary);
|
||||
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; }
|
||||
.lineHeight15 { line-height: 1.5em; }
|
||||
.lineHeight2 { line-height: 2em; }
|
||||
|
@ -402,12 +405,15 @@ body {
|
|||
}
|
||||
|
||||
.heightMax100VH { max-height: 100vh; }
|
||||
.heightMax100PC { max-height: 100%; }
|
||||
.heightMax80VH { max-height: 80vh; }
|
||||
.heightMax200PX { max-height: 200px; }
|
||||
.heightMax56PX { max-height: 56px; }
|
||||
.heightCalc53PX { height: calc(100vh - 53px); }
|
||||
|
||||
.heightMin100VH { min-height: 100vh; }
|
||||
.heightMin50VH { min-height: 50vh; }
|
||||
.heightMin100PX { min-height: 100px; }
|
||||
.heightMin50PX { min-height: 50px; }
|
||||
|
||||
.height100PC { height: 100%; }
|
||||
|
@ -422,8 +428,10 @@ body {
|
|||
.height24PX { height: 24px; }
|
||||
.height22PX { height: 22px; }
|
||||
.height20PX { height: 20px; }
|
||||
.height10PX { height: 10px; }
|
||||
.height4PX { height: 4px; }
|
||||
.height1PX { height: 1px; }
|
||||
.heightAuto { height: auto; }
|
||||
|
||||
.maxWidth100PC { max-width: 100%; }
|
||||
.maxWidth640PX { max-width: 640px; }
|
||||
|
@ -442,9 +450,10 @@ body {
|
|||
.width72PX { width: 72px; }
|
||||
.width50PX { width: 50px; }
|
||||
.width20PX { width: 20px; }
|
||||
.width10PX { width: 10px; }
|
||||
.width4PX { width: 4px; }
|
||||
.width1PX { width: 1px; }
|
||||
|
||||
.widthAuto { width: auto; }
|
||||
|
||||
@media (min-width: 1480px) {
|
||||
.width1015PX {
|
||||
|
@ -561,9 +570,8 @@ body {
|
|||
.underline { text-decoration: underline; }
|
||||
.underline_onHover:hover { text-decoration: underline; }
|
||||
|
||||
.objectFitCover {
|
||||
object-fit: cover;
|
||||
}
|
||||
.objectFitCover { object-fit: cover; }
|
||||
.objectFitContain { object-fit: contain; }
|
||||
|
||||
.textShadow {
|
||||
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))
|
||||
|
||||
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
|
||||
|
||||
def language_from_option(str)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
class HashtagQueryService < BaseService
|
||||
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)
|
||||
all = tags_for(params[:all])
|
||||
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))
|
||||
|
||||
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
|
||||
|
||||
def language_from_option(str)
|
||||
|
|
|
@ -724,7 +724,7 @@ en:
|
|||
limit: You have reached the maximum amount of lists
|
||||
media_attachments:
|
||||
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
|
||||
migrations:
|
||||
acct: username@domain of the new account
|
||||
|
|
|
@ -685,7 +685,7 @@ en_GB:
|
|||
limit: You have reached the maximum amount of lists
|
||||
media_attachments:
|
||||
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
|
||||
migrations:
|
||||
acct: username@domain of the new account
|
||||
|
|
|
@ -181,7 +181,7 @@ io:
|
|||
upload: Kargar
|
||||
media_attachments:
|
||||
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
|
||||
notification_mailer:
|
||||
digest:
|
||||
|
|
|
@ -118,6 +118,7 @@
|
|||
"markdown-draft-js": "^2.2.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"moment-mini": "^2.24.0",
|
||||
"npmlog": "^4.1.2",
|
||||
"object-assign": "^4.1.1",
|
||||
"object-fit-images": "^3.2.3",
|
||||
|
@ -145,6 +146,7 @@
|
|||
"react-router-scroll-4": "^1.0.0-beta.1",
|
||||
"react-stickynode": "^2.1.1",
|
||||
"react-swipeable-views": "^0.13.0",
|
||||
"react-textarea-autosize": "^7.1.2",
|
||||
"redis": "^2.7.1",
|
||||
"redux": "^4.0.1",
|
||||
"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:
|
||||
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:
|
||||
version "0.5.28"
|
||||
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"
|
||||
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:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
||||
|
|
Loading…
Reference in New Issue