Merge branch 'develop' of https://code.gab.com/gab/social/gab-social into develop

This commit is contained in:
Rob Colbert 2019-08-28 02:22:15 -04:00
commit 81e8b3294f
13 changed files with 144 additions and 73 deletions

View File

@ -19,10 +19,10 @@ We have diverged from Mastodon in several ways in pursuit of our own goals.
1. Quote posting
## BTCPay
In order to make BTC flow work, 3 enviornment variables need to be set:
In order to make BTC flow work, 3 environment variables need to be set:
- `BTCPAY_LEGACY_TOKEN`: So called Legacy Tokens can be found in https://btcpay.xxx.com/stores/yyy/Tokens
- `BTCPAY_PUB_KEY`: Public key that is used when creating an access token or pairing https://btcpay.xxx.com/stores/yyy/Tokens/Create
- `BTCPAY_LEGACY_TOKEN`: So called Legacy Tokens can be found in https://btcpay.[yourdomain].com/stores/[yourstore]/Tokens
- `BTCPAY_PUB_KEY`: Public key that is used when creating an access token or pairing https://btcpay.[yourdomain].com/stores/[yourstore]/Tokens/Create
- `BTCPAY_MERCHANT_TOKEN`: Token created for facade *merchant*
## Deployment

View File

@ -137,6 +137,32 @@ export function directCompose(account, routerHistory) {
};
};
export function handleComposeSubmit(dispatch, getState, response, status) {
if (!dispatch || !getState) return;
dispatch(insertIntoTagHistory(response.data.tags, status));
dispatch(submitComposeSuccess({ ...response.data }));
// To make the app more responsive, immediately push the status into the columns
const insertIfOnline = timelineId => {
const timeline = getState().getIn(['timelines', timelineId]);
if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) {
let dequeueArgs = {};
if (timelineId === 'community') dequeueArgs.onlyMedia = getState().getIn(['settings', 'community', 'other', 'onlyMedia']);
dispatch(dequeueTimeline(timelineId, null, dequeueArgs));
dispatch(updateTimeline(timelineId, { ...response.data }));
}
};
if (response.data.visibility !== 'direct') {
insertIfOnline('home');
} else if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
insertIfOnline('community');
insertIfOnline('public');
}
}
export function submitCompose(routerHistory, group) {
return function (dispatch, getState) {
if (!me) return;
@ -175,33 +201,7 @@ export function submitCompose(routerHistory, group) {
if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
routerHistory.push('/messages');
}
dispatch(insertIntoTagHistory(response.data.tags, status));
dispatch(submitComposeSuccess({ ...response.data }));
// To make the app more responsive, immediately push the status
// into the columns
const insertIfOnline = timelineId => {
const timeline = getState().getIn(['timelines', timelineId]);
if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) {
let dequeueArgs = {};
if (timelineId === 'community') dequeueArgs.onlyMedia = getState().getIn(['settings', 'community', 'other', 'onlyMedia']),
dispatch(dequeueTimeline(timelineId, null, dequeueArgs));
dispatch(updateTimeline(timelineId, { ...response.data }));
}
};
if (response.data.visibility !== 'direct') {
insertIfOnline('home');
}
if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
insertIfOnline('community');
insertIfOnline('public');
}
handleComposeSubmit(dispatch, getState, response, status);
}).catch(function (error) {
dispatch(submitComposeFail(error));
});

View File

@ -10,6 +10,7 @@ import { updateNotificationsQueue, expandNotifications } from './notifications';
import { updateConversations } from './conversations';
import { fetchFilters } from './filters';
import { getLocale } from '../locales';
import { handleComposeSubmit } from './compose';
const { messages } = getLocale();
@ -61,3 +62,18 @@ export const connectHashtagStream = (id, tag, accept) => connectTimelineStream
export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);
export const connectGroupStream = id => connectTimelineStream(`group:${id}`, `group&group=${id}`);
export const connectStatusUpdateStream = () => {
return connectStream('statuscard', null, (dispatch, getState) => {
return {
onConnect() {},
onDisconnect() {},
onReceive (data) {
if (!data['event'] || !data['payload']) return;
if (data.event === 'update') {
handleComposeSubmit(dispatch, getState, {data: JSON.parse(data.payload)}, null)
}
},
};
});
}

View File

