This commit is contained in:
mgabdev
2020-03-27 18:57:03 -04:00
parent 0532890c0e
commit bc631b65db
40 changed files with 689 additions and 607 deletions

View File

@@ -253,9 +253,26 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
<div className={[_s.default, _s.flexGrow1].join(' ')}>
<div className={[_s.default, _s.ml5].join(' ')}>
<Composer
{/*<Composer
/>*/}
<Textarea
className={_s.default}
inputRef={this.setTextbox}
disabled={disabled}
placeholder={placeholder}
autoFocus={autoFocus}
value={value}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onKeyUp={onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur}
onPaste={this.onPaste}
aria-autocomplete='list'
/>
{ /*
<Textarea
className={_s.default}

View File

@@ -0,0 +1,41 @@
import { length } from 'stringz'
export default class CharacterCounter extends PureComponent {
static propTypes = {
text: PropTypes.string.isRequired,
max: PropTypes.number.isRequired,
small: PropTypes.bool,
}
render () {
const { text, max, small } = this.props
const actualRadius = small ? '10' : '16'
const radius = small ? 8 : 12
const circumference = 2 * Math.PI * radius
const diff = length(text) / max
const dashoffset = circumference * (1 - diff)
return (
<div className={[_s.default, _s.mr10, _s.justifyContentCenter, _s.alignItemsCenter].join(' ')}>
<svg width={actualRadius * 2} height={actualRadius * 2} viewBox={`0 0 ${actualRadius * 2} ${actualRadius * 2}`}>
<circle fill='none' cx={actualRadius} cy={actualRadius} r={radius} fill="none" stroke="#e6e6e6" strokeWidth="2" />
<circle style={{
// transform: 'rotate(-90deg)',
strokeDashoffset: dashoffset,
strokeDasharray: circumference,
}}
fill='none'
cx={actualRadius}
cy={actualRadius}
r={radius}
strokeWidth="2"
strokeLinecap='round'
stroke='#21cf7a'
/>
</svg>
</div>
)
}
}

View File

@@ -40,6 +40,7 @@ export default class ColumnHeader extends PureComponent {
{
showBackBtn &&
<Button
color='primary'
backgroundColor='none'
className={[_s.alignItemsCenter, _s.pl0, _s.justifyContentCenter].join(' ')}
icon='back'

View File

@@ -1,4 +1,96 @@
import * as I from '../assets'
import AddIcon from '../assets/add_icon'
import AngleRightIcon from '../assets/angle_right_icon'
import AppsIcon from '../assets/apps_icon'
import AudioIcon from '../assets/audio_icon'
import AudioMuteIcon from '../assets/audio_mute_icon'
import BackIcon from '../assets/back_icon'
import CalendarIcon from '../assets/calendar_icon'
import ChatIcon from '../assets/chat_icon'
import CircleIcon from '../assets/circle_icon'
import CloseIcon from '../assets/close_icon'
import CommentIcon from '../assets/comment_icon'
import DissenterIcon from '../assets/dissenter_icon'
import EllipsisIcon from '../assets/ellipsis_icon'
import ErrorIcon from '../assets/error_icon'
import FullscreenIcon from '../assets/fullscreen_icon'
import GifIcon from '../assets/gif_icon'
import GlobeIcon from '../assets/globe_icon'
import GroupIcon from '../assets/group_icon'
import GroupAddIcon from '../assets/group_add_icon'
import HappyIcon from '../assets/happy_icon'
import HomeIcon from '../assets/home_icon'
import LikeIcon from '../assets/like_icon'
import LinkIcon from '../assets/link_icon'
import ListIcon from '../assets/list_icon'
import ListAddIcon from '../assets/list_add_icon'
import LoadingIcon from '../assets/loading_icon'
import MediaIcon from '../assets/media_icon'
import MinimizeFullscreenIcon from '../assets/minimize_fullscreen_icon'
import MissingIcon from '../assets/missing_icon'
import MoreIcon from '../assets/more_icon'
import NotificationsIcon from '../assets/notifications_icon'
import PauseIcon from '../assets/pause_icon'
import PinIcon from '../assets/pin_icon'
import PlayIcon from '../assets/play_icon'
import PollIcon from '../assets/poll_icon'
import RepostIcon from '../assets/repost_icon'
import RichTextIcon from '../assets/rich_text_icon'
import SearchIcon from '../assets/search_icon'
import SearchAltIcon from '../assets/search_alt_icon'
import ShareIcon from '../assets/share_icon'
import ShopIcon from '../assets/shop_icon'
import SubtractIcon from '../assets/subtract_icon'
import TrendsIcon from '../assets/trends_icon'
import VerifiedIcon from '../assets/verified_icon'
import WarningIcon from '../assets/warning_icon'
const ICONS = {
'add': AddIcon,
'angle-right': AngleRightIcon,
'apps': AppsIcon,
'audio': AudioIcon,
'audio-mute': AudioMuteIcon,
'back': BackIcon,
'calendar': CalendarIcon,
'chat': ChatIcon,
'close': CloseIcon,
'comment': CommentIcon,
'dissenter': DissenterIcon,
'ellipsis': EllipsisIcon,
'error': ErrorIcon,
'fullscreen': FullscreenIcon,
'gif': GifIcon,
'globe': GlobeIcon,
'group': GroupIcon,
'group-add': GroupAddIcon,
'happy': HappyIcon,
'home': HomeIcon,
'like': LikeIcon,
'link': LinkIcon,
'list': ListIcon,
'list-add': ListAddIcon,
'loading': LoadingIcon,
'media': MediaIcon,
'minimize-fullscreen': MinimizeFullscreenIcon,
'missing': MissingIcon,
'more': MoreIcon,
'notifications': NotificationsIcon,
'pause': PauseIcon,
'pin': PinIcon,
'play': PlayIcon,
'poll': PollIcon,
'repost': RepostIcon,
'rich-text': RichTextIcon,
'search': SearchIcon,
'search-alt': SearchAltIcon,
'share': ShareIcon,
'shop': ShopIcon,
'subtract': SubtractIcon,
'trends': TrendsIcon,
'verified': VerifiedIcon,
'warning': WarningIcon,
'': CircleIcon,
}
export default class Icon extends PureComponent {
@@ -12,94 +104,10 @@ export default class Icon extends PureComponent {
render() {
const { id, ...options } = this.props
switch (id) {
case 'add':
return <I.AddIcon {...options} />
case 'angle-right':
return <I.AngleRightIcon {...options} />
case 'apps':
return <I.AppsIcon {...options} />
case 'audio':
return <I.AudioIcon {...options} />
case 'audio-mute':
return <I.AudioMuteIcon {...options} />
case 'back':
return <I.BackIcon {...options} />
case 'calendar':
return <I.CalendarIcon {...options} />
case 'chat':
return <I.ChatIcon {...options} />
case 'close':
return <I.CloseIcon {...options} />
case 'comment':
return <I.CommentIcon {...options} />
case 'dissenter':
return <I.DissenterIcon {...options} />
case 'ellipsis':
return <I.EllipsisIcon {...options} />
case 'error':
return <I.ErrorIcon {...options} />
case 'fullscreen':
return <I.FullscreenIcon {...options} />
case 'globe':
return <I.GlobeIcon {...options} />
case 'group':
return <I.GroupIcon {...options} />
case 'group-add':
return <I.GroupAddIcon {...options} />
case 'happy':
return <I.HappyIcon {...options} />
case 'home':
return <I.HomeIcon {...options} />
case 'like':
return <I.LikeIcon {...options} />
case 'link':
return <I.LinkIcon {...options} />
case 'list':
return <I.ListIcon {...options} />
case 'list-add':
return <I.ListAddIcon {...options} />
case 'loading':
return <I.LoadingIcon {...options} />
case 'media':
return <I.MediaIcon {...options} />
case 'minimize-fullscreen':
return <I.MinimizeFullscreenIcon {...options} />
case 'missing':
return <I.MissingIcon {...options} />
case 'more':
return <I.MoreIcon {...options} />
case 'notifications':
return <I.NotificationsIcon {...options} />
case 'pause':
return <I.PauseIcon {...options} />
case 'pin':
return <I.PinIcon {...options} />
case 'play':
return <I.PlayIcon {...options} />
case 'poll':
return <I.PollIcon {...options} />
case 'repost':
return <I.RepostIcon {...options} />
case 'search':
return <I.SearchIcon {...options} />
case 'search-alt':
return <I.SearchAltIcon {...options} />
case 'share':
return <I.ShareIcon {...options} />
case 'shop':
return <I.ShopIcon {...options} />
case 'subtract':
return <I.SubtractIcon {...options} />
case 'trends':
return <I.TrendsIcon {...options} />
case 'verified':
return <I.VerifiedIcon {...options} />
case 'warning':
return <I.WarningIcon {...options} />
default:
return <I.CircleIcon {...options} />
}
// : todo : add all required icons
const Asset = ICONS[id] || CircleIcon
return <Asset {...options} />
}
}

View File

@@ -115,12 +115,19 @@ class IntersectionObserverArticle extends Component {
}
render() {
const { children, id, index, listLength, cachedHeight } = this.props
const {
children,
id,
index,
listLength,
cachedHeight
} = this.props
const { isIntersecting, isHidden } = this.state
if (!isIntersecting && (isHidden || cachedHeight)) {
return (
<article
className={[_s.outlineNone].join(' ')}
ref={this.handleRef}
aria-posinset={index + 1}
aria-setsize={listLength}
@@ -128,7 +135,10 @@ class IntersectionObserverArticle extends Component {
data-id={id}
tabIndex='0'
>
{children && React.cloneElement(children, { hidden: true })}
{
children &&
React.cloneElement(children, { hidden: true })
}
</article>
)
}
@@ -141,7 +151,10 @@ class IntersectionObserverArticle extends Component {
data-id={id}
tabIndex='0'
>
{children && React.cloneElement(children, { hidden: false })}
{
children &&
React.cloneElement(children, { hidden: false })
}
</article>
)
}

View File

@@ -0,0 +1,27 @@
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import Button from '../button'
import Text from '../text'
import ModalLayout from './modal_layout'
const messages = defineMessages({
})
export default
@injectIntl
class EditProfileModal extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
}
render() {
const { intl } = this.props
return (
<ModalLayout>
</ModalLayout>
)
}
}

View File

@@ -1,20 +1,20 @@
import classNames from 'classnames';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ReactSwipeableViews from 'react-swipeable-views';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Video from '../../features/video';
import ExtendedVideoPlayer from '../extended_video_player';
import Button from '../button';
import ImageLoader from '../image_loader';
import Icon from '../icon';
import classNames from 'classnames'
import { defineMessages, injectIntl } from 'react-intl'
import ReactSwipeableViews from 'react-swipeable-views'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import Video from '../video'
import ExtendedVideoPlayer from '../extended_video_player'
import Button from '../button'
import ImageLoader from '../image_loader'
import Icon from '../icon'
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
next: { id: 'lightbox.next', defaultMessage: 'Next' },
viewContext: { id: 'lightbox.view_context', defaultMessage: 'View context' },
});
})
export const previewState = 'previewMediaModal';

View File

@@ -7,7 +7,7 @@ import {
EmbedModal,
// ListEditor,
// ListAdder,
StatusRevisionModal,
StatusRevisionsModal,
} from '../../features/ui/util/async_components'
import ModalBase from './modal_base'
@@ -57,10 +57,10 @@ const MODAL_COMPONENTS = {
LIST_EDITOR: () => Promise.resolve({ default: ListEditorModal }),
LIST_TIMELINE_SETTINGS: () => Promise.resolve({ default: ListTimelineSettingsModal }),
MEDIA: () => Promise.resolve({ default: MediaModal }),
'MUTE': MuteModal,
MUTE: MuteModal,
PRO_UPGRADE: () => Promise.resolve({ default: ProUpgradeModal }),
REPORT: ReportModal,
STATUS_REVISION: () => Promise.resolve({ default: StatusRevisionModal }),
STATUS_REVISIONS: StatusRevisionsModal,
UNAUTHORIZED: () => Promise.resolve({ default: UnauthorizedModal }),
UNFOLLOW: () => Promise.resolve({ default: UnfollowModal }),
VIDEO: () => Promise.resolve({ default: VideoModal }),

View File

@@ -1,40 +0,0 @@
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import StatusRevisionListContainer from '../../containers/status_revision_list_container'
import Button from '../button'
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
})
export default
@injectIntl
class StatusRevisionModal extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired,
status: ImmutablePropTypes.map.isRequired
}
render() {
const { intl, onClose, status } = this.props
return (
<div className='modal-root__modal status-revisions-root'>
<div className='status-revisions'>
<div className='status-revisions__header'>
<h3 className='status-revisions__header__title'>
<FormattedMessage id='status_revisions.heading' defaultMessage='Revision History' />
</h3>
<Button className='status-revisions__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
</div>
<div className='status-revisions__content'>
<StatusRevisionListContainer id={status.get('id')} />
</div>
</div>
</div>
)
}
}

View File

@@ -0,0 +1,97 @@
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import classNames from 'classnames/bind'
// import StatusRevisionListContainer from '../../containers/status_revisions_list_container'
import { loadStatusRevisions } from '../../actions/status_revisions'
import ModalLayout from './modal_layout'
import RelativeTimestamp from '../relative_timestamp'
import ScrollableList from '../scrollable_list'
import Text from '../text'
const cx = classNames.bind(_s)
const messages = defineMessages({
title: { id: 'status_revisions.heading', defaultMessage: 'Revision History' },
})
const mapStateToProps = state => ({
loading: state.getIn(['status_revisions', 'loading']),
error: state.getIn(['status_revisions', 'error']),
revisions: state.getIn(['status_revisions', 'revisions']),
})
const mapDispatchToProps = dispatch => ({
onLoadStatusRevisions(statusId) {
dispatch(loadStatusRevisions(statusId))
},
})
export default
@injectIntl
@connect(mapStateToProps, mapDispatchToProps)
class StatusRevisionsModal extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
status: ImmutablePropTypes.map.isRequired,
onLoadStatusRevisions: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
error: PropTypes.bool,
revisions: ImmutablePropTypes.list.isRequired,
}
componentDidMount() {
this.props.onLoadStatusRevisions(this.props.status.get('id'))
}
render() {
const {
intl,
status,
revisions
} = this.props
console.log("revisions:", revisions)
console.log("revisions.size:", revisions.size)
return (
<ModalLayout title={intl.formatMessage(messages.title)} width='480'>
<div className={[_s.default]}>
<ScrollableList>
{
revisions.map((revision, i) => {
const isFirst = i === 0
const isLast = i === revisions.size - 1
const containerClasses = cx({
default: 1,
pt5: 1,
pb10: 1,
mt5: !isFirst,
borderColorSecondary: !isLast,
borderBottom1PX: !isLast,
})
return (
<div key={`status-revision-${i}`} className={containerClasses}>
<div className={[_s.default, _s.pb5].join(' ')}>
<Text size='medium'>
{revision.get('text')}
</Text>
</div>
<div className={[_s.default]}>
<Text size='small' color='secondary'>
Edited on <RelativeTimestamp timestamp={revision.get('created_at')} />
</Text>
</div>
</div>
)
})
}
</ScrollableList>
</div>
</ModalLayout>
)
}
}

View File

@@ -1,9 +1,9 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl';
import Video from '../../features/video';
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { FormattedMessage } from 'react-intl'
import Video from '../video'
export const previewState = 'previewVideoModal';
export const previewState = 'previewVideoModal'
export default class VideoModal extends ImmutablePureComponent {
@@ -12,45 +12,45 @@ export default class VideoModal extends ImmutablePureComponent {
status: ImmutablePropTypes.map,
time: PropTypes.number,
onClose: PropTypes.func.isRequired,
};
}
static contextTypes = {
router: PropTypes.object,
};
}
componentDidMount () {
if (this.context.router) {
const history = this.context.router.history;
const history = this.context.router.history
history.push(history.location.pathname, previewState);
history.push(history.location.pathname, previewState)
this.unlistenHistory = history.listen(() => {
this.props.onClose();
});
this.props.onClose()
})
}
}
componentWillUnmount () {
if (this.context.router) {
this.unlistenHistory();
this.unlistenHistory()
if (this.context.router.history.location.state === previewState) {
this.context.router.history.goBack();
this.context.router.history.goBack()
}
}
}
handleStatusClick = e => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/${this.props.status.getIn(['account', 'acct'])}/posts/${this.props.status.get('id')}`);
e.preventDefault()
this.context.router.history.push(`/${this.props.status.getIn(['account', 'acct'])}/posts/${this.props.status.get('id')}`)
}
}
render () {
const { media, status, time, onClose } = this.props;
const { media, status, time, onClose } = this.props
const link = status && <a href={status.get('url')} onClick={this.handleStatusClick}><FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>;
const link = status && <a href={status.get('url')} onClick={this.handleStatusClick}><FormattedMessage id='lightbox.view_context' defaultMessage='View context' /></a>
return (
<div className='modal-root__modal video-modal'>
@@ -67,7 +67,7 @@ export default class VideoModal extends ImmutablePureComponent {
/>
</div>
</div>
);
)
}
}

View File

@@ -1,23 +1,23 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { NavLink } from 'react-router-dom';
import DisplayName from './display_name';
import Icon from './icon';
import ImmutablePropTypes from 'react-immutable-proptypes'
import { FormattedMessage } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { NavLink } from 'react-router-dom'
import DisplayName from './display_name'
import Icon from './icon'
export default class MovedNote extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
}
static propTypes = {
to: ImmutablePropTypes.map.isRequired,
};
}
render () {
const { to } = this.props;
const displayNameHtml = { __html: from.get('display_name_html') };
const displayNameHtml = { __html: from.get('display_name_html') }
return (
<div className='moved-note'>

View File

@@ -1,9 +1,82 @@
export default class DatePickerPopover extends PureComponent {
import DatePicker from 'react-datepicker'
import { changeScheduledAt } from '../../actions/compose'
import { openModal } from '../../actions/modal'
import { me } from '../../initial_state'
import { isMobile } from '../../utils/is_mobile'
import PopoverLayout from './popover_layout'
// import 'react-datepicker/dist/react-datepicker.css'
const mapStateToProps = state => ({
date: state.getIn(['compose', 'scheduled_at']),
isPro: state.getIn(['accounts', me, 'is_pro']),
})
const mapDispatchToProps = dispatch => ({
setScheduledAt (date) {
dispatch(changeScheduledAt(date))
},
onOpenProUpgradeModal() {
dispatch(openModal('PRO_UPGRADE'))
},
})
export default
@connect(mapStateToProps, mapDispatchToProps)
class DatePickerPopover extends PureComponent {
static propTypes = {
date: PropTypes.instanceOf(Date),
setScheduledAt: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
isPro: PropTypes.bool,
onOpenProUpgradeModal: PropTypes.func.isRequired,
position: PropTypes.string,
small: PropTypes.bool,
}
handleSetDate = (date) => {
this.props.setScheduledAt(date)
}
render() {
const { date, isPro, position } = this.props
const open = !!date
const datePickerDisabled = !isPro
const withPortal = isMobile(window.innerWidth)
const popperPlacement = position || undefined
return (
<div>
{ /* */ }
</div>
<PopoverLayout>
<DatePicker
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}
popperPlacement={popperPlacement}
popperModifiers={{
offset: {
enabled: true,
offset: '0px, 5px'
},
preventOverflow: {
enabled: true,
escapeWithReference: false,
boundariesElement: 'viewport'
}
}}
/>
</PopoverLayout>
)
}
}

