revision history ui
This commit is contained in:
parent
7219c9b5e6
commit
498630f20f
|
@ -6,7 +6,7 @@ class Api::V1::StatusesController < Api::BaseController
|
||||||
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :update, :destroy]
|
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :update, :destroy]
|
||||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :update, :destroy]
|
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :update, :destroy]
|
||||||
before_action :require_user!, except: [:show, :context, :card]
|
before_action :require_user!, except: [:show, :context, :card]
|
||||||
before_action :set_status, only: [:show, :context, :card, :update]
|
before_action :set_status, only: [:show, :context, :card, :update, :revisions]
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
|
@ -33,14 +33,10 @@ class Api::V1::StatusesController < Api::BaseController
|
||||||
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
|
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def card
|
def revisions
|
||||||
@card = @status.preview_cards.first
|
@revisions = @status.revisions
|
||||||
|
|
||||||
if @card.nil?
|
render json: @revisions, each_serializer: REST::StatusRevisionSerializer
|
||||||
render_empty
|
|
||||||
else
|
|
||||||
render json: @card, serializer: REST::PreviewCardSerializer
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
|
export const STATUS_REVISION_LIST_LOAD = 'STATUS_REVISION_LIST';
|
||||||
|
export const STATUS_REVISION_LIST_LOAD_SUCCESS = 'STATUS_REVISION_LIST_SUCCESS';
|
||||||
|
export const STATUS_REVISION_LIST_LOAD_FAIL = 'STATUS_REVISION_LIST_FAIL';
|
||||||
|
|
||||||
|
const loadSuccess = data => ({ type: STATUS_REVISION_LIST_LOAD_SUCCESS, payload: data });
|
||||||
|
const loadFail = e => ({ type: STATUS_REVISION_LIST_LOAD_FAIL, payload: e });
|
||||||
|
|
||||||
|
export function load(statusId) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
api(getState).get(`/api/v1/statuses/${statusId}/revisions`)
|
||||||
|
.then(res => dispatch(loadSuccess(res.data)))
|
||||||
|
.catch(e => dispatch(loadFail(e)));
|
||||||
|
};
|
||||||
|
}
|
|
@ -67,6 +67,7 @@ class Status extends ImmutablePureComponent {
|
||||||
otherAccounts: ImmutablePropTypes.list,
|
otherAccounts: ImmutablePropTypes.list,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
onReply: PropTypes.func,
|
onReply: PropTypes.func,
|
||||||
|
onShowRevisions: PropTypes.func,
|
||||||
onQuote: PropTypes.func,
|
onQuote: PropTypes.func,
|
||||||
onFavourite: PropTypes.func,
|
onFavourite: PropTypes.func,
|
||||||
onReblog: PropTypes.func,
|
onReblog: PropTypes.func,
|
||||||
|
@ -438,9 +439,10 @@ class Status extends ImmutablePureComponent {
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!group && status.get('group') && (
|
{((!group && status.get('group')) || status.get('revised_at') !== null) && (
|
||||||
<div className='status__meta'>
|
<div className='status__meta'>
|
||||||
Posted in <NavLink to={`/groups/${status.getIn(['group', 'id'])}`}>{status.getIn(['group', 'title'])}</NavLink>
|
{!group && status.get('group') && <React.Fragment>Posted in <NavLink to={`/groups/${status.getIn(['group', 'id'])}`}>{status.getIn(['group', 'title'])}</NavLink></React.Fragment>}
|
||||||
|
{status.get('revised_at') !== null && <a onClick={() => other.onShowRevisions(status)}> Edited</a>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onShowRevisions (status) {
|
||||||
|
dispatch(openModal('STATUS_REVISION', { status }));
|
||||||
|
},
|
||||||
|
|
||||||
onFavourite (status) {
|
onFavourite (status) {
|
||||||
if (status.get('favourited')) {
|
if (status.get('favourited')) {
|
||||||
dispatch(unfavourite(status));
|
dispatch(unfavourite(status));
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
EmbedModal,
|
EmbedModal,
|
||||||
ListEditor,
|
ListEditor,
|
||||||
ListAdder,
|
ListAdder,
|
||||||
|
StatusRevisionModal,
|
||||||
} from '../../../features/ui/util/async-components';
|
} from '../../../features/ui/util/async-components';
|
||||||
|
|
||||||
const MODAL_COMPONENTS = {
|
const MODAL_COMPONENTS = {
|
||||||
|
@ -35,6 +36,7 @@ const MODAL_COMPONENTS = {
|
||||||
'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
|
'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
|
||||||
'LIST_ADDER':ListAdder,
|
'LIST_ADDER':ListAdder,
|
||||||
'HOTKEYS': () => Promise.resolve({ default: HotkeysModal }),
|
'HOTKEYS': () => Promise.resolve({ default: HotkeysModal }),
|
||||||
|
'STATUS_REVISION': StatusRevisionModal,
|
||||||
'COMPOSE': () => Promise.resolve({ default: ComposeModal }),
|
'COMPOSE': () => Promise.resolve({ default: ComposeModal }),
|
||||||
'UNAUTHORIZED': () => Promise.resolve({ default: UnauthorizedModal }),
|
'UNAUTHORIZED': () => Promise.resolve({ default: UnauthorizedModal }),
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { injectIntl } from 'react-intl';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import ModalLoading from './modal_loading';
|
||||||
|
import RelativeTimestamp from '../../../components/relative_timestamp';
|
||||||
|
|
||||||
|
export default @injectIntl
|
||||||
|
class StatusRevisionsList extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
loading: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
|
data: PropTypes.array
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { loading, error, data } = this.props;
|
||||||
|
|
||||||
|
if (loading || !data) return <ModalLoading />;
|
||||||
|
|
||||||
|
if (error) return (
|
||||||
|
<div className='status-revisions-list'>
|
||||||
|
<div className='status-revisions-list__error'>
|
||||||
|
An error occured
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='status-revisions-list'>
|
||||||
|
{data.map((revision, i) => (
|
||||||
|
<div key={i} className='status-revisions-list__item'>
|
||||||
|
<div className='status-revisions-list__item__timestamp'>
|
||||||
|
<RelativeTimestamp timestamp={revision.created_at} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='status-revisions-list__item__text'>{revision.text}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import IconButton from 'gabsocial/components/icon_button';
|
||||||
|
import StatusRevisionListContainer from '../containers/status_revision_list_container';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @injectIntl
|
||||||
|
class StatusRevisionModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
status: ImmutablePropTypes.map.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { intl, onClose, status } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='modal-root__modal'>
|
||||||
|
<div className='status-revisions'>
|
||||||
|
<div className='status-revisions__header'>
|
||||||
|
<h3 className='status-revisions__header__title'><FormattedMessage id='status_revisions.heading' defaultMessage='Revision History' /></h3>
|
||||||
|
<IconButton className='status-revisions__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='status-revisions__content'>
|
||||||
|
<StatusRevisionListContainer id={status.get('id')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { load } from '../../../actions/status_revision_list';
|
||||||
|
import StatusRevisionList from '../components/status_revision_list';
|
||||||
|
|
||||||
|
class StatusRevisionListContainer extends ImmutablePureComponent {
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.load(this.props.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <StatusRevisionList {...this.props} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
loading: state.getIn(['status_revision_list', 'loading']),
|
||||||
|
error: state.getIn(['status_revision_list', 'error']),
|
||||||
|
data: state.getIn(['status_revision_list', 'data']),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
load
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(StatusRevisionListContainer);
|
|
@ -122,6 +122,10 @@ export function MuteModal () {
|
||||||
return import(/* webpackChunkName: "modals/mute_modal" */'../components/mute_modal');
|
return import(/* webpackChunkName: "modals/mute_modal" */'../components/mute_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function StatusRevisionModal () {
|
||||||
|
return import(/* webpackChunkName: "modals/mute_modal" */'../components/status_revision_modal');
|
||||||
|
}
|
||||||
|
|
||||||
export function ReportModal () {
|
export function ReportModal () {
|
||||||
return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal');
|
return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal');
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ import group_relationships from './group_relationships';
|
||||||
import group_lists from './group_lists';
|
import group_lists from './group_lists';
|
||||||
import group_editor from './group_editor';
|
import group_editor from './group_editor';
|
||||||
import sidebar from './sidebar';
|
import sidebar from './sidebar';
|
||||||
|
import status_revision_list from './status_revision_list';
|
||||||
|
|
||||||
const reducers = {
|
const reducers = {
|
||||||
dropdown_menu,
|
dropdown_menu,
|
||||||
|
@ -77,6 +78,7 @@ const reducers = {
|
||||||
group_lists,
|
group_lists,
|
||||||
group_editor,
|
group_editor,
|
||||||
sidebar,
|
sidebar,
|
||||||
|
status_revision_list,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default combineReducers(reducers);
|
export default combineReducers(reducers);
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { Map as ImmutableMap } from 'immutable';
|
||||||
|
import {
|
||||||
|
STATUS_REVISION_LIST_LOAD,
|
||||||
|
STATUS_REVISION_LIST_LOAD_SUCCESS,
|
||||||
|
STATUS_REVISION_LIST_LOAD_FAIL
|
||||||
|
} from '../actions/status_revision_list';
|
||||||
|
|
||||||
|
const initialState = ImmutableMap({
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
data: null
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function statusRevisionList(state = initialState, action) {
|
||||||
|
switch(action.type) {
|
||||||
|
case STATUS_REVISION_LIST_LOAD:
|
||||||
|
return initialState;
|
||||||
|
case STATUS_REVISION_LIST_LOAD_SUCCESS:
|
||||||
|
return state.withMutations(mutable => {
|
||||||
|
mutable.set('loading', false);
|
||||||
|
mutable.set('data', action.payload);
|
||||||
|
});
|
||||||
|
case STATUS_REVISION_LIST_LOAD_FAIL:
|
||||||
|
return state.withMutations(mutable => {
|
||||||
|
mutable.set('loading', false);
|
||||||
|
mutable.set('error', action.payload);
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
|
@ -32,6 +32,7 @@
|
||||||
@import 'gabsocial/components/group-form';
|
@import 'gabsocial/components/group-form';
|
||||||
@import 'gabsocial/components/group-sidebar-panel';
|
@import 'gabsocial/components/group-sidebar-panel';
|
||||||
@import 'gabsocial/components/sidebar-menu';
|
@import 'gabsocial/components/sidebar-menu';
|
||||||
|
@import 'gabsocial/components/status-revisions';
|
||||||
|
|
||||||
@import 'gabsocial/polls';
|
@import 'gabsocial/polls';
|
||||||
@import 'gabsocial/introduction';
|
@import 'gabsocial/introduction';
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
.status-revisions {
|
||||||
|
padding: 8px 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: $classic-base-color;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
@media screen and (max-width: 960px) {
|
||||||
|
height: 90vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
border-bottom: 1px solid lighten($classic-base-color, 8%);
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
display: block;
|
||||||
|
width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 24px;
|
||||||
|
color: $primary-text-color;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 500px;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
height: calc(100% - 80px);
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
widows: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-list {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
padding: 15px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid lighten($classic-base-color, 8%);
|
||||||
|
|
||||||
|
&__timestamp {
|
||||||
|
opacity: 0.5;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class REST::StatusRevisionSerializer < ActiveModel::Serializer
|
||||||
|
attributes :created_at, :text
|
||||||
|
end
|
|
@ -305,6 +305,7 @@ Rails.application.routes.draw do
|
||||||
member do
|
member do
|
||||||
get :context
|
get :context
|
||||||
get :card
|
get :card
|
||||||
|
get :revisions
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue