fix account admin info, and links in the footer on the left

This commit is contained in:
Baptiste Lemoine 2019-12-18 12:52:46 +01:00
parent 626fa25f4b
commit 4e336c8e6b
13 changed files with 1041 additions and 576 deletions

View File

@ -49,10 +49,13 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
onKeyDown : PropTypes.func,
onPaste : PropTypes.func.isRequired,
autoFocus : PropTypes.bool,
directMessage : PropTypes.bool,
directMessageRecipient : PropTypes.string,
};
static defaultProps = {
autoFocus : true,
directMessage: false,
};
state = {
@ -75,7 +78,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
}
this.props.onChange(e);
}
};
onKeyDown = (e) => {
const { suggestions, disabled } = this.props;
@ -124,7 +127,6 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
e.stopPropagation();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
}
break;
}
@ -133,25 +135,25 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
}
this.props.onKeyDown(e);
}
};
onBlur = () => {
this.setState({ suggestionsHidden: true, focused: false });
}
};
onFocus = (e) => {
this.setState({ focused: true });
if (this.props.onFocus) {
this.props.onFocus(e);
}
}
};
onSuggestionClick = (e) => {
const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
e.preventDefault();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
this.textarea.focus();
}
};
componentWillReceiveProps(nextProps) {
if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
@ -161,14 +163,14 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
setTextarea = (c) => {
this.textarea = c;
}
};
onPaste = (e) => {
if (e.clipboardData && e.clipboardData.files.length === 1) {
this.props.onPaste(e.clipboardData.files);
e.preventDefault();
}
}
};
renderSuggestion = (suggestion, i) => {
const { selectedSuggestion } = this.state;
@ -186,11 +188,18 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
}
return (
<div role='button' tabIndex='0' key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
<div
role='button'
tabIndex='0'
key={key}
data-index={i}
className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })}
onMouseDown={this.onSuggestionClick}
>
{inner}
</div >
);
}
};
render() {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children } = this.props;
@ -202,7 +211,10 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
}
return [
<div className='compose-form__autosuggest-wrapper' key='autosuggest-wrapper'>
<div
className='compose-form__autosuggest-wrapper'
key='autosuggest-wrapper'
>
<div className='autosuggest-textarea'>
<label >
<span style={{ display: 'none' }}>{placeholder}</span >
@ -228,8 +240,13 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
{children}
</div >,
<div className='autosuggest-textarea__suggestions-wrapper' key='suggestions-wrapper'>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
<div
className='autosuggest-textarea__suggestions-wrapper'
key='suggestions-wrapper'
>
<div
className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}
>
{suggestions.map(this.renderSuggestion)}
</div >
</div >,

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { createPortal } from 'react-dom';
import classNames from 'classnames';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import Icon from 'mastodon/components/icon';
const messages = defineMessages({
@ -46,39 +46,39 @@ class ColumnHeader extends React.PureComponent {
} else {
this.context.router.history.goBack();
}
}
};
handleToggleClick = (e) => {
e.stopPropagation();
this.setState({ collapsed: !this.state.collapsed, animating: true });
}
};
handleTitleClick = () => {
this.props.onClick();
}
};
handleMoveLeft = () => {
this.props.onMove(-1);
}
};
handleMoveRight = () => {
this.props.onMove(1);
}
};
handleBackClick = () => {
this.historyBack();
}
};
handleTransitionEnd = () => {
this.setState({ animating: false });
}
};
handlePin = () => {
if (!this.props.pinned) {
this.historyBack();
}
this.props.onPin();
}
};
render() {
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder } = this.props;
@ -105,30 +105,81 @@ class ColumnHeader extends React.PureComponent {
if (children) {
extraContent = (
<div key='extra-content' className='column-header__collapsible__extra'>
<div
key='extra-content'
className='column-header__collapsible__extra'
>
{children}
</div >
);
}
if (multiColumn && pinned) {
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>;
pinButton =
(<button
key='pin-button'
className='text-btn column-header__setting-btn'
onClick={this.handlePin}
><Icon
id='times'
/> <FormattedMessage
id='column_header.unpin'
defaultMessage='Unpin'
/></button >);
moveButtons = (
<div key='move-buttons' className='column-header__setting-arrows'>
<button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='text-btn column-header__setting-btn' onClick={this.handleMoveLeft}><Icon id='chevron-left' /></button>
<button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><Icon id='chevron-right' /></button>
<div
key='move-buttons'
className='column-header__setting-arrows'
>
<button
title={formatMessage(messages.moveLeft)}
aria-label={formatMessage(messages.moveLeft)}
className='text-btn column-header__setting-btn'
onClick={this.handleMoveLeft}
><Icon
id='chevron-left'
/></button >
<button
title={formatMessage(messages.moveRight)}
aria-label={formatMessage(messages.moveRight)}
className='text-btn column-header__setting-btn'
onClick={this.handleMoveRight}
><Icon
id='chevron-right'
/></button >
</div >
);
} else if (multiColumn && this.props.onPin) {
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
pinButton =
(<button
key='pin-button'
className='text-btn column-header__setting-btn'
onClick={this.handlePin}
><Icon
id='plus'
/> <FormattedMessage
id='column_header.pin'
defaultMessage='Pin'
/>
</button >);
}
if (!pinned && (multiColumn || showBackButton)) {
backButton = (
<button onClick={this.handleBackClick} className='column-header__back-button'>
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
<button
onClick={this.handleBackClick}
className='column-header__back-button'
>
<Icon
id='chevron-left'
className='column-back-button__icon'
fixedWidth
/>
<FormattedMessage
id='column_back_button.label'
defaultMessage='Back'
/>
</button >
);
}
@ -143,7 +194,16 @@ class ColumnHeader extends React.PureComponent {
}
if (children || (multiColumn && this.props.onPin)) {
collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><Icon id='sliders' /></button>;
collapseButton =
(<button
className={collapsibleButtonClassName}
title={formatMessage(collapsed ? messages.show : messages.hide)}
aria-label={formatMessage(collapsed ? messages.show : messages.hide)}
aria-pressed={collapsed ? 'false' : 'true'}
onClick={this.handleToggleClick}
>
<Icon id='sliders' />
</button >);
}
const hasTitle = icon && title;
@ -153,7 +213,11 @@ class ColumnHeader extends React.PureComponent {
<h1 className={buttonClassName}>
{hasTitle && (
<button onClick={this.handleTitleClick}>
<Icon id={icon} fixedWidth className='column-header__icon' />
<Icon
id={icon}
fixedWidth
className='column-header__icon'
/>
{title}
</button >
)}
@ -167,7 +231,11 @@ class ColumnHeader extends React.PureComponent {
</div >
</h1 >
<div className={collapsibleClassName} tabIndex={collapsed ? -1 : null} onTransitionEnd={this.handleTransitionEnd}>
<div
className={collapsibleClassName}
tabIndex={collapsed ? -1 : null}
onTransitionEnd={this.handleTransitionEnd}
>
<div className='column-header__collapsible-inner'>
{(!collapsed || animating) && collapsedContent}
</div >

View File

@ -42,15 +42,15 @@ export default class DisplayName extends React.PureComponent {
handleEmojiMouseEnter = ({ target }) => {
target.src = target.getAttribute('data-original');
}
};
handleEmojiMouseLeave = ({ target }) => {
target.src = target.getAttribute('data-static');
}
};
setRef = (c) => {
this.node = c;
}
};
render() {
const { others, localDomain } = this.props;
@ -58,7 +58,14 @@ export default class DisplayName extends React.PureComponent {
let displayName, suffix, account;
if (others && others.size > 1) {
displayName = others.take(2).map(a => <bdi key={a.get('id')}><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi>).reduce((prev, cur) => [prev, ', ', cur]);
displayName = others.take(2).map(a =>
(<bdi key={a.get('id')}>
<strong
className='display-name__html'
dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }}
/>
</bdi >)).reduce((prev, cur) => [prev, ', ', cur]);
if (others.size - 2 > 0) {
suffix = `+${others.size - 2}`;
@ -76,12 +83,20 @@ export default class DisplayName extends React.PureComponent {
acct = `${acct}@${localDomain}`;
}
displayName = <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>;
displayName = <bdi ><strong
className='display-name__html'
dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }}
/>
</bdi >;
suffix = <span className='display-name__account'>@{acct}</span >;
}
return (
<span className='display-name' ref={this.setRef}>
<span
className='display-name'
ref={this.setRef}
>
{displayName} {suffix}
</span >
);

