Merge branch 'feature/revision-history' of https://code.gab.com/gab/social/gab-social into develop
This commit is contained in:
commit
4de30bd8d0
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
# account_id :bigint(8)
|
# account_id :bigint(8)
|
||||||
# image_file_name :string
|
# image_file_name :string
|
||||||
# image_content_type :string
|
# image_content_type :string
|
||||||
# image_file_size :bigint(8)
|
# image_file_size :integer
|
||||||
# image_updated_at :datetime
|
# image_updated_at :datetime
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
# poll_id :bigint(8)
|
# poll_id :bigint(8)
|
||||||
# group_id :integer
|
# group_id :integer
|
||||||
# quote_of_id :bigint(8)
|
# quote_of_id :bigint(8)
|
||||||
|
# revised_at :datetime
|
||||||
#
|
#
|
||||||
|
|
||||||
class Status < ApplicationRecord
|
class Status < ApplicationRecord
|
||||||
|
@ -61,6 +62,7 @@ class Status < ApplicationRecord
|
||||||
has_many :mentions, dependent: :destroy, inverse_of: :status
|
has_many :mentions, dependent: :destroy, inverse_of: :status
|
||||||
has_many :active_mentions, -> { active }, class_name: 'Mention', inverse_of: :status
|
has_many :active_mentions, -> { active }, class_name: 'Mention', inverse_of: :status
|
||||||
has_many :media_attachments, dependent: :nullify
|
has_many :media_attachments, dependent: :nullify
|
||||||
|
has_many :revisions, class_name: 'StatusRevision', dependent: :destroy
|
||||||
|
|
||||||
has_and_belongs_to_many :tags
|
has_and_belongs_to_many :tags
|
||||||
has_and_belongs_to_many :preview_cards
|
has_and_belongs_to_many :preview_cards
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: status_revisions
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# status_id :bigint(8)
|
||||||
|
# text :string
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
|
||||||
|
class StatusRevision < ApplicationRecord
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class REST::StatusRevisionSerializer < ActiveModel::Serializer
|
||||||
|
attributes :created_at, :text
|
||||||
|
end
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::StatusSerializer < ActiveModel::Serializer
|
class REST::StatusSerializer < ActiveModel::Serializer
|
||||||
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
|
attributes :id, :created_at, :revised_at, :in_reply_to_id, :in_reply_to_account_id,
|
||||||
:sensitive, :spoiler_text, :visibility, :language,
|
:sensitive, :spoiler_text, :visibility, :language,
|
||||||
:uri, :url, :replies_count, :reblogs_count,
|
:uri, :url, :replies_count, :reblogs_count,
|
||||||
:favourites_count, :quote_of_id
|
:favourites_count, :quote_of_id
|
||||||
|
|
|
@ -25,9 +25,11 @@ class EditStatusService < BaseService
|
||||||
|
|
||||||
validate_media!
|
validate_media!
|
||||||
preprocess_attributes!
|
preprocess_attributes!
|
||||||
|
revision_text = prepare_revision_text
|
||||||
|
|
||||||
process_status!
|
process_status!
|
||||||
postprocess_status!
|
postprocess_status!
|
||||||
|
create_revision! revision_text
|
||||||
|
|
||||||
redis.setex(idempotency_key, 3_600, @status.id) if idempotency_given?
|
redis.setex(idempotency_key, 3_600, @status.id) if idempotency_given?
|
||||||
|
|
||||||
|
@ -60,6 +62,25 @@ class EditStatusService < BaseService
|
||||||
LinkCrawlWorker.perform_async(@status.id) unless @status.spoiler_text?
|
LinkCrawlWorker.perform_async(@status.id) unless @status.spoiler_text?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def prepare_revision_text
|
||||||
|
text = @status.text
|
||||||
|
current_media_ids = @status.media_attachments.pluck(:id)
|
||||||
|
new_media_ids = @options[:media_ids].take(4).map(&:to_i)
|
||||||
|
|
||||||
|
if current_media_ids.sort != new_media_ids.sort
|
||||||
|
text = "" if text == @options[:text]
|
||||||
|
text += " [Media attachments changed]"
|
||||||
|
end
|
||||||
|
|
||||||
|
text.strip()
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_revision!(text)
|
||||||
|
@status.revisions.create!({
|
||||||
|
text: text
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
def validate_media!
|
def validate_media!
|
||||||
return if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
|
return if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
|
||||||
|
|
||||||
|
@ -100,6 +121,7 @@ class EditStatusService < BaseService
|
||||||
|
|
||||||
def status_attributes
|
def status_attributes
|
||||||
{
|
{
|
||||||
|
revised_at: Time.now,
|
||||||
text: @text,
|
text: @text,
|
||||||
media_attachments: @media || [],
|
media_attachments: @media || [],
|
||||||
sensitive: (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present?,
|
sensitive: (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present?,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
class CreateStatusRevisions < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
create_table :status_revisions do |t|
|
||||||
|
t.bigint :status_id
|
||||||
|
t.string :text
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddRevisedAtToStatuses < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
add_column :statuses, :revised_at, :datetime
|
||||||
|
end
|
||||||
|
end
|
10
db/schema.rb
10
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2019_09_03_162122) do
|
ActiveRecord::Schema.define(version: 2019_09_17_141707) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -672,6 +672,13 @@ ActiveRecord::Schema.define(version: 2019_09_03_162122) do
|
||||||
t.index ["account_id", "status_id"], name: "index_status_pins_on_account_id_and_status_id", unique: true
|
t.index ["account_id", "status_id"], name: "index_status_pins_on_account_id_and_status_id", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "status_revisions", force: :cascade do |t|
|
||||||
|
t.bigint "status_id"
|
||||||
|
t.string "text"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
end
|
||||||
|
|
||||||
create_table "status_stats", force: :cascade do |t|
|
create_table "status_stats", force: :cascade do |t|
|
||||||
t.bigint "status_id", null: false
|
t.bigint "status_id", null: false
|
||||||
t.bigint "replies_count", default: 0, null: false
|
t.bigint "replies_count", default: 0, null: false
|
||||||
|
@ -703,6 +710,7 @@ ActiveRecord::Schema.define(version: 2019_09_03_162122) do
|
||||||
t.bigint "poll_id"
|
t.bigint "poll_id"
|
||||||
t.integer "group_id"
|
t.integer "group_id"
|
||||||
t.bigint "quote_of_id"
|
t.bigint "quote_of_id"
|
||||||
|
t.datetime "revised_at"
|
||||||
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20180106", order: { id: :desc }
|
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20180106", order: { id: :desc }
|
||||||
t.index ["group_id"], name: "index_statuses_on_group_id"
|
t.index ["group_id"], name: "index_statuses_on_group_id"
|
||||||
t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id"
|
t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id"
|
||||||
|
|
Loading…
Reference in New Issue