icons for dropdown

This commit is contained in:
Baptiste Lemoine 2020-11-01 12:10:34 +01:00
parent 32dbed3864
commit 6497665819
9 changed files with 424 additions and 194 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -128,11 +128,13 @@ class DropdownMenu extends React.PureComponent {
return <li key={`sep-${i}`} className='dropdown-menu__separator' />; return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
} }
const { text, href = '#', target = '_blank', method } = option; const { text, href = '#', target = '_blank', method, iconName } = option;
return ( return (
<li className='dropdown-menu__item' key={`${text}-${i}`}> <li className='dropdown-menu__item' key={`${text}-${i}`}>
<a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}> <a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
<i className={'fa fa-'+iconName} />
{text} {text}
</a> </a>
</li> </li>

View File

@ -120,7 +120,7 @@ export default class IconButton extends React.PureComponent {
}); });
if (typeof counter !== 'undefined') { if (typeof counter !== 'undefined') {
style.width = 'auto'; style.width = '100%';
} }
return ( return (

View File

@ -6,41 +6,41 @@ import IconButton from './icon_button';
import DropdownMenuContainer from '../containers/dropdown_menu_container'; import DropdownMenuContainer from '../containers/dropdown_menu_container';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { me, isStaff } from '../initial_state'; import { isStaff, me } from '../initial_state';
import classNames from 'classnames'; import classNames from 'classnames';
const messages = defineMessages({ const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' }, delete : { id: 'status.delete', defaultMessage: 'Delete' },
redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' }, redraft : { id: 'status.redraft', defaultMessage: 'Delete & re-draft' },
direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' }, direct : { id: 'status.direct', defaultMessage: 'Direct message @{name}' },
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, mention : { id: 'status.mention', defaultMessage: 'Mention @{name}' },
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, mute : { id: 'account.mute', defaultMessage: 'Mute @{name}' },
block: { id: 'account.block', defaultMessage: 'Block @{name}' }, block : { id: 'account.block', defaultMessage: 'Block @{name}' },
reply: { id: 'status.reply', defaultMessage: 'Reply' }, reply : { id: 'status.reply', defaultMessage: 'Reply' },
share: { id: 'status.share', defaultMessage: 'Share' }, share : { id: 'status.share', defaultMessage: 'Share' },
more: { id: 'status.more', defaultMessage: 'More' }, more : { id: 'status.more', defaultMessage: 'More' },
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' }, replyAll : { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, reblog : { id: 'status.reblog', defaultMessage: 'Boost' },
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' }, reblog_private : { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, cannot_reblog : { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, favourite : { id: 'status.favourite', defaultMessage: 'Favourite' },
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' }, bookmark : { id: 'status.bookmark', defaultMessage: 'Bookmark' },
removeBookmark: { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' }, removeBookmark : { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' },
open: { id: 'status.open', defaultMessage: 'Expand this status' }, open : { id: 'status.open', defaultMessage: 'Expand this status' },
report: { id: 'status.report', defaultMessage: 'Report @{name}' }, report : { id: 'status.report', defaultMessage: 'Report @{name}' },
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' }, muteConversation : { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, unmuteConversation : { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' }, pin : { id: 'status.pin', defaultMessage: 'Pin on profile' },
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' }, unpin : { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
embed: { id: 'status.embed', defaultMessage: 'Embed' }, embed : { id: 'status.embed', defaultMessage: 'Embed' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, admin_account : { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' }, admin_status : { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' },
copy: { id: 'status.copy', defaultMessage: 'Copy link to status' }, copy : { id: 'status.copy', defaultMessage: 'Copy link to status' },
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' }, blockDomain : { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' }, unblockDomain : { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, unmute : { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, unblock : { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
}); });
const mapStateToProps = (state, { status }) => ({ const mapStateToProps = (state, { status }) => ({
@ -56,28 +56,28 @@ class StatusActionBar extends ImmutablePureComponent {
}; };
static propTypes = { static propTypes = {
status: ImmutablePropTypes.map.isRequired, status : ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.map, relationship : ImmutablePropTypes.map,
onReply: PropTypes.func, onReply : PropTypes.func,
onFavourite: PropTypes.func, onFavourite : PropTypes.func,
onReblog: PropTypes.func, onReblog : PropTypes.func,
onDelete: PropTypes.func, onDelete : PropTypes.func,
onDirect: PropTypes.func, onDirect : PropTypes.func,
onMention: PropTypes.func, onMention : PropTypes.func,
onMute: PropTypes.func, onMute : PropTypes.func,
onUnmute: PropTypes.func, onUnmute : PropTypes.func,
onBlock: PropTypes.func, onBlock : PropTypes.func,
onUnblock: PropTypes.func, onUnblock : PropTypes.func,
onBlockDomain: PropTypes.func, onBlockDomain : PropTypes.func,
onUnblockDomain: PropTypes.func, onUnblockDomain : PropTypes.func,
onReport: PropTypes.func, onReport : PropTypes.func,
onEmbed: PropTypes.func, onEmbed : PropTypes.func,
onMuteConversation: PropTypes.func, onMuteConversation: PropTypes.func,
onPin: PropTypes.func, onPin : PropTypes.func,
onBookmark: PropTypes.func, onBookmark : PropTypes.func,
withDismiss: PropTypes.bool, withDismiss : PropTypes.bool,
scrollKey: PropTypes.string, scrollKey : PropTypes.string,
intl: PropTypes.object.isRequired, intl : PropTypes.object.isRequired,
}; };
// Avoid checking props that are functions (and whose equality will always // Avoid checking props that are functions (and whose equality will always
@ -86,7 +86,7 @@ class StatusActionBar extends ImmutablePureComponent {
'status', 'status',
'relationship', 'relationship',
'withDismiss', 'withDismiss',
] ];
handleReplyClick = () => { handleReplyClick = () => {
if (me) { if (me) {
@ -94,16 +94,16 @@ class StatusActionBar extends ImmutablePureComponent {
} else { } else {
this._openInteractionDialog('reply'); this._openInteractionDialog('reply');
} }
} };
handleShareClick = () => { handleShareClick = () => {
navigator.share({ navigator.share({
text: this.props.status.get('search_index'), text: this.props.status.get('search_index'),
url: this.props.status.get('url'), url : this.props.status.get('url'),
}).catch((e) => { }).catch((e) => {
if (e.name !== 'AbortError') console.error(e); if (e.name !== 'AbortError') console.error(e);
}); });
} };
handleFavouriteClick = () => { handleFavouriteClick = () => {
if (me) { if (me) {
@ -111,7 +111,7 @@ class StatusActionBar extends ImmutablePureComponent {
} else { } else {
this._openInteractionDialog('favourite'); this._openInteractionDialog('favourite');
} }
} };
handleReblogClick = e => { handleReblogClick = e => {
if (me) { if (me) {
@ -119,35 +119,35 @@ class StatusActionBar extends ImmutablePureComponent {
} else { } else {
this._openInteractionDialog('reblog'); this._openInteractionDialog('reblog');
} }
} };
_openInteractionDialog = type => { _openInteractionDialog = type => {
window.open(`/interact/${this.props.status.get('id')}?type=${type}`, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); window.open(`/interact/${this.props.status.get('id')}?type=${type}`, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
} };
handleBookmarkClick = () => { handleBookmarkClick = () => {
this.props.onBookmark(this.props.status); this.props.onBookmark(this.props.status);
} };
handleDeleteClick = () => { handleDeleteClick = () => {
this.props.onDelete(this.props.status, this.context.router.history); this.props.onDelete(this.props.status, this.context.router.history);
} };
handleRedraftClick = () => { handleRedraftClick = () => {
this.props.onDelete(this.props.status, this.context.router.history, true); this.props.onDelete(this.props.status, this.context.router.history, true);
} };
handlePinClick = () => { handlePinClick = () => {
this.props.onPin(this.props.status); this.props.onPin(this.props.status);
} };
handleMentionClick = () => { handleMentionClick = () => {
this.props.onMention(this.props.status.get('account'), this.context.router.history); this.props.onMention(this.props.status.get('account'), this.context.router.history);
} };
handleDirectClick = () => { handleDirectClick = () => {
this.props.onDirect(this.props.status.get('account'), this.context.router.history); this.props.onDirect(this.props.status.get('account'), this.context.router.history);
} };
handleMuteClick = () => { handleMuteClick = () => {
const { status, relationship, onMute, onUnmute } = this.props; const { status, relationship, onMute, onUnmute } = this.props;
@ -158,7 +158,7 @@ class StatusActionBar extends ImmutablePureComponent {
} else { } else {
onMute(account); onMute(account);
} }
} };
handleBlockClick = () => { handleBlockClick = () => {
const { status, relationship, onBlock, onUnblock } = this.props; const { status, relationship, onBlock, onUnblock } = this.props;
@ -169,43 +169,43 @@ class StatusActionBar extends ImmutablePureComponent {
} else { } else {
onBlock(status); onBlock(status);
} }
} };
handleBlockDomain = () => { handleBlockDomain = () => {
const { status, onBlockDomain } = this.props; const { status, onBlockDomain } = this.props;
const account = status.get('account'); const account = status.get('account');
onBlockDomain(account.get('acct').split('@')[1]); onBlockDomain(account.get('acct').split('@')[1]);
} };
handleUnblockDomain = () => { handleUnblockDomain = () => {
const { status, onUnblockDomain } = this.props; const { status, onUnblockDomain } = this.props;
const account = status.get('account'); const account = status.get('account');
onUnblockDomain(account.get('acct').split('@')[1]); onUnblockDomain(account.get('acct').split('@')[1]);
} };
handleOpen = () => { handleOpen = () => {
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`); this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
} };
handleEmbed = () => { handleEmbed = () => {
this.props.onEmbed(this.props.status); this.props.onEmbed(this.props.status);
} };
handleReport = () => { handleReport = () => {
this.props.onReport(this.props.status); this.props.onReport(this.props.status);
} };
handleConversationMuteClick = () => { handleConversationMuteClick = () => {
this.props.onMuteConversation(this.props.status); this.props.onMuteConversation(this.props.status);
} };
handleCopy = () => { handleCopy = () => {
const url = this.props.status.get('url'); const url = this.props.status.get('url');
const textarea = document.createElement('textarea'); const textarea = document.createElement('textarea');
textarea.textContent = url; textarea.textContent = url;
textarea.style.position = 'fixed'; textarea.style.position = 'fixed';
document.body.appendChild(textarea); document.body.appendChild(textarea);
@ -218,58 +218,97 @@ class StatusActionBar extends ImmutablePureComponent {
} finally { } finally {
document.body.removeChild(textarea); document.body.removeChild(textarea);
} }
} };
render () { render() {
const { status, relationship, intl, withDismiss, scrollKey } = this.props; const { status, relationship, intl, withDismiss, scrollKey } = this.props;
const mutingConversation = status.get('muted'); const mutingConversation = status.get('muted');
const anonymousAccess = !me; const anonymousAccess = !me;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const account = status.get('account'); const account = status.get('account');
let menu = []; let menu = [];
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen }); menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen , iconName: 'arrow-right' });
if (publicStatus) { if (publicStatus) {
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy }); menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy, iconName: 'copy' });
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed, iconName: 'screen' });
} }
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick }); menu.push({
text : intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark),
action: this.handleBookmarkClick,
iconName: 'bookmark',
});
menu.push(null); menu.push(null);
if (status.getIn(['account', 'id']) === me || withDismiss) { if (status.getIn(['account', 'id']) === me || withDismiss) {
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, iconName: 'hide',
});
menu.push(null); menu.push(null);
} }
if (status.getIn(['account', 'id']) === me) { if (status.getIn(['account', 'id']) === me) {
if (publicStatus) { 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,
iconName: 'gears',
});
} }
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick }); menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick });
} else { } else {
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick }); menu.push({
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick }); text : intl.formatMessage(messages.mention, { name: account.get('username') }),
action: this.handleMentionClick,
iconName: 'plane',
});
menu.push({
text : intl.formatMessage(messages.direct, { name: account.get('username') }),
action: this.handleDirectClick,
iconName: 'enveloppe-o',
});
menu.push(null); menu.push(null);
if (relationship && relationship.get('muting')) { 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,
iconName: 'times',
});
} else { } 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,
iconName: 'times',
});
} }
if (relationship && relationship.get('blocking')) { 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,
iconName: 'plus',
});
} else { } 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,
iconName: 'times',
});
} }
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.handleReport }); menu.push({
text : intl.formatMessage(messages.report, { name: account.get('username') }),
action: this.handleReport,
iconName: 'flag',
});
if (account.get('acct') !== account.get('username')) { if (account.get('acct') !== account.get('username')) {
const domain = account.get('acct').split('@')[1]; const domain = account.get('acct').split('@')[1];
@ -285,8 +324,18 @@ class StatusActionBar extends ImmutablePureComponent {
if (isStaff) { if (isStaff) {
menu.push(null); menu.push(null);
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` }); menu.push({
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` }); text: intl.formatMessage(messages.admin_account,
{ name: account.get('username') }),
href: `/admin/accounts/${status.getIn(['account', 'id'])}`,
iconName: 'gears',
});
menu.push({
text: intl.formatMessage(messages.admin_status),
href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}`,
iconName: 'menu',
});
} }
} }
@ -314,14 +363,42 @@ class StatusActionBar extends ImmutablePureComponent {
} }
const shareButton = ('share' in navigator) && publicStatus && ( const shareButton = ('share' in navigator) && publicStatus && (
<IconButton className='status__action-bar-button' title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} /> <IconButton
className='status__action-bar-button'
title={intl.formatMessage(messages.share)}
icon='share-alt'
onClick={this.handleShareClick}
/>
); );
return ( return (
<div className='status__action-bar'> <div className='status__action-bar'>
<IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount /> <IconButton
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} /> className='status__action-bar-button'
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /> title={replyTitle}
icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon}
onClick={this.handleReplyClick}
counter={status.get('replies_count')}
obfuscateCount
/>
<IconButton
className={classNames('status__action-bar-button', { reblogPrivate })}
disabled={!publicStatus && !reblogPrivate}
active={status.get('reblogged')}
pressed={status.get('reblogged')}
title={reblogTitle}
icon='retweet'
onClick={this.handleReblogClick}
/>
<IconButton
className='status__action-bar-button star-icon'
animate
active={status.get('favourited')}
pressed={status.get('favourited')}
title={intl.formatMessage(messages.favourite)}
icon='star'
onClick={this.handleFavouriteClick}
/>
{shareButton} {shareButton}
@ -336,8 +413,8 @@ class StatusActionBar extends ImmutablePureComponent {
direction='right' direction='right'
title={intl.formatMessage(messages.more)} title={intl.formatMessage(messages.more)}
/> />
</div> </div >
</div> </div >
); );
} }

View File

@ -6,7 +6,6 @@ import { FormattedMessage } from 'react-intl';
import Permalink from './permalink'; import Permalink from './permalink';
import classnames from 'classnames'; import classnames from 'classnames';
import PollContainer from 'mastodon/containers/poll_container'; import PollContainer from 'mastodon/containers/poll_container';
import Icon from 'mastodon/components/icon';
import { autoPlayGif } from 'mastodon/initial_state'; import { autoPlayGif } from 'mastodon/initial_state';
const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top) const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top)
@ -31,7 +30,7 @@ export default class StatusContent extends React.PureComponent {
hidden: true, hidden: true,
}; };
_updateStatusLinks () { _updateStatusLinks() {
const node = this.node; const node = this.node;
if (!node) { if (!node) {
@ -65,18 +64,18 @@ export default class StatusContent extends React.PureComponent {
if (this.props.status.get('collapsed', null) === null) { if (this.props.status.get('collapsed', null) === null) {
let collapsed = let collapsed =
this.props.collapsable this.props.collapsable
&& this.props.onClick && this.props.onClick
&& node.clientHeight > MAX_HEIGHT && node.clientHeight > MAX_HEIGHT
&& this.props.status.get('spoiler_text').length === 0; && this.props.status.get('spoiler_text').length === 0;
if(this.props.onCollapsedToggle) this.props.onCollapsedToggle(collapsed); if (this.props.onCollapsedToggle) this.props.onCollapsedToggle(collapsed);
this.props.status.set('collapsed', collapsed); this.props.status.set('collapsed', collapsed);
} }
} }
_updateStatusEmojis () { _updateStatusEmojis() {
const node = this.node; const node = this.node;
if (!node || autoPlayGif) { if (!node || autoPlayGif) {
@ -97,12 +96,12 @@ export default class StatusContent extends React.PureComponent {
} }
} }
componentDidMount () { componentDidMount() {
this._updateStatusLinks(); this._updateStatusLinks();
this._updateStatusEmojis(); this._updateStatusEmojis();
} }
componentDidUpdate () { componentDidUpdate() {
this._updateStatusLinks(); this._updateStatusLinks();
this._updateStatusEmojis(); this._updateStatusEmojis();
} }
@ -140,8 +139,8 @@ export default class StatusContent extends React.PureComponent {
return; return;
} }
const [ startX, startY ] = this.startXY; const [startX, startY] = this.startXY;
const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)]; const [deltaX, deltaY] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)];
let element = e.target; let element = e.target;
while (element) { while (element) {
@ -173,7 +172,7 @@ export default class StatusContent extends React.PureComponent {
this.node = c; this.node = c;
}; };
render () { render() {
const { status } = this.props; const { status } = this.props;
if (status.get('content').length === 0) { if (status.get('content').length === 0) {
@ -202,10 +201,13 @@ export default class StatusContent extends React.PureComponent {
className='status__content__read-more-button' className='status__content__read-more-button'
onClick={this.props.onClick} onClick={this.props.onClick}
> >
<FormattedMessage <i className='fa fa-arrow-right' />
id='status.show_thread' <span className='see-more'>
defaultMessage='Show thread' <FormattedMessage
/> <i className='fa fa-comment' /> id='status.show_thread'
defaultMessage='Show thread'
/>
</span >
</button > </button >
); );
@ -215,10 +217,14 @@ export default class StatusContent extends React.PureComponent {
onClick={this.props.onClick} onClick={this.props.onClick}
key='read-more' key='read-more'
> >
<FormattedMessage <i className='fa fa-comment' />
id='status.read_more' <span className='see-more'>
defaultMessage='Read more'
/> <i className='fa fa-comment' /> <FormattedMessage
id='status.read_more'
defaultMessage='Read more'
/>
</span >
</button > </button >
); );
@ -226,19 +232,37 @@ export default class StatusContent extends React.PureComponent {
let mentionsPlaceholder = ''; let mentionsPlaceholder = '';
const mentionLinks = status.get('mentions').map(item => ( const mentionLinks = status.get('mentions').map(item => (
<Permalink to={`/accounts/${item.get('id')}`} href={item.get('url')} key={item.get('id')} className='mention'> <Permalink
@<span>{item.get('username')}</span> to={`/accounts/${item.get('id')}`}
</Permalink> href={item.get('url')}
key={item.get('id')}
className='mention'
>
@<span >{item.get('username')}</span >
</Permalink >
)).reduce((aggregate, item) => [...aggregate, item, ' '], []); )).reduce((aggregate, item) => [...aggregate, item, ' '], []);
const toggleText = hidden ? <FormattedMessage id='status.show_more' defaultMessage='Show more' /> : <FormattedMessage id='status.show_less' defaultMessage='Show less' />; const toggleText = hidden ? (<FormattedMessage
id='status.show_more'
defaultMessage='Show more'
/>) : (<FormattedMessage
id='status.show_less'
defaultMessage='Show less'
/>);
if (hidden) { if (hidden) {
mentionsPlaceholder = <div>{mentionLinks}</div>; mentionsPlaceholder = <div >{mentionLinks}</div >;
} }
return ( return (
<div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}> <div
className={classNames}
ref={this.setRef}
tabIndex='0'
style={directionStyle}
onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp}
>
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}> <p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
<span dangerouslySetInnerHTML={spoilerContent} /> <span dangerouslySetInnerHTML={spoilerContent} />
{' '} {' '}

View File

@ -21,6 +21,10 @@ import { length } from 'stringz';
import { countableText } from '../util/counter'; import { countableText } from '../util/counter';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
// import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
// import { mascot } from '../../initial_state';
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d'; const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
const messages = defineMessages({ const messages = defineMessages({

View File

@ -4,35 +4,34 @@ import NavigationContainer from './containers/navigation_container';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { mountCompose, unmountCompose } from '../../actions/compose'; import { changeComposing, mountCompose, unmountCompose } from '../../actions/compose';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { injectIntl, defineMessages } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import SearchContainer from './containers/search_container'; import SearchContainer from './containers/search_container';
import Motion from '../ui/util/optional_motion'; import Motion from '../ui/util/optional_motion';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import SearchResultsContainer from './containers/search_results_container'; import SearchResultsContainer from './containers/search_results_container';
import { changeComposing } from '../../actions/compose';
import { openModal } from 'mastodon/actions/modal'; import { openModal } from 'mastodon/actions/modal';
import elephantUIPlane from '../../../images/elephant_ui_plane.svg'; import cipherblissLogo from '../../../images/logo_cipherbliss.png';
import { mascot } from '../../initial_state'; import { mascot } from '../../initial_state';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
import { logOut } from 'mastodon/utils/log_out'; import { logOut } from 'mastodon/utils/log_out';
const messages = defineMessages({ const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, start : { id: 'getting_started.heading', defaultMessage: 'Getting started' },
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' }, home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, public : { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, community : { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, preferences : { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, logout : { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' }, compose : { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' },
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' }, logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' }, logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
}); });
const mapStateToProps = (state, ownProps) => ({ const mapStateToProps = (state, ownProps) => ({
columns: state.getIn(['settings', 'columns']), columns : state.getIn(['settings', 'columns']),
showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage, showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage,
}); });
@ -41,15 +40,15 @@ export default @connect(mapStateToProps)
class Compose extends React.PureComponent { class Compose extends React.PureComponent {
static propTypes = { static propTypes = {
dispatch: PropTypes.func.isRequired, dispatch : PropTypes.func.isRequired,
columns: ImmutablePropTypes.list.isRequired, columns : ImmutablePropTypes.list.isRequired,
multiColumn: PropTypes.bool, multiColumn : PropTypes.bool,
showSearch: PropTypes.bool, showSearch : PropTypes.bool,
isSearchPage: PropTypes.bool, isSearchPage: PropTypes.bool,
intl: PropTypes.object.isRequired, intl : PropTypes.object.isRequired,
}; };
componentDidMount () { componentDidMount() {
const { isSearchPage } = this.props; const { isSearchPage } = this.props;
if (!isSearchPage) { if (!isSearchPage) {
@ -57,7 +56,7 @@ class Compose extends React.PureComponent {
} }
} }
componentWillUnmount () { componentWillUnmount() {
const { isSearchPage } = this.props; const { isSearchPage } = this.props;
if (!isSearchPage) { if (!isSearchPage) {
@ -72,23 +71,23 @@ class Compose extends React.PureComponent {
e.stopPropagation(); e.stopPropagation();
dispatch(openModal('CONFIRM', { dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.logoutMessage), message : intl.formatMessage(messages.logoutMessage),
confirm: intl.formatMessage(messages.logoutConfirm), confirm : intl.formatMessage(messages.logoutConfirm),
onConfirm: () => logOut(), onConfirm: () => logOut(),
})); }));
return false; return false;
} };
onFocus = () => { onFocus = () => {
this.props.dispatch(changeComposing(true)); this.props.dispatch(changeComposing(true));
} };
onBlur = () => { onBlur = () => {
this.props.dispatch(changeComposing(false)); this.props.dispatch(changeComposing(false));
} };
render () { render() {
const { multiColumn, showSearch, isSearchPage, intl } = this.props; const { multiColumn, showSearch, isSearchPage, intl } = this.props;
let header = ''; let header = '';
@ -97,51 +96,126 @@ class Compose extends React.PureComponent {
const { columns } = this.props; const { columns } = this.props;
header = ( header = (
<nav className='drawer__header'> <nav className='drawer__header'>
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><Icon id='bars' fixedWidth /></Link> <Link
to='/getting-started'
className='drawer__tab'
title={intl.formatMessage(messages.start)}
aria-label={intl.formatMessage(messages.start)}
><Icon
id='bars'
fixedWidth
/></Link >
{!columns.some(column => column.get('id') === 'HOME') && ( {!columns.some(column => column.get('id') === 'HOME') && (
<Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link> <Link
to='/timelines/home'
className='drawer__tab'
title={intl.formatMessage(messages.home_timeline)}
aria-label={intl.formatMessage(messages.home_timeline)}
><Icon
id='home'
fixedWidth
/></Link >
)} )}
{!columns.some(column => column.get('id') === 'NOTIFICATIONS') && ( {!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' fixedWidth /></Link> <Link
to='/notifications'
className='drawer__tab'
title={intl.formatMessage(messages.notifications)}
aria-label={intl.formatMessage(messages.notifications)}
><Icon
id='bell'
fixedWidth
/></Link >
)} )}
{!columns.some(column => column.get('id') === 'COMMUNITY') && ( {!columns.some(column => column.get('id') === 'COMMUNITY') && (
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link> <Link
to='/timelines/public/local'
className='drawer__tab'
title={intl.formatMessage(messages.community)}
aria-label={intl.formatMessage(messages.community)}
><Icon
id='users'
fixedWidth
/></Link >
)} )}
{!columns.some(column => column.get('id') === 'PUBLIC') && ( {!columns.some(column => column.get('id') === 'PUBLIC') && (
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link> <Link
to='/timelines/public'
className='drawer__tab'
title={intl.formatMessage(messages.public)}
aria-label={intl.formatMessage(messages.public)}
><Icon
id='globe'
fixedWidth
/></Link >
)} )}
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a> <a
<a href='/auth/sign_out' className='drawer__tab' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)} onClick={this.handleLogoutClick}><Icon id='sign-out' fixedWidth /></a> href='/settings/preferences'
</nav> className='drawer__tab'
title={intl.formatMessage(messages.preferences)}
aria-label={intl.formatMessage(messages.preferences)}
><Icon
id='cog'
fixedWidth
/></a >
<a
href='/auth/sign_out'
className='drawer__tab'
title={intl.formatMessage(messages.logout)}
aria-label={intl.formatMessage(messages.logout)}
onClick={this.handleLogoutClick}
><Icon
id='sign-out'
fixedWidth
/></a >
</nav >
); );
} }
return ( return (
<div className='drawer' role='region' aria-label={intl.formatMessage(messages.compose)}> <div
className='drawer'
role='region'
aria-label={intl.formatMessage(messages.compose)}
>
{header} {header}
{(multiColumn || isSearchPage) && <SearchContainer /> } {(multiColumn || isSearchPage) && <SearchContainer />}
<div className='drawer__pager'> <div className='drawer__pager'>
{!isSearchPage && <div className='drawer__inner' onFocus={this.onFocus}> {!isSearchPage && <div
className='drawer__inner'
onFocus={this.onFocus}
>
<NavigationContainer onClose={this.onBlur} /> <NavigationContainer onClose={this.onBlur} />
<ComposeFormContainer /> <ComposeFormContainer />
<div className='drawer__inner__mastodon'> <div className='drawer__inner__mastodon'>
<img alt='' draggable='false' src={mascot || elephantUIPlane} /> <img
</div> className='cipherbliss_logo'
</div>} src={mascot || cipherblissLogo}
alt='logo'
/>
{/*<img alt='' draggable='false' src={mascot || elephantUIPlane} />*/}
</div >
</div >}
<Motion defaultStyle={{ x: isSearchPage ? 0 : -100 }} style={{ x: spring(showSearch || isSearchPage ? 0 : -100, { stiffness: 210, damping: 20 }) }}> <Motion
defaultStyle={{ x: isSearchPage ? 0 : -100 }}
style={{ x: spring(showSearch || isSearchPage ? 0 : -100, { stiffness: 210, damping: 20 }) }}
>
{({ x }) => ( {({ x }) => (
<div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}> <div
className='drawer__inner darker'
style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}
>
<SearchResultsContainer /> <SearchResultsContainer />
</div> </div >
)} )}
</Motion> </Motion >
</div> </div >
</div> </div >
); );
} }

