Deleting statuses from UI
This commit is contained in:
parent
a41c3487bd
commit
ef2b50c9ac
12 changed files with 242 additions and 34 deletions
|
@ -5,6 +5,10 @@ export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
|
||||||
export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS';
|
export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS';
|
||||||
export const STATUS_FETCH_FAIL = 'STATUS_FETCH_FAIL';
|
export const STATUS_FETCH_FAIL = 'STATUS_FETCH_FAIL';
|
||||||
|
|
||||||
|
export const STATUS_DELETE_REQUEST = 'STATUS_DELETE_REQUEST';
|
||||||
|
export const STATUS_DELETE_SUCCESS = 'STATUS_DELETE_SUCCESS';
|
||||||
|
export const STATUS_DELETE_FAIL = 'STATUS_DELETE_FAIL';
|
||||||
|
|
||||||
export function fetchStatusRequest(id) {
|
export function fetchStatusRequest(id) {
|
||||||
return {
|
return {
|
||||||
type: STATUS_FETCH_REQUEST,
|
type: STATUS_FETCH_REQUEST,
|
||||||
|
@ -41,3 +45,37 @@ export function fetchStatusFail(id, error) {
|
||||||
error: error
|
error: error
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function deleteStatus(id) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch(deleteStatusRequest(id));
|
||||||
|
|
||||||
|
api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
|
||||||
|
dispatch(deleteStatusSuccess(id));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(deleteStatusFail(id, error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function deleteStatusRequest(id) {
|
||||||
|
return {
|
||||||
|
type: STATUS_DELETE_REQUEST,
|
||||||
|
id: id
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function deleteStatusSuccess(id) {
|
||||||
|
return {
|
||||||
|
type: STATUS_DELETE_SUCCESS,
|
||||||
|
id: id
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function deleteStatusFail(id, error) {
|
||||||
|
return {
|
||||||
|
type: STATUS_DELETE_FAIL,
|
||||||
|
id: id,
|
||||||
|
error: error
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -26,8 +26,16 @@ const IconButton = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
const style = {
|
||||||
|
display: 'inline-block',
|
||||||
|
fontSize: `${this.props.size}px`,
|
||||||
|
width: `${this.props.size}px`,
|
||||||
|
height: `${this.props.size}px`,
|
||||||
|
lineHeight: `${this.props.size}px`
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a href='#' title={this.props.title} className={`icon-button ${this.props.active ? 'active' : ''}`} onClick={this.handleClick} style={{ display: 'inline-block', fontSize: `${this.props.size}px`, width: `${this.props.size}px`, height: `${this.props.size}px`, lineHeight: `${this.props.size}px`}}>
|
<a href='#' title={this.props.title} className={`icon-button ${this.props.active ? 'active' : ''}`} onClick={this.handleClick} style={style}>
|
||||||
<i className={`fa fa-fw fa-${this.props.icon}`}></i>
|
<i className={`fa fa-fw fa-${this.props.icon}`}></i>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,11 +2,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import Avatar from './avatar';
|
import Avatar from './avatar';
|
||||||
import RelativeTimestamp from './relative_timestamp';
|
import RelativeTimestamp from './relative_timestamp';
|
||||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||||
import IconButton from './icon_button';
|
|
||||||
import DisplayName from './display_name';
|
import DisplayName from './display_name';
|
||||||
import MediaGallery from './media_gallery';
|
import MediaGallery from './media_gallery';
|
||||||
import VideoPlayer from './video_player';
|
import VideoPlayer from './video_player';
|
||||||
import StatusContent from './status_content';
|
import StatusContent from './status_content';
|
||||||
|
import StatusActionBar from './status_action_bar';
|
||||||
|
|
||||||
const Status = React.createClass({
|
const Status = React.createClass({
|
||||||
|
|
||||||
|
@ -19,23 +19,13 @@ const Status = React.createClass({
|
||||||
wrapped: React.PropTypes.bool,
|
wrapped: React.PropTypes.bool,
|
||||||
onReply: React.PropTypes.func,
|
onReply: React.PropTypes.func,
|
||||||
onFavourite: React.PropTypes.func,
|
onFavourite: React.PropTypes.func,
|
||||||
onReblog: React.PropTypes.func
|
onReblog: React.PropTypes.func,
|
||||||
|
onDelete: React.PropTypes.func,
|
||||||
|
me: React.PropTypes.number
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [PureRenderMixin],
|
mixins: [PureRenderMixin],
|
||||||
|
|
||||||
handleReplyClick () {
|
|
||||||
this.props.onReply(this.props.status);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleFavouriteClick () {
|
|
||||||
this.props.onFavourite(this.props.status);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleReblogClick () {
|
|
||||||
this.props.onReblog(this.props.status);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleClick () {
|
handleClick () {
|
||||||
const { status } = this.props;
|
const { status } = this.props;
|
||||||
this.context.router.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
|
this.context.router.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
|
||||||
|
@ -96,11 +86,7 @@ const Status = React.createClass({
|
||||||
|
|
||||||
{media}
|
{media}
|
||||||
|
|
||||||
<div style={{ marginTop: '10px', overflow: 'hidden' }}>
|
<StatusActionBar {...this.props} />
|
||||||
<div style={{ float: 'left', marginRight: '10px'}}><IconButton title='Reply' icon='reply' onClick={this.handleReplyClick} /></div>
|
|
||||||
<div style={{ float: 'left', marginRight: '10px'}}><IconButton active={status.get('reblogged')} title='Reblog' icon='retweet' onClick={this.handleReblogClick} /></div>
|
|
||||||
<div style={{ float: 'left'}}><IconButton active={status.get('favourited')} title='Favourite' icon='star' onClick={this.handleFavouriteClick} /></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||||
|
import IconButton from './icon_button';
|
||||||
|
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
|
||||||
|
|
||||||
|
const StatusActionBar = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
status: ImmutablePropTypes.map.isRequired,
|
||||||
|
onReply: React.PropTypes.func,
|
||||||
|
onFavourite: React.PropTypes.func,
|
||||||
|
onReblog: React.PropTypes.func,
|
||||||
|
onDelete: React.PropTypes.func
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [PureRenderMixin],
|
||||||
|
|
||||||
|
handleReplyClick () {
|
||||||
|
this.props.onReply(this.props.status);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleFavouriteClick () {
|
||||||
|
this.props.onFavourite(this.props.status);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleReblogClick () {
|
||||||
|
this.props.onReblog(this.props.status);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleDeleteClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.props.onDelete(this.props.status);
|
||||||
|
},
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { status, me } = this.props;
|
||||||
|
let menu = '';
|
||||||
|
|
||||||
|
if (status.getIn(['account', 'id']) === me) {
|
||||||
|
menu = (
|
||||||
|
<ul>
|
||||||
|
<li><a href='#' onClick={this.handleDeleteClick}>Delete</a></li>
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginTop: '10px', overflow: 'hidden' }}>
|
||||||
|
<div style={{ float: 'left', marginRight: '18px'}}><IconButton title='Reply' icon='reply' onClick={this.handleReplyClick} /></div>
|
||||||
|
<div style={{ float: 'left', marginRight: '18px'}}><IconButton active={status.get('reblogged')} title='Reblog' icon='retweet' onClick={this.handleReblogClick} /></div>
|
||||||
|
<div style={{ float: 'left', marginRight: '18px'}}><IconButton active={status.get('favourited')} title='Favourite' icon='star' onClick={this.handleFavouriteClick} /></div>
|
||||||
|
|
||||||
|
<div onClick={e => e.stopPropagation()} style={{ width: '18px', height: '18px', float: 'left' }}>
|
||||||
|
<Dropdown>
|
||||||
|
<DropdownTrigger className='icon-button' style={{ fontSize: '18px', lineHeight: '18px', width: '18px', height: '18px' }}>
|
||||||
|
<i className='fa fa-fw fa-ellipsis-h' />
|
||||||
|
</DropdownTrigger>
|
||||||
|
|
||||||
|
<DropdownContent>{menu}</DropdownContent>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export default StatusActionBar;
|
|
@ -9,7 +9,9 @@ const StatusList = React.createClass({
|
||||||
onReply: React.PropTypes.func,
|
onReply: React.PropTypes.func,
|
||||||
onReblog: React.PropTypes.func,
|
onReblog: React.PropTypes.func,
|
||||||
onFavourite: React.PropTypes.func,
|
onFavourite: React.PropTypes.func,
|
||||||
onScrollToBottom: React.PropTypes.func
|
onDelete: React.PropTypes.func,
|
||||||
|
onScrollToBottom: React.PropTypes.func,
|
||||||
|
me: React.PropTypes.number
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [PureRenderMixin],
|
mixins: [PureRenderMixin],
|
||||||
|
@ -23,11 +25,13 @@ const StatusList = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
const { statuses, onScrollToBottom, ...other } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ overflowY: 'scroll', flex: '1 1 auto' }} className='scrollable' onScroll={this.handleScroll}>
|
<div style={{ overflowY: 'scroll', flex: '1 1 auto' }} className='scrollable' onScroll={this.handleScroll}>
|
||||||
<div>
|
<div>
|
||||||
{this.props.statuses.map((status) => {
|
{statuses.map((status) => {
|
||||||
return <Status key={status.get('id')} status={status} onReply={this.props.onReply} onReblog={this.props.onReblog} onFavourite={this.props.onFavourite} />;
|
return <Status key={status.get('id')} {...other} status={status} />;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
fetchAccountTimeline,
|
fetchAccountTimeline,
|
||||||
expandAccountTimeline
|
expandAccountTimeline
|
||||||
} from '../../actions/accounts';
|
} from '../../actions/accounts';
|
||||||
|
import { deleteStatus } from '../../actions/statuses';
|
||||||
import { replyCompose } from '../../actions/compose';
|
import { replyCompose } from '../../actions/compose';
|
||||||
import { favourite, reblog } from '../../actions/interactions';
|
import { favourite, reblog } from '../../actions/interactions';
|
||||||
import Header from './components/header';
|
import Header from './components/header';
|
||||||
|
@ -72,6 +73,10 @@ const Account = React.createClass({
|
||||||
this.props.dispatch(favourite(status));
|
this.props.dispatch(favourite(status));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleDelete (status) {
|
||||||
|
this.props.dispatch(deleteStatus(status.get('id')));
|
||||||
|
},
|
||||||
|
|
||||||
handleScrollToBottom () {
|
handleScrollToBottom () {
|
||||||
this.props.dispatch(expandAccountTimeline(this.props.account.get('id')));
|
this.props.dispatch(expandAccountTimeline(this.props.account.get('id')));
|
||||||
},
|
},
|
||||||
|
@ -87,7 +92,7 @@ const Account = React.createClass({
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', 'flex': '0 0 auto', height: '100%' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', 'flex': '0 0 auto', height: '100%' }}>
|
||||||
<Header account={account} />
|
<Header account={account} />
|
||||||
<ActionBar account={account} me={me} onFollow={this.handleFollow} onUnfollow={this.handleUnfollow} />
|
<ActionBar account={account} me={me} onFollow={this.handleFollow} onUnfollow={this.handleUnfollow} />
|
||||||
<StatusList statuses={statuses} onScrollToBottom={this.handleScrollToBottom} onReply={this.handleReply} onReblog={this.handleReblog} onFavourite={this.handleFavourite} />
|
<StatusList statuses={statuses} me={me} onScrollToBottom={this.handleScrollToBottom} onReply={this.handleReply} onReblog={this.handleReblog} onFavourite={this.handleFavourite} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,29 +4,35 @@ import { replyCompose } from '../../../actions/compose';
|
||||||
import { reblog, favourite } from '../../../actions/interactions';
|
import { reblog, favourite } from '../../../actions/interactions';
|
||||||
import { expandTimeline } from '../../../actions/timelines';
|
import { expandTimeline } from '../../../actions/timelines';
|
||||||
import { selectStatus } from '../../../reducers/timelines';
|
import { selectStatus } from '../../../reducers/timelines';
|
||||||
|
import { deleteStatus } from '../../../actions/statuses';
|
||||||
|
|
||||||
const mapStateToProps = function (state, props) {
|
const mapStateToProps = function (state, props) {
|
||||||
return {
|
return {
|
||||||
statuses: state.getIn(['timelines', props.type]).map(id => selectStatus(state, id))
|
statuses: state.getIn(['timelines', props.type]).map(id => selectStatus(state, id)),
|
||||||
|
me: state.getIn(['timelines', 'me'])
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = function (dispatch, props) {
|
const mapDispatchToProps = function (dispatch, props) {
|
||||||
return {
|
return {
|
||||||
onReply: function (status) {
|
onReply (status) {
|
||||||
dispatch(replyCompose(status));
|
dispatch(replyCompose(status));
|
||||||
},
|
},
|
||||||
|
|
||||||
onFavourite: function (status) {
|
onFavourite (status) {
|
||||||
dispatch(favourite(status));
|
dispatch(favourite(status));
|
||||||
},
|
},
|
||||||
|
|
||||||
onReblog: function (status) {
|
onReblog (status) {
|
||||||
dispatch(reblog(status));
|
dispatch(reblog(status));
|
||||||
},
|
},
|
||||||
|
|
||||||
onScrollToBottom: function () {
|
onScrollToBottom () {
|
||||||
dispatch(expandTimeline(props.type));
|
dispatch(expandTimeline(props.type));
|
||||||
|
},
|
||||||
|
|
||||||
|
onDelete (status) {
|
||||||
|
dispatch(deleteStatus(status.get('id')));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,10 @@ import {
|
||||||
ACCOUNT_TIMELINE_FETCH_FAIL,
|
ACCOUNT_TIMELINE_FETCH_FAIL,
|
||||||
ACCOUNT_TIMELINE_EXPAND_FAIL
|
ACCOUNT_TIMELINE_EXPAND_FAIL
|
||||||
} from '../actions/accounts';
|
} from '../actions/accounts';
|
||||||
import { STATUS_FETCH_FAIL } from '../actions/statuses';
|
import {
|
||||||
|
STATUS_FETCH_FAIL,
|
||||||
|
STATUS_DELETE_FAIL
|
||||||
|
} from '../actions/statuses';
|
||||||
import Immutable from 'immutable';
|
import Immutable from 'immutable';
|
||||||
|
|
||||||
const initialState = Immutable.List();
|
const initialState = Immutable.List();
|
||||||
|
@ -51,6 +54,7 @@ export default function notifications(state = initialState, action) {
|
||||||
case ACCOUNT_TIMELINE_FETCH_FAIL:
|
case ACCOUNT_TIMELINE_FETCH_FAIL:
|
||||||
case ACCOUNT_TIMELINE_EXPAND_FAIL:
|
case ACCOUNT_TIMELINE_EXPAND_FAIL:
|
||||||
case STATUS_FETCH_FAIL:
|
case STATUS_FETCH_FAIL:
|
||||||
|
case STATUS_DELETE_FAIL:
|
||||||
return notificationFromError(state, action.error);
|
return notificationFromError(state, action.error);
|
||||||
case NOTIFICATION_DISMISS:
|
case NOTIFICATION_DISMISS:
|
||||||
return state.filterNot(item => item.get('key') === action.notification.key);
|
return state.filterNot(item => item.get('key') === action.notification.key);
|
||||||
|
|
|
@ -16,7 +16,10 @@ import {
|
||||||
ACCOUNT_TIMELINE_FETCH_SUCCESS,
|
ACCOUNT_TIMELINE_FETCH_SUCCESS,
|
||||||
ACCOUNT_TIMELINE_EXPAND_SUCCESS
|
ACCOUNT_TIMELINE_EXPAND_SUCCESS
|
||||||
} from '../actions/accounts';
|
} from '../actions/accounts';
|
||||||
import { STATUS_FETCH_SUCCESS } from '../actions/statuses';
|
import {
|
||||||
|
STATUS_FETCH_SUCCESS,
|
||||||
|
STATUS_DELETE_SUCCESS
|
||||||
|
} from '../actions/statuses';
|
||||||
import { FOLLOW_SUBMIT_SUCCESS } from '../actions/follow';
|
import { FOLLOW_SUBMIT_SUCCESS } from '../actions/follow';
|
||||||
import Immutable from 'immutable';
|
import Immutable from 'immutable';
|
||||||
|
|
||||||
|
@ -142,10 +145,28 @@ function updateTimeline(state, timeline, status) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function deleteStatus(state, id) {
|
function deleteStatus(state, id) {
|
||||||
|
const status = state.getIn(['statuses', id]);
|
||||||
|
|
||||||
|
if (!status) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove references from timelines
|
||||||
['home', 'mentions'].forEach(function (timeline) {
|
['home', 'mentions'].forEach(function (timeline) {
|
||||||
state = state.update(timeline, list => list.filterNot(item => item === id));
|
state = state.update(timeline, list => list.filterNot(item => item === id));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Remove references from account timelines
|
||||||
|
state = state.updateIn(['accounts_timelines', status.get('account')], Immutable.List(), list => list.filterNot(item => item === id));
|
||||||
|
|
||||||
|
// Remove reblogs of deleted status
|
||||||
|
const references = state.get('statuses').filter(item => item.get('reblog') === id);
|
||||||
|
|
||||||
|
references.forEach(referencingId => {
|
||||||
|
state = deleteStatus(state, referencingId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove normalized status
|
||||||
return state.deleteIn(['statuses', id]);
|
return state.deleteIn(['statuses', id]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -153,7 +174,7 @@ function normalizeAccount(state, account, relationship) {
|
||||||
if (relationship) {
|
if (relationship) {
|
||||||
state = normalizeRelationship(state, relationship);
|
state = normalizeRelationship(state, relationship);
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.setIn(['accounts', account.get('id')], account);
|
return state.setIn(['accounts', account.get('id')], account);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -194,6 +215,7 @@ export default function timelines(state = initialState, action) {
|
||||||
case TIMELINE_UPDATE:
|
case TIMELINE_UPDATE:
|
||||||
return updateTimeline(state, action.timeline, Immutable.fromJS(action.status));
|
return updateTimeline(state, action.timeline, Immutable.fromJS(action.status));
|
||||||
case TIMELINE_DELETE:
|
case TIMELINE_DELETE:
|
||||||
|
case STATUS_DELETE_SUCCESS:
|
||||||
return deleteStatus(state, action.id);
|
return deleteStatus(state, action.id);
|
||||||
case REBLOG_SUCCESS:
|
case REBLOG_SUCCESS:
|
||||||
case FAVOURITE_SUCCESS:
|
case FAVOURITE_SUCCESS:
|
||||||
|
|
|
@ -156,3 +156,64 @@
|
||||||
.transparent-background {
|
.transparent-background {
|
||||||
background: image-url('void.png');
|
background: image-url('void.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown__content {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown--active .dropdown__content {
|
||||||
|
display: block;
|
||||||
|
z-index: 9999;
|
||||||
|
box-shadow: 0 0 15px rgba(0, 0, 0, 0.4);
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0 4.5px 7.8px 4.5px;
|
||||||
|
border-color: transparent transparent #d9e1e8 transparent;
|
||||||
|
top: -7px;
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
&:first-child a {
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child a {
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child:last-child a {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: 13px;
|
||||||
|
display: block;
|
||||||
|
padding: 6px 16px;
|
||||||
|
width: 120px;
|
||||||
|
text-decoration: none;
|
||||||
|
background: #d9e1e8;
|
||||||
|
color: #282c37;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #2b90d9;
|
||||||
|
color: #d9e1e8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class Api::V1::AppsController < ApplicationController
|
class Api::V1::AppsController < ApiController
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
!!! 5
|
!!! 5
|
||||||
%html
|
%html
|
||||||
%head
|
%head
|
||||||
%meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
|
%meta{:content => 'text/html; charset=UTF-8', 'http-equiv' => 'Content-Type'}/
|
||||||
|
%meta{:charset => 'utf-8'}/
|
||||||
|
%meta{:name => 'viewport', :content => 'width=device-width, initial-scale=1'}/
|
||||||
|
%meta{'http-equiv' => 'X-UA-Compatible', :content => 'IE=edge'}/
|
||||||
|
|
||||||
%title
|
%title
|
||||||
= "#{yield(:page_title)} - " if content_for?(:page_title)
|
= "#{yield(:page_title)} - " if content_for?(:page_title)
|
||||||
Mastodon
|
Mastodon
|
||||||
|
|
||||||
= stylesheet_link_tag 'application', media: 'all'
|
= stylesheet_link_tag 'application', media: 'all'
|
||||||
= csrf_meta_tags
|
= csrf_meta_tags
|
||||||
|
|
||||||
= yield :header_tags
|
= yield :header_tags
|
||||||
|
|
||||||
%body{ class: @body_classes }
|
%body{ class: @body_classes }
|
||||||
= content_for?(:content) ? yield(:content) : yield
|
= content_for?(:content) ? yield(:content) : yield
|
||||||
|
|
Loading…
Reference in a new issue