Merge branch 'develop' of https://code.gab.com/gab/social/gab-social into develop
This commit is contained in:
commit
81e8b3294f
@ -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
|
||||
|
@ -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));
|
||||
});
|
||||
|
@ -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)
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
@ -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}
|
||||
|
@ -189,6 +189,7 @@ class Item extends React.PureComponent {
|
||||
autoPlay={autoPlay}
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
|
||||
<span className='media-gallery__gifv__label'>GIF</span>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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 () {
|
||||
|
@ -127,6 +127,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||
autoPlay={autoPlay}
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
|
||||
<span className='media-gallery__gifv__label'>GIF</span>
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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)));
|
||||
|
Loading…
x
Reference in New Issue
Block a user