View File

@@ -9,6 +9,7 @@ import { changeSetting } from '../../actions/settings'
import { useEmoji } from '../../actions/emojis'
import { EmojiPicker as EmojiPickerAsync } from '../../features/ui/util/async_components'
import { buildCustomEmojis } from '../emoji/emoji'
import PopoverLayout from './popover_layout'
const messages = defineMessages({
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
@@ -430,21 +431,23 @@ class EmojiPickerPopover extends ImmutablePureComponent {
frequentlyUsedEmojis
} = this.props
const { active, loading } = this.state;
const { active, loading } = this.state
return (
<div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
<EmojiPickerMenu
custom_emojis={this.props.custom_emojis}
loading={loading}
onClose={this.onHideDropdown}
onPick={onPickEmoji}
onSkinTone={onSkinTone}
skinTone={skinTone}
frequentlyUsedEmojis={frequentlyUsedEmojis}
/>
</div>
);
<PopoverLayout>
<div className={_s.emojiMart} onKeyDown={this.handleKeyDown}>
<EmojiPickerMenu
custom_emojis={this.props.custom_emojis}
loading={loading}
onClose={this.onHideDropdown}
onPick={onPickEmoji}
onSkinTone={onSkinTone}
skinTone={skinTone}
frequentlyUsedEmojis={frequentlyUsedEmojis}
/>
</div>
</PopoverLayout>
)
}
}

