[New] Implement a feature of quote
This commit is contained in:
parent
0c24c865b7
commit
c77947f15a
16 changed files with 231 additions and 10 deletions
|
@ -21,6 +21,8 @@ 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_DIRECT = 'COMPOSE_DIRECT';
|
||||
export const COMPOSE_QUOTE = 'COMPOSE_QUOTE';
|
||||
export const COMPOSE_QUOTE_CANCEL = 'COMPOSE_QUOTE_CANCEL';
|
||||
export const COMPOSE_MENTION = 'COMPOSE_MENTION';
|
||||
export const COMPOSE_RESET = 'COMPOSE_RESET';
|
||||
export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST';
|
||||
|
@ -106,6 +108,25 @@ export function cancelReplyCompose() {
|
|||
};
|
||||
};
|
||||
|
||||
export function quoteCompose(status, router) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: COMPOSE_QUOTE,
|
||||
status: status,
|
||||
});
|
||||
|
||||
if (!getState().getIn(['compose', 'mounted'])) {
|
||||
router.push('/statuses/new');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export function cancelQuoteCompose() {
|
||||
return {
|
||||
type: COMPOSE_QUOTE_CANCEL,
|
||||
};
|
||||
};
|
||||
|
||||
export function resetCompose() {
|
||||
return {
|
||||
type: COMPOSE_RESET,
|
||||
|
@ -136,13 +157,22 @@ export function directCompose(account, routerHistory) {
|
|||
|
||||
export function submitCompose(routerHistory) {
|
||||
return function (dispatch, getState) {
|
||||
const status = getState().getIn(['compose', 'text'], '');
|
||||
let status = getState().getIn(['compose', 'text'], '');
|
||||
const media = getState().getIn(['compose', 'media_attachments']);
|
||||
const quoteId = getState().getIn(['compose', 'quote_from'], null);
|
||||
|
||||
if ((!status || !status.length) && media.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (quoteId) {
|
||||
status = [
|
||||
status,
|
||||
"~~~~~~~~~~",
|
||||
`[${quoteId}][${getState().getIn(['compose', 'quote_from_uri'], null)}]`
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
dispatch(submitComposeRequest());
|
||||
|
||||
api(getState).post('/api/v1/statuses', {
|
||||
|
|
|
@ -24,6 +24,7 @@ const messages = defineMessages({
|
|||
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
|
||||
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
|
||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||
quote: { id: 'status.quote', defaultMessage: 'Quote' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||
removeBookmark: { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' },
|
||||
|
@ -61,6 +62,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
onReply: PropTypes.func,
|
||||
onFavourite: PropTypes.func,
|
||||
onReblog: PropTypes.func,
|
||||
onQuote: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
onDirect: PropTypes.func,
|
||||
onMention: PropTypes.func,
|
||||
|
@ -129,6 +131,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
this.props.onBookmark(this.props.status);
|
||||
}
|
||||
|
||||
handleQuoteClick = () => {
|
||||
this.props.onQuote(this.props.status, this.context.router.history);
|
||||
}
|
||||
|
||||
handleDeleteClick = () => {
|
||||
this.props.onDelete(this.props.status, this.context.router.history);
|
||||
}
|
||||
|
@ -326,6 +332,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
<IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount />
|
||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} />
|
||||
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
||||
<IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.quote)} icon='quote-right' onClick={this.handleQuoteClick} />
|
||||
|
||||
{shareButton}
|
||||
|
||||
|
|
|
@ -38,6 +38,8 @@ export default class StatusContent extends React.PureComponent {
|
|||
}
|
||||
|
||||
const links = node.querySelectorAll('a');
|
||||
const QuoteUrlFormat = /(?:https?|ftp):\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+\/users\/[\w-_]+(\/statuses\/\w+)/;
|
||||
const quote = node.innerText.match(new RegExp(`\\[(\\w+)\\]\\[${QuoteUrlFormat.source}\\]`));
|
||||
|
||||
for (var i = 0; i < links.length; ++i) {
|
||||
let link = links[i];
|
||||
|
@ -46,6 +48,12 @@ export default class StatusContent extends React.PureComponent {
|
|||
}
|
||||
link.classList.add('status-link');
|
||||
|
||||
if (quote) {
|
||||
if (link.href.match(QuoteUrlFormat)) {
|
||||
link.addEventListener('click', this.onQuoteClick.bind(this, quote[1]), false);
|
||||
}
|
||||
}
|
||||
|
||||
let mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));
|
||||
|
||||
if (mention) {
|
||||
|
@ -125,6 +133,15 @@ export default class StatusContent extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
onQuoteClick = (statusId, e) => {
|
||||
let statusUrl = `/statuses/${statusId}`;
|
||||
|
||||
if (this.context.router && e.button === 0) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(statusUrl);
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseDown = (e) => {
|
||||
this.startXY = [e.clientX, e.clientY];
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import Status from '../components/status';
|
|||
import { makeGetStatus, makeGetPictureInPicture } from '../selectors';
|
||||
import {
|
||||
replyCompose,
|
||||
quoteCompose,
|
||||
mentionCompose,
|
||||
directCompose,
|
||||
} from '../actions/compose';
|
||||
|
@ -99,6 +100,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
}
|
||||
},
|
||||
|
||||
onQuote (status, router) {
|
||||
dispatch(quoteCompose(status, router));
|
||||
},
|
||||
|
||||
onFavourite (status) {
|
||||
if (status.get('favourited')) {
|
||||
dispatch(unfavourite(status));
|
||||
|
|
|
@ -4,6 +4,7 @@ import Button from '../../../components/button';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
|
||||
import QuoteIndicatorContainer from '../containers/quote_indicator_container';
|
||||
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
||||
import AutosuggestInput from '../../../components/autosuggest_input';
|
||||
import PollButtonContainer from '../containers/poll_button_container';
|
||||
|
@ -209,6 +210,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
<WarningContainer />
|
||||
|
||||
<ReplyIndicatorContainer />
|
||||
<QuoteIndicatorContainer />
|
||||
|
||||
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`} ref={this.setRef}>
|
||||
<AutosuggestInput
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import IconButton from '../../../components/icon_button';
|
||||
import DisplayName from '../../../components/display_name';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { isRtl } from '../../../rtl';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel: { id: 'quote_indicator.cancel', defaultMessage: 'Cancel' },
|
||||
});
|
||||
|
||||
@injectIntl
|
||||
export default class QuoteIndicator extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onCancel();
|
||||
}
|
||||
|
||||
handleAccountClick = (e) => {
|
||||
if (e.button === 0) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { status, intl } = this.props;
|
||||
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = { __html: status.get('contentHtml') };
|
||||
const style = {
|
||||
direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='quote-indicator'>
|
||||
<div className='quote-indicator__header'>
|
||||
<div className='quote-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} /></div>
|
||||
|
||||
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='quote-indicator__display-name'>
|
||||
<div className='quote-indicator__display-avatar'><Avatar account={status.get('account')} size={24} /></div>
|
||||
<DisplayName account={status.get('account')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className='quote-indicator__content' style={style} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { cancelQuoteCompose } from '../../../actions/compose';
|
||||
import { makeGetStatus } from '../../../selectors';
|
||||
import QuoteIndicator from '../components/quote_indicator';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getStatus = makeGetStatus();
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
status: getStatus(state, state.getIn(['compose', 'quote_from'])),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onCancel () {
|
||||
dispatch(cancelQuoteCompose());
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(QuoteIndicator);
|
|
@ -18,6 +18,7 @@ const messages = defineMessages({
|
|||
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
|
||||
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
|
||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||
quote: { id: 'status.quote', defaultMessage: 'Quote' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||
more: { id: 'status.more', defaultMessage: 'More' },
|
||||
|
@ -56,6 +57,7 @@ class ActionBar extends React.PureComponent {
|
|||
relationship: ImmutablePropTypes.map,
|
||||
onReply: PropTypes.func.isRequired,
|
||||
onReblog: PropTypes.func.isRequired,
|
||||
onQuote: PropTypes.func.isRequired,
|
||||
onFavourite: PropTypes.func.isRequired,
|
||||
onBookmark: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
|
@ -82,6 +84,10 @@ class ActionBar extends React.PureComponent {
|
|||
this.props.onReblog(this.props.status, e);
|
||||
}
|
||||
|
||||
handleQuoteClick = () => {
|
||||
this.props.onQuote(this.props.status, this.context.router.history);
|
||||
}
|
||||
|
||||
handleFavouriteClick = () => {
|
||||
this.props.onFavourite(this.props.status);
|
||||
}
|
||||
|
@ -277,6 +283,7 @@ class ActionBar extends React.PureComponent {
|
|||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
|
||||
<div className='detailed-status__button' ><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton disabled={reblog_disabled} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.quote)} icon='quote-right' onClick={this.handleQuoteClick} /></div>
|
||||
{shareButton}
|
||||
<div className='detailed-status__button'><IconButton className='bookmark-icon' active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
|
||||
|
||||
|
|
|
@ -60,6 +60,10 @@ const addAutoPlay = html => {
|
|||
|
||||
export default class Card extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
card: ImmutablePropTypes.map,
|
||||
maxDescription: PropTypes.number,
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
} from '../../actions/interactions';
|
||||
import {
|
||||
replyCompose,
|
||||
quoteCompose,
|
||||
mentionCompose,
|
||||
directCompose,
|
||||
} from '../../actions/compose';
|
||||
|
@ -259,6 +260,10 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleQuoteClick = (status) => {
|
||||
this.props.dispatch(quoteCompose(status, this.context.router.history));
|
||||
}
|
||||
|
||||
handleDeleteClick = (status, history, withRedraft = false) => {
|
||||
const { dispatch, intl } = this.props;
|
||||
|
||||
|
@ -566,6 +571,7 @@ class Status extends ImmutablePureComponent {
|
|||
onFavourite={this.handleFavouriteClick}
|
||||
onReblog={this.handleReblogClick}
|
||||
onBookmark={this.handleBookmarkClick}
|
||||
onQuote={this.handleQuoteClick}
|
||||
onDelete={this.handleDeleteClick}
|
||||
onDirect={this.handleDirectClick}
|
||||
onMention={this.handleMentionClick}
|
||||
|
|
|
@ -449,6 +449,10 @@
|
|||
"defaultMessage": "This post cannot be boosted",
|
||||
"id": "status.cannot_reblog"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Quote",
|
||||
"id": "status.quote"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Favourite",
|
||||
"id": "status.favourite"
|
||||
|
@ -1229,6 +1233,15 @@
|
|||
],
|
||||
"path": "app/javascript/mastodon/features/compose/components/privacy_dropdown.json"
|
||||
},
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
"defaultMessage": "Cancel",
|
||||
"id": "quote_indicator.cancel"
|
||||
}
|
||||
],
|
||||
"path": "app/javascript/mastodon/features/compose/components/quote_indicator.json"
|
||||
},
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
|
|
|
@ -350,6 +350,7 @@
|
|||
"privacy.public.short": "Public",
|
||||
"privacy.unlisted.long": "Visible for all, but not in public timelines",
|
||||
"privacy.unlisted.short": "Unlisted",
|
||||
"quote_indicator.cancel": "Cancel",
|
||||
"refresh": "Refresh",
|
||||
"regeneration_indicator.label": "Loading…",
|
||||
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
|
||||
|
@ -401,6 +402,7 @@
|
|||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned post",
|
||||
"status.read_more": "Read more",
|
||||
"status.quote": "Quote",
|
||||
"status.reblog": "Boost",
|
||||
"status.reblog_private": "Boost with original visibility",
|
||||
"status.reblogged_by": "{name} boosted",
|
||||
|
|
|
@ -350,6 +350,7 @@
|
|||
"privacy.public.short": "公開",
|
||||
"privacy.unlisted.long": "誰でも閲覧可、公開TLに非表示",
|
||||
"privacy.unlisted.short": "未収載",
|
||||
"quote_indicator.cancel": "キャンセル",
|
||||
"refresh": "更新",
|
||||
"regeneration_indicator.label": "読み込み中…",
|
||||
"regeneration_indicator.sublabel": "ホームタイムラインは準備中です!",
|
||||
|
@ -401,6 +402,7 @@
|
|||
"status.pin": "プロフィールに固定表示",
|
||||
"status.pinned": "固定された投稿",
|
||||
"status.read_more": "もっと見る",
|
||||
"status.quote": "引用ブースト",
|
||||
"status.reblog": "ブースト",
|
||||
"status.reblog_private": "ブースト",
|
||||
"status.reblogged_by": "{name}さんがブースト",
|
||||
|
|
|
@ -5,6 +5,8 @@ import {
|
|||
COMPOSE_REPLY,
|
||||
COMPOSE_REPLY_CANCEL,
|
||||
COMPOSE_DIRECT,
|
||||
COMPOSE_QUOTE,
|
||||
COMPOSE_QUOTE_CANCEL,
|
||||
COMPOSE_MENTION,
|
||||
COMPOSE_SUBMIT_REQUEST,
|
||||
COMPOSE_SUBMIT_SUCCESS,
|
||||
|
@ -62,6 +64,8 @@ const initialState = ImmutableMap({
|
|||
caretPosition: null,
|
||||
preselectDate: null,
|
||||
in_reply_to: null,
|
||||
quote_from: null,
|
||||
quote_from_uri: null,
|
||||
is_composing: false,
|
||||
is_submitting: false,
|
||||
is_changing_upload: false,
|
||||
|
@ -112,6 +116,7 @@ function clearAll(state) {
|
|||
map.set('is_submitting', false);
|
||||
map.set('is_changing_upload', false);
|
||||
map.set('in_reply_to', null);
|
||||
map.set('quote_from', null);
|
||||
map.set('privacy', state.get('default_privacy'));
|
||||
map.set('sensitive', false);
|
||||
map.update('media_attachments', list => list.clear());
|
||||
|
@ -302,6 +307,8 @@ export default function compose(state = initialState, action) {
|
|||
case COMPOSE_REPLY:
|
||||
return state.withMutations(map => {
|
||||
map.set('in_reply_to', action.status.get('id'));
|
||||
map.set('quote_from', null);
|
||||
map.set('quote_from_uri', null);
|
||||
map.set('text', statusToTextMentions(state, action.status));
|
||||
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
|
||||
map.set('focusDate', new Date());
|
||||
|
@ -317,10 +324,24 @@ export default function compose(state = initialState, action) {
|
|||
map.set('spoiler_text', '');
|
||||
}
|
||||
});
|
||||
case COMPOSE_QUOTE:
|
||||
return state.withMutations(map => {
|
||||
map.set('in_reply_to', null);
|
||||
map.set('quote_from', action.status.get('id'));
|
||||
map.set('quote_from_uri', action.status.get('uri'));
|
||||
map.set('text', '');
|
||||
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
|
||||
map.set('focusDate', new Date());
|
||||
map.set('preselectDate', new Date());
|
||||
map.set('idempotencyKey', uuid());
|
||||
});
|
||||
case COMPOSE_REPLY_CANCEL:
|
||||
case COMPOSE_QUOTE_CANCEL:
|
||||
case COMPOSE_RESET:
|
||||
return state.withMutations(map => {
|
||||
map.set('in_reply_to', null);
|
||||
map.set('quote_from', null);
|
||||
map.set('quote_from_uri', null);
|
||||
map.set('text', '');
|
||||
map.set('spoiler', false);
|
||||
map.set('spoiler_text', '');
|
||||
|
|
|
@ -761,26 +761,37 @@
|
|||
}
|
||||
|
||||
.reply-indicator {
|
||||
background: $ui-primary-color;
|
||||
}
|
||||
|
||||
.quote-indicator {
|
||||
background: $success-green;
|
||||
}
|
||||
|
||||
.reply-indicator,
|
||||
.quote-indicator {
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
background: $ui-primary-color;
|
||||
padding: 10px;
|
||||
min-height: 23px;
|
||||
overflow-y: auto;
|
||||
flex: 0 2 auto;
|
||||
}
|
||||
|
||||
.reply-indicator__header {
|
||||
.reply-indicator__header,
|
||||
.quote-indicator__header {
|
||||
margin-bottom: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.reply-indicator__cancel {
|
||||
.reply-indicator__cancel,
|
||||
.quote-indicator__cancel {
|
||||
float: right;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.reply-indicator__display-name {
|
||||
.reply-indicator__display-name,
|
||||
.quote-indicator__display-name {
|
||||
color: $inverted-text-color;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
|
@ -790,7 +801,8 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
.reply-indicator__display-avatar {
|
||||
.reply-indicator__display-avatar,
|
||||
.quote-indicator__display-avatar {
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
@ -804,7 +816,8 @@
|
|||
}
|
||||
|
||||
.status__content,
|
||||
.reply-indicator__content {
|
||||
.reply-indicator__content,
|
||||
.quote-indicator__content {
|
||||
position: relative;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
|
@ -1254,7 +1267,8 @@
|
|||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.reply-indicator__content {
|
||||
.reply-indicator__content,
|
||||
.quote-indicator__content {
|
||||
color: $inverted-text-color;
|
||||
font-size: 14px;
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ class FetchLinkCardService < BaseService
|
|||
|
||||
def bad_url?(uri)
|
||||
# Avoid local instance URLs and invalid URLs
|
||||
uri.host.blank? || TagManager.instance.local_url?(uri.to_s) || !%w(http https).include?(uri.scheme)
|
||||
uri.host.blank? || (TagManager.instance.local_url?(uri.to_s) && uri.to_s !~ %r(/users/[\w_-]+/statuses/\w+)) || !%w(http https).include?(uri.scheme)
|
||||
end
|
||||
|
||||
# rubocop:disable Naming/MethodParameterName
|
||||
|
@ -132,7 +132,7 @@ class FetchLinkCardService < BaseService
|
|||
# Most providers rely on <script> tags, which is a no-no
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
@card.save_with_optional_image!
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue