Conflicts: - `app/views/settings/preferences/appearance/show.html.haml`: Upstream renamed some helper functions that were used in a part of the settings page which glitch-soc slightly changed the layout of. Ported the change.main
commit
322e907e04
@ -0,0 +1,37 @@
|
||||
import api from '../api';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
|
||||
export const HISTORY_FETCH_REQUEST = 'HISTORY_FETCH_REQUEST';
|
||||
export const HISTORY_FETCH_SUCCESS = 'HISTORY_FETCH_SUCCESS';
|
||||
export const HISTORY_FETCH_FAIL = 'HISTORY_FETCH_FAIL';
|
||||
|
||||
export const fetchHistory = statusId => (dispatch, getState) => {
|
||||
const loading = getState().getIn(['history', statusId, 'loading']);
|
||||
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchHistoryRequest(statusId));
|
||||
|
||||
api(getState).get(`/api/v1/statuses/${statusId}/history`).then(({ data }) => {
|
||||
dispatch(importFetchedAccounts(data.map(x => x.account)));
|
||||
dispatch(fetchHistorySuccess(statusId, data));
|
||||
}).catch(error => dispatch(fetchHistoryFail(error)));
|
||||
};
|
||||
|
||||
export const fetchHistoryRequest = statusId => ({
|
||||
type: HISTORY_FETCH_REQUEST,
|
||||
statusId,
|
||||
});
|
||||
|
||||
export const fetchHistorySuccess = (statusId, history) => ({
|
||||
type: HISTORY_FETCH_SUCCESS,
|
||||
statusId,
|
||||
history,
|
||||
});
|
||||
|
||||
export const fetchHistoryFail = error => ({
|
||||
type: HISTORY_FETCH_FAIL,
|
||||
error,
|
||||
});
|
@ -0,0 +1,27 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { openDropdownMenu, closeDropdownMenu } from 'mastodon/actions/dropdown_menu';
|
||||
import { fetchHistory } from 'mastodon/actions/history';
|
||||
import DropdownMenu from 'mastodon/components/dropdown_menu';
|
||||
|
||||
const mapStateToProps = (state, { statusId }) => ({
|
||||
dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
|
||||
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
|
||||
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
|
||||
items: state.getIn(['history', statusId, 'items']),
|
||||
loading: state.getIn(['history', statusId, 'loading']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, { statusId }) => ({
|
||||
|
||||
onOpen (id, onItemClick, dropdownPlacement, keyboard) {
|
||||
dispatch(fetchHistory(statusId));
|
||||
dispatch(openDropdownMenu(id, dropdownPlacement, keyboard));
|
||||
},
|
||||
|
||||
onClose (id) {
|
||||
dispatch(closeDropdownMenu(id));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu);
|
@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import DropdownMenu from './containers/dropdown_menu_container';
|
||||
import { connect } from 'react-redux';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import RelativeTimestamp from 'mastodon/components/relative_timestamp';
|
||||
import InlineAccount from 'mastodon/components/inline_account';
|
||||
|
||||
const mapDispatchToProps = (dispatch, { statusId }) => ({
|
||||
|
||||
onItemClick (index) {
|
||||
dispatch(openModal('COMPARE_HISTORY', { index, statusId }));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default @connect(null, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class EditedTimestamp extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
statusId: PropTypes.string.isRequired,
|
||||
timestamp: PropTypes.string.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onItemClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
handleItemClick = (item, i) => {
|
||||
const { onItemClick } = this.props;
|
||||
onItemClick(i);
|
||||
};
|
||||
|
||||
renderHeader = items => {
|
||||
return (
|
||||
<FormattedMessage id='status.edited_x_times' defaultMessage='Edited {count, plural, one {{count} time} other {{count} times}}' values={{ count: items.size - 1 }} />
|
||||
);
|
||||
}
|
||||
|
||||
renderItem = (item, index, { onClick, onKeyPress }) => {
|
||||
const formattedDate = <RelativeTimestamp timestamp={item.get('created_at')} short={false} />;
|
||||
const formattedName = <InlineAccount accountId={item.get('account')} />;
|
||||
|
||||
const label = item.get('original') ? (
|
||||
<FormattedMessage id='status.history.created' defaultMessage='{name} created {date}' values={{ name: formattedName, date: formattedDate }} />
|
||||
) : (
|
||||
<FormattedMessage id='status.history.edited' defaultMessage='{name} edited {date}' values={{ name: formattedName, date: formattedDate }} />
|
||||
);
|
||||
|
||||
return (
|
||||
<li className='dropdown-menu__item edited-timestamp__history__item' key={item.get('created_at')}>
|
||||
<button data-index={index} onClick={onClick} onKeyPress={onKeyPress}>{label}</button>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { timestamp, intl, statusId } = this.props;
|
||||
|
||||
return (
|
||||
<DropdownMenu statusId={statusId} renderItem={this.renderItem} scrollable renderHeader={this.renderHeader} onItemClick={this.handleItemClick}>
|
||||
<button className='dropdown-menu__text-button'>
|
||||
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: intl.formatDate(timestamp, { hour12: false, month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) }} /> <Icon id='caret-down' />
|
||||
</button>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import { makeGetAccount } from 'mastodon/selectors';
|
||||
import Avatar from 'mastodon/components/avatar';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
const mapStateToProps = (state, { accountId }) => ({
|
||||
account: getAccount(state, accountId),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export default @connect(makeMapStateToProps)
|
||||
class InlineAccount extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { account } = this.props;
|
||||
|
||||
return (
|
||||
<span className='inline-account'>
|
||||
<Avatar size={13} account={account} /> <strong>{account.get('username')}</strong>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { closeModal } from 'mastodon/actions/modal';
|
||||
import emojify from 'mastodon/features/emoji/emoji';
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import InlineAccount from 'mastodon/components/inline_account';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
import RelativeTimestamp from 'mastodon/components/relative_timestamp';
|
||||
|
||||
const mapStateToProps = (state, { statusId }) => ({
|
||||
versions: state.getIn(['history', statusId, 'items']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onClose() {
|
||||
dispatch(closeModal());
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
class CompareHistoryModal extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
statusId: PropTypes.string.isRequired,
|
||||
versions: ImmutablePropTypes.list.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { index, versions, onClose } = this.props;
|
||||
const currentVersion = versions.get(index);
|
||||
|
||||
const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => {
|
||||
obj[`:${emoji.get('shortcode')}:`] = emoji.toJS();
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
const content = { __html: emojify(currentVersion.get('content'), emojiMap) };
|
||||
const spoilerContent = { __html: emojify(escapeTextContentForBrowser(currentVersion.get('spoiler_text')), emojiMap) };
|
||||
|
||||
const formattedDate = <RelativeTimestamp timestamp={currentVersion.get('created_at')} short={false} />;
|
||||
const formattedName = <InlineAccount accountId={currentVersion.get('account')} />;
|
||||
|
||||
const label = currentVersion.get('original') ? (
|
||||
<FormattedMessage id='status.history.created' defaultMessage='{name} created {date}' values={{ name: formattedName, date: formattedDate }} />
|
||||
) : (
|
||||
<FormattedMessage id='status.history.edited' defaultMessage='{name} edited {date}' values={{ name: formattedName, date: formattedDate }} />
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal compare-history-modal'>
|
||||
<div className='report-modal__target'>
|
||||
<IconButton className='report-modal__close' icon='times' onClick={onClose} size={20} />
|
||||
{label}
|
||||
</div>
|
||||
|
||||
<div className='compare-history-modal__container'>
|
||||
<div className='status__content'>
|
||||
{currentVersion.get('spoiler_text').length > 0 && (
|
||||
<React.Fragment>
|
||||
<div className='translate' dangerouslySetInnerHTML={spoilerContent} />
|
||||
<hr />
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import { HISTORY_FETCH_REQUEST, HISTORY_FETCH_SUCCESS, HISTORY_FETCH_FAIL } from 'mastodon/actions/history';
|
||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
|
||||
const initialHistory = ImmutableMap({
|
||||
loading: false,
|
||||
items: ImmutableList(),
|
||||
});
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
export default function history(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case HISTORY_FETCH_REQUEST:
|
||||
return state.update(action.statusId, initialHistory, history => history.withMutations(map => {
|
||||
map.set('loading', true);
|
||||
map.set('items', ImmutableList());
|
||||
}));
|
||||
case HISTORY_FETCH_SUCCESS:
|
||||
return state.update(action.statusId, initialHistory, history => history.withMutations(map => {
|
||||
map.set('loading', false);
|
||||
map.set('items', fromJS(action.history.map((x, i) => ({ ...x, account: x.account.id, original: i === 0 })).reverse()));
|
||||
}));
|
||||
case HISTORY_FETCH_FAIL:
|
||||
return state.update(action.statusId, initialHistory, history => history.set('loading', false));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
@ -1,6 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::StatusEditSerializer < ActiveModel::Serializer
|
||||
attributes :text, :spoiler_text, :media_attachments_changed,
|
||||
:created_at
|
||||
has_one :account, serializer: REST::AccountSerializer
|
||||
|
||||
attributes :content, :spoiler_text,
|
||||
:media_attachments_changed, :created_at
|
||||
|
||||
has_many :emojis, serializer: REST::CustomEmojiSerializer
|
||||
|
||||
def content
|
||||
Formatter.instance.format(object)
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in new issue