mirror of https://github.com/mastodon/mastodon
Work on private messaging implementation
This commit is contained in:
parent
4986c727d9
commit
fe68151be6
|
@ -22,3 +22,5 @@ public/assets
|
|||
.env.*
|
||||
node_modules/
|
||||
neo4j/
|
||||
.secret.keybase
|
||||
.secret.paperclip
|
||||
|
|
|
@ -2,18 +2,20 @@ import api from '../api'
|
|||
|
||||
import { updateTimeline } from './timelines';
|
||||
|
||||
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
|
||||
export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
|
||||
export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
|
||||
export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL';
|
||||
export const COMPOSE_REPLY = 'COMPOSE_REPLY';
|
||||
export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL';
|
||||
export const COMPOSE_MENTION = 'COMPOSE_MENTION';
|
||||
export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST';
|
||||
export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS';
|
||||
export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL';
|
||||
export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
|
||||
export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
|
||||
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
|
||||
export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
|
||||
export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
|
||||
export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL';
|
||||
export const COMPOSE_REPLY = 'COMPOSE_REPLY';
|
||||
export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL';
|
||||
export const COMPOSE_PRIVATE_MESSAGE = 'COMPOSE_PRIVATE_MESSAGE';
|
||||
export const COMPOSE_PRIVATE_MESSAGE_CANCEL = 'COMPOSE_PRIVATE_MESSAGE_CANCEL';
|
||||
export const COMPOSE_MENTION = 'COMPOSE_MENTION';
|
||||
export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST';
|
||||
export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS';
|
||||
export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL';
|
||||
export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
|
||||
export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
|
||||
|
||||
export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
|
||||
export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
|
||||
|
@ -50,6 +52,20 @@ export function cancelReplyCompose() {
|
|||
};
|
||||
};
|
||||
|
||||
export function privateMessageCompose(recipient) {
|
||||
console.log('privateMessageCompose');
|
||||
return {
|
||||
type: COMPOSE_PRIVATE_MESSAGE,
|
||||
recipient: recipient
|
||||
};
|
||||
};
|
||||
|
||||
export function cancelPrivateMessageCompose() {
|
||||
return {
|
||||
type: COMPOSE_PRIVATE_MESSAGE_CANCEL
|
||||
};
|
||||
};
|
||||
|
||||
export function mentionCompose(account) {
|
||||
return {
|
||||
type: COMPOSE_MENTION,
|
||||
|
@ -64,6 +80,7 @@ export function submitCompose() {
|
|||
api(getState).post('/api/v1/statuses', {
|
||||
status: getState().getIn(['compose', 'text'], ''),
|
||||
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
|
||||
private_message_recipient_id: getState().getIn(['compose', 'private_message_to', 'id'], null),
|
||||
media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
|
||||
sensitive: getState().getIn(['compose', 'sensitive'])
|
||||
}).then(function (response) {
|
||||
|
|
|
@ -11,15 +11,6 @@ import { FormattedMessage } from 'react-intl';
|
|||
import emojify from '../emoji';
|
||||
import escapeTextContentForBrowser from 'react/lib/escapeTextContentForBrowser';
|
||||
|
||||
const outerStyle = {
|
||||
padding: '8px 10px',
|
||||
paddingLeft: '68px',
|
||||
position: 'relative',
|
||||
minHeight: '48px',
|
||||
borderBottom: '1px solid #363c4b',
|
||||
cursor: 'default'
|
||||
};
|
||||
|
||||
const Status = React.createClass({
|
||||
|
||||
contextTypes: {
|
||||
|
@ -90,6 +81,19 @@ const Status = React.createClass({
|
|||
}
|
||||
}
|
||||
|
||||
let outerStyle = {
|
||||
padding: '8px 10px',
|
||||
paddingLeft: '68px',
|
||||
position: 'relative',
|
||||
minHeight: '48px',
|
||||
borderBottom: '1px solid #363c4b',
|
||||
cursor: 'default'
|
||||
};
|
||||
|
||||
if (status.get('is_private')) {
|
||||
outerStyle.background = 'rgb(75, 64, 60)';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={this.props.muted ? 'muted' : ''} style={outerStyle}>
|
||||
<div style={{ fontSize: '15px' }}>
|
||||
|
|
|
@ -57,7 +57,24 @@ const StatusContent = React.createClass({
|
|||
},
|
||||
|
||||
render () {
|
||||
const content = { __html: emojify(this.props.status.get('content')) };
|
||||
let _content = null;
|
||||
if (this.props.status.get('is_private')) {
|
||||
_content = this.props.status.get('private_content');
|
||||
if (_content == null) {
|
||||
// User doesn't have access to view this status.
|
||||
_content = this.props.status.get('content');
|
||||
} else {
|
||||
// Prepend the recipient account handle so it's visible in the UI
|
||||
_content =
|
||||
'<a href="' + this.props.status.getIn(['private_recipient', 'url']) +
|
||||
'" className="h-card u-url p-nickname mention">@<span>' +
|
||||
this.props.status.getIn(['private_recipient', 'username']) +
|
||||
'</span></a> ' + _content;
|
||||
}
|
||||
} else {
|
||||
_content = this.props.status.get('content');
|
||||
}
|
||||
const content = { __html: emojify(_content) };
|
||||
return <div className='status__content' style={{ cursor: 'pointer' }} dangerouslySetInnerHTML={content} onClick={this.props.onClick} />;
|
||||
},
|
||||
|
||||
|
|
|
@ -6,12 +6,12 @@ import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'r
|
|||
|
||||
const messages = defineMessages({
|
||||
mention: { id: 'account.mention', defaultMessage: 'Mention' },
|
||||
message: { id: 'account.message', defaultMessage: 'Message' },
|
||||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock' },
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
block: { id: 'account.block', defaultMessage: 'Block' },
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
block: { id: 'account.block', defaultMessage: 'Block' }
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' }
|
||||
});
|
||||
|
||||
const outerStyle = {
|
||||
|
@ -41,7 +41,8 @@ const ActionBar = React.createClass({
|
|||
me: React.PropTypes.number.isRequired,
|
||||
onFollow: React.PropTypes.func.isRequired,
|
||||
onBlock: React.PropTypes.func.isRequired,
|
||||
onMention: React.PropTypes.func.isRequired
|
||||
onMention: React.PropTypes.func.isRequired,
|
||||
onMessage: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
@ -52,6 +53,7 @@ const ActionBar = React.createClass({
|
|||
let menu = [];
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.mention), action: this.props.onMention });
|
||||
menu.push({ text: intl.formatMessage(messages.message), action: this.props.onMessage });
|
||||
|
||||
if (account.get('id') === me) {
|
||||
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
|
||||
|
|
|
@ -10,7 +10,10 @@ import {
|
|||
fetchAccountTimeline,
|
||||
expandAccountTimeline
|
||||
} from '../../actions/accounts';
|
||||
import { mentionCompose } from '../../actions/compose';
|
||||
import {
|
||||
mentionCompose,
|
||||
privateMessageCompose
|
||||
} from '../../actions/compose';
|
||||
import Header from './components/header';
|
||||
import {
|
||||
getAccountTimeline,
|
||||
|
@ -73,6 +76,11 @@ const Account = React.createClass({
|
|||
this.props.dispatch(mentionCompose(this.props.account));
|
||||
},
|
||||
|
||||
handleMessage () {
|
||||
console.log('handleMessage');
|
||||
this.props.dispatch(privateMessageCompose(this.props.account));
|
||||
},
|
||||
|
||||
render () {
|
||||
const { account, me } = this.props;
|
||||
|
||||
|
@ -88,7 +96,7 @@ const Account = React.createClass({
|
|||
<Column>
|
||||
<ColumnBackButton />
|
||||
<Header account={account} me={me} onFollow={this.handleFollow} />
|
||||
<ActionBar account={account} me={me} onBlock={this.handleBlock} onMention={this.handleMention} />
|
||||
<ActionBar account={account} me={me} onBlock={this.handleBlock} onMention={this.handleMention} onMessage={this.handleMessage} />
|
||||
|
||||
{this.props.children}
|
||||
</Column>
|
||||
|
|
|
@ -3,6 +3,7 @@ import Button from '../../../components/button';
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ReplyIndicator from './reply_indicator';
|
||||
import PrivateMessageIndicator from './private_message_indicator';
|
||||
import UploadButton from './upload_button';
|
||||
import Autosuggest from 'react-autosuggest';
|
||||
import AutosuggestAccountContainer from '../../compose/containers/autosuggest_account_container';
|
||||
|
@ -72,9 +73,11 @@ const ComposeForm = React.createClass({
|
|||
is_submitting: React.PropTypes.bool,
|
||||
is_uploading: React.PropTypes.bool,
|
||||
in_reply_to: ImmutablePropTypes.map,
|
||||
private_message_to: ImmutablePropTypes.map,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
onSubmit: React.PropTypes.func.isRequired,
|
||||
onCancelReply: React.PropTypes.func.isRequired,
|
||||
onCancelPrivateMessage: React.PropTypes.func.isRequired,
|
||||
onClearSuggestions: React.PropTypes.func.isRequired,
|
||||
onFetchSuggestions: React.PropTypes.func.isRequired,
|
||||
onSuggestionSelected: React.PropTypes.func.isRequired,
|
||||
|
@ -102,7 +105,7 @@ const ComposeForm = React.createClass({
|
|||
},
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (prevProps.text !== this.props.text || prevProps.in_reply_to !== this.props.in_reply_to) {
|
||||
if (prevProps.text !== this.props.text || prevProps.in_reply_to !== this.props.in_reply_to || prevProps.private_message_to !== this.props.private_message_to) {
|
||||
const textarea = this.autosuggest.input;
|
||||
|
||||
if (textarea) {
|
||||
|
@ -149,10 +152,13 @@ const ComposeForm = React.createClass({
|
|||
render () {
|
||||
const { intl } = this.props;
|
||||
let replyArea = '';
|
||||
let messageToArea = '';
|
||||
const disabled = this.props.is_submitting || this.props.is_uploading;
|
||||
|
||||
if (this.props.in_reply_to) {
|
||||
replyArea = <ReplyIndicator status={this.props.in_reply_to} onCancel={this.props.onCancelReply} />;
|
||||
} else if (this.props.private_message_to) {
|
||||
messageToArea = <PrivateMessageIndicator recipient={this.props.private_message_to} onCancel={this.props.onCancelPrivateMessage} />;
|
||||
}
|
||||
|
||||
const inputProps = {
|
||||
|
@ -166,6 +172,7 @@ const ComposeForm = React.createClass({
|
|||
return (
|
||||
<div style={{ padding: '10px' }}>
|
||||
{replyArea}
|
||||
{messageToArea}
|
||||
|
||||
<Autosuggest
|
||||
ref={this.setRef}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
import emojify from '../../../emoji';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' }
|
||||
});
|
||||
|
||||
const PrivateMessageIndicator = React.createClass({
|
||||
|
||||
contextTypes: {
|
||||
router: React.PropTypes.object
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
recipient: ImmutablePropTypes.map.isRequired,
|
||||
onCancel: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
handleClick () {
|
||||
this.props.onCancel();
|
||||
},
|
||||
|
||||
handleAccountClick (e) {
|
||||
if (e.button === 0) {
|
||||
e.preventDefault();
|
||||
this.context.router.push(`/accounts/${this.props.recipient.get('id')}`);
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<div style={{ background: '#c8ae9b', padding: '10px' }}>
|
||||
<div style={{ overflow: 'hidden', marginBottom: '5px' }}>
|
||||
<div style={{ float: 'right', lineHeight: '24px' }}><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} /></div>
|
||||
|
||||
<a href={this.props.recipient.get('url')} onClick={this.handleAccountClick} className='reply-indicator__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px', color: '#282c37', textDecoration: 'none', overflow: 'hidden', lineHeight: '24px' }}>
|
||||
<div style={{ float: 'left', marginRight: '5px' }}><Avatar size={24} src={this.props.recipient.get('avatar')} /></div>
|
||||
<DisplayName account={this.props.recipient} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className='reply-indicator__content'>
|
||||
<p style={{ fontStyle: 'italic' }}>This message will be visible to you, the recipient, and instance administrators.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default injectIntl(PrivateMessageIndicator);
|
|
@ -4,6 +4,7 @@ import {
|
|||
changeCompose,
|
||||
submitCompose,
|
||||
cancelReplyCompose,
|
||||
cancelPrivateMessageCompose,
|
||||
clearComposeSuggestions,
|
||||
fetchComposeSuggestions,
|
||||
selectComposeSuggestion,
|
||||
|
@ -22,7 +23,8 @@ const makeMapStateToProps = () => {
|
|||
sensitive: state.getIn(['compose', 'sensitive']),
|
||||
is_submitting: state.getIn(['compose', 'is_submitting']),
|
||||
is_uploading: state.getIn(['compose', 'is_uploading']),
|
||||
in_reply_to: getStatus(state, state.getIn(['compose', 'in_reply_to']))
|
||||
in_reply_to: getStatus(state, state.getIn(['compose', 'in_reply_to'])),
|
||||
private_message_to: state.getIn(['compose', 'private_message_to'])
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -43,6 +45,10 @@ const mapDispatchToProps = function (dispatch) {
|
|||
dispatch(cancelReplyCompose());
|
||||
},
|
||||
|
||||
onCancelPrivateMessage () {
|
||||
dispatch(cancelPrivateMessageCompose());
|
||||
},
|
||||
|
||||
onClearSuggestions () {
|
||||
dispatch(clearComposeSuggestions());
|
||||
},
|
||||
|
|
|
@ -4,6 +4,8 @@ import {
|
|||
COMPOSE_CHANGE,
|
||||
COMPOSE_REPLY,
|
||||
COMPOSE_REPLY_CANCEL,
|
||||
COMPOSE_PRIVATE_MESSAGE,
|
||||
COMPOSE_PRIVATE_MESSAGE_CANCEL,
|
||||
COMPOSE_MENTION,
|
||||
COMPOSE_SUBMIT_REQUEST,
|
||||
COMPOSE_SUBMIT_SUCCESS,
|
||||
|
@ -27,6 +29,7 @@ const initialState = Immutable.Map({
|
|||
sensitive: false,
|
||||
text: '',
|
||||
in_reply_to: null,
|
||||
private_message_to: null,
|
||||
is_submitting: false,
|
||||
is_uploading: false,
|
||||
progress: 0,
|
||||
|
@ -121,6 +124,16 @@ export default function compose(state = initialState, action) {
|
|||
return state.set('progress', Math.round((action.loaded / action.total) * 100));
|
||||
case COMPOSE_MENTION:
|
||||
return state.update('text', text => `${text}@${action.account.get('acct')} `);
|
||||
case COMPOSE_PRIVATE_MESSAGE:
|
||||
console.log('COMPOSE_PRIVATE_MESSAGE');
|
||||
return state.withMutations(map => {
|
||||
map.set('private_message_to', action.recipient);
|
||||
});
|
||||
case COMPOSE_PRIVATE_MESSAGE_CANCEL:
|
||||
return state.withMutations(map => {
|
||||
map.set('private_message_to', null);
|
||||
map.set('text', '');
|
||||
});
|
||||
case COMPOSE_SUGGESTIONS_CLEAR:
|
||||
return state.update('suggestions', Immutable.List(), list => list.clear()).set('suggestion_token', null);
|
||||
case COMPOSE_SUGGESTIONS_READY:
|
||||
|
|
|
@ -52,7 +52,7 @@ class Api::V1::StatusesController < ApiController
|
|||
end
|
||||
|
||||
def create
|
||||
@status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids], sensitive: params[:sensitive])
|
||||
@status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), params[:private_message_recipient_id].blank? ? nil : Account.find(params[:private_message_recipient_id]), media_ids: params[:media_ids], sensitive: params[:sensitive])
|
||||
render action: :show
|
||||
end
|
||||
|
||||
|
|
|
@ -9,10 +9,10 @@ class Formatter
|
|||
include ActionView::Helpers::TextHelper
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
|
||||
def format(status)
|
||||
return reformat(status.content) unless status.local?
|
||||
def format(status, is_private = false)
|
||||
return reformat(is_private ? status.private_text : status.content) unless status.local?
|
||||
|
||||
html = status.text
|
||||
html = is_private ? status.private_text : status.text
|
||||
html = encode(html)
|
||||
html = simple_format(html, sanitize: false)
|
||||
html = link_urls(html)
|
||||
|
|
|
@ -16,6 +16,8 @@ class Status < ApplicationRecord
|
|||
has_many :media_attachments, dependent: :destroy
|
||||
has_and_belongs_to_many :tags
|
||||
|
||||
belongs_to :private_recipient, foreign_key: 'private_recipient_id', class_name: 'Account', optional: true
|
||||
|
||||
has_one :notification, as: :activity, dependent: :destroy
|
||||
|
||||
validates :account, presence: true
|
||||
|
@ -61,6 +63,10 @@ class Status < ApplicationRecord
|
|||
content
|
||||
end
|
||||
|
||||
def is_private
|
||||
!private_recipient_id.nil?
|
||||
end
|
||||
|
||||
def reblogs_count
|
||||
attributes['reblogs_count'] || reblogs.count
|
||||
end
|
||||
|
|
|
@ -9,8 +9,17 @@ class PostStatusService < BaseService
|
|||
# @option [Boolean] :sensitive
|
||||
# @option [Enumerable] :media_ids Optional array of media IDs to attach
|
||||
# @return [Status]
|
||||
def call(account, text, in_reply_to = nil, options = {})
|
||||
status = account.statuses.create!(text: text, thread: in_reply_to, sensitive: options[:sensitive])
|
||||
def call(account, text, in_reply_to = nil, private_message_recipient = nil, options = {})
|
||||
if private_message_recipient != nil
|
||||
# Ensure that we don't accidentally leak private message through any API, by using
|
||||
# a dedicated colun in the table to store the private message. For any code that
|
||||
# reads the text column (and for any other instances in the federation that don't
|
||||
# support private messaging), they'll just see "This is a private message" as the
|
||||
# content of the post.
|
||||
status = account.statuses.create!(private_text: text, text: 'This is a private message', thread: in_reply_to, private_recipient: private_message_recipient, sensitive: options[:sensitive])
|
||||
else
|
||||
status = account.statuses.create!(text: text, thread: in_reply_to, sensitive: options[:sensitive])
|
||||
end
|
||||
attach_media(status, options[:media_ids])
|
||||
process_mentions_service.call(status)
|
||||
process_hashtags_service.call(status)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
attributes :id, :created_at, :in_reply_to_id, :sensitive
|
||||
attributes :id, :created_at, :in_reply_to_id, :is_private, :sensitive
|
||||
|
||||
node(:uri) { |status| TagManager.instance.uri_for(status) }
|
||||
node(:content) { |status| Formatter.instance.format(status) }
|
||||
|
@ -6,6 +6,42 @@ node(:url) { |status| TagManager.instance.url_for(status) }
|
|||
node(:reblogs_count) { |status| defined?(@reblogs_counts_map) ? (@reblogs_counts_map[status.id] || 0) : status.reblogs_count }
|
||||
node(:favourites_count) { |status| defined?(@favourites_counts_map) ? (@favourites_counts_map[status.id] || 0) : status.favourites_count }
|
||||
|
||||
node(:current_user_id) { |status|
|
||||
current_user.id
|
||||
}
|
||||
|
||||
node(:account_id) { |status|
|
||||
status.account_id
|
||||
}
|
||||
|
||||
node(:private_recipient_id) { |status|
|
||||
status.private_recipient_id
|
||||
}
|
||||
|
||||
node(:private_content) { |status|
|
||||
if status.is_private then
|
||||
if current_user.id == status.account_id || current_user.id == status.private_recipient_id then
|
||||
Formatter.instance.format(status, true)
|
||||
else
|
||||
nil
|
||||
end
|
||||
else
|
||||
nil
|
||||
end
|
||||
}
|
||||
|
||||
node(:private_recipient) { |status|
|
||||
if status.is_private then
|
||||
if current_user.id == status.account_id || current_user.id == status.private_recipient_id then
|
||||
partial('api/v1/accounts/show', object: status.private_recipient)
|
||||
else
|
||||
nil
|
||||
end
|
||||
else
|
||||
nil
|
||||
end
|
||||
}
|
||||
|
||||
child :account do
|
||||
extends 'api/v1/accounts/show'
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddPrivateMessageRecipientId < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :statuses, :private_recipient_id, :integer, null: true, default: nil
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class AddPrivateText < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :statuses, :private_text, :text, null: false, default: ''
|
||||
end
|
||||
end
|
|
@ -0,0 +1,51 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Environment variables
|
||||
export REDIS_HOST=localhost
|
||||
export REDIS_PORT=6379
|
||||
export DB_HOST=localhost
|
||||
export DB_USER=postgres
|
||||
export DB_NAME=postgres
|
||||
export DB_PASS=postgres
|
||||
export DB_PORT=5432
|
||||
export NEO4J_HOST=localhost
|
||||
export NEO4J_PORT=7474
|
||||
|
||||
# Federation
|
||||
export LOCAL_DOMAIN=localhost
|
||||
export LOCAL_HTTPS=false
|
||||
|
||||
# Application secrets
|
||||
if [ ! -f .secret.paperclip ]; then
|
||||
echo "$(rake secret)" > .secret.paperclip
|
||||
fi
|
||||
if [ ! -f .secret.keybase ]; then
|
||||
echo "$(rake secret)" > .secret.keybase
|
||||
fi
|
||||
export PAPERCLIP_SECRET=$(<.secret.paperclip)
|
||||
export SECRET_KEY_BASE=$(<.secret.keybase)
|
||||
|
||||
# E-mail configuration
|
||||
export SMTP_SERVER=smtp.mailgun.org
|
||||
export SMTP_PORT=587
|
||||
export SMTP_LOGIN=
|
||||
export SMTP_PASSWORD=
|
||||
export SMTP_FROM_ADDRESS=notifications@example.com
|
||||
|
||||
# Set us in production mode so that the configuration uses
|
||||
# the DB_HOST variables.
|
||||
export RAILS_ENV=production
|
||||
|
||||
# Install dependencies
|
||||
#bundle install
|
||||
|
||||
# Upgrade database
|
||||
rails db:migrate
|
||||
|
||||
# Compile assets
|
||||
rails assets:precompile
|
||||
|
||||
# Run web server
|
||||
rails server
|
Loading…
Reference in New Issue