View File

@ -10,17 +10,17 @@ import StatusContent from './status_content';
import StatusActionBar from './status_action_bar';
import AttachmentList from './attachment_list';
import Card from '../features/status/components/card';
import { injectIntl, FormattedMessage } from 'react-intl';
import { FormattedMessage, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
import { Audio, MediaGallery, Video } from '../features/ui/util/async-components';
import { HotKeys } from 'react-hotkeys';
import classNames from 'classnames';
import Icon from 'mastodon/components/icon';
import { displayMedia } from '../initial_state';
// 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/components/bundle';
import imageShowThread from '../../images/icon_reply.svg';
export const textForScreenReader = (intl, status, rebloggedByText = false) => {
const displayName = status.getIn(['account', 'display_name']);
@ -102,6 +102,17 @@ class Status extends ImmutablePureComponent {
statusId : undefined,
};
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
return {
showMedia: defaultMediaVisibility(nextProps.status),
statusId : nextProps.status.get('id'),
};
} else {
return null;
}
}
// Track height changes we know about to compensate scrolling
componentDidMount() {
this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card');
@ -115,17 +126,6 @@ class Status extends ImmutablePureComponent {
}
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
return {
showMedia: defaultMediaVisibility(nextProps.status),
statusId: nextProps.status.get('id'),
};
} else {
return null;
}
}
// Compensate height changes
componentDidUpdate(prevProps, prevState, snapshot) {
const doShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card');
@ -154,7 +154,7 @@ class Status extends ImmutablePureComponent {
handleToggleMediaVisibility = () => {
this.setState({ showMedia: !this.state.showMedia });
}
};
handleClick = () => {
if (this.props.onClick) {
@ -168,7 +168,7 @@ class Status extends ImmutablePureComponent {
const { status } = this.props;
this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
}
};
handleExpandClick = (e) => {
if (this.props.onClick) {
@ -184,7 +184,7 @@ class Status extends ImmutablePureComponent {
const { status } = this.props;
this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
}
}
};
handleAccountClick = (e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
@ -192,27 +192,36 @@ class Status extends ImmutablePureComponent {
e.preventDefault();
this.context.router.history.push(`/accounts/${id}`);
}
}
};
handleExpandedToggle = () => {
this.props.onToggleHidden(this._properStatus());
};
renderLoadingMediaGallery() {
return <div className='media-gallery' style={{ height: '110px' }} />;
return <div
className='media-gallery'
style={{ height: '110px' }}
/>;
}
renderLoadingVideoPlayer() {
return <div className='video-player' style={{ height: '110px' }} />;
return <div
className='video-player'
style={{ height: '110px' }}
/>;
}
renderLoadingAudioPlayer() {
return <div className='audio-player' style={{ height: '110px' }} />;
return <div
className='audio-player'
style={{ height: '110px' }}
/>;
}
handleOpenVideo = (media, startTime) => {
this.props.onOpenVideo(media, startTime);
}
};
handleHotkeyOpenMedia = e => {
const { onOpenMedia, onOpenVideo } = this.props;
@ -229,49 +238,49 @@ class Status extends ImmutablePureComponent {
onOpenMedia(status.get('media_attachments'), 0);
}
}
}
};
handleHotkeyReply = e => {
e.preventDefault();
this.props.onReply(this._properStatus(), this.context.router.history);
}
};
handleHotkeyFavourite = () => {
this.props.onFavourite(this._properStatus());
}
};
handleHotkeyBoost = e => {
this.props.onReblog(this._properStatus(), e);
}
};
handleHotkeyMention = e => {
e.preventDefault();
this.props.onMention(this._properStatus().get('account'), this.context.router.history);
}
};
handleHotkeyOpen = () => {
this.context.router.history.push(`/statuses/${this._properStatus().get('id')}`);
}
};
handleHotkeyOpenProfile = () => {
this.context.router.history.push(`/accounts/${this._properStatus().getIn(['account', 'id'])}`);
}
};
handleHotkeyMoveUp = e => {
this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured'));
}
};
handleHotkeyMoveDown = e => {
this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured'));
}
};
handleHotkeyToggleHidden = () => {
this.props.onToggleHidden(this._properStatus());
}
};
handleHotkeyToggleSensitive = () => {
this.handleToggleMediaVisibility();
}
};
_properStatus() {
const { status } = this.props;
@ -285,7 +294,7 @@ class Status extends ImmutablePureComponent {
handleRef = c => {
this.node = c;
}
};
render() {
let media = null;
@ -316,7 +325,11 @@ class Status extends ImmutablePureComponent {
if (hidden) {
return (
<HotKeys handlers={handlers}>
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex='0'>
<div
ref={this.handleRef}
className={classNames('status__wrapper', { focusable: !this.props.muted })}
tabIndex='0'
>
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
{status.get('content')}
</div >
@ -332,8 +345,15 @@ class Status extends ImmutablePureComponent {
return (
<HotKeys handlers={minHandlers}>
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0' ref={this.handleRef}>
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />
<div
className='status__wrapper status__wrapper--filtered focusable'
tabIndex='0'
ref={this.handleRef}
>
<FormattedMessage
id='status.filtered'
defaultMessage='Filtered'
/>
</div >
</HotKeys >
);
@ -342,8 +362,15 @@ class Status extends ImmutablePureComponent {
if (featured) {
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><Icon id='thumb-tack' className='status__prepend-icon' fixedWidth /></div>
<FormattedMessage id='status.pinned' defaultMessage='Pinned toot' />
<div className='status__prepend-icon-wrapper'><Icon
id='thumb-tack'
className='status__prepend-icon'
fixedWidth
/></div >
<FormattedMessage
id='status.pinned'
defaultMessage='Pinned toot'
/>
</div >
);
} else if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
@ -351,12 +378,33 @@ class Status extends ImmutablePureComponent {
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><Icon id='retweet' className='status__prepend-icon' fixedWidth /></div>
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
<div className='status__prepend-icon-wrapper'><Icon
id='retweet'
className='status__prepend-icon'
fixedWidth
/>
</div >
<FormattedMessage
id='status.reblogged_by'
defaultMessage='{name} boosted'
values={{
name: <a
onClick={this.handleAccountClick}
data-id={status.getIn(['account', 'id'])}
href={status.getIn(['account', 'url'])}
className='status__display-name muted'
>
<bdi ><strong dangerouslySetInnerHTML={display_name_html} /></bdi >
</a >,
}}
/>
</div >
);
rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: status.getIn(['account', 'acct']) });
rebloggedByText = intl.formatMessage({
id : 'status.reblogged_by',
defaultMessage: '{name} boosted',
}, { name: status.getIn(['account', 'acct']) });
account = status.get('account');
status = status.get('reblog');
@ -374,7 +422,10 @@ class Status extends ImmutablePureComponent {
const attachment = status.getIn(['media_attachments', 0]);
media = (
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
<Bundle
fetchComponent={Audio}
loading={this.renderLoadingAudioPlayer}
>
{Component => (
<Component
src={attachment.get('url')}
@ -390,7 +441,10 @@ class Status extends ImmutablePureComponent {
const attachment = status.getIn(['media_attachments', 0]);
media = (
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
<Bundle
fetchComponent={Video}
loading={this.renderLoadingVideoPlayer}
>
{Component => (
<Component
preview={attachment.get('preview_url')}
@ -411,7 +465,10 @@ class Status extends ImmutablePureComponent {
);
} else {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
<Bundle
fetchComponent={MediaGallery}
loading={this.renderLoadingMediaGallery}
>
{Component => (
<Component
media={status.get('media_attachments')}
@ -440,43 +497,108 @@ class Status extends ImmutablePureComponent {
}
if (otherAccounts && otherAccounts.size > 0) {
statusAvatar = <AvatarComposite accounts={otherAccounts} size={48} />;
statusAvatar = <AvatarComposite
accounts={otherAccounts}
size={48}
/>;
} else if (account === undefined || account === null) {
statusAvatar = <Avatar account={status.get('account')} size={48} />;
statusAvatar = <Avatar
account={status.get('account')}
size={48}
/>;
} else {
statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
statusAvatar = <AvatarOverlay
account={status.get('account')}
friend={account}
/>;
}
return (
<HotKeys handlers={handlers}>
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
<div
className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, {
'status__wrapper-reply': !!status.get('in_reply_to_id'),
read : unread === false,
focusable : !this.props.muted,
})}
tabIndex={this.props.muted ? null : 0}
data-featured={featured ? 'true' : null}
aria-label={textForScreenReader(intl, status, rebloggedByText)}
ref={this.handleRef}
>
{prepend}
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
<div className='status__expand' onClick={this.handleExpandClick} role='presentation' />
<div
className={classNames('status', `status-${status.get('visibility')}`, {
'status-reply': !!status.get('in_reply_to_id'),
muted : this.props.muted,
read : unread === false,
})}
data-id={status.get('id')}
>
<div
className='status__expand'
onClick={this.handleExpandClick}
role='presentation'
/>
<div className='status__info'>
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
<a
href={status.get('url')}
className='status__relative-time'
target='_blank'
rel='noopener noreferrer'
><RelativeTimestamp timestamp={status.get('created_at')} /></a >
<a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
<a
onClick={this.handleAccountClick}
data-id={status.getIn(['account', 'id'])}
href={status.getIn(['account', 'url'])}
title={status.getIn(['account', 'acct'])}
className='status__display-name'
target='_blank'
rel='noopener noreferrer'
>
<div className='status__avatar'>
{statusAvatar}
</div >
<DisplayName account={status.get('account')} others={otherAccounts} />
<DisplayName
account={status.get('account')}
others={otherAccounts}
/>
</a >
</div >
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} collapsable />
<StatusContent
status={status}
onClick={this.handleClick}
expanded={!status.get('hidden')}
onExpandedToggle={this.handleExpandedToggle}
collapsable
/>
{media}
{showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) && (
<button className='status__content__read-more-button' onClick={this.handleClick}>
<FormattedMessage id='status.show_thread' defaultMessage='Show thread' />
<button
className='status__content__read-more-button'
onClick={this.handleClick}
>
<FormattedMessage
id='status.show_thread'
defaultMessage='Show thread'
/>
<img
src={imageShowThread}
alt='=> '
/>
</button >
)}
<StatusActionBar status={status} account={account} {...other} />
<StatusActionBar
status={status}
account={account} {...other} />
</div >
</div >
</HotKeys >

View File

@ -5,7 +5,7 @@ import IconButton from '../../../components/icon_button';
import ImmutablePropTypes from 'react-immutable-proptypes';
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
import { defineMessages, injectIntl } from 'react-intl';
import { me, isStaff } from '../../../initial_state';
import { isStaff, me } from '../../../initial_state';
const messages = defineMessages({
delete : { id: 'status.delete', defaultMessage: 'Delete' },
@ -74,35 +74,35 @@ class ActionBar extends React.PureComponent {
handleReplyClick = () => {
this.props.onReply(this.props.status);
}
};
handleReblogClick = (e) => {
this.props.onReblog(this.props.status, e);
}
};
handleFavouriteClick = () => {
this.props.onFavourite(this.props.status);
}
};
handleBookmarkClick = (e) => {
this.props.onBookmark(this.props.status, e);
}
};
handleDeleteClick = () => {
this.props.onDelete(this.props.status, this.context.router.history);
}
};
handleRedraftClick = () => {
this.props.onDelete(this.props.status, this.context.router.history, true);
}
};
handleDirectClick = () => {
this.props.onDirect(this.props.status.get('account'), this.context.router.history);
}
};
handleMentionClick = () => {
this.props.onMention(this.props.status.get('account'), this.context.router.history);
}
};
handleMuteClick = () => {
const { status, relationship, onMute, onUnmute } = this.props;
@ -113,7 +113,7 @@ class ActionBar extends React.PureComponent {
} else {
onMute(account);
}
}
};
handleBlockClick = () => {
const { status, relationship, onBlock, onUnblock } = this.props;
@ -124,44 +124,44 @@ class ActionBar extends React.PureComponent {
} else {
onBlock(status);
}
}
};
handleBlockDomain = () => {
const { status, onBlockDomain } = this.props;
const account = status.get('account');
onBlockDomain(account.get('acct').split('@')[1]);
}
};
handleUnblockDomain = () => {
const { status, onUnblockDomain } = this.props;
const account = status.get('account');
onUnblockDomain(account.get('acct').split('@')[1]);
}
};
handleConversationMuteClick = () => {
this.props.onMuteConversation(this.props.status);
}
};
handleReport = () => {
this.props.onReport(this.props.status);
}
};
handlePinClick = () => {
this.props.onPin(this.props.status);
}
};
handleShare = () => {
navigator.share({
text: this.props.status.get('search_index'),
url : this.props.status.get('url'),
});
}
};
handleEmbed = () => {
this.props.onEmbed(this.props.status);
}
};
handleCopy = () => {
const url = this.props.status.get('url');
@ -180,7 +180,7 @@ class ActionBar extends React.PureComponent {
} finally {
document.body.removeChild(textarea);
}
}
};
render() {
const { status, relationship, intl } = this.props;
@ -199,36 +199,66 @@ class ActionBar extends React.PureComponent {
if (me === status.getIn(['account', 'id'])) {
if (publicStatus) {
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
menu.push({
text : intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin),
action: this.handlePinClick,
});
} else {
if (status.get('visibility') === 'private') {
menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick });
menu.push({
text : intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private),
action: this.handleReblogClick,
});
}
}
menu.push(null);
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
menu.push({
text : intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation),
action: this.handleConversationMuteClick,
});
menu.push(null);
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick });
} else {
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick });
menu.push({
text : intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }),
action: this.handleMentionClick,
});
menu.push({
text : intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }),
action: this.handleDirectClick,
});
menu.push(null);
if (relationship && relationship.get('muting')) {
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
menu.push({
text : intl.formatMessage(messages.unmute, { name: account.get('username') }),
action: this.handleMuteClick,
});
} else {
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick });
menu.push({
text : intl.formatMessage(messages.mute, { name: account.get('username') }),
action: this.handleMuteClick,
});
}
if (relationship && relationship.get('blocking')) {
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
menu.push({
text : intl.formatMessage(messages.unblock, { name: account.get('username') }),
action: this.handleBlockClick,
});
} else {
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick });
menu.push({
text : intl.formatMessage(messages.block, { name: account.get('username') }),
action: this.handleBlockClick,
});
}
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
menu.push({
text : intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }),
action: this.handleReport,
});
if (account.get('acct') !== account.get('username')) {
const domain = account.get('acct').split('@')[1];
@ -244,13 +274,23 @@ class ActionBar extends React.PureComponent {
if (isStaff) {
menu.push(null);
menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
menu.push({
text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }),
href: `/admin/accounts/${status.getIn(['account', 'id'])}`,
});
menu.push({
text: intl.formatMessage(messages.admin_status),
href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}`,
});
}
}
const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShare} /></div>
<div className='detailed-status__button'><IconButton
title={intl.formatMessage(messages.share)}
icon='share-alt'
onClick={this.handleShare}
/></div >
);
let replyIcon;
@ -268,14 +308,46 @@ class ActionBar extends React.PureComponent {
return (
<div className='detailed-status__action-bar'>
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
<div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
<div className='detailed-status__button'><IconButton
title={intl.formatMessage(messages.reply)}
icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon}
onClick={this.handleReplyClick}
/></div >
<div className='detailed-status__button'><IconButton
disabled={reblog_disabled}
active={status.get('reblogged')}
title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)}
icon={reblogIcon}
onClick={this.handleReblogClick}
/></div >
<div className='detailed-status__button'>
<IconButton
className='star-icon'
animate
active={status.get('favourited')}
title={intl.formatMessage(messages.favourite)}
icon='star'
onClick={this.handleFavouriteClick}
/></div >
{shareButton}
<div className='detailed-status__button'><IconButton className='bookmark-icon' active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
<div className='detailed-status__button'>
<IconButton
className='bookmark-icon'
active={status.get('bookmarked')}
title={intl.formatMessage(messages.bookmark)}
icon='bookmark'
onClick={this.handleBookmarkClick}
/></div >
<div className='detailed-status__action-bar-dropdown'>
<DropdownMenuContainer size={18} icon='ellipsis-h' status={status} items={menu} direction='left' title='More' />
<DropdownMenuContainer
size={18}
icon='ellipsis-h'
status={status}
items={menu}
direction='left'
title='More'
/>
</div >
</div >
);

View File

@ -5,41 +5,24 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { createSelector } from 'reselect';
import { fetchStatus } from '../../actions/statuses';
import { deleteStatus, fetchStatus, hideStatus, muteStatus, revealStatus, unmuteStatus } from '../../actions/statuses';
import MissingIndicator from '../../components/missing_indicator';
import DetailedStatus from './components/detailed_status';
import ActionBar from './components/action_bar';
import Column from '../ui/components/column';
import {
favourite,
unfavourite,
bookmark,
unbookmark,
reblog,
unreblog,
favourite,
pin,
reblog,
unbookmark,
unfavourite,
unpin,
unreblog,
} from '../../actions/interactions';
import {
replyCompose,
mentionCompose,
directCompose,
} from '../../actions/compose';
import {
muteStatus,
unmuteStatus,
deleteStatus,
hideStatus,
revealStatus,
} from '../../actions/statuses';
import {
unblockAccount,
unmuteAccount,
} from '../../actions/accounts';
import {
blockDomain,
unblockDomain,
} from '../../actions/domain_blocks';
import { directCompose, mentionCompose, replyCompose } from '../../actions/compose';
import { unblockAccount, unmuteAccount } from '../../actions/accounts';
import { blockDomain, unblockDomain } from '../../actions/domain_blocks';
import { initMuteModal } from '../../actions/mutes';
import { initBlockModal } from '../../actions/blocks';
import { initReport } from '../../actions/reports';
@ -49,24 +32,33 @@ import ColumnBackButton from '../../components/column_back_button';
import ColumnHeader from '../../components/column_header';
import StatusContainer from '../../containers/status_container';
import { openModal } from '../../actions/modal';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import { boostModal, deleteModal } from '../../initial_state';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
import { defaultMediaVisibility, textForScreenReader } from '../../components/status';
import Icon from 'mastodon/components/icon';
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?' },
deleteMessage : {
id : 'confirmations.delete.message',
defaultMessage: 'Are you sure you want to delete this status?',
},
redraftConfirm : { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
redraftMessage : {
id : 'confirmations.redraft.message',
defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.',
},
revealAll : { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
hideAll : { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
detailedStatus : { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
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?' },
replyMessage : {
id : 'confirmations.reply.message',
defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?',
},
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
});
@ -116,7 +108,8 @@ const makeMapStateToProps = () => {
let insertAt = descendantsIds.findIndex((id) => statuses.get(id).get('in_reply_to_account_id') !== statuses.get(id).get('account'));
if (insertAt !== -1) {
descendantsIds.forEach((id, idx) => {
if (idx > insertAt && statuses.get(id).get('in_reply_to_account_id') === statuses.get(id).get('account')) {
let account = statuses.get(id).get('account');
if (idx > insertAt && statuses.get(id).get('in_reply_to_account_id') === account) {
descendantsIds.splice(idx, 1);
descendantsIds.splice(insertAt, 0, id);
insertAt += 1;
@ -190,13 +183,16 @@ class Status extends ImmutablePureComponent {
}
if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) {
this.setState({ showMedia: defaultMediaVisibility(nextProps.status), loadedStatusId: nextProps.status.get('id') });
this.setState({
showMedia : defaultMediaVisibility(nextProps.status),
loadedStatusId: nextProps.status.get('id'),
});
}
}
handleToggleMediaVisibility = () => {
this.setState({ showMedia: !this.state.showMedia });
}
};
handleFavouriteClick = (status) => {
if (status.get('favourited')) {
@ -204,7 +200,7 @@ class Status extends ImmutablePureComponent {
} else {
this.props.dispatch(favourite(status));
}
}
};
handlePin = (status) => {
if (status.get('pinned')) {
@ -212,7 +208,7 @@ class Status extends ImmutablePureComponent {
} else {
this.props.dispatch(pin(status));
}
}
};
handleReplyClick = (status) => {
let { askReplyConfirmation, dispatch, intl } = this.props;
@ -225,11 +221,11 @@ class Status extends ImmutablePureComponent {
} else {
dispatch(replyCompose(status, this.context.router.history));
}
}
};
handleModalReblog = (status) => {
this.props.dispatch(reblog(status));
}
};
handleReblogClick = (status, e) => {
if (status.get('reblogged')) {
@ -241,7 +237,7 @@ class Status extends ImmutablePureComponent {
this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
}
}
}
};
handleBookmarkClick = (status) => {
if (status.get('bookmarked')) {
@ -249,7 +245,7 @@ class Status extends ImmutablePureComponent {
} else {
this.props.dispatch(bookmark(status));
}
}
};
handleDeleteClick = (status, history, withRedraft = false) => {
const { dispatch, intl } = this.props;
@ -263,23 +259,23 @@ class Status extends ImmutablePureComponent {
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
}));
}
}
};
handleDirectClick = (account, router) => {
this.props.dispatch(directCompose(account, router));
}
};
handleMentionClick = (account, router) => {
this.props.dispatch(mentionCompose(account, router));
}
};
handleOpenMedia = (media, index) => {
this.props.dispatch(openModal('MEDIA', { media, index }));
}
};
handleOpenVideo = (media, time) => {
this.props.dispatch(openModal('VIDEO', { media, time }));
}
};
handleHotkeyOpenMedia = e => {
const status = this._properStatus();
@ -295,11 +291,11 @@ class Status extends ImmutablePureComponent {
this.handleOpenMedia(status.get('media_attachments'), 0);
}
}
}
};
handleMuteClick = (account) => {
this.props.dispatch(initMuteModal(account));
}
};
handleConversationMuteClick = (status) => {
if (status.get('muted')) {
@ -307,7 +303,7 @@ class Status extends ImmutablePureComponent {
} else {
this.props.dispatch(muteStatus(status.get('id')));
}
}
};
handleToggleHidden = (status) => {
if (status.get('hidden')) {
@ -315,7 +311,7 @@ class Status extends ImmutablePureComponent {
} else {
this.props.dispatch(hideStatus(status.get('id')));
}
}
};
handleToggleAll = () => {
const { status, ancestorsIds, descendantsIds } = this.props;
@ -326,80 +322,83 @@ class Status extends ImmutablePureComponent {
} else {
this.props.dispatch(hideStatus(statusIds));
}
}
};
handleBlockClick = (status) => {
const { dispatch } = this.props;
const account = status.get('account');
dispatch(initBlockModal(account));
}
};
handleReport = (status) => {
this.props.dispatch(initReport(status.get('account'), status));
}
};
handleEmbed = (status) => {
this.props.dispatch(openModal('EMBED', { url: status.get('url') }));
}
};
handleUnmuteClick = account => {
this.props.dispatch(unmuteAccount(account.get('id')));
}
};
handleUnblockClick = account => {
this.props.dispatch(unblockAccount(account.get('id')));
}
};
handleBlockDomainClick = domain => {
this.props.dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,
message : <FormattedMessage
id='confirmations.domain_block.message'
defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.'
values={{ domain: <strong >{domain}</strong > }}
/>,
confirm : this.props.intl.formatMessage(messages.blockDomainConfirm),
onConfirm: () => this.props.dispatch(blockDomain(domain)),
}));
}
};
handleUnblockDomainClick = domain => {
this.props.dispatch(unblockDomain(domain));
}
};
handleHotkeyMoveUp = () => {
this.handleMoveUp(this.props.status.get('id'));
}
};
handleHotkeyMoveDown = () => {
this.handleMoveDown(this.props.status.get('id'));
}
};
handleHotkeyReply = e => {
e.preventDefault();
this.handleReplyClick(this.props.status);
}
};
handleHotkeyFavourite = () => {
this.handleFavouriteClick(this.props.status);
}
};
handleHotkeyBoost = () => {
this.handleReblogClick(this.props.status);
}
};
handleHotkeyMention = e => {
e.preventDefault();
this.handleMentionClick(this.props.status.get('account'));
}
};
handleHotkeyOpenProfile = () => {
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
}
};
handleHotkeyToggleHidden = () => {
this.handleToggleHidden(this.props.status);
}
};
handleHotkeyToggleSensitive = () => {
this.handleToggleMediaVisibility();
}
};
handleMoveUp = id => {
const { status, ancestorsIds, descendantsIds } = this.props;
@ -416,7 +415,7 @@ class Status extends ImmutablePureComponent {
this._selectChild(index - 1, true);
}
}
}
};
handleMoveDown = id => {
const { status, ancestorsIds, descendantsIds } = this.props;
@ -433,7 +432,7 @@ class Status extends ImmutablePureComponent {
this._selectChild(index + 1, false);
}
}
}
};
_selectChild(index, align_top) {
const container = this.node;
@ -463,7 +462,7 @@ class Status extends ImmutablePureComponent {
setRef = c => {
this.node = c;
}
};
componentDidUpdate() {
if (this._scrolledIntoView) {
@ -488,7 +487,7 @@ class Status extends ImmutablePureComponent {
onFullScreenChange = () => {
this.setState({ fullscreen: isFullscreen() });
}
};
render() {
let ancestors, descendants;
@ -526,21 +525,40 @@ class Status extends ImmutablePureComponent {
};
return (
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.detailedStatus)}>
<Column
bindToDocument={!multiColumn}
label={intl.formatMessage(messages.detailedStatus)}
>
<ColumnHeader
showBackButton
multiColumn={multiColumn}
extraButton={(
<button className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button>
<button
className='column-header__button'
title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)}
aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)}
onClick={this.handleToggleAll}
aria-pressed={status.get('hidden') ? 'false' : 'true'}
><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button >
)}
/>
<ScrollContainer scrollKey='thread' shouldUpdateScroll={shouldUpdateScroll}>
<div className={classNames('scrollable', { fullscreen })} ref={this.setRef}>
<ScrollContainer
scrollKey='thread'
shouldUpdateScroll={shouldUpdateScroll}
>
<div
className={classNames('scrollable', { fullscreen })}
ref={this.setRef}
>
{ancestors}
<HotKeys handlers={handlers}>
<div className={classNames('focusable', 'detailed-status__wrapper')} tabIndex='0' aria-label={textForScreenReader(intl, status, false)}>
<div
className={classNames('focusable', 'detailed-status__wrapper')}
tabIndex='0'
aria-label={textForScreenReader(intl, status, false)}
>
<DetailedStatus
status={status}
onOpenVideo={this.handleOpenVideo}

View File

@ -48,69 +48,117 @@ class LinkFooter extends React.PureComponent {
<div className='getting-started__footer'>
<ul >
<li >
<a href="https://liberapay.com/cipherbliss">Supportez Cipherbliss</a>
<a href='https://liberapay.com/cipherbliss'>Supportez Cipherbliss</a >
</li >
<li >
<a href="/@tykayn">
<i className="fa fa-envelope"></i>
contactez Cipherbliss</a>
<a href='https://mastodon.cipherbliss.com/@tykayn'>
<i className='fa fa-paper-plane' />
contactez nous</a >
</li >
<li >
<a href='/admin/tags?pending_review=1'>
<i className="fa fa-fire"></i>
<i className='fa fa-fire' />
Trending hashtags</a >
<hr />
</li >
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage
{invitesEnabled && <li ><a
href='/invites'
target='_blank'
><FormattedMessage
id='getting_started.invite'
defaultMessage='Invite people'
/></a> ·
/> ·</a >
</li >}
{withHotkeys && <li><Link to='/keyboard-shortcuts'><FormattedMessage
{withHotkeys && <li ><Link to='/keyboard-shortcuts'>
<FormattedMessage
id='navigation_bar.keyboard_shortcuts'
defaultMessage='Hotkeys'
/></Link> ·
/> ·
</Link >
</li >}
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security'/></a> ·
<li >
<a href='/auth/edit'>
<FormattedMessage
id='getting_started.security'
defaultMessage='Security'
/> ·
</a >
</li >
<li><a href='/about/more' target='_blank'><FormattedMessage
<li >
<a
href='/about/more'
target='_blank'
><FormattedMessage
id='navigation_bar.info'
defaultMessage='About this server'
/></a> ·
/> ·
</a >
</li >
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage
<li >
<a
href='https://joinmastodon.org/apps'
target='_blank'
><FormattedMessage
id='navigation_bar.apps'
defaultMessage='Mobile apps'
/></a> ·
/> ·
</a >
</li >
<li><a href='/terms' target='_blank'><FormattedMessage
<li >
<a
href='/terms'
target='_blank'
>
<FormattedMessage
id='getting_started.terms'
defaultMessage='Terms of service'
/></a> ·
/> ·</a >
</li >
<li><a href='/settings/applications' target='_blank'><FormattedMessage
<li >
<a
href='/settings/applications'
target='_blank'
><FormattedMessage
id='getting_started.developers'
defaultMessage='Developers'
/></a> ·
/> ·
</a >
</li >
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage
id='getting_started.documentation' defaultMessage='Documentation'
/></a> ·
<li >
<a
href='https://docs.joinmastodon.org'
target='_blank'
>
<FormattedMessage
id='getting_started.documentation'
defaultMessage='Documentation'
/>
</a > ·
</li >
<li><a href='/auth/sign_out' onClick={this.handleLogoutClick}><FormattedMessage
<li >
<a
href='/auth/sign_out'
onClick={this.handleLogoutClick}
>
<FormattedMessage
id='navigation_bar.logout'
defaultMessage='Logout'
/></a>
/>
</a >
</li >
</ul >
<p >
<FormattedMessage
id='getting_started.open_source_notice'
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
defaultMessage='Mastodon is open source software. You can contribute or report issues on the forge at {forge}.'
values={{
github: <span><a href={source_url} rel='noopener noreferrer' target='_blank'>{repository}</a> (v{version})</span>,
forge: <span ><a
href={source_url}
rel='noopener noreferrer'
target='_blank'
>{repository}</a > (v{version})</span >,
}}
/>
</p >

View File

@ -14,14 +14,23 @@ const NavigationPanel = () => (
<div className='navigation-panel'>
<NavLink
className='column-link column-link--transparent' to='/timelines/home' data-preview-title-id='column.home'
className='column-link column-link--transparent'
to='/timelines/home'
data-preview-title-id='column.home'
data-preview-icon='home'
><Icon className='column-link__icon' id='home' fixedWidth/><FormattedMessage
id='tabs_bar.home' defaultMessage='Home'
><Icon
className='column-link__icon'
id='home'
fixedWidth
/><FormattedMessage
id='tabs_bar.home'
defaultMessage='Home'
/></NavLink >
<NavLink
className='column-link column-link--transparent' to='/notifications'
data-preview-title-id='column.notifications' data-preview-icon='bell'
className='column-link column-link--transparent'
to='/notifications'
data-preview-title-id='column.notifications'
data-preview-icon='bell'
><NotificationsCounterIcon
className='column-link__icon'
/><FormattedMessage
@ -30,79 +39,114 @@ const NavigationPanel = () => (
/></NavLink >
<FollowRequestsNavLink />
<NavLink
className='column-link column-link--transparent' to='/timelines/public/local'
data-preview-title-id='column.community' data-preview-icon='users'
className='column-link column-link--transparent'
to='/timelines/public/local'
data-preview-title-id='column.community'
data-preview-icon='users'
><Icon
className='column-link__icon'
id='users'
fixedWidth
/><FormattedMessage
id='tabs_bar.local_timeline' defaultMessage='Local'
id='tabs_bar.local_timeline'
defaultMessage='Local'
/></NavLink >
<NavLink
className='column-link column-link--transparent' exact to='/timelines/public'
data-preview-title-id='column.public' data-preview-icon='globe'
className='column-link column-link--transparent'
exact
to='/timelines/public'
data-preview-title-id='column.public'
data-preview-icon='globe'
><Icon
className='column-link__icon'
id='globe'
fixedWidth
/><FormattedMessage
id='tabs_bar.federated_timeline' defaultMessage='Federated'
id='tabs_bar.federated_timeline'
defaultMessage='Federated'
/></NavLink >
<NavLink className='column-link column-link--transparent' to='/timelines/direct'><Icon
<NavLink
className='column-link column-link--transparent'
to='/timelines/direct'
><Icon
className='column-link__icon'
id='envelope'
fixedWidth
/><FormattedMessage
id='navigation_bar.direct' defaultMessage='Direct messages'
id='navigation_bar.direct'
defaultMessage='Direct messages'
/></NavLink >
<NavLink className='column-link column-link--transparent' to='/favourites'><Icon
<NavLink
className='column-link column-link--transparent'
to='/favourites'
><Icon
className='column-link__icon'
id='star'
fixedWidth
/><FormattedMessage
id='navigation_bar.favourites' defaultMessage='Favourites'
id='navigation_bar.favourites'
defaultMessage='Favourites'
/></NavLink >
<NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon
<NavLink
className='column-link column-link--transparent'
to='/bookmarks'
><Icon
className='column-link__icon'
id='bookmark'
fixedWidth
/><FormattedMessage
id='navigation_bar.bookmarks' defaultMessage='Bookmarks'
id='navigation_bar.bookmarks'
defaultMessage='Bookmarks'
/></NavLink >
<NavLink className='column-link column-link--transparent' to='/lists'><Icon
<NavLink
className='column-link column-link--transparent'
to='/lists'
><Icon
className='column-link__icon'
id='list-ul'
fixedWidth
/><FormattedMessage
id='navigation_bar.lists' defaultMessage='Lists'
id='navigation_bar.lists'
defaultMessage='Lists'
/></NavLink >
{profile_directory &&
<NavLink className='column-link column-link--transparent' to='/directory'><Icon
<NavLink
className='column-link column-link--transparent'
to='/directory'
><Icon
className='column-link__icon'
id='address-book-o'
fixedWidth
/><FormattedMessage
id='getting_started.directory' defaultMessage='Profile directory'
id='getting_started.directory'
defaultMessage='Profile directory'
/></NavLink >}
<ListPanel />
<hr />
<a className='column-link column-link--transparent' href='/settings/preferences'><Icon
<a
className='column-link column-link--transparent'
href='/settings/preferences'
><Icon
className='column-link__icon'
id='cog'
fixedWidth
/><FormattedMessage
id='navigation_bar.preferences' defaultMessage='Preferences'
id='navigation_bar.preferences'
defaultMessage='Preferences'
/></a >
<a className='column-link column-link--transparent' href='/relationships'><Icon
<a
className='column-link column-link--transparent'
href='/relationships'
><Icon
className='column-link__icon'
id='users'
fixedWidth
/><FormattedMessage
id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers'
id='navigation_bar.follows_and_followers'
defaultMessage='Follows and followers'
/></a >
{showTrends && <div className='flex-spacer' />}
@ -110,7 +154,10 @@ const NavigationPanel = () => (
<div className='messaging-box'>
<div className='title'>
<i role='img' className='fa fa-envelope column-header__icon fa-fw'/>
<i
role='img'
className='fa fa-envelope column-header__icon fa-fw'
/>
Messaging box
</div >
<div className='user-list column-header'>
@ -134,23 +181,32 @@ const NavigationPanel = () => (
<li className='conversations_item has-new-message'>
<div className='title'>
<i role='img' className='fa fa-envelope column-header__icon fa-fw'/>
<i
role='img'
className='fa fa-envelope column-header__icon fa-fw'
/>
Un Gens
<span className='new-message-counter'>
(3)</span >
<button className='btn-small'>
<i role='img' className='fa fa-caret-down column-header__icon fa-fw'/>
<i
role='img'
className='fa fa-caret-down column-header__icon fa-fw'
/>
</button >
</div >
<div className='conversation_stream'>
<div className='message theirs'>
<p >oh hello there! 😋 </p >
<div className='arrow-down' />
</div >
<div className='message mine'>
<p >General Emoji</p >
<div className='arrow-down' />
</div >
<div className='message theirs'>
<p >we just achieved comedy</p >
<div className='arrow-down' />
</div >
</div >
<div className='conversation_input'>

View File

@ -853,11 +853,19 @@
background: transparent;
padding: 0;
padding-top: 8px;
text-align: right;
width: 100%;
&:hover,
&:active {
text-decoration: underline;
}
img {
transform: rotateY(180deg);
margin-left: 1ch;
}
}
.status__content__spoiler-link {
@ -875,6 +883,13 @@
vertical-align: middle;
}
.status__wrapper {
&:hover {
background: mix($ui-base-lighter-color, $ui-base-color);
animation: background ease 0.5s;
}
}
.status__wrapper--filtered {
color: $dark-text-color;
border: 0;

View File

@ -137,7 +137,6 @@
}
.getting-started__footer {
text-align: justify;
ul {
list-style-type: none;

View File

@ -30,6 +30,10 @@ $messagingBoxHeight: 20em;
}
}
.conversation_created-at {
margin-right: 1em;
}
.conversation_stream {
padding-top: 1em;
height: $messagingBoxHeight;
@ -42,16 +46,39 @@ $messagingBoxHeight: 20em;
border-radius: 0.5rem;
margin-bottom: 0.5em;
padding: 0.5em 1em;
width: 80%;
}
.mine {
text-align: right;
background: $classic-primary-color;
float: right;
.arrow-down {
border-top-color: $classic-primary-color;
left: 1em;
}
}
.theirs {
text-align: left;
background: $ui-highlight-color;
float: left;
.arrow-down {
border-top-color: $ui-highlight-color;
right: 1em;
}
}
.arrow-down {
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 20px solid transparent;
border-top: 20px solid $classic-primary-color;
position: relative;
bottom: -1em;
}
}

View File

@ -20,3 +20,11 @@
- else
= table_link_to 'circle', t('admin.accounts.web'), web_path("accounts/#{account.id}")
= table_link_to 'globe', t('admin.accounts.public'), ActivityPub::TagManager.instance.url_for(account)
%td
= number_with_delimiter account.statuses_count
%td
= number_with_delimiter account.following_count
%td
= number_with_delimiter account.followers_count
%td
\-

View File

@ -44,10 +44,10 @@
%th= t('admin.accounts.most_recent_ip')
%th= t('admin.accounts.most_recent_activity')
%th links
%th sign in
%th statuses
%th following
%th followers
%th nuke
%tbody
= render @accounts