@ -44,9 +44,9 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
return (
<div className='extended-video-player'>
<video
playsInline
ref={this.setRef}
src={src}
autoPlay
role='button'
tabIndex='0'
aria-label={alt}

View File

@ -189,6 +189,7 @@ class Item extends React.PureComponent {
autoPlay={autoPlay}
loop
muted
playsInline
/>
<span className='media-gallery__gifv__label'>GIF</span>

View File

@ -380,7 +380,6 @@ class Status extends ImmutablePureComponent {
<Card
onOpenMedia={this.props.onOpenMedia}
card={status.get('card')}
compact
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
/>

View File

@ -11,7 +11,10 @@ import UI from '../features/ui';
import Introduction from '../features/introduction';
import { fetchCustomEmojis } from '../actions/custom_emojis';
import { hydrateStore } from '../actions/store';
import { connectUserStream } from '../actions/streaming';
import {
connectUserStream,
connectStatusUpdateStream,
} from '../actions/streaming';
import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from '../locales';
import initialState from '../initial_state';
@ -70,6 +73,7 @@ export default class GabSocial extends React.PureComponent {
componentDidMount() {
this.disconnect = store.dispatch(connectUserStream());
store.dispatch(connectStatusUpdateStream());
}
componentWillUnmount () {

View File

@ -127,6 +127,7 @@ export default class MediaItem extends ImmutablePureComponent {
autoPlay={autoPlay}
loop
muted
playsInline
/>
<span className='media-gallery__gifv__label'>GIF</span>

View File

@ -58,16 +58,12 @@ export default class Card extends React.PureComponent {
static propTypes = {
card: ImmutablePropTypes.map,
maxDescription: PropTypes.number,
onOpenMedia: PropTypes.func.isRequired,
compact: PropTypes.bool,
defaultWidth: PropTypes.number,
cacheWidth: PropTypes.func,
};
static defaultProps = {
maxDescription: 50,
compact: false,
};
state = {
@ -131,37 +127,52 @@ export default class Card extends React.PureComponent {
ref={this.setRef}
className='status-card__image status-card-video'
dangerouslySetInnerHTML={content}
style={{ height }}
style={{
height,
paddingBottom: 0,
}}
/>
);
}
render () {
const { card, maxDescription, compact } = this.props;
const { card } = this.props;
const { width, embedded } = this.state;
if (card === null) {
return null;
}
const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
const horizontal = (!compact && card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded;
const maxDescription = 150;
const cardImg = card.get('image');
const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
const horizontal = (card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded;
const interactive = card.get('type') !== 'link';
const className = classnames('status-card', { horizontal, compact, interactive });
const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
const ratio = card.get('width') / card.get('height');
const height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
const className = classnames('status-card', {
horizontal,
interactive,
compact: !cardImg,
});
const title = interactive ?
<a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener' target='_blank'>
<strong>{card.get('title')}</strong>
</a>
: <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
const description = (
<div className='status-card__content'>
{title}
{!(horizontal || compact) && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>}
<span className='status-card__host'>{provider}</span>
{!horizontal && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>}
<span className='status-card__host'>
<Icon id='link' fixedWidth />
{' '}
{provider}
</span>
</div>
);
let embed = '';
let thumbnail = <div style={{ backgroundImage: `url(${card.get('image')})`, width: horizontal ? width : null, height: horizontal ? height : null }} className='status-card__image-image' />;
let embed = '';
let thumbnail = <div style={{ backgroundImage: `url(${cardImg})` }} className='status-card__image-image' />;
if (interactive) {
if (embedded) {
@ -176,7 +187,6 @@ export default class Card extends React.PureComponent {
embed = (
<div className='status-card__image'>
{thumbnail}
<div className='status-card__actions'>
<div>
<button onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
@ -190,10 +200,10 @@ export default class Card extends React.PureComponent {
return (
<div className={className} ref={this.setRef}>
{embed}
{!compact && description}
{description}
</div>
);
} else if (card.get('image')) {
} else if (cardImg) {
embed = (
<div className='status-card__image'>
{thumbnail}

View File

@ -344,6 +344,7 @@ class Video extends React.PureComponent {
}
handleProgress = () => {
if (!this.video.buffered) return;
if (this.video.buffered.length > 0) {
this.setState({ buffer: this.video.buffered.end(0) / this.video.duration * 100 });
}
@ -432,6 +433,7 @@ class Video extends React.PureComponent {
<canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': revealed })} />
{revealed && <video
playsInline
ref={this.setVideoRef}
src={src}
poster={preview}

View File

@ -2192,13 +2192,14 @@ a.account__display-name {
.status-card {
display: flex;
font-size: 14px;
border: 1px solid lighten($ui-base-color, 8%);
font-size: 15px;
border: 1px solid lighten($ui-base-color, 12%);
border-radius: 4px;
color: $dark-text-color;
margin-top: 14px;
text-decoration: none;
overflow: hidden;
flex-direction: column;
&__actions {
bottom: 0;
@ -2274,7 +2275,7 @@ a.status-card {
display: block;
font-weight: 500;
margin-bottom: 5px;
color: $darker-text-color;
color: $primary-text-color;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@ -2284,26 +2285,33 @@ a.status-card {
.status-card__content {
flex: 1 1 auto;
overflow: hidden;
padding: 14px 14px 14px 8px;
padding: 12px 10px;
border-top: 1px solid lighten($ui-base-color, 12%);
}
.status-card__description {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
max-height: 40px;
overflow: hidden;
color: $darker-text-color;
}
.status-card__host {
display: block;
margin-top: 5px;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: $darker-text-color;
}
.status-card__image {
flex: 0 0 100px;
background: lighten($ui-base-color, 8%);
display: block;
padding-bottom: 52.25%;
position: relative;
background: lighten($ui-base-color, 8%);
& > .fa {
font-size: 21px;
@ -2332,7 +2340,7 @@ a.status-card {
}
.status-card.compact {
border-color: lighten($ui-base-color, 4%);
flex-direction: row;
&.interactive {
border: 0;
@ -2348,7 +2356,8 @@ a.status-card {
}
.status-card__image {
flex: 0 0 60px;
flex: 0 0 94px;
padding-bottom: 0;
}
}
@ -2357,11 +2366,13 @@ a.status-card.compact:hover {
}
.status-card__image-image {
border-radius: 4px 0 0 4px;
display: block;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
margin: 0;
width: 100%;
height: 100%;
object-fit: cover;
background-size: cover;
background-position: center center;

View File

@ -15,7 +15,15 @@ class FetchLinkCardService < BaseService
@status = status
@url = parse_urls
return if @url.nil? || @status.preview_cards.any?
if @status.preview_cards.any?
if @url.nil?
detach_card
return
end
return if @status.preview_cards.first.url == @url
end
return if @url.nil?
@url = @url.to_s
@ -39,12 +47,6 @@ class FetchLinkCardService < BaseService
def process_url
@card ||= PreviewCard.new(url: @url)
failed = Request.new(:head, @url).perform do |res|
res.code != 405 && res.code != 501 && (res.code != 200 || res.mime_type != 'text/html')
end
return if failed
Request.new(:get, @url).perform do |res|
if res.code == 200 && res.mime_type == 'text/html'
@html = res.body_with_limit
@ -55,13 +57,23 @@ class FetchLinkCardService < BaseService
end
end
return if @html.nil?
if @html.nil?
detach_card
return
end
attempt_oembed || attempt_opengraph
end
def attach_card
@status.preview_cards << @card
@status.preview_cards = [@card]
send_status_update_payload(@status)
Rails.cache.delete(@status)
end
def detach_card
@status.preview_cards = []
send_status_update_payload(@status)
Rails.cache.delete(@status)
end
@ -171,4 +183,10 @@ class FetchLinkCardService < BaseService
def lock_options
{ redis: Redis.current, key: "fetch:#{@url}" }
end
def send_status_update_payload(status)
@payload = InlineRenderer.render(status, nil, :status)
@payload = Oj.dump(event: :update, payload: @payload)
Redis.current.publish('statuscard', @payload)
end
end

View File

@ -534,6 +534,11 @@ const startWorker = (workerId) => {
app.use(authenticationMiddleware);
app.use(errorMiddleware);
app.get('/api/v1/streaming/statuscard', (req, res) => {
const channel = `statuscard`;
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req, subscriptionHeartbeat(channel)));
});
app.get('/api/v1/streaming/user', (req, res) => {
const channel = `timeline:${req.accountId}`;
streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req, subscriptionHeartbeat(channel)));
@ -608,6 +613,10 @@ const startWorker = (workerId) => {
let channel;
switch(location.query.stream) {
case 'statuscard':
channel = `statuscard`;
streamFrom(channel, req, streamToWs(req, ws), streamWsEnd(req, ws, subscriptionHeartbeat(channel)));
break;
case 'user':
channel = `timeline:${req.accountId}`;
streamFrom(channel, req, streamToWs(req, ws), streamWsEnd(req, ws, subscriptionHeartbeat(channel)));