View File

@@ -6,7 +6,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import classNames from 'classnames/bind'
import { displayMedia } from '../../initial_state';
import Card from '../../features/status/components/card';
import StatusCard from '../status_card'
import { MediaGallery, Video } from '../../features/ui/util/async_components';
import ComposeFormContainer from '../../features/compose/containers/compose_form_container'
import Avatar from '../avatar';
@@ -425,7 +425,7 @@ class Status extends ImmutablePureComponent {
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
// console.log("card:", status.get('card'))
media = (
<Card
<StatusCard
onOpenMedia={this.props.onOpenMedia}
card={status.get('card')}
cacheWidth={this.props.cacheMediaWidth}

View File

@@ -1,57 +1,56 @@
import Immutable from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import punycode from 'punycode';
import classnames from 'classnames';
import Icon from './icon';
import Immutable from 'immutable'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import punycode from 'punycode'
import Icon from './icon'
const IDNA_PREFIX = 'xn--';
const IDNA_PREFIX = 'xn--'
const decodeIDNA = domain => {
return domain
.split('.')
.map(part => part.indexOf(IDNA_PREFIX) === 0 ? punycode.decode(part.slice(IDNA_PREFIX.length)) : part)
.join('.');
};
.join('.')
}
const getHostname = url => {
const parser = document.createElement('a');
parser.href = url;
return parser.hostname;
};
const parser = document.createElement('a')
parser.href = url
return parser.hostname
}
const trim = (text, len) => {
const cut = text.indexOf(' ', len);
const cut = text.indexOf(' ', len)
if (cut === -1) {
return text;
return text
}
return text.substring(0, cut) + (text.length > len ? '…' : '');
};
return text.substring(0, cut) + (text.length > len ? '…' : '')
}
const domParser = new DOMParser();
const domParser = new DOMParser()
const addAutoPlay = html => {
const document = domParser.parseFromString(html, 'text/html').documentElement;
const iframe = document.querySelector('iframe');
const document = domParser.parseFromString(html, 'text/html').documentElement
const iframe = document.querySelector('iframe')
if (iframe) {
if (iframe.src.indexOf('?') !== -1) {
iframe.src += '&';
iframe.src += '&'
} else {
iframe.src += '?';
iframe.src += '?'
}
iframe.src += 'autoplay=1&auto_play=1';
iframe.src += 'autoplay=1&auto_play=1'
// DOM parser creates html/body elements around original HTML fragment,
// so we need to get innerHTML out of the body and not the entire document
return document.querySelector('body').innerHTML;
return document.querySelector('body').innerHTML
}
return html;
};
return html
}
export default class Card extends ImmutablePureComponent {
@@ -60,24 +59,21 @@ export default class Card extends ImmutablePureComponent {
onOpenMedia: PropTypes.func.isRequired,
defaultWidth: PropTypes.number,
cacheWidth: PropTypes.func,
};
static defaultProps = {
};
}
state = {
width: this.props.defaultWidth || 280,
embedded: false,
};
}
componentWillReceiveProps (nextProps) {
if (!Immutable.is(this.props.card, nextProps.card)) {
this.setState({ embedded: false });
this.setState({ embedded: false })
}
}
handlePhotoClick = () => {
const { card, onOpenMedia } = this.props;
const { card, onOpenMedia } = this.props
onOpenMedia(
Immutable.fromJS([
@@ -94,23 +90,23 @@ export default class Card extends ImmutablePureComponent {
},
]),
0
);
};
)
}
handleEmbedClick = () => {
const { card } = this.props;
const { card } = this.props
if (card.get('type') === 'photo') {
this.handlePhotoClick();
this.handlePhotoClick()
} else {
this.setState({ embedded: true });
this.setState({ embedded: true })
}
}
setRef = c => {
if (c) {
if (this.props.cacheWidth) this.props.cacheWidth(c.offsetWidth);
this.setState({ width: c.offsetWidth });
if (this.props.cacheWidth) this.props.cacheWidth(c.offsetWidth)
this.setState({ width: c.offsetWidth })
}
}

View File

@@ -13,6 +13,12 @@ import Button from './button'
import Avatar from './avatar'
const mapDispatchToProps = (dispatch) => ({
onOpenStatusRevisionsPopover(status) {
dispatch(openModal('STATUS_REVISIONS', {
status,
}))
},
onOpenStatusOptionsPopover(targetRef, status) {
dispatch(openPopover('STATUS_OPTIONS', {
targetRef,
@@ -28,6 +34,7 @@ class StatusHeader extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map,
onOpenStatusRevisionsPopover: PropTypes.func.isRequired,
onOpenStatusOptionsPopover: PropTypes.func.isRequired,
}
@@ -36,12 +43,7 @@ class StatusHeader extends ImmutablePureComponent {
}
handleOpenStatusEdits = () => {
// : todo :
this.props.dispatch(openPopover('REPOST', {
targetRef: this.statusOptionsButton,
position: 'top',
status: this.props.status,
}))
this.props.onOpenStatusRevisionsPopover(this.props.status)
}
handleDeleteClick = () => {

View File

@@ -5,7 +5,7 @@ import RelativeTimestamp from './relative_timestamp';
export default
@injectIntl
class StatusRevisionsList extends ImmutablePureComponent {
class StatusRevisionItem extends ImmutablePureComponent {
static propTypes = {
loading: PropTypes.bool.isRequired,

View File

@@ -3,11 +3,11 @@ import { is } from 'immutable'
import { throttle } from 'lodash'
import classNames from 'classnames/bind'
import { decode } from 'blurhash'
import { isFullscreen, requestFullscreen, exitFullscreen } from '../../utils/fullscreen'
import { isPanoramic, isPortrait, minimumAspectRatio, maximumAspectRatio } from '../../utils/media_aspect_ratio'
import { displayMedia } from '../../initial_state'
import Button from '../../components/button'
import Text from '../../components/text'
import { isFullscreen, requestFullscreen, exitFullscreen } from '../utils/fullscreen'
import { isPanoramic, isPortrait, minimumAspectRatio, maximumAspectRatio } from '../utils/media_aspect_ratio'
import { displayMedia } from '../initial_state'
import Button from './button'
import Text from './text'
const cx = classNames.bind(_s)