View File

@ -28,8 +28,7 @@ class AccountAuthorize extends ImmutablePureComponent {
const { intl, account, onAuthorize, onReject } = this.props; const { intl, account, onAuthorize, onReject } = this.props;
const content = { __html: account.get('note_emojified') }; const content = { __html: account.get('note_emojified') };
const fields = account.get('fields');
const fields = account.get('fields');
return ( return (
<div className='account-authorize__wrapper'> <div className='account-authorize__wrapper'>
<div className='account-authorize'> <div className='account-authorize'>
@ -46,7 +45,7 @@ class AccountAuthorize extends ImmutablePureComponent {
</Permalink > </Permalink >
<div className='more'> <div className='more'>
{account.get('remote_url')} {account.get('remote_url')}
</div> </div >
<div <div
className='account__header__content' className='account__header__content'
@ -85,7 +84,8 @@ class AccountAuthorize extends ImmutablePureComponent {
to={`/accounts/${account.get('id')}/followers`} to={`/accounts/${account.get('id')}/followers`}
title={intl.formatNumber(account.get('followers_count'))} title={intl.formatNumber(account.get('followers_count'))}
> >
<strong >{(account.get('followers_count'))}</strong > <FormattedMessage <strong >{(account.get('followers_count'))}</strong >
<FormattedMessage
id='account.followers' id='account.followers'
defaultMessage='Followers' defaultMessage='Followers'
/> />
@ -94,16 +94,21 @@ class AccountAuthorize extends ImmutablePureComponent {
</span > </span >
<div className='account--panel'> <div className='account--panel'>
<div className='account--panel__button'><IconButton <div className='account--panel__button text-center'>
title={intl.formatMessage(messages.authorize)} <IconButton
icon='check' title={intl.formatMessage(messages.authorize)}
onClick={onAuthorize} size='50'
/></div > icon='check'
<div className='account--panel__button'><IconButton onClick={onAuthorize}
title={intl.formatMessage(messages.reject)} />
icon='times' </div >
onClick={onReject} <div className='account--panel__button text-center'>
/></div > <IconButton
title={intl.formatMessage(messages.reject)}
size='50'
icon='times'
onClick={onReject}
/></div >
</div > </div >
</div > </div >
); );

View File

@ -1,3 +1,8 @@
.columns-area__panels {
background: url('../images/elephant_ui_plane.svg') no-repeat left bottom fixed, url('../images/logo_cipherbliss.png') no-repeat right bottom fixed;
}
.status__content { .status__content {
min-height: 5em; min-height: 5em;
} }
@ -21,11 +26,12 @@
display: inline-block; display: inline-block;
} }
.fa{ .fa {
&:hover{ &:hover {
color: $gold-star; color: $gold-star;
} }
} }
a { a {
display: inline-block; display: inline-block;
padding: .5em; padding: .5em;
@ -38,7 +44,8 @@
&:visited { &:visited {
color: #528dc8; color: #528dc8;
} }
&:hover{
&:hover {
color: $gold-star; color: $gold-star;
} }
@ -61,10 +68,14 @@
.compose-form { .compose-form {
.reply-indicator { .reply-indicator {
display: none; display: block;
} }
} }
.autosuggest-textarea__textarea{
color: #528dc8;
}
.account-authorize--more-data { .account-authorize--more-data {
padding: 1em; padding: 1em;
color: white; color: white;
@ -77,18 +88,51 @@
} }
} }
.account__relationship{ .account__relationship {
button{ button {
width: 10em; width: 10em;
height: 3em; height: 3em;
} }
} }
.account--panel { .account--panel {
margin-top: 1em; margin-top: 1em;
} }
.account--panel__button { .account--panel__button {
padding: 1em;
button { button {
display: block; display: block;
} }
} }
.status__content__read-more-button {
.fa{
float:left;
}
.see-more {
opacity: 0;
transition: opacity ease 0.2s;
float:left;
margin-left: 1em;
}
&:hover .see-more{
display: block;
opacity: 1;
}
}
.dropdown-menu__item{
.fa{
margin-right:1em;
}
}
@media all and (max-width: 600px) {
.columns-area__panels {
background: none;
}
}