Progress
This commit is contained in:
parent
1a33759e19
commit
80d41b8d94
|
@ -12,7 +12,9 @@ class Api::V1::GroupsController < Api::BaseController
|
|||
def index
|
||||
case current_tab
|
||||
when 'featured'
|
||||
@groups = Group.where(is_featured: true).limit(50).all
|
||||
@groups = Group.where(is_featured: true, is_archived: false).limit(50).all
|
||||
when 'new'
|
||||
@groups = Group.where(is_archived: false).limit(24).order('created_at DESC').all
|
||||
when 'member'
|
||||
@groups = Group.joins(:group_accounts).where(is_archived: false, group_accounts: { account: current_account }).order('group_accounts.unread_count DESC, group_accounts.id DESC').all
|
||||
when 'admin'
|
||||
|
@ -24,7 +26,7 @@ class Api::V1::GroupsController < Api::BaseController
|
|||
|
||||
def current_tab
|
||||
tab = 'featured'
|
||||
tab = params[:tab] if ['featured', 'member', 'admin'].include? params[:tab]
|
||||
tab = params[:tab] if ['featured', 'member', 'admin', 'new'].include? params[:tab]
|
||||
return tab
|
||||
end
|
||||
|
||||
|
|
|
@ -607,8 +607,9 @@ export function changeScheduledAt(date) {
|
|||
};
|
||||
};
|
||||
|
||||
export function changeRichTextEditorControlsVisibility() {
|
||||
export function changeRichTextEditorControlsVisibility(status) {
|
||||
return {
|
||||
type: COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY,
|
||||
status: status,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
||||
import api, { getLinks } from '../api';
|
||||
import { Map as ImmutableMap, List as ImmutableList, toJS } from 'immutable';
|
||||
|
||||
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
|
||||
export const TIMELINE_DELETE = 'TIMELINE_DELETE';
|
||||
export const TIMELINE_CLEAR = 'TIMELINE_CLEAR';
|
||||
export const TIMELINE_UPDATE_QUEUE = 'TIMELINE_UPDATE_QUEUE';
|
||||
export const TIMELINE_DEQUEUE = 'TIMELINE_DEQUEUE';
|
||||
export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
|
||||
|
||||
export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST';
|
||||
export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS';
|
||||
export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL';
|
||||
|
||||
export const TIMELINE_CONNECT = 'TIMELINE_CONNECT';
|
||||
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
|
||||
|
||||
export const MAX_QUEUED_ITEMS = 40;
|
||||
|
||||
export function updateTimeline(timeline, status, accept) {
|
||||
return dispatch => {
|
||||
if (typeof accept === 'function' && !accept(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(importFetchedStatus(status));
|
||||
|
||||
dispatch({
|
||||
type: TIMELINE_UPDATE,
|
||||
timeline,
|
||||
status,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function updateTimelineQueue(timeline, status, accept) {
|
||||
return dispatch => {
|
||||
if (typeof accept === 'function' && !accept(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: TIMELINE_UPDATE_QUEUE,
|
||||
timeline,
|
||||
status,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export function dequeueTimeline(timeline, expandFunc, optionalExpandArgs) {
|
||||
return (dispatch, getState) => {
|
||||
const queuedItems = getState().getIn(['timelines', timeline, 'queuedItems'], ImmutableList());
|
||||
const totalQueuedItemsCount = getState().getIn(['timelines', timeline, 'totalQueuedItemsCount'], 0);
|
||||
|
||||
let shouldDispatchDequeue = true;
|
||||
|
||||
if (totalQueuedItemsCount == 0) {
|
||||
return;
|
||||
}
|
||||
else if (totalQueuedItemsCount > 0 && totalQueuedItemsCount <= MAX_QUEUED_ITEMS) {
|
||||
queuedItems.forEach(status => {
|
||||
dispatch(updateTimeline(timeline, status.toJS(), null));
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (typeof expandFunc === 'function') {
|
||||
dispatch(clearTimeline(timeline));
|
||||
expandFunc();
|
||||
}
|
||||
else {
|
||||
if (timeline === 'home') {
|
||||
dispatch(clearTimeline(timeline));
|
||||
dispatch(expandHomeTimeline(optionalExpandArgs));
|
||||
}
|
||||
else if (timeline === 'community') {
|
||||
dispatch(clearTimeline(timeline));
|
||||
dispatch(expandCommunityTimeline(optionalExpandArgs));
|
||||
}
|
||||
else {
|
||||
shouldDispatchDequeue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldDispatchDequeue) return;
|
||||
|
||||
dispatch({
|
||||
type: TIMELINE_DEQUEUE,
|
||||
timeline,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export function deleteFromTimelines(id) {
|
||||
return (dispatch, getState) => {
|
||||
const accountId = getState().getIn(['statuses', id, 'account']);
|
||||
const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]);
|
||||
const reblogOf = getState().getIn(['statuses', id, 'reblog'], null);
|
||||
|
||||
dispatch({
|
||||
type: TIMELINE_DELETE,
|
||||
id,
|
||||
accountId,
|
||||
references,
|
||||
reblogOf,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function clearTimeline(timeline) {
|
||||
return (dispatch) => {
|
||||
dispatch({ type: TIMELINE_CLEAR, timeline });
|
||||
};
|
||||
};
|
||||
|
||||
const noOp = () => { };
|
||||
|
||||
const parseTags = (tags = {}, mode) => {
|
||||
return (tags[mode] || []).map((tag) => {
|
||||
return tag.value;
|
||||
});
|
||||
};
|
||||
|
||||
export function expandTimeline(timelineId, path, params = {}, done = noOp) {
|
||||
return (dispatch, getState) => {
|
||||
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap());
|
||||
const isLoadingMore = !!params.max_id;
|
||||
|
||||
if (timeline.get('isLoading')) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!params.max_id && !params.pinned && timeline.get('items', ImmutableList()).size > 0) {
|
||||
params.since_id = timeline.getIn(['items', 0]);
|
||||
}
|
||||
|
||||
const isLoadingRecent = !!params.since_id;
|
||||
|
||||
dispatch(expandTimelineRequest(timelineId, isLoadingMore));
|
||||
|
||||
api(getState).get(path, { params }).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore));
|
||||
done();
|
||||
}).catch(error => {
|
||||
dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
|
||||
done();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
|
||||
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
|
||||
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
|
||||
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
|
||||
export const expandAccountMediaTimeline = (accountId, { maxId, limit } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: limit || 20 });
|
||||
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
|
||||
export const expandGroupTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`group:${id}`, `/api/v1/timelines/group/${id}`, { max_id: maxId }, done);
|
||||
export const expandHashtagTimeline = (hashtag, { maxId, tags } = {}, done = noOp) => {
|
||||
return expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, {
|
||||
max_id: maxId,
|
||||
any: parseTags(tags, 'any'),
|
||||
all: parseTags(tags, 'all'),
|
||||
none: parseTags(tags, 'none'),
|
||||
}, done);
|
||||
};
|
||||
|
||||
export function expandTimelineRequest(timeline, isLoadingMore) {
|
||||
return {
|
||||
type: TIMELINE_EXPAND_REQUEST,
|
||||
timeline,
|
||||
skipLoading: !isLoadingMore,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore) {
|
||||
return {
|
||||
type: TIMELINE_EXPAND_SUCCESS,
|
||||
timeline,
|
||||
statuses,
|
||||
next,
|
||||
partial,
|
||||
isLoadingRecent,
|
||||
skipLoading: !isLoadingMore,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandTimelineFail(timeline, error, isLoadingMore) {
|
||||
return {
|
||||
type: TIMELINE_EXPAND_FAIL,
|
||||
timeline,
|
||||
error,
|
||||
skipLoading: !isLoadingMore,
|
||||
};
|
||||
};
|
||||
|
||||
export function connectTimeline(timeline) {
|
||||
return {
|
||||
type: TIMELINE_CONNECT,
|
||||
timeline,
|
||||
};
|
||||
};
|
||||
|
||||
export function disconnectTimeline(timeline) {
|
||||
return {
|
||||
type: TIMELINE_DISCONNECT,
|
||||
timeline,
|
||||
};
|
||||
};
|
||||
|
||||
export function scrollTopTimeline(timeline, top) {
|
||||
return {
|
||||
type: TIMELINE_SCROLL_TOP,
|
||||
timeline,
|
||||
top,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,104 @@
|
|||
import axios from 'axios'
|
||||
import { me, tenorkey } from '../initial_state'
|
||||
|
||||
export const GIFS_CLEAR_RESULTS = 'GIFS_CLEAR_RESULTS'
|
||||
export const GIF_SET_SELECTED = 'GIF_SET_SELECTED'
|
||||
export const GIF_CHANGE_SEARCH_TEXT = 'GIF_CHANGE_SEARCH_TEXT'
|
||||
|
||||
export const GIF_RESULTS_FETCH_REQUEST = 'GIF_RESULTS_FETCH_REQUEST'
|
||||
export const GIF_RESULTS_FETCH_SUCCESS = 'GIF_RESULTS_FETCH_SUCCESS'
|
||||
export const GIF_RESULTS_FETCH_FAIL = 'GIF_RESULTS_FETCH_FAIL'
|
||||
|
||||
export const GIF_CATEGORIES_FETCH_REQUEST = 'GIF_CATEGORIES_FETCH_REQUEST'
|
||||
export const GIF_CATEGORIES_FETCH_SUCCESS = 'GIF_CATEGORIES_FETCH_SUCCESS'
|
||||
export const GIF_CATEGORIES_FETCH_FAIL = 'GIF_CATEGORIES_FETCH_FAIL'
|
||||
|
||||
export const fetchGifCategories = () => {
|
||||
return function (dispatch) {
|
||||
if (!me) return
|
||||
|
||||
dispatch(fetchGifCategoriesRequest())
|
||||
|
||||
axios.get(`https://api.tenor.com/v1/categories?media_filter=minimal&limit=30&key=${tenorkey}`)
|
||||
.then((response) => {
|
||||
console.log("fetchGifCategoriesSuccess:", response)
|
||||
dispatch(fetchGifCategoriesSuccess(response.data.tags))
|
||||
}).catch(function (error) {
|
||||
dispatch(fetchGifCategoriesFail(error))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchGifResults = () => {
|
||||
return function (dispatch, getState) {
|
||||
if (!me) return
|
||||
|
||||
dispatch(fetchGifResultsRequest())
|
||||
|
||||
const searchText = getState().getIn(['tenor', 'searchText'], '');
|
||||
|
||||
axios.get(`https://api.tenor.com/v1/search?q=${searchText}&media_filter=minimal&limit=30&key=QHFJ0C5EWGBH`)
|
||||
.then((response) => {
|
||||
console.log("response:", response)
|
||||
dispatch(fetchGifResultsSuccess(response.data.results))
|
||||
}).catch(function (error) {
|
||||
dispatch(fetchGifResultsFail(error))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const clearGifResults = () => ({
|
||||
type: GIFS_CLEAR_RESULTS,
|
||||
})
|
||||
|
||||
export const setSelectedGif = (url) => ({
|
||||
type: GIF_SET_SELECTED,
|
||||
url,
|
||||
})
|
||||
|
||||
export function changeGifSearchText(text) {
|
||||
return {
|
||||
type: GIF_CHANGE_SEARCH_TEXT,
|
||||
text,
|
||||
}
|
||||
}
|
||||
|
||||
function fetchGifResultsRequest() {
|
||||
return {
|
||||
type: GIF_RESULTS_FETCH_REQUEST,
|
||||
}
|
||||
}
|
||||
|
||||
function fetchGifResultsSuccess(results) {
|
||||
return {
|
||||
type: GIF_RESULTS_FETCH_SUCCESS,
|
||||
results,
|
||||
}
|
||||
}
|
||||
|
||||
function fetchGifResultsFail(error) {
|
||||
return {
|
||||
type: GIF_RESULTS_FETCH_FAIL,
|
||||
error,
|
||||
}
|
||||
}
|
||||
|
||||
function fetchGifCategoriesRequest() {
|
||||
return {
|
||||
type: GIF_CATEGORIES_FETCH_REQUEST,
|
||||
}
|
||||
}
|
||||
|
||||
function fetchGifCategoriesSuccess(categories) {
|
||||
return {
|
||||
type: GIF_CATEGORIES_FETCH_SUCCESS,
|
||||
categories,
|
||||
}
|
||||
}
|
||||
|
||||
function fetchGifCategoriesFail(error) {
|
||||
return {
|
||||
type: GIF_CATEGORIES_FETCH_FAIL,
|
||||
error,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
const BlockQuoteIcon = ({
|
||||
className = '',
|
||||
width = '16px',
|
||||
height = '16px',
|
||||
viewBox = '0 0 34 32',
|
||||
title = 'Block Quote',
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
y='0px'
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
xmlSpace='preserve'
|
||||
aria-label={title}
|
||||
>
|
||||
<g>
|
||||
<path d='M 0 18.285156 L 6.855469 18.285156 L 2.285156 27.429688 L 9.144531 27.429688 L 13.714844 18.285156 L 13.714844 4.570312 L 0 4.570312 Z M 0 18.285156' />
|
||||
<path d='M 18.285156 4.570312 L 18.285156 18.285156 L 25.144531 18.285156 L 20.570312 27.429688 L 27.429688 27.429688 L 32 18.285156 L 32 4.570312 Z M 18.285156 4.570312' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default BlockQuoteIcon
|
|
@ -0,0 +1,26 @@
|
|||
const BoldIcon = ({
|
||||
className = '',
|
||||
width = '16px',
|
||||
height = '16px',
|
||||
viewBox = '0 0 34 32',
|
||||
title = 'Strikethrough',
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
y='0px'
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
xmlSpace='preserve'
|
||||
aria-label={title}
|
||||
>
|
||||
<g>
|
||||
<path d='M 23.371094 15.519531 C 25.578125 13.976562 27.144531 11.484375 27.144531 9.144531 C 27.144531 3.988281 23.152344 0 18 0 L 3.714844 0 L 3.714844 32 L 19.804688 32 C 24.59375 32 28.285156 28.113281 28.285156 23.335938 C 28.285156 19.863281 26.308594 16.902344 23.371094 15.519531 Z M 10.570312 5.714844 L 17.429688 5.714844 C 19.324219 5.714844 20.855469 7.246094 20.855469 9.144531 C 20.855469 11.039062 19.324219 12.570312 17.429688 12.570312 L 10.570312 12.570312 Z M 18.570312 26.285156 L 10.570312 26.285156 L 10.570312 19.429688 L 18.570312 19.429688 C 20.46875 19.429688 22 20.960938 22 22.855469 C 22 24.753906 20.46875 26.285156 18.570312 26.285156 Z M 18.570312 26.285156' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default BoldIcon
|
|
@ -0,0 +1,26 @@
|
|||
const ItalicIcon = ({
|
||||
className = '',
|
||||
width = '16px',
|
||||
height = '16px',
|
||||
viewBox = '0 0 34 32',
|
||||
title = 'Italic',
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
y='0px'
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
xmlSpace='preserve'
|
||||
aria-label={title}
|
||||
>
|
||||
<g>
|
||||
<path d='M 11.429688 0 L 11.429688 6.855469 L 16.492188 6.855469 L 8.652344 25.144531 L 2.285156 25.144531 L 2.285156 32 L 20.570312 32 L 20.570312 25.144531 L 15.507812 25.144531 L 23.347656 6.855469 L 29.714844 6.855469 L 29.714844 0 Z M 11.429688 0' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default ItalicIcon
|
|
@ -0,0 +1,31 @@
|
|||
const OLListIcon = ({
|
||||
className = '',
|
||||
width = '16px',
|
||||
height = '16px',
|
||||
viewBox = '0 0 34 32',
|
||||
title = 'Ordered List',
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
y='0px'
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
xmlSpace='preserve'
|
||||
aria-label={title}
|
||||
>
|
||||
<g>
|
||||
<path d='M 0 24.421875 L 3.367188 24.421875 L 3.367188 25.261719 L 1.683594 25.261719 L 1.683594 26.949219 L 3.367188 26.949219 L 3.367188 27.789062 L 0 27.789062 L 0 29.472656 L 5.050781 29.472656 L 5.050781 22.738281 L 0 22.738281 Z M 0 24.421875' />
|
||||
<path d='M 1.683594 9.261719 L 3.367188 9.261719 L 3.367188 2.527344 L 0 2.527344 L 0 4.210938 L 1.683594 4.210938 Z M 1.683594 9.261719' />
|
||||
<path d='M 0 14.316406 L 3.03125 14.316406 L 0 17.851562 L 0 19.367188 L 5.050781 19.367188 L 5.050781 17.683594 L 2.019531 17.683594 L 5.050781 14.148438 L 5.050781 12.632812 L 0 12.632812 Z M 0 14.316406' />
|
||||
<path d='M 8.421875 24.421875 L 32 24.421875 L 32 27.789062 L 8.421875 27.789062 Z M 8.421875 24.421875' />
|
||||
<path d='M 8.421875 4.210938 L 32 4.210938 L 32 7.578125 L 8.421875 7.578125 Z M 8.421875 4.210938' />
|
||||
<path d='M 8.421875 14.316406 L 32 14.316406 L 32 17.683594 L 8.421875 17.683594 Z M 8.421875 14.316406' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default OLListIcon
|
|
@ -0,0 +1,27 @@
|
|||
const StrikethroughIcon = ({
|
||||
className = '',
|
||||
width = '16px',
|
||||
height = '16px',
|
||||
viewBox = '0 0 34 32',
|
||||
title = 'Strikethrough',
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
y='0px'
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
xmlSpace='preserve'
|
||||
aria-label={title}
|
||||
>
|
||||
<g>
|
||||
<path d='M 7.527344 12.890625 C 7.617188 13.050781 7.714844 13.191406 7.804688 13.34375 L 16 13.34375 C 14.863281 12.949219 14.167969 12.535156 13.503906 12.097656 C 12.632812 11.519531 12.195312 10.800781 12.195312 9.945312 C 12.195312 9.539062 12.285156 9.136719 12.453125 8.773438 C 12.621094 8.410156 12.878906 8.089844 13.21875 7.8125 C 13.554688 7.539062 13.980469 7.324219 14.496094 7.171875 C 15.023438 7.023438 15.625 6.941406 16.328125 6.941406 C 17.058594 6.941406 17.6875 7.03125 18.222656 7.21875 C 18.753906 7.394531 19.199219 7.664062 19.554688 7.992188 C 19.910156 8.320312 20.179688 8.71875 20.347656 9.191406 C 20.515625 9.652344 20.605469 10.160156 20.605469 10.703125 L 25.957031 10.703125 C 25.957031 9.539062 25.734375 8.460938 25.28125 7.484375 C 24.828125 6.496094 24.1875 5.652344 23.351562 4.941406 C 22.515625 4.222656 21.511719 3.671875 20.335938 3.269531 C 19.164062 2.871094 17.859375 2.675781 16.417969 2.675781 C 15.011719 2.675781 13.734375 2.851562 12.558594 3.199219 C 11.386719 3.546875 10.382812 4.035156 9.527344 4.667969 C 8.675781 5.296875 8.019531 6.0625 7.546875 6.960938 C 7.074219 7.859375 6.84375 8.84375 6.84375 9.929688 C 6.84375 11.058594 7.074219 12.042969 7.527344 12.890625 Z M 7.527344 12.890625' />
|
||||
<path d='M 0 15.121094 L 0 18.675781 L 17.109375 18.675781 C 17.429688 18.800781 17.820312 18.925781 18.089844 19.039062 C 18.746094 19.332031 19.261719 19.644531 19.636719 19.945312 C 20.007812 20.257812 20.257812 20.59375 20.390625 20.949219 C 20.523438 21.316406 20.585938 21.722656 20.585938 22.167969 C 20.585938 22.585938 20.507812 22.976562 20.347656 23.332031 C 20.1875 23.699219 19.9375 24.007812 19.609375 24.265625 C 19.28125 24.523438 18.851562 24.730469 18.347656 24.878906 C 17.832031 25.03125 17.234375 25.101562 16.542969 25.101562 C 15.769531 25.101562 15.066406 25.023438 14.4375 24.871094 C 13.804688 24.71875 13.269531 24.472656 12.828125 24.132812 C 12.382812 23.796875 12.035156 23.351562 11.785156 22.808594 C 11.539062 22.265625 11.332031 21.449219 11.332031 20.667969 L 6.042969 20.667969 C 6.042969 21.644531 6.1875 22.675781 6.460938 23.476562 C 6.738281 24.277344 7.128906 24.996094 7.617188 25.625 C 8.105469 26.257812 8.691406 26.800781 9.359375 27.261719 C 10.027344 27.722656 10.746094 28.117188 11.527344 28.417969 C 12.3125 28.730469 13.121094 28.960938 13.980469 29.101562 C 14.835938 29.253906 15.699219 29.324219 16.550781 29.324219 C 17.972656 29.324219 19.269531 29.164062 20.425781 28.835938 C 21.582031 28.507812 22.578125 28.035156 23.394531 27.429688 C 24.214844 26.816406 24.84375 26.070312 25.296875 25.171875 C 25.75 24.277344 25.964844 23.261719 25.964844 22.125 C 25.964844 21.058594 25.777344 20.097656 25.414062 19.253906 C 25.324219 19.050781 25.226562 18.851562 25.109375 18.65625 L 32 18.65625 L 32 15.121094 Z M 0 15.121094' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default StrikethroughIcon
|
|
@ -0,0 +1,27 @@
|
|||
const TextSizeIcon = ({
|
||||
className = '',
|
||||
width = '16px',
|
||||
height = '16px',
|
||||
viewBox = '0 0 34 32',
|
||||
title = 'Text Size',
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
y='0px'
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
xmlSpace='preserve'
|
||||
aria-label={title}
|
||||
>
|
||||
<g>
|
||||
<path d='M 0 16.84375 L 5.050781 16.84375 L 5.050781 28.632812 L 10.105469 28.632812 L 10.105469 16.84375 L 15.15625 16.84375 L 15.15625 11.789062 L 0 11.789062 Z M 0 16.84375 '/>
|
||||
<path d='M 10.105469 3.367188 L 10.105469 8.421875 L 18.527344 8.421875 L 18.527344 28.632812 L 23.578125 28.632812 L 23.578125 8.421875 L 32 8.421875 L 32 3.367188 Z M 10.105469 3.367188 '/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default TextSizeIcon
|
|
@ -0,0 +1,31 @@
|
|||
const ULListIcon = ({
|
||||
className = '',
|
||||
width = '16px',
|
||||
height = '16px',
|
||||
viewBox = '0 0 34 32',
|
||||
title = 'Unordered List',
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
y='0px'
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
xmlSpace='preserve'
|
||||
aria-label={title}
|
||||
>
|
||||
<g>
|
||||
<path d='M 2.59375 3.027344 C 1.160156 3.027344 0 4.1875 0 5.621094 C 0 7.058594 1.160156 8.214844 2.59375 8.214844 C 4.03125 8.214844 5.1875 7.058594 5.1875 5.621094 C 5.1875 4.1875 4.03125 3.027344 2.59375 3.027344 Z M 2.59375 3.027344' />
|
||||
<path d='M 2.59375 13.40625 C 1.160156 13.40625 0 14.5625 0 16 C 0 17.4375 1.160156 18.59375 2.59375 18.59375 C 4.03125 18.59375 5.1875 17.4375 5.1875 16 C 5.1875 14.5625 4.03125 13.40625 2.59375 13.40625 Z M 2.59375 13.40625' />
|
||||
<path d='M 2.59375 23.785156 C 1.148438 23.785156 0 24.953125 0 26.378906 C 0 27.804688 1.167969 28.972656 2.59375 28.972656 C 4.023438 28.972656 5.1875 27.804688 5.1875 26.378906 C 5.1875 24.953125 4.039062 23.785156 2.59375 23.785156 Z M 2.59375 23.785156' />
|
||||
<path d='M 7.785156 24.648438 L 32 24.648438 L 32 28.109375 L 7.785156 28.109375 Z M 7.785156 24.648438' />
|
||||
<path d='M 7.785156 3.890625 L 32 3.890625 L 32 7.351562 L 7.785156 7.351562 Z M 7.785156 3.890625' />
|
||||
<path d='M 7.785156 14.269531 L 32 14.269531 L 32 17.730469 L 7.785156 17.730469 Z M 7.785156 14.269531' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default ULListIcon
|
|
@ -0,0 +1,27 @@
|
|||
const UnderlineIcon = ({
|
||||
className = '',
|
||||
width = '16px',
|
||||
height = '16px',
|
||||
viewBox = '0 0 34 32',
|
||||
title = 'Underline',
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
y='0px'
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
xmlSpace='preserve'
|
||||
aria-label={title}
|
||||
>
|
||||
<g>
|
||||
<path d='M 16 24.890625 C 21.894531 24.890625 26.667969 20.117188 26.667969 14.222656 L 26.667969 0 L 22.222656 0 L 22.222656 14.222656 C 22.222656 17.664062 19.441406 20.445312 16 20.445312 C 12.558594 20.445312 9.777344 17.664062 9.777344 14.222656 L 9.777344 0 L 5.332031 0 L 5.332031 14.222656 C 5.332031 20.117188 10.105469 24.890625 16 24.890625 Z M 16 24.890625' />
|
||||
<path d='M 3.554688 28.445312 L 28.445312 28.445312 L 28.445312 32 L 3.554688 32 Z M 3.554688 28.445312' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default UnderlineIcon
|
|
@ -36,6 +36,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||
onBlur: PropTypes.func,
|
||||
textarea: PropTypes.bool,
|
||||
small: PropTypes.bool,
|
||||
prependIcon: PropTypes.string,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -208,7 +209,8 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||
className,
|
||||
id,
|
||||
maxLength,
|
||||
textarea
|
||||
textarea,
|
||||
prependIcon
|
||||
} = this.props
|
||||
|
||||
const { suggestionsHidden } = this.state
|
||||
|
@ -232,26 +234,11 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||
mr5: small,
|
||||
})
|
||||
|
||||
// <div aria-activedescendant="typeaheadFocus-0.35973815699338085"
|
||||
// aria-autocomplete="list"
|
||||
// aria-controls="typeaheadDropdownWrapped-0"
|
||||
// aria-describedby="placeholder-7g4r6"
|
||||
// aria-label="Tweet text"
|
||||
// aria-multiline="true"
|
||||
// class="notranslate public-DraftEditor-content"
|
||||
// contenteditable="true"
|
||||
// data-testid="tweetTextarea_0"
|
||||
// role="textbox"
|
||||
// spellcheck="true"
|
||||
// tabindex="0"
|
||||
// no-focuscontainer-refocus="true"
|
||||
// style="outline: none; user-select: text; white-space: pre-wrap; overflow-wrap: break-word;">
|
||||
|
||||
if (textarea) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={[_s.default, _s.flexGrow1].join(' ')}>
|
||||
<div className={[_s.default, _s.ml5].join(' ')}>
|
||||
<div className={[_s.default].join(' ')}>
|
||||
|
||||
<Composer
|
||||
inputRef={this.setTextbox}
|
||||
|
@ -265,6 +252,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onPaste={this.onPaste}
|
||||
small={small}
|
||||
/>
|
||||
|
||||
{ /* <Textarea
|
||||
|
@ -350,6 +338,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||
id={id}
|
||||
className={className}
|
||||
maxLength={maxLength}
|
||||
prependIcon={prependIcon}
|
||||
/>
|
||||
</label>
|
||||
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
import Text from './text'
|
||||
|
||||
export default class Badge extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
}
|
||||
|
||||
state = {
|
||||
hovering: false,
|
||||
}
|
||||
|
||||
handleOnMouseEnter = () => {
|
||||
this.setState({ hovering: true })
|
||||
}
|
||||
|
||||
handleOnMouseLeave = () => {
|
||||
this.setState({ hovering: false })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, description } = this.props
|
||||
const { hovering } = this.state // : todo : tooltip
|
||||
|
||||
return (
|
||||
<Text
|
||||
color='white'
|
||||
size='extraSmall'
|
||||
className={[_s.backgroundColorBrand, _s.px5, _s.lineHeight125, _s.radiusSmall].join(' ')}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -6,8 +6,12 @@ import {
|
|||
} from 'draft-js'
|
||||
import { urlRegex } from '../features/compose/util/url_regex'
|
||||
import classNames from 'classnames/bind'
|
||||
import { me } from '../initial_state'
|
||||
import { makeGetAccount } from '../selectors'
|
||||
import Button from './button'
|
||||
|
||||
import 'draft-js/dist/Draft.css'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
const getBlockStyle = (block) => {
|
||||
|
@ -57,55 +61,61 @@ const RTE_ITEMS = [
|
|||
label: 'Bold',
|
||||
style: 'BOLD',
|
||||
type: 'style',
|
||||
icon: 'circle',
|
||||
icon: 'bold',
|
||||
},
|
||||
{
|
||||
label: 'Italic',
|
||||
style: 'ITALIC',
|
||||
type: 'style',
|
||||
icon: 'circle',
|
||||
icon: 'italic',
|
||||
},
|
||||
{
|
||||
label: 'Underline',
|
||||
style: 'UNDERLINE',
|
||||
type: 'style',
|
||||
icon: 'circle',
|
||||
icon: 'underline',
|
||||
},
|
||||
{
|
||||
label: 'Monospace',
|
||||
style: 'CODE',
|
||||
label: 'Strikethrough',
|
||||
style: 'STRIKETHROUGH',
|
||||
type: 'style',
|
||||
icon: 'circle',
|
||||
icon: 'strikethrough',
|
||||
},
|
||||
// {
|
||||
// label: 'Monospace',
|
||||
// style: 'CODE',
|
||||
// type: 'style',
|
||||
// icon: 'circle',
|
||||
// },
|
||||
{
|
||||
label: 'H1',
|
||||
style: 'header-one',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
icon: 'text-size',
|
||||
},
|
||||
{
|
||||
label: 'Blockquote',
|
||||
style: 'blockquote',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
},
|
||||
{
|
||||
label: 'UL',
|
||||
style: 'unordered-list-item',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
},
|
||||
{
|
||||
label: 'OL',
|
||||
style: 'ordered-list-item',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
icon: 'blockquote',
|
||||
},
|
||||
{
|
||||
label: 'Code Block',
|
||||
style: 'code-block',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
icon: 'code',
|
||||
},
|
||||
{
|
||||
label: 'UL',
|
||||
style: 'unordered-list-item',
|
||||
type: 'block',
|
||||
icon: 'ul-list',
|
||||
},
|
||||
{
|
||||
label: 'OL',
|
||||
style: 'ordered-list-item',
|
||||
type: 'block',
|
||||
icon: 'ol-list',
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -127,7 +137,26 @@ const compositeDecorator = new CompositeDecorator([
|
|||
const HANDLE_REGEX = /\@[\w]+/g;
|
||||
const HASHTAG_REGEX = /\#[\w\u0590-\u05ff]+/g;
|
||||
|
||||
export default class Composer extends PureComponent {
|
||||
const mapStateToProps = state => {
|
||||
const getAccount = makeGetAccount()
|
||||
const account = getAccount(state, me)
|
||||
const isPro = account.get('is_pro')
|
||||
|
||||
return {
|
||||
isPro,
|
||||
rteControlsVisible: state.getIn(['compose', 'rte_controls_visible']),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class Composer extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
inputRef: PropTypes.func,
|
||||
|
@ -141,6 +170,9 @@ export default class Composer extends PureComponent {
|
|||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
onPaste: PropTypes.func,
|
||||
small: PropTypes.bool,
|
||||
isPro: PropTypes.bool.isRequired,
|
||||
rteControlsVisible: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
state = {
|
||||
|
@ -176,22 +208,21 @@ export default class Composer extends PureComponent {
|
|||
this.onChange(RichUtils.onTab(e, this.state.editorState, maxDepth))
|
||||
}
|
||||
|
||||
toggleBlockType = (blockType) => {
|
||||
toggleEditorStyle = (style, type) => {
|
||||
console.log("toggleEditorStyle:", style, type)
|
||||
if (type === 'style') {
|
||||
this.onChange(
|
||||
RichUtils.toggleBlockType(
|
||||
this.state.editorState,
|
||||
blockType
|
||||
RichUtils.toggleInlineStyle(this.state.editorState, style)
|
||||
)
|
||||
} else if (type === 'block') {
|
||||
this.onChange(
|
||||
RichUtils.toggleBlockType(this.state.editorState, style)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
toggleInlineStyle = (inlineStyle) => {
|
||||
this.onChange(
|
||||
RichUtils.toggleInlineStyle(
|
||||
this.state.editorState,
|
||||
inlineStyle
|
||||
)
|
||||
)
|
||||
handleOnTogglePopoutEditor = () => {
|
||||
//
|
||||
}
|
||||
|
||||
setRef = (n) => {
|
||||
|
@ -210,28 +241,61 @@ export default class Composer extends PureComponent {
|
|||
onKeyUp,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onPaste
|
||||
onPaste,
|
||||
small,
|
||||
isPro,
|
||||
rteControlsVisible
|
||||
} = this.props
|
||||
const { editorState } = this.state
|
||||
|
||||
const editorContainerClasses = cx({
|
||||
default: 1,
|
||||
RTE: 1,
|
||||
cursorText: 1,
|
||||
text: 1,
|
||||
fontSize16PX: !small,
|
||||
fontSize14PX: small,
|
||||
pt15: !small,
|
||||
px15: !small,
|
||||
px10: small,
|
||||
pt10: small,
|
||||
pb10: 1,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={[_s.default].join(' ')}>
|
||||
|
||||
{
|
||||
rteControlsVisible && isPro &&
|
||||
<div className={[_s.default, _s.backgroundColorPrimary, _s.borderBottom1PX, _s.borderColorSecondary, _s.py5, _s.px15, _s.alignItemsCenter, _s.flexRow].join(' ')}>
|
||||
{
|
||||
RTE_ITEMS.map((item, i) => (
|
||||
<StyleButton
|
||||
key={`rte-button-${i}`}
|
||||
editorState={editorState}
|
||||
onClick={this.toggleEditorStyle}
|
||||
{...item}
|
||||
/>
|
||||
))
|
||||
}
|
||||
<Button
|
||||
backgroundColor='none'
|
||||
color='secondary'
|
||||
onClick={this.handleOnTogglePopoutEditor}
|
||||
title='Fullscreen'
|
||||
className={[_s.px10, _s.noSelect, _s.marginLeftAuto].join(' ')}
|
||||
icon='fullscreen'
|
||||
iconClassName={_s.inheritFill}
|
||||
iconWidth='12px'
|
||||
iconHeight='12px'
|
||||
radiusSmall
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div
|
||||
onClick={this.focus}
|
||||
className={[_s.text, _s.fontSize16PX].join(' ')}
|
||||
className={editorContainerClasses}
|
||||
>
|
||||
<Editor
|
||||
blockStyleFn={getBlockStyle}
|
||||
|
@ -240,7 +304,7 @@ export default class Composer extends PureComponent {
|
|||
handleKeyCommand={this.handleKeyCommand}
|
||||
onChange={this.onChange}
|
||||
onTab={this.onTab}
|
||||
placeholder={placeholder}
|
||||
// placeholder={placeholder}
|
||||
ref={this.setRef}
|
||||
/>
|
||||
</div>
|
||||
|
@ -252,17 +316,17 @@ export default class Composer extends PureComponent {
|
|||
|
||||
class StyleButton extends PureComponent {
|
||||
static propTypes = {
|
||||
onToggle: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
label: PropTypes.string,
|
||||
style: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
}
|
||||
|
||||
handleOnToggle = (e) => {
|
||||
const { onToggle, style } = this.props
|
||||
handleOnClick
|
||||
= (e) => {
|
||||
e.preventDefault()
|
||||
onToggle(style)
|
||||
this.props.onClick(this.props.style, this.props.type)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -279,13 +343,13 @@ class StyleButton extends PureComponent {
|
|||
const currentStyle = editorState.getCurrentInlineStyle()
|
||||
const blockType = editorState.getCurrentContent().getBlockForKey(selection.getStartKey()).getType()
|
||||
|
||||
let active
|
||||
// active={type.style === blockType}
|
||||
// active={currentStyle.has(type.style)}
|
||||
const active = type === 'block' ? style === blockType : currentStyle.has(style)
|
||||
const color = active ? 'white' : 'secondary'
|
||||
|
||||
const btnClasses = cx({
|
||||
px10: 1,
|
||||
mr5: 1,
|
||||
noSelect: 1,
|
||||
backgroundSubtle2Dark_onHover: 1,
|
||||
backgroundColorBrandLight: active,
|
||||
// py10: !small,
|
||||
|
@ -293,23 +357,20 @@ class StyleButton extends PureComponent {
|
|||
// px5: small,
|
||||
})
|
||||
|
||||
const iconClasses = cx({
|
||||
fillColorSecondary: !active,
|
||||
fillColorWhite: active,
|
||||
})
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={btnClasses}
|
||||
backgroundColor='none'
|
||||
onClick={this.handleOnToggle}
|
||||
color={color}
|
||||
onClick={this.handleOnClick}
|
||||
title={label}
|
||||
icon={'rich-text'}
|
||||
iconClassName={iconClasses}
|
||||
iconWidth='10px'
|
||||
iconHeight='10px'
|
||||
icon={icon}
|
||||
iconClassName={_s.inheritFill}
|
||||
iconWidth='12px'
|
||||
iconHeight='12px'
|
||||
radiusSmall
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,15 +5,18 @@ import { NavLink } from 'react-router-dom'
|
|||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import classNames from 'classnames/bind'
|
||||
import { shortNumberFormat } from '../utils/numbers'
|
||||
import Image from './image'
|
||||
import Text from './text'
|
||||
import Button from './button'
|
||||
import DotTextSeperator from './dot_text_seperator'
|
||||
import Image from './image'
|
||||
import Text from './text'
|
||||
|
||||
const messages = defineMessages({
|
||||
members: { id: 'groups.card.members', defaultMessage: 'Members' },
|
||||
new_statuses: { id: 'groups.sidebar-panel.item.view', defaultMessage: 'new gabs' },
|
||||
no_recent_activity: { id: 'groups.sidebar-panel.item.no_recent_activity', defaultMessage: 'No recent activity' },
|
||||
viewGroup: { id: 'view_group', defaultMessage: 'View Group' },
|
||||
member: { id: 'member', defaultMessage: 'Member' },
|
||||
admin: { id: 'admin', defaultMessage: 'Admin' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
|
@ -49,8 +52,7 @@ class GroupCollectionItem extends ImmutablePureComponent {
|
|||
|
||||
const imageHeight = '200px'
|
||||
|
||||
// : todo :
|
||||
const isMember = false
|
||||
const isMember = relationships.get('member')
|
||||
|
||||
const outsideClasses = cx({
|
||||
default: 1,
|
||||
|
@ -83,6 +85,25 @@ class GroupCollectionItem extends ImmutablePureComponent {
|
|||
height={imageHeight}
|
||||
/>
|
||||
|
||||
<div className={[_s.default, _s.flexRow, _s.positionAbsolute, _s.top0, _s.right0, _s.pt10, _s.mr10].join(' ')}>
|
||||
<Text
|
||||
badge
|
||||
className={_s.backgroundColorWhite}
|
||||
size='extraSmall'
|
||||
color='brand'
|
||||
>
|
||||
{intl.formatMessage(messages.member)}
|
||||
</Text>
|
||||
<Text
|
||||
badge
|
||||
className={[_s.backgroundColorBlack, _s.ml5].join(' ')}
|
||||
size='extraSmall'
|
||||
color='white'
|
||||
>
|
||||
{intl.formatMessage(messages.admin)}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className={[_s.default, _s.px10, _s.my10].join(' ')}>
|
||||
<Text color='primary' size='medium' weight='bold'>
|
||||
{group.get('title')}
|
||||
|
@ -101,8 +122,6 @@ class GroupCollectionItem extends ImmutablePureComponent {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
!isMember &&
|
||||
<div className={[_s.default, _s.px10, _s.mb10].join(' ')}>
|
||||
<Button
|
||||
color='primary'
|
||||
|
@ -110,11 +129,10 @@ class GroupCollectionItem extends ImmutablePureComponent {
|
|||
radiusSmall
|
||||
>
|
||||
<Text color='inherit' weight='bold'>
|
||||
Join
|
||||
{intl.formatMessage(messages.viewGroup)}
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
|
||||
</NavLink>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,8 @@ import AppsIcon from '../assets/apps_icon'
|
|||
import AudioIcon from '../assets/audio_icon'
|
||||
import AudioMuteIcon from '../assets/audio_mute_icon'
|
||||
import BackIcon from '../assets/back_icon'
|
||||
import BlockquoteIcon from '../assets/blockquote_icon'
|
||||
import BoldIcon from '../assets/bold_icon'
|
||||
import CalendarIcon from '../assets/calendar_icon'
|
||||
import ChatIcon from '../assets/chat_icon'
|
||||
import CircleIcon from '../assets/circle_icon'
|
||||
|
@ -22,6 +24,7 @@ import GroupAddIcon from '../assets/group_add_icon'
|
|||
import HappyIcon from '../assets/happy_icon'
|
||||
import HomeIcon from '../assets/home_icon'
|
||||
import InvestorIcon from '../assets/investor_icon'
|
||||
import ItalicIcon from '../assets/italic_icon'
|
||||
import LikeIcon from '../assets/like_icon'
|
||||
import LinkIcon from '../assets/link_icon'
|
||||
import ListIcon from '../assets/list_icon'
|
||||
|
@ -32,6 +35,7 @@ import MinimizeFullscreenIcon from '../assets/minimize_fullscreen_icon'
|
|||
import MissingIcon from '../assets/missing_icon'
|
||||
import MoreIcon from '../assets/more_icon'
|
||||
import NotificationsIcon from '../assets/notifications_icon'
|
||||
import OLListIcon from '../assets/ol_list_icon'
|
||||
import PauseIcon from '../assets/pause_icon'
|
||||
import PinIcon from '../assets/pin_icon'
|
||||
import PlayIcon from '../assets/play_icon'
|
||||
|
@ -43,8 +47,12 @@ import SearchIcon from '../assets/search_icon'
|
|||
import SearchAltIcon from '../assets/search_alt_icon'
|
||||
import ShareIcon from '../assets/share_icon'
|
||||
import ShopIcon from '../assets/shop_icon'
|
||||
import StrikethroughIcon from '../assets/strikethrough_icon'
|
||||
import SubtractIcon from '../assets/subtract_icon'
|
||||
import TextSizeIcon from '../assets/text_size_icon'
|
||||
import TrendsIcon from '../assets/trends_icon'
|
||||
import ULListIcon from '../assets/ul_list_icon'
|
||||
import UnderlineIcon from '../assets/underline_icon'
|
||||
import VerifiedIcon from '../assets/verified_icon'
|
||||
import WarningIcon from '../assets/warning_icon'
|
||||
|
||||
|
@ -55,6 +63,8 @@ const ICONS = {
|
|||
'audio': AudioIcon,
|
||||
'audio-mute': AudioMuteIcon,
|
||||
'back': BackIcon,
|
||||
'blockquote': BlockquoteIcon,
|
||||
'bold': BoldIcon,
|
||||
'calendar': CalendarIcon,
|
||||
'chat': ChatIcon,
|
||||
'close': CloseIcon,
|
||||
|
@ -72,6 +82,7 @@ const ICONS = {
|
|||
'happy': HappyIcon,
|
||||
'home': HomeIcon,
|
||||
'investor': InvestorIcon,
|
||||
'italic': ItalicIcon,
|
||||
'like': LikeIcon,
|
||||
'link': LinkIcon,
|
||||
'list': ListIcon,
|
||||
|
@ -82,6 +93,7 @@ const ICONS = {
|
|||
'missing': MissingIcon,
|
||||
'more': MoreIcon,
|
||||
'notifications': NotificationsIcon,
|
||||
'ol-list': OLListIcon,
|
||||
'pause': PauseIcon,
|
||||
'pin': PinIcon,
|
||||
'play': PlayIcon,
|
||||
|
@ -93,8 +105,12 @@ const ICONS = {
|
|||
'search-alt': SearchAltIcon,
|
||||
'share': ShareIcon,
|
||||
'shop': ShopIcon,
|
||||
'strikethrough': StrikethroughIcon,
|
||||
'subtract': SubtractIcon,
|
||||
'text-size': TextSizeIcon,
|
||||
'trends': TrendsIcon,
|
||||
'ul-list': ULListIcon,
|
||||
'underline': UnderlineIcon,
|
||||
'verified': VerifiedIcon,
|
||||
'warning': WarningIcon,
|
||||
'': CircleIcon,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Fragment } from 'react'
|
||||
import classNames from 'classnames/bind'
|
||||
import Button from './button'
|
||||
import Icon from './icon'
|
||||
import Text from './text'
|
||||
|
||||
|
@ -71,6 +72,12 @@ export default class Input extends PureComponent {
|
|||
displayNone: hideLabel,
|
||||
})
|
||||
|
||||
const btnClasses = cx({
|
||||
displayNone: value.length === 0,
|
||||
px10: 1,
|
||||
mr5: 1,
|
||||
})
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{
|
||||
|
@ -103,9 +110,16 @@ export default class Input extends PureComponent {
|
|||
|
||||
{
|
||||
hasClear &&
|
||||
<div role='button' tabIndex='0' className={'btnClasses'} onClick={onClear}>
|
||||
<Icon id='close' width='10px' height='10px' className={_s.fillColorWhite} aria-label='Clear' />
|
||||
</div>
|
||||
<Button
|
||||
className={btnClasses}
|
||||
tabIndex='0'
|
||||
title='Clear'
|
||||
onClick={onClear}
|
||||
icon='close'
|
||||
iconClassName={_s.inheritFill}
|
||||
iconHeight='10px'
|
||||
iconWidth='10px'
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</Fragment>
|
||||
|
|
|
@ -124,6 +124,7 @@ class Item extends ImmutablePureComponent {
|
|||
let right = 'auto';
|
||||
let float = 'left';
|
||||
let position = 'relative';
|
||||
let borderRadius = '0 0 0 0';
|
||||
|
||||
if (dimensions) {
|
||||
width = dimensions.w;
|
||||
|
@ -134,13 +135,20 @@ class Item extends ImmutablePureComponent {
|
|||
left = dimensions.l || 'auto';
|
||||
float = dimensions.float || 'left';
|
||||
position = dimensions.pos || 'relative';
|
||||
|
||||
const br = dimensions.br || []
|
||||
const hasTL = br.indexOf('tl') > -1
|
||||
const hasTR = br.indexOf('tr') > -1
|
||||
const hasBR = br.indexOf('br') > -1
|
||||
const hasBL = br.indexOf('bl') > -1
|
||||
borderRadius = `${hasTL ? '8px' : '0'} ${hasTR ? '8px' : '0'} ${hasBR ? '8px' : '0'} ${hasBL ? '8px' : '0'}`
|
||||
}
|
||||
|
||||
let thumbnail = '';
|
||||
|
||||
if (attachment.get('type') === 'unknown') {
|
||||
return (
|
||||
<div className={[_s.default].join(' ')} key={attachment.get('id')} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}>
|
||||
<div className={[_s.default].join(' ')} key={attachment.get('id')} style={{ position, float, left, top, right, bottom, height, borderRadius, width: `${width}%` }}>
|
||||
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url')} target='_blank' style={{ cursor: 'pointer' }}>
|
||||
<canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' />
|
||||
</a>
|
||||
|
@ -169,6 +177,7 @@ class Item extends ImmutablePureComponent {
|
|||
href={attachment.get('remote_url') || originalUrl}
|
||||
onClick={this.handleClick}
|
||||
target='_blank'
|
||||
style={{ borderRadius }}
|
||||
>
|
||||
<img
|
||||
src={previewUrl}
|
||||
|
@ -235,6 +244,7 @@ class MediaGallery extends PureComponent {
|
|||
cacheWidth: PropTypes.func,
|
||||
visible: PropTypes.bool,
|
||||
onToggleVisibility: PropTypes.func,
|
||||
reduced: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -278,8 +288,15 @@ class MediaGallery extends PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { media, intl, sensitive, height, defaultWidth } = this.props;
|
||||
const { visible } = this.state;
|
||||
const {
|
||||
media,
|
||||
intl,
|
||||
sensitive,
|
||||
height,
|
||||
defaultWidth,
|
||||
reduced
|
||||
} = this.props
|
||||
const { visible } = this.state
|
||||
|
||||
const width = this.state.width || defaultWidth;
|
||||
|
||||
|
@ -331,34 +348,34 @@ class MediaGallery extends PureComponent {
|
|||
|
||||
if (isPortrait(ar1) && isPortrait(ar2)) {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: '100%', r: '2px' },
|
||||
{ w: 50, h: '100%', l: '2px' }
|
||||
{ w: 50, h: '100%', r: '2px', br: ['tl', 'bl'] },
|
||||
{ w: 50, h: '100%', l: '2px', br: ['tr', 'br'] },
|
||||
];
|
||||
} else if (isPanoramic(ar1) && isPanoramic(ar2)) {
|
||||
itemsDimensions = [
|
||||
{ w: 100, h: panoSize_px, b: '2px' },
|
||||
{ w: 100, h: panoSize_px, t: '2px' }
|
||||
{ w: 100, h: panoSize_px, b: '2px', br: ['tl', 'tr'] },
|
||||
{ w: 100, h: panoSize_px, t: '2px', br: ['bl', 'br'] },
|
||||
];
|
||||
} else if (
|
||||
(isPanoramic(ar1) && isPortrait(ar2)) ||
|
||||
(isPanoramic(ar1) && isNonConformingRatio(ar2))
|
||||
) {
|
||||
itemsDimensions = [
|
||||
{ w: 100, h: `${(width / maximumAspectRatio)}px`, b: '2px' },
|
||||
{ w: 100, h: `${(width * 0.6)}px`, t: '2px' },
|
||||
{ w: 100, h: `${(width / maximumAspectRatio)}px`, b: '2px', br: ['tl', 'tr'] },
|
||||
{ w: 100, h: `${(width * 0.6)}px`, t: '2px', br: ['bl', 'br'] },
|
||||
];
|
||||
} else if (
|
||||
(isPortrait(ar1) && isPanoramic(ar2)) ||
|
||||
(isNonConformingRatio(ar1) && isPanoramic(ar2))
|
||||
) {
|
||||
itemsDimensions = [
|
||||
{ w: 100, h: `${(width * 0.6)}px`, b: '2px' },
|
||||
{ w: 100, h: `${(width / maximumAspectRatio)}px`, t: '2px' },
|
||||
{ w: 100, h: `${(width * 0.6)}px`, b: '2px', br: ['tl', 'tr'] },
|
||||
{ w: 100, h: `${(width / maximumAspectRatio)}px`, t: '2px', br: ['bl', 'br'] },
|
||||
];
|
||||
} else {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: '100%', r: '2px' },
|
||||
{ w: 50, h: '100%', l: '2px' }
|
||||
{ w: 50, h: '100%', r: '2px', br: ['tl', 'bl'] },
|
||||
{ w: 50, h: '100%', l: '2px', br: ['tr', 'br'] },
|
||||
];
|
||||
}
|
||||
} else if (size == 3) {
|
||||
|
@ -374,60 +391,60 @@ class MediaGallery extends PureComponent {
|
|||
|
||||
if (isPanoramic(ar1) && isNonConformingRatio(ar2) && isNonConformingRatio(ar3)) {
|
||||
itemsDimensions = [
|
||||
{ w: 100, h: `50%`, b: '2px' },
|
||||
{ w: 50, h: '50%', t: '2px', r: '2px' },
|
||||
{ w: 50, h: '50%', t: '2px', l: '2px' }
|
||||
{ w: 100, h: `50%`, b: '2px', br: ['tl', 'tr'] },
|
||||
{ w: 50, h: '50%', t: '2px', r: '2px', br: ['bl'] },
|
||||
{ w: 50, h: '50%', t: '2px', l: '2px', br: ['br'] },
|
||||
];
|
||||
} else if (isPanoramic(ar1) && isPanoramic(ar2) && isPanoramic(ar3)) {
|
||||
itemsDimensions = [
|
||||
{ w: 100, h: panoSize_px, b: '4px' },
|
||||
{ w: 100, h: panoSize_px, b: '4px', br: ['tl', 'tr'] },
|
||||
{ w: 100, h: panoSize_px },
|
||||
{ w: 100, h: panoSize_px, t: '4px' }
|
||||
{ w: 100, h: panoSize_px, t: '4px', br: ['bl', 'br'] },
|
||||
];
|
||||
} else if (isPortrait(ar1) && isNonConformingRatio(ar2) && isNonConformingRatio(ar3)) {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: `100%`, r: '2px' },
|
||||
{ w: 50, h: '50%', b: '2px', l: '2px' },
|
||||
{ w: 50, h: '50%', t: '2px', l: '2px' },
|
||||
{ w: 50, h: `100%`, r: '2px', br: ['tl', 'bl'] },
|
||||
{ w: 50, h: '50%', b: '2px', l: '2px', br: ['tr'] },
|
||||
{ w: 50, h: '50%', t: '2px', l: '2px', br: ['br'] },
|
||||
];
|
||||
} else if (isNonConformingRatio(ar1) && isNonConformingRatio(ar2) && isPortrait(ar3)) {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: '50%', b: '2px', r: '2px' },
|
||||
{ w: 50, h: '50%', l: '-2px', b: '-2px', pos: 'absolute', float: 'none' },
|
||||
{ w: 50, h: `100%`, r: '-2px', t: '0px', b: '0px', pos: 'absolute', float: 'none' }
|
||||
{ w: 50, h: '50%', b: '2px', r: '2px', br: ['tl'] },
|
||||
{ w: 50, h: '50%', l: '-2px', b: '-2px', pos: 'absolute', float: 'none', br: ['bl'] },
|
||||
{ w: 50, h: `100%`, r: '-2px', t: '0px', b: '0px', pos: 'absolute', float: 'none', br: ['tr', 'br'] },
|
||||
];
|
||||
} else if (
|
||||
(isNonConformingRatio(ar1) && isPortrait(ar2) && isNonConformingRatio(ar3)) ||
|
||||
(isPortrait(ar1) && isPortrait(ar2) && isPortrait(ar3))
|
||||
) {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: '50%', b: '2px', r: '2px' },
|
||||
{ w: 50, h: `100%`, l: '2px', float: 'right' },
|
||||
{ w: 50, h: '50%', t: '2px', r: '2px' }
|
||||
{ w: 50, h: '50%', b: '2px', r: '2px', br: ['tl'] },
|
||||
{ w: 50, h: `100%`, l: '2px', float: 'right', br: ['tr', 'br'] },
|
||||
{ w: 50, h: '50%', t: '2px', r: '2px', br: ['bl'] },
|
||||
];
|
||||
} else if (
|
||||
(isPanoramic(ar1) && isPanoramic(ar2) && isNonConformingRatio(ar3)) ||
|
||||
(isPanoramic(ar1) && isPanoramic(ar2) && isPortrait(ar3))
|
||||
) {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: panoSize_px, b: '2px', r: '2px' },
|
||||
{ w: 50, h: panoSize_px, b: '2px', l: '2px' },
|
||||
{ w: 100, h: `${width - panoSize}px`, t: '2px' }
|
||||
{ w: 50, h: panoSize_px, b: '2px', r: '2px', br: ['tl'] },
|
||||
{ w: 50, h: panoSize_px, b: '2px', l: '2px', br: ['tr'] },
|
||||
{ w: 100, h: `${width - panoSize}px`, t: '2px', br: ['bl', 'br'] },
|
||||
];
|
||||
} else if (
|
||||
(isNonConformingRatio(ar1) && isPanoramic(ar2) && isPanoramic(ar3)) ||
|
||||
(isPortrait(ar1) && isPanoramic(ar2) && isPanoramic(ar3))
|
||||
) {
|
||||
itemsDimensions = [
|
||||
{ w: 100, h: `${width - panoSize}px`, b: '2px' },
|
||||
{ w: 50, h: panoSize_px, t: '2px', r: '2px' },
|
||||
{ w: 50, h: panoSize_px, t: '2px', l: '2px' },
|
||||
{ w: 100, h: `${width - panoSize}px`, b: '2px', br: ['tl', 'tr'] },
|
||||
{ w: 50, h: panoSize_px, t: '2px', r: '2px', br: ['bl'] },
|
||||
{ w: 50, h: panoSize_px, t: '2px', l: '2px', br: ['br'] },
|
||||
];
|
||||
} else {
|
||||
itemsDimensions = [
|
||||
{ w: 50, h: '50%', b: '2px', r: '2px' },
|
||||
{ w: 50, h: '50%', b: '2px', l: '2px' },
|
||||
{ w: 100, h: `50%`, t: '2px' }
|
||||
{ w: 50, h: '50%', b: '2px', r: '2px', br: ['tl'] },
|
||||
{ w: 50, h: '50%', b: '2px', l: '2px', br: ['tr'] },
|
||||
{ w: 100, h: `50%`, t: '2px', br: ['bl', 'br'] },
|
||||
];
|
||||
}
|
||||
} else if (size == 4) {
|
||||
|
@ -489,6 +506,12 @@ class MediaGallery extends PureComponent {
|
|||
style.height = height;
|
||||
}
|
||||
|
||||
//If reduced (i.e. like in a quoted post)
|
||||
//then we need to make media smaller
|
||||
if (reduced) {
|
||||
style.height = width / 2
|
||||
}
|
||||
|
||||
children = media.take(4).map((attachment, i) => (
|
||||
<Item
|
||||
key={attachment.get('id')}
|
||||
|
@ -530,13 +553,16 @@ class MediaGallery extends PureComponent {
|
|||
style={style}
|
||||
ref={this.handleRef}
|
||||
>
|
||||
{ /*
|
||||
|
||||
{ /* : todo :
|
||||
<div className={classNames('spoiler-button', { 'spoiler-button--minified': visible })}>
|
||||
{spoilerButton}
|
||||
</div> */ }
|
||||
|
||||
<div className={[_s.default, _s.displayBlock, _s.width100PC, _s.height100PC, _s.overflowHidden].join(' ')}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,275 @@
|
|||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import {
|
||||
fetchGifCategories,
|
||||
fetchGifResults,
|
||||
clearGifResults,
|
||||
setSelectedGif,
|
||||
changeGifSearchText
|
||||
} from '../../actions/tenor'
|
||||
import { closeModal } from '../../actions/modal'
|
||||
import Block from '../block'
|
||||
import Button from '../button'
|
||||
import ColumnIndicator from '../column_indicator'
|
||||
import Image from '../image'
|
||||
import Input from '../input'
|
||||
import Text from '../text'
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
title: { id: 'pick_gif', defaultMessage: 'Select a GIF' },
|
||||
searchGifs: { id: 'search_gifs', defaultMessage: 'Search for GIFs' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
categories: state.getIn(['tenor', 'categories']),
|
||||
suggestions: state.getIn(['tenor', 'suggestions']),
|
||||
results: state.getIn(['tenor', 'results']),
|
||||
loading: state.getIn(['tenor', 'loading']),
|
||||
error: state.getIn(['tenor', 'error']),
|
||||
searchText: state.getIn(['tenor', 'searchText']),
|
||||
})
|
||||
|
||||
export const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
handleCloseModal() {
|
||||
dispatch(changeGifSearchText(''))
|
||||
dispatch(clearGifResults())
|
||||
dispatch(closeModal())
|
||||
},
|
||||
|
||||
handleFetchCategories: () => {
|
||||
dispatch(fetchGifCategories())
|
||||
},
|
||||
|
||||
handleOnChange: (value) => {
|
||||
dispatch(changeGifSearchText(value))
|
||||
if (value.length >= 3) {
|
||||
dispatch(fetchGifResults())
|
||||
} else if (value.length === 0) {
|
||||
dispatch(clearGifResults())
|
||||
}
|
||||
},
|
||||
|
||||
handleSelectResult: (resultId) => {
|
||||
|
||||
},
|
||||
|
||||
// dispatchSubmit: (e) => {
|
||||
// e.preventDefault();
|
||||
// dispatch(getGifs());
|
||||
// },
|
||||
|
||||
})
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class GifPickerModal extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
handleCloseModal: PropTypes.func.isRequired,
|
||||
handleFetchCategories: PropTypes.func.isRequired,
|
||||
handleOnChange: PropTypes.func.isRequired,
|
||||
categories: PropTypes.array.isRequired,
|
||||
results: PropTypes.array.isRequired,
|
||||
loading: PropTypes.bool,
|
||||
error: PropTypes.bool,
|
||||
chosenUrl: PropTypes.string,
|
||||
searchText: PropTypes.string,
|
||||
}
|
||||
|
||||
state = {
|
||||
row: 0,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.handleFetchCategories()
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
this.props.handleOnChange(e.target.value)
|
||||
}
|
||||
|
||||
onHandleCloseModal = () => {
|
||||
this.props.handleCloseModal()
|
||||
}
|
||||
|
||||
handleSelectCategory = (category) => {
|
||||
this.props.handleOnChange(category)
|
||||
}
|
||||
|
||||
handleSelectGifResult = (resultId) => {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
categories,
|
||||
results,
|
||||
loading,
|
||||
error,
|
||||
searchText
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div style={{ width: '560px' }}>
|
||||
<Block>
|
||||
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.justifyContentCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.height53PX, _s.px15].join(' ')}>
|
||||
<div className={[_s.default, _s.flexGrow1, _s.mr5].join(' ')}>
|
||||
<Input
|
||||
onChange={this.onChange}
|
||||
value={searchText}
|
||||
prependIcon='search'
|
||||
placeholder={intl.formatMessage(messages.searchGifs)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
backgroundColor='none'
|
||||
title={intl.formatMessage(messages.close)}
|
||||
className={_s.marginLeftAuto}
|
||||
onClick={this.onHandleCloseModal}
|
||||
color='secondary'
|
||||
icon='close'
|
||||
iconWidth='10px'
|
||||
iconWidth='10px'
|
||||
/>
|
||||
</div>
|
||||
<div className={[_s.default, _s.heightMin50VH, _s.heightMax80VH, _s.overflowYScroll].join(' ')}>
|
||||
{
|
||||
error &&
|
||||
<ColumnIndicator type='error' />
|
||||
}
|
||||
{
|
||||
(loading && results.length === 0 && categories.length === 0) &&
|
||||
<ColumnIndicator type='loading' />
|
||||
}
|
||||
|
||||
{
|
||||
(results.length > 0 || categories.length > 0) &&
|
||||
<div className={[_s.default, _s.width100PC, _s.height100PC].join(' ')}>
|
||||
{
|
||||
results.length === 0 && categories.length > 0 &&
|
||||
<GifCategoriesCollection categories={categories} handleSelectCategory={this.handleSelectCategory} />
|
||||
}
|
||||
{
|
||||
results.length > 0 &&
|
||||
<GifResultsCollection results={results} handleSelectGifResult={this.handleSelectGifResult} />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Block>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class GifResultsCollectionColumn extends PureComponent {
|
||||
static propTypes = {
|
||||
results: PropTypes.array.isRequired,
|
||||
handleSelectGifResult: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
onClick = (resultId) => {
|
||||
this.props.handleSelectGifResult(resultId)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { results } = this.props
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.flexNormal].join(' ')}>
|
||||
{
|
||||
results.map((result, i) => (
|
||||
<button
|
||||
key={`gif-result-item-${i}`}
|
||||
onClick={() => this.onClick(result.id)}
|
||||
className={[_s.default, _s.cursorPointer, _s.px2, _s.py2].join(' ')}
|
||||
>
|
||||
<Image
|
||||
height={result.media[0].tinygif.dims[1]}
|
||||
src={result.media[0].tinygif.url}
|
||||
/>
|
||||
</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class GifResultsCollection extends PureComponent {
|
||||
static propTypes = {
|
||||
results: PropTypes.array.isRequired,
|
||||
handleSelectGifResult: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { results, handleSelectGifResult } = this.props
|
||||
|
||||
const count = results.length
|
||||
const columnIndex = 10
|
||||
|
||||
console.log("results:", results)
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.height100PC, _s.flexRow, _s.width100PC].join(' ')}>
|
||||
<GifResultsCollectionColumn
|
||||
results={results.slice(0, columnIndex)}
|
||||
handleSelectGifResult={handleSelectGifResult}
|
||||
/>
|
||||
<GifResultsCollectionColumn
|
||||
results={results.slice(columnIndex, columnIndex * 2)}
|
||||
handleSelectGifResult={handleSelectGifResult}
|
||||
/>
|
||||
<GifResultsCollectionColumn
|
||||
results={results.slice(columnIndex * 2, count)}
|
||||
handleSelectGifResult={handleSelectGifResult}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class GifCategoriesCollection extends PureComponent {
|
||||
static propTypes = {
|
||||
categories: PropTypes.array.isRequired,
|
||||
handleSelectCategory: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
onClick = (term) => {
|
||||
this.props.handleSelectCategory(term)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { categories } = this.props
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.height100PC, _s.width100PC, _s.flexRow, _s.flexWrap].join(' ')}>
|
||||
{
|
||||
categories.map((category, i) => (
|
||||
<button
|
||||
key={`gif-category-${i}`}
|
||||
onClick={() => this.onClick(category.searchterm)}
|
||||
className={[_s.default, _s.px2, _s.py2, _s.width50PC].join(' ')}
|
||||
>
|
||||
<div className={[_s.default, _s.cursorPointer].join(' ')}>
|
||||
<Image
|
||||
height={150}
|
||||
src={category.image}
|
||||
/>
|
||||
<div className={[_s.default, _s.positionAbsolute, _s.videoPlayerControlsBackground, _s.right0, _s.bottom0, _s.left0, _s.py10, _s.px10].join(' ')}>
|
||||
<Text color='white' weight='bold' size='large' align='left'>
|
||||
{category.searchterm}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import BoostModal from './boost_modal'
|
|||
import CommunityTimelineSettingsModal from './community_timeline_settings_modal'
|
||||
import ComposeModal from './compose_modal'
|
||||
import ConfirmationModal from './confirmation_modal'
|
||||
import GifPickerModal from './gif_picker_modal'
|
||||
import GroupCreateModal from './group_create_modal'
|
||||
import GroupDeleteModal from './group_delete_modal'
|
||||
import GroupEditorModal from './group_editor_modal'
|
||||
|
@ -46,6 +47,7 @@ const MODAL_COMPONENTS = {
|
|||
COMPOSE: () => Promise.resolve({ default: ComposeModal }),
|
||||
CONFIRM: () => Promise.resolve({ default: ConfirmationModal }),
|
||||
EMBED: () => Promise.resolve({ default: EmbedModal }),
|
||||
GIF_PICKER: () => Promise.resolve({ default: GifPickerModal }),
|
||||
GROUP_CREATE: () => Promise.resolve({ default: GroupCreateModal }),
|
||||
GROUP_DELETE: () => Promise.resolve({ default: GroupDeleteModal }),
|
||||
GROUP_EDITOR: () => Promise.resolve({ default: GroupEditorModal }),
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import DatePicker from 'react-datepicker'
|
||||
import { changeScheduledAt } from '../../actions/compose'
|
||||
import { openModal } from '../../actions/modal'
|
||||
import { me } from '../../initial_state'
|
||||
import { isMobile } from '../../utils/is_mobile'
|
||||
import PopoverLayout from './popover_layout'
|
||||
|
@ -16,22 +15,15 @@ const mapDispatchToProps = dispatch => ({
|
|||
setScheduledAt (date) {
|
||||
dispatch(changeScheduledAt(date))
|
||||
},
|
||||
|
||||
onOpenProUpgradeModal() {
|
||||
dispatch(openModal('PRO_UPGRADE'))
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class DatePickerPopover extends PureComponent {
|
||||
static propTypes = {
|
||||
date: PropTypes.instanceOf(Date),
|
||||
setScheduledAt: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
isPro: PropTypes.bool,
|
||||
onOpenProUpgradeModal: PropTypes.func.isRequired,
|
||||
position: PropTypes.string,
|
||||
small: PropTypes.bool,
|
||||
}
|
||||
|
@ -46,7 +38,6 @@ class DatePickerPopover extends PureComponent {
|
|||
const open = !!date
|
||||
const datePickerDisabled = !isPro
|
||||
const withPortal = isMobile(window.innerWidth)
|
||||
const popperPlacement = position || undefined
|
||||
|
||||
return (
|
||||
<PopoverLayout>
|
||||
|
@ -63,7 +54,6 @@ class DatePickerPopover extends PureComponent {
|
|||
disabled={datePickerDisabled}
|
||||
showTimeSelect
|
||||
withPortal={withPortal}
|
||||
popperPlacement={popperPlacement}
|
||||
popperModifiers={{
|
||||
offset: {
|
||||
enabled: true,
|
||||
|
|
|
@ -38,8 +38,7 @@ const mapStateToProps = state => ({
|
|||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onClose(optionalType) {
|
||||
//
|
||||
dispatch(closePopover())
|
||||
dispatch(closePopover(optionalType))
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -62,17 +61,6 @@ class PopoverRoot extends PureComponent {
|
|||
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
style: PropTypes.object,
|
||||
placement: PropTypes.string,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
style: {},
|
||||
placement: 'bottom',
|
||||
}
|
||||
|
||||
state = {
|
||||
mounted: false,
|
||||
}
|
||||
|
||||
handleDocumentClick = e => {
|
||||
|
@ -85,8 +73,6 @@ class PopoverRoot extends PureComponent {
|
|||
document.addEventListener('click', this.handleDocumentClick, false)
|
||||
document.addEventListener('keydown', this.handleKeyDown, false)
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions)
|
||||
|
||||
this.setState({ mounted: true })
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -160,10 +146,8 @@ class PopoverRoot extends PureComponent {
|
|||
render() {
|
||||
const {
|
||||
type,
|
||||
style,
|
||||
props,
|
||||
} = this.props
|
||||
const { mounted } = this.state
|
||||
const visible = !!type
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { Fragment } from 'react'
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
@ -9,14 +7,10 @@ import { displayMedia } from '../../initial_state';
|
|||
import StatusCard from '../status_card'
|
||||
import { MediaGallery, Video } from '../../features/ui/util/async_components';
|
||||
import ComposeFormContainer from '../../features/compose/containers/compose_form_container'
|
||||
import Avatar from '../avatar';
|
||||
import StatusQuote from '../status_quote';
|
||||
import RelativeTimestamp from '../relative_timestamp';
|
||||
import DisplayName from '../display_name';
|
||||
import RecursiveStatusContainer from '../../containers/recursive_status_container'
|
||||
import StatusContent from '../status_content'
|
||||
import StatusPrepend from '../status_prepend'
|
||||
import StatusActionBar from '../status_action_bar';
|
||||
import Block from '../block';
|
||||
import Icon from '../icon';
|
||||
import Poll from '../poll';
|
||||
import StatusHeader from '../status_header'
|
||||
import Text from '../text'
|
||||
|
@ -30,13 +24,14 @@ const cx = classNames.bind(_s)
|
|||
export const textForScreenReader = (intl, status, rebloggedByText = false) => {
|
||||
const displayName = status.getIn(['account', 'display_name']);
|
||||
|
||||
// : todo :
|
||||
const values = [
|
||||
displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName,
|
||||
status.get('spoiler_text') && status.get('hidden')
|
||||
? status.get('spoiler_text')
|
||||
: status.get('search_index').slice(status.get('spoiler_text').length),
|
||||
intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
|
||||
status.getIn(['account', 'acct']),
|
||||
// displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName,
|
||||
// status.get('spoiler_text') && status.get('hidden')
|
||||
// ? status.get('spoiler_text')
|
||||
// : status.get('search_index').slice(status.get('spoiler_text').length),
|
||||
// intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
|
||||
// status.getIn(['account', 'acct']),
|
||||
];
|
||||
|
||||
if (rebloggedByText) {
|
||||
|
@ -47,19 +42,17 @@ export const textForScreenReader = (intl, status, rebloggedByText = false) => {
|
|||
};
|
||||
|
||||
export const defaultMediaVisibility = status => {
|
||||
if (!status) return undefined;
|
||||
if (!status) return undefined
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
status = status.get('reblog');
|
||||
status = status.get('reblog')
|
||||
}
|
||||
|
||||
return (displayMedia !== 'hide_all' && !status.get('sensitive')) || displayMedia === 'show_all';
|
||||
};
|
||||
return (displayMedia !== 'hide_all' && !status.get('sensitive')) || displayMedia === 'show_all'
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
filtered: { id: 'status.filtered', defaultMessage: 'Filtered' },
|
||||
promoted: { id:'status.promoted', defaultMessage: 'Promoted gab' },
|
||||
pinned: { id: 'status.pinned', defaultMessage: 'Pinned gab' },
|
||||
})
|
||||
|
||||
export default
|
||||
|
@ -72,7 +65,6 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
account: ImmutablePropTypes.map,
|
||||
onClick: PropTypes.func,
|
||||
onReply: PropTypes.func,
|
||||
onShowRevisions: PropTypes.func,
|
||||
|
@ -91,7 +83,6 @@ class Status extends ImmutablePureComponent {
|
|||
onToggleHidden: PropTypes.func,
|
||||
muted: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
unread: PropTypes.bool,
|
||||
onMoveUp: PropTypes.func,
|
||||
onMoveDown: PropTypes.func,
|
||||
showThread: PropTypes.bool,
|
||||
|
@ -104,16 +95,17 @@ class Status extends ImmutablePureComponent {
|
|||
onOpenProUpgradeModal: PropTypes.func,
|
||||
intl: PropTypes.object.isRequired,
|
||||
borderless: PropTypes.bool,
|
||||
};
|
||||
isChild: PropTypes.bool,
|
||||
}
|
||||
|
||||
// Avoid checking props that are functions (and whose equality will always
|
||||
// evaluate to false. See react-immutable-pure-component for usage.
|
||||
updateOnProps = ['status', 'account', 'muted', 'hidden'];
|
||||
updateOnProps = ['status', 'account', 'muted', 'hidden']
|
||||
|
||||
state = {
|
||||
showMedia: defaultMediaVisibility(this.props.status),
|
||||
statusId: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// Track height changes we know about to compensate scrolling
|
||||
componentDidMount() {
|
||||
|
@ -122,10 +114,10 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
getSnapshotBeforeUpdate() {
|
||||
if (this.props.getScrollPosition) {
|
||||
return this.props.getScrollPosition();
|
||||
return this.props.getScrollPosition()
|
||||
}
|
||||
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
|
@ -237,34 +229,34 @@ class Status extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleHotkeyMoveUp = e => {
|
||||
this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured'));
|
||||
};
|
||||
|
||||
handleHotkeyMoveDown = e => {
|
||||
this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured'));
|
||||
};
|
||||
|
||||
handleHotkeyToggleHidden = () => {
|
||||
this.props.onToggleHidden(this._properStatus());
|
||||
};
|
||||
|
||||
handleHotkeyToggleSensitive = () => {
|
||||
this.handleToggleMediaVisibility();
|
||||
};
|
||||
|
||||
_properStatus() {
|
||||
const { status } = this.props;
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
return status.get('reblog');
|
||||
this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured'))
|
||||
}
|
||||
|
||||
return status;
|
||||
handleHotkeyMoveDown = e => {
|
||||
this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured'))
|
||||
}
|
||||
|
||||
handleHotkeyToggleHidden = () => {
|
||||
this.props.onToggleHidden(this._properStatus())
|
||||
}
|
||||
|
||||
handleHotkeyToggleSensitive = () => {
|
||||
this.handleToggleMediaVisibility()
|
||||
}
|
||||
|
||||
_properStatus() {
|
||||
const { status } = this.props
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
return status.get('reblog')
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
handleRef = c => {
|
||||
this.node = c;
|
||||
};
|
||||
this.node = c
|
||||
}
|
||||
|
||||
|
||||
handleOpenProUpgradeModal = () => {
|
||||
|
@ -276,21 +268,30 @@ class Status extends ImmutablePureComponent {
|
|||
intl,
|
||||
hidden,
|
||||
featured,
|
||||
unread,
|
||||
showThread,
|
||||
group,
|
||||
promoted,
|
||||
borderless
|
||||
borderless,
|
||||
isChild
|
||||
} = this.props
|
||||
|
||||
let media = null
|
||||
let prepend, rebloggedByText, reblogContent
|
||||
let rebloggedByText, reblogContent
|
||||
|
||||
// rebloggedByText = intl.formatMessage(
|
||||
// { id: 'status.reposted_by', defaultMessage: '{name} reposted' },
|
||||
// { name: status.getIn(['account', 'acct']) }
|
||||
// );
|
||||
|
||||
// reblogContent = status.get('contentHtml');
|
||||
// status = status.get('reblog');
|
||||
// }
|
||||
|
||||
let { status, ...other } = this.props;
|
||||
|
||||
// console.log("replies:", this.props.replies)
|
||||
|
||||
let { status, account, ...other } = this.props;
|
||||
|
||||
if (status === null) return null;
|
||||
if (!status) return null
|
||||
|
||||
if (hidden) {
|
||||
return (
|
||||
|
@ -302,12 +303,10 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
if (status.get('filtered') || status.getIn(['reblog', 'filtered'])) {
|
||||
const minHandlers = this.props.muted
|
||||
? {}
|
||||
: {
|
||||
const minHandlers = this.props.muted ? {} : {
|
||||
moveUp: this.handleHotkeyMoveUp,
|
||||
moveDown: this.handleHotkeyMoveDown,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<HotKeys handlers={minHandlers}>
|
||||
|
@ -315,73 +314,14 @@ class Status extends ImmutablePureComponent {
|
|||
<Text>{intl.formatMessage(messages.filtered)}</Text>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
if (promoted) {
|
||||
prepend = (
|
||||
<button className='status__prepend status__prepend--promoted' onClick={this.handleOpenProUpgradeModal}>
|
||||
<div className='status__prepend-icon-wrapper'>
|
||||
<Icon id='star' className='status__prepend-icon' fixedWidth />
|
||||
</div>
|
||||
<Text>{intl.formatMessage(messages.promoted)}</Text>
|
||||
</button>
|
||||
);
|
||||
} else if (featured) {
|
||||
prepend = (
|
||||
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.py5, _s.px15].join(' ')}>
|
||||
<Icon
|
||||
id='pin'
|
||||
width='10px'
|
||||
height='10px'
|
||||
className={_s.fillColorSecondary}
|
||||
/>
|
||||
<Text size='small' color='secondary' className={_s.ml5}>
|
||||
{intl.formatMessage(messages.pinned)}
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
} else if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
|
||||
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'>
|
||||
<Icon id='repost' className='status__prepend-icon' fixedWidth />
|
||||
</div>
|
||||
{/*<FormattedMessage
|
||||
id='status.reposted_by'
|
||||
defaultMessage='{name} reposted'
|
||||
values={{
|
||||
name: (
|
||||
<NavLink to={`/${status.getIn(['account', 'acct'])}`} className='status__display-name muted'>
|
||||
<bdi>
|
||||
<strong dangerouslySetInnerHTML={display_name_html} />
|
||||
</bdi>
|
||||
</NavLink>
|
||||
),
|
||||
}}
|
||||
/> */ }
|
||||
</div>
|
||||
);
|
||||
|
||||
rebloggedByText = intl.formatMessage(
|
||||
{ id: 'status.reposted_by', defaultMessage: '{name} reposted' },
|
||||
{ name: status.getIn(['account', 'acct']) }
|
||||
);
|
||||
|
||||
account = status.get('account');
|
||||
reblogContent = status.get('contentHtml');
|
||||
status = status.get('reblog');
|
||||
)
|
||||
}
|
||||
|
||||
if (status.get('poll')) {
|
||||
media = <Poll pollId={status.get('poll')} />
|
||||
} else if (status.get('media_attachments').size > 0) {
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
const video = status.getIn(['media_attachments', 0]);
|
||||
|
||||
// console.log("VIDEO HERE")
|
||||
const video = status.getIn(['media_attachments', 0])
|
||||
|
||||
media = (
|
||||
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer}>
|
||||
|
@ -409,6 +349,7 @@ class Status extends ImmutablePureComponent {
|
|||
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
|
||||
{Component => (
|
||||
<Component
|
||||
reduced={isChild}
|
||||
media={status.get('media_attachments')}
|
||||
sensitive={status.get('sensitive')}
|
||||
height={110}
|
||||
|
@ -423,7 +364,6 @@ class Status extends ImmutablePureComponent {
|
|||
)
|
||||
}
|
||||
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
|
||||
// console.log("card:", status.get('card'))
|
||||
media = (
|
||||
<StatusCard
|
||||
onOpenMedia={this.props.onOpenMedia}
|
||||
|
@ -434,10 +374,6 @@ class Status extends ImmutablePureComponent {
|
|||
)
|
||||
}
|
||||
|
||||
// console.log("da status:", status)
|
||||
let quotedStatus = status.get('quotedStatus');
|
||||
// console.log("quotedStatus:", quotedStatus)
|
||||
|
||||
const handlers = this.props.muted ? {} : {
|
||||
reply: this.handleHotkeyReply,
|
||||
favorite: this.handleHotkeyFavorite,
|
||||
|
@ -451,15 +387,13 @@ class Status extends ImmutablePureComponent {
|
|||
toggleSensitive: this.handleHotkeyToggleSensitive,
|
||||
}
|
||||
|
||||
const statusUrl = `/${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`;
|
||||
|
||||
const containerClasses = cx({
|
||||
default: 1,
|
||||
mb15: !borderless,
|
||||
mb15: !borderless && !isChild,
|
||||
backgroundColorPrimary: 1,
|
||||
pb15: featured,
|
||||
borderBottom1PX: featured,
|
||||
borderColorSecondary: featured,
|
||||
borderBottom1PX: featured && !isChild,
|
||||
borderColorSecondary: featured && !isChild,
|
||||
})
|
||||
|
||||
const innerContainerClasses = cx({
|
||||
|
@ -468,6 +402,10 @@ class Status extends ImmutablePureComponent {
|
|||
radiusSmall: !borderless,
|
||||
borderColorSecondary: !borderless,
|
||||
border1PX: !borderless,
|
||||
pb10: isChild && status.get('media_attachments').size === 0,
|
||||
pb5: isChild && status.get('media_attachments').size > 1,
|
||||
cursorPointer: isChild,
|
||||
backgroundSubtle_onHover: isChild,
|
||||
})
|
||||
|
||||
return (
|
||||
|
@ -478,14 +416,15 @@ class Status extends ImmutablePureComponent {
|
|||
data-featured={featured ? 'true' : null}
|
||||
aria-label={textForScreenReader(intl, status, rebloggedByText)}
|
||||
ref={this.handleRef}
|
||||
// onClick={this.handleClick}
|
||||
>
|
||||
<div className={innerContainerClasses}>
|
||||
|
||||
{prepend}
|
||||
|
||||
<div data-id={status.get('id')}>
|
||||
|
||||
<StatusHeader status={status} />
|
||||
<StatusPrepend status={status} promoted={promoted} featured={featured} />
|
||||
|
||||
<StatusHeader status={status} reduced={isChild} />
|
||||
|
||||
<div className={_s.default}>
|
||||
<StatusContent
|
||||
|
@ -500,21 +439,24 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
{media}
|
||||
|
||||
{ /* status.get('quote') && <StatusQuote
|
||||
id={status.get('quote')}
|
||||
/> */ }
|
||||
|
||||
{ /* showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) && (
|
||||
<button className='status__content__read-more-button' onClick={this.handleClick}>
|
||||
<FormattedMessage id='status.show_thread' defaultMessage='Show thread' />
|
||||
</button>
|
||||
) */ }
|
||||
|
||||
<StatusActionBar status={status} account={account} {...other} />
|
||||
|
||||
<div className={[_s.default, _s.borderTop1PX, _s.borderColorSecondary, _s.pt10, _s.px15, _s.mb10].join(' ')}>
|
||||
{ /* <ComposeFormContainer replyToId={status.get('id')} shouldCondense /> */ }
|
||||
{
|
||||
!!status.get('quote') && !isChild &&
|
||||
<div className={[_s.default, _s.mt10, _s.px10].join(' ')}>
|
||||
<Status status={status.get('quoted_status')} isChild />
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
!isChild &&
|
||||
<StatusActionBar status={status} {...other} />
|
||||
}
|
||||
|
||||
{
|
||||
!isChild &&
|
||||
<div className={[_s.default, _s.borderTop1PX, _s.borderColorSecondary, _s.pt10, _s.px15, _s.mb10].join(' ')}>
|
||||
<ComposeFormContainer replyToId={status.get('id')} shouldCondense />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -154,7 +154,11 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
)
|
||||
|
||||
const hasInteractions = favoriteCount > 0 || replyCount > 0 || repostCount > 0
|
||||
const shouldCondense = (!!status.get('card') || status.get('media_attachments').size > 0) && !hasInteractions
|
||||
const shouldCondense = (
|
||||
!!status.get('card') ||
|
||||
status.get('media_attachments').size > 0 ||
|
||||
!!status.get('quote')
|
||||
) && !hasInteractions
|
||||
|
||||
const containerClasses = cx({
|
||||
default: 1,
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Fragment } from 'react'
|
|||
import { NavLink } from 'react-router-dom'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import classNames from 'classnames/bind'
|
||||
import { openPopover } from '../actions/popover'
|
||||
import { openModal } from '../actions/modal'
|
||||
import RelativeTimestamp from './relative_timestamp'
|
||||
|
@ -12,6 +13,8 @@ import Icon from './icon'
|
|||
import Button from './button'
|
||||
import Avatar from './avatar'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onOpenStatusRevisionsPopover(status) {
|
||||
dispatch(openModal('STATUS_REVISIONS', {
|
||||
|
@ -36,6 +39,7 @@ class StatusHeader extends ImmutablePureComponent {
|
|||
status: ImmutablePropTypes.map,
|
||||
onOpenStatusRevisionsPopover: PropTypes.func.isRequired,
|
||||
onOpenStatusOptionsPopover: PropTypes.func.isRequired,
|
||||
reduced: PropTypes.bool,
|
||||
}
|
||||
|
||||
handleOpenStatusOptionsPopover = () => {
|
||||
|
@ -122,21 +126,33 @@ class StatusHeader extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { status } = this.props
|
||||
const { status, reduced } = this.props
|
||||
|
||||
const statusUrl = `/${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`;
|
||||
|
||||
const containerClasses = cx({
|
||||
default: 1,
|
||||
px15: 1,
|
||||
py10: !reduced,
|
||||
pb10: reduced,
|
||||
})
|
||||
|
||||
const avatarSize = reduced ? 20 : 46
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.px15, _s.py10].join(' ')}>
|
||||
<div className={containerClasses}>
|
||||
<div className={[_s.default, _s.flexRow, _s.mt5].join(' ')}>
|
||||
|
||||
{
|
||||
!reduced &&
|
||||
<NavLink
|
||||
to={`/${status.getIn(['account', 'acct'])}`}
|
||||
title={status.getIn(['account', 'acct'])}
|
||||
className={[_s.default, _s.mr10].join(' ')}
|
||||
>
|
||||
<Avatar account={status.get('account')} size={50} />
|
||||
<Avatar account={status.get('account')} size={avatarSize} />
|
||||
</NavLink>
|
||||
}
|
||||
|
||||
<div className={[_s.default, _s.alignItemsStart, _s.flexGrow1, _s.mt5].join(' ')}>
|
||||
|
||||
|
@ -149,6 +165,8 @@ class StatusHeader extends ImmutablePureComponent {
|
|||
<DisplayName account={status.get('account')} />
|
||||
</NavLink>
|
||||
|
||||
{
|
||||
!reduced &&
|
||||
<Button
|
||||
text
|
||||
backgroundColor='none'
|
||||
|
@ -161,6 +179,7 @@ class StatusHeader extends ImmutablePureComponent {
|
|||
onClick={this.handleOpenStatusOptionsPopover}
|
||||
buttonRef={this.setStatusOptionsButton}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.lineHeight15].join(' ')}>
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import { NavLink } from 'react-router-dom'
|
||||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import Icon from './icon'
|
||||
import Text from './text'
|
||||
|
||||
const messages = defineMessages({
|
||||
filtered: { id: 'status.filtered', defaultMessage: 'Filtered' },
|
||||
promoted: { id: 'status.promoted', defaultMessage: 'Promoted gab' },
|
||||
pinned: { id: 'status.pinned', defaultMessage: 'Pinned gab' },
|
||||
reposted: { id: 'status.reposted_by', defaultMessage: '{name} reposted' },
|
||||
})
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
class StatusPrepend extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
status: ImmutablePropTypes.map,
|
||||
featured: PropTypes.bool,
|
||||
promoted: PropTypes.bool,
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
status,
|
||||
featured,
|
||||
promoted,
|
||||
} = this.props
|
||||
|
||||
if (!status) return null
|
||||
|
||||
const isRepost = (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object')
|
||||
|
||||
if (!featured && !promoted && !isRepost) return null
|
||||
|
||||
const iconId = featured ? 'pin' : promoted ? 'star' : 'repost'
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.width100PC, _s.alignItemsStart, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
|
||||
<div className={[_s.default, _s.width100PC, _s.flexRow, _s.alignItemsCenter, _s.py5, _s.px15].join(' ')}>
|
||||
<Icon id={iconId} width='12px' height='12px' className={[_s.fillColorSecondary, _s.mr5].join(' ')} />
|
||||
{
|
||||
isRepost &&
|
||||
<div className={[_s.default, _s.flexRow].join(' ')}>
|
||||
<Text size='small' color='secondary'>
|
||||
<FormattedMessage
|
||||
id='status.reposted_by'
|
||||
defaultMessage='{name} reposted'
|
||||
values={{
|
||||
name: (
|
||||
<NavLink
|
||||
className={[_s.noUnderline, _s.underline_onHover].join(' ')}
|
||||
to={`/${status.getIn(['account', 'acct'])}`}
|
||||
>
|
||||
<Text size='small' color='secondary'>
|
||||
<bdi>
|
||||
<span dangerouslySetInnerHTML={{ __html: status.getIn(['account', 'display_name_html']) }} />
|
||||
</bdi>
|
||||
</Text>
|
||||
</NavLink>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
!isRepost &&
|
||||
<Text color='secondary' size='small'>
|
||||
{intl.formatMessage(featured ? messages.pinned : messages.promoted)}
|
||||
</Text>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import StatusContent from './status_content';
|
||||
import DisplayName from './display_name';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
status: state.getIn(['statuses', id]),
|
||||
account: state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
|
||||
});
|
||||
|
||||
@connect(mapStateToProps)
|
||||
export default class StatusQuote extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { status, account } = this.props;
|
||||
|
||||
const statusUrl = `/${account.get('acct')}/posts/${status.get('id')}`;
|
||||
|
||||
return (
|
||||
<NavLink to={statusUrl} className="status__quote">
|
||||
<DisplayName account={account} />
|
||||
<StatusContent
|
||||
status={status}
|
||||
expanded={false}
|
||||
onClick
|
||||
collapsable
|
||||
/>
|
||||
</NavLink>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import Avatar from '../../../../components/avatar';
|
||||
import Button from '../../../../components/button';
|
||||
import DisplayName from '../../../../components/display_name';
|
||||
import { isRtl } from '../../../../utils/rtl';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
|
||||
});
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
class ReplyIndicator extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onCancel();
|
||||
}
|
||||
|
||||
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='reply-indicator'>
|
||||
<div className='reply-indicator__header'>
|
||||
<div className='reply-indicator__cancel'>
|
||||
<Button title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} inverted />
|
||||
</div>
|
||||
|
||||
<NavLink to={`/${status.getIn(['account', 'acct'])}`} className='reply-indicator__display-name'>
|
||||
<div className='reply-indicator__display-avatar'>
|
||||
<Avatar account={status.get('account')} size={24} />
|
||||
</div>
|
||||
<DisplayName account={status.get('account')} />
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div className='reply-indicator-content' style={style} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -43,6 +43,7 @@ export default class Text extends PureComponent {
|
|||
weight: PropTypes.oneOf(Object.keys(WEIGHTS)),
|
||||
align: PropTypes.oneOf(Object.keys(ALIGNMENTS)),
|
||||
underline: PropTypes.bool,
|
||||
badge: PropTypes.bool,
|
||||
htmlFor: PropTypes.string,
|
||||
}
|
||||
|
||||
|
@ -63,13 +64,18 @@ export default class Text extends PureComponent {
|
|||
weight,
|
||||
underline,
|
||||
align,
|
||||
htmlFor
|
||||
htmlFor,
|
||||
badge
|
||||
} = this.props
|
||||
|
||||
const classes = cx(className, {
|
||||
default: 1,
|
||||
text: 1,
|
||||
|
||||
radiusSmall: badge,
|
||||
lineHeight15: badge,
|
||||
px5: badge,
|
||||
|
||||
colorPrimary: color === COLORS.primary,
|
||||
colorSecondary: color === COLORS.secondary,
|
||||
colorBrand: color === COLORS.brand,
|
||||
|
|
|
@ -63,9 +63,7 @@ class TimelineComposeBlock extends ImmutablePureComponent {
|
|||
{intl.formatMessage(messages.createPost)}
|
||||
</Heading>
|
||||
</div>
|
||||
<div className={[_s.default, _s.flexRow, _s.px15, _s.pt15, _s.pb10].join(' ')}>
|
||||
<ComposeFormContainer {...rest} />
|
||||
</div>
|
||||
</Block>
|
||||
</section>
|
||||
)
|
||||
|
|
|
@ -488,7 +488,6 @@ class Video extends PureComponent {
|
|||
|
||||
return (
|
||||
<div
|
||||
role='menuitem'
|
||||
className={[_s.default].join(' ')}
|
||||
style={playerStyle}
|
||||
ref={this.setPlayerRef}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
// import StatusContainer from './status_container'
|
||||
|
||||
// export default class RecursiveStatusContainer extends PureComponent {
|
||||
// render() {
|
||||
// return (
|
||||
// <StatusContainer id={this.props.id} />
|
||||
// )
|
||||
// }
|
||||
// }
|
|
@ -27,7 +27,7 @@ export default class ComposeExtraButton extends PureComponent {
|
|||
} = this.props
|
||||
|
||||
const btnClasses = cx({
|
||||
backgroundSubtle_onHover: 1,
|
||||
backgroundSubtle_onHover: !active,
|
||||
backgroundColorBrandLight: active,
|
||||
py10: !small,
|
||||
px10: !small,
|
||||
|
|
|
@ -16,7 +16,6 @@ import EmojiPickerButton from '../../components/emoji_picker_button'
|
|||
import PollFormContainer from '../../containers/poll_form_container'
|
||||
import SchedulePostButton from '../schedule_post_button'
|
||||
import QuotedStatusPreviewContainer from '../../containers/quoted_status_preview_container'
|
||||
import Icon from '../../../../components/icon'
|
||||
import Button from '../../../../components/button'
|
||||
import Avatar from '../../../../components/avatar'
|
||||
import { isMobile } from '../../../../utils/is_mobile'
|
||||
|
@ -238,7 +237,13 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || length(text) > maxPostCharacterCount || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
|
||||
const shouldAutoFocus = autoFocus && !showSearch && !isMobile(window.innerWidth)
|
||||
|
||||
const containerClasses = cx({
|
||||
const parentContainerClasses = cx({
|
||||
default: 1,
|
||||
flexRow: !shouldCondense,
|
||||
pb10: !shouldCondense,
|
||||
})
|
||||
|
||||
const childContainerClasses = cx({
|
||||
default: 1,
|
||||
flexNormal: 1,
|
||||
flexRow: shouldCondense,
|
||||
|
@ -252,29 +257,30 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
flexRow: 1,
|
||||
alignItemsCenter: 1,
|
||||
mt10: !shouldCondense,
|
||||
px15: !shouldCondense,
|
||||
})
|
||||
|
||||
const contentWarningClasses = cx({
|
||||
default: 1,
|
||||
pt5: 1,
|
||||
pb10: 1,
|
||||
px15: 1,
|
||||
py10: 1,
|
||||
borderBottom1PX: 1,
|
||||
borderColorSecondary: 1,
|
||||
mb10: 1,
|
||||
displayNone: !spoiler
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={parentContainerClasses}>
|
||||
<div className={[_s.default, _s.flexRow, _s.width100PC].join(' ')}>
|
||||
{
|
||||
shouldCondense &&
|
||||
<div className={[_s.default, _s.mr10, _s.mt5]}>
|
||||
<div className={[_s.default, _s.mr10, _s.mt5].join(' ')}>
|
||||
<Avatar account={account} size='28' />
|
||||
</div>
|
||||
}
|
||||
|
||||
<div
|
||||
className={containerClasses}
|
||||
className={childContainerClasses}
|
||||
ref={this.setForm}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
|
@ -292,6 +298,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSpoilerSuggestionSelected}
|
||||
searchTokens={[':']}
|
||||
prependIcon='warning'
|
||||
id='cw-spoiler-input'
|
||||
/>
|
||||
</div>
|
||||
|
@ -314,7 +321,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
textarea
|
||||
>
|
||||
|
||||
<div className='compose-form__modifiers'>
|
||||
<div className={[_s.default, _s.px15].join(' ')}>
|
||||
<UploadForm replyToId={replyToId} />
|
||||
{
|
||||
!edit &&
|
||||
|
@ -359,6 +366,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,7 @@ const messages = defineMessages({
|
|||
})
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
// unavailable: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 0),
|
||||
// active: state.getIn(['compose', 'poll']) !== null,
|
||||
active: state.get('popover').popoverType === 'EMOJI_PICKER',
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
@ -33,7 +32,8 @@ class EmojiPickerButton extends PureComponent {
|
|||
small: PropTypes.bool,
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
handleClick = (e) => {
|
||||
e.preventDefault()
|
||||
this.props.onClick(this.button)
|
||||
}
|
||||
|
||||
|
@ -44,13 +44,11 @@ class EmojiPickerButton extends PureComponent {
|
|||
render() {
|
||||
const { active, small, intl } = this.props
|
||||
|
||||
const title = intl.formatMessage(messages.emoji)
|
||||
|
||||
return (
|
||||
<ComposeExtraButton
|
||||
title={intl.formatMessage(messages.emoji)}
|
||||
onClick={this.handleClick}
|
||||
icon='happy'
|
||||
title={title}
|
||||
small={small}
|
||||
active={active}
|
||||
buttonRef={this.setButton}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { injectIntl, defineMessages } from 'react-intl'
|
||||
import { changeComposeSpoilerness } from '../../../actions/compose'
|
||||
import ComposeExtraButton from './compose_extra_button'
|
||||
import { openModal } from '../../../actions/modal'
|
||||
|
||||
const messages = defineMessages({
|
||||
marked: { id: 'compose_form.spoiler.marked', defaultMessage: 'Text is hidden behind warning' },
|
||||
|
@ -9,13 +9,13 @@ const messages = defineMessages({
|
|||
})
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
active: state.getIn(['compose', 'spoiler']),
|
||||
active: !!state.getIn(['compose', 'gif']) || state.get('modal').modalType === 'GIF_PICKER',
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
onClick() {
|
||||
dispatch(changeComposeSpoilerness())
|
||||
dispatch(openModal('GIF_PICKER'))
|
||||
},
|
||||
|
||||
})
|
||||
|
@ -23,29 +23,35 @@ const mapDispatchToProps = dispatch => ({
|
|||
export default
|
||||
@injectIntl
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class SpoilerButton extends PureComponent {
|
||||
class GifSelectorButton extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
active: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
active: PropTypes.bool,
|
||||
small: PropTypes.bool,
|
||||
}
|
||||
|
||||
handleClick = (e) => {
|
||||
e.preventDefault()
|
||||
this.props.onClick()
|
||||
this.props.onClick(this.button)
|
||||
}
|
||||
|
||||
setButton = (n) => {
|
||||
this.button = n
|
||||
}
|
||||
|
||||
render() {
|
||||
const { active, intl, small } = this.props
|
||||
const { active, small, intl } = this.props
|
||||
|
||||
return (
|
||||
<ComposeExtraButton
|
||||
title={intl.formatMessage(messages.title)}
|
||||
icon='gif'
|
||||
onClick={this.handleClick}
|
||||
icon='gif'
|
||||
small={small}
|
||||
active={active}
|
||||
buttonRef={this.setButton}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { injectIntl, defineMessages } from 'react-intl'
|
||||
import { changeComposeSpoilerness } from '../../../actions/compose'
|
||||
import { changeRichTextEditorControlsVisibility } from '../../../actions/compose'
|
||||
import ComposeExtraButton from './compose_extra_button'
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -9,13 +9,13 @@ const messages = defineMessages({
|
|||
})
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
active: state.getIn(['compose', 'spoiler']),
|
||||
active: state.getIn(['compose', 'rte_controls_visible']),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onClick () {
|
||||
dispatch(changeComposeSpoilerness())
|
||||
onClick (status) {
|
||||
dispatch(changeRichTextEditorControlsVisibility(status))
|
||||
},
|
||||
|
||||
})
|
||||
|
@ -23,7 +23,7 @@ const mapDispatchToProps = dispatch => ({
|
|||
export default
|
||||
@injectIntl
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class SpoilerButton extends PureComponent {
|
||||
class RichTextEditorButton extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
active: PropTypes.bool,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { fetchGroups } from '../actions/groups'
|
||||
import ColumnIndicator from '../components/column_indicator'
|
||||
import ScrollableList from '../components/scrollable_list'
|
||||
import GroupCollectionItem from '../components/group_collection_item'
|
||||
|
||||
|
@ -30,6 +31,9 @@ class GroupsCollection extends ImmutablePureComponent {
|
|||
render() {
|
||||
const { groupIds } = this.props
|
||||
|
||||
if (!groupIds) {
|
||||
return <ColumnIndicator type='loading' />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.flexRow, _s.flexWrap].join(' ')}>
|
||||
|
|
|
@ -27,5 +27,6 @@ export const promotions = initialState && initialState.promotions;
|
|||
export const unreadCount = getMeta('unread_count');
|
||||
export const monthlyExpensesComplete = getMeta('monthly_expenses_complete');
|
||||
export const favouritesCount = getMeta('favourites_count');
|
||||
export const tenorkey = getMeta('tenorkey');
|
||||
|
||||
export default initialState;
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
import { Fragment } from 'react'
|
||||
import { me } from '../initial_state'
|
||||
import { openModal } from '../actions/modal'
|
||||
import LinkFooter from '../components/link_footer'
|
||||
import GroupsPanel from '../components/panel/groups_panel'
|
||||
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
|
||||
import DefaultLayout from '../layouts/default_layout'
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const account = state.getIn(['accounts', me])
|
||||
|
||||
return {
|
||||
isPro: account.get('is_pro'),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onOpenGroupCreateModal() {
|
||||
|
@ -13,10 +21,11 @@ const mapDispatchToProps = dispatch => ({
|
|||
})
|
||||
|
||||
export default
|
||||
@connect(null, mapDispatchToProps)
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class GroupsPage extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
isPro: PropTypes.bool,
|
||||
onOpenGroupCreateModal: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
|
@ -25,9 +34,9 @@ class GroupsPage extends PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { children, onOpenGroupCreateModal } = this.props
|
||||
const { children, isPro, onOpenGroupCreateModal } = this.props
|
||||
|
||||
const tabs = [
|
||||
let tabs = [
|
||||
{
|
||||
title: 'Featured',
|
||||
to: '/groups'
|
||||
|
@ -40,21 +49,25 @@ class GroupsPage extends PureComponent {
|
|||
title: 'My Groups',
|
||||
to: '/groups/browse/member'
|
||||
},
|
||||
{ // only if is_pro
|
||||
]
|
||||
|
||||
let actions = []
|
||||
if (isPro) {
|
||||
actions = [{
|
||||
icon: 'group-add',
|
||||
onClick: onOpenGroupCreateModal,
|
||||
}]
|
||||
|
||||
tabs.push({
|
||||
title: 'Admin',
|
||||
to: '/groups/browse/admin'
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<DefaultLayout
|
||||
title='Groups'
|
||||
actions={[
|
||||
{
|
||||
icon: 'group-add',
|
||||
onClick: onOpenGroupCreateModal
|
||||
},
|
||||
]}
|
||||
actions={actions}
|
||||
layout={(
|
||||
<Fragment>
|
||||
<WhoToFollowPanel />
|
||||
|
|
|
@ -54,6 +54,7 @@ const initialState = ImmutableMap({
|
|||
spoiler_text: '',
|
||||
privacy: null,
|
||||
text: '',
|
||||
markdown_text: '',
|
||||
focusDate: null,
|
||||
caretPosition: null,
|
||||
preselectDate: null,
|
||||
|
@ -75,6 +76,7 @@ const initialState = ImmutableMap({
|
|||
tagHistory: ImmutableList(),
|
||||
scheduled_at: null,
|
||||
rte_controls_visible: false,
|
||||
gif: null,
|
||||
});
|
||||
|
||||
const initialPoll = ImmutableMap({
|
||||
|
@ -298,6 +300,7 @@ export default function compose(state = initialState, action) {
|
|||
map.set('poll', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
map.set('scheduled_at', null);
|
||||
map.set('rte_controls_visible', false);
|
||||
});
|
||||
case COMPOSE_SUBMIT_REQUEST:
|
||||
return state.set('is_submitting', true);
|
||||
|
@ -389,7 +392,9 @@ export default function compose(state = initialState, action) {
|
|||
case COMPOSE_SCHEDULED_AT_CHANGE:
|
||||
return state.set('scheduled_at', action.date);
|
||||
case COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY:
|
||||
return ''
|
||||
return state.withMutations(map => {
|
||||
map.set('rte_controls_visible', !state.get('rte_controls_visible'));
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import {
|
||||
GIFS_CLEAR_RESULTS,
|
||||
GIF_SET_SELECTED,
|
||||
GIF_CHANGE_SEARCH_TEXT,
|
||||
GIF_RESULTS_FETCH_REQUEST,
|
||||
GIF_RESULTS_FETCH_SUCCESS,
|
||||
GIF_RESULTS_FETCH_FAIL,
|
||||
GIF_CATEGORIES_FETCH_REQUEST,
|
||||
GIF_CATEGORIES_FETCH_SUCCESS,
|
||||
GIF_CATEGORIES_FETCH_FAIL
|
||||
} from '../actions/tenor'
|
||||
import { Map as ImmutableMap } from 'immutable'
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
categories: [],
|
||||
results: [],
|
||||
chosenUrl: '',
|
||||
searchText: '',
|
||||
loading: false,
|
||||
error: false,
|
||||
})
|
||||
|
||||
export default function (state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case GIF_RESULTS_FETCH_REQUEST:
|
||||
case GIF_CATEGORIES_FETCH_REQUEST:
|
||||
return state.set('loading', true)
|
||||
case GIF_RESULTS_FETCH_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('results', action.results);
|
||||
map.set('error', false);
|
||||
map.set('loading', false);
|
||||
});
|
||||
case GIF_CATEGORIES_FETCH_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('categories', action.categories);
|
||||
map.set('error', false);
|
||||
map.set('loading', false);
|
||||
});
|
||||
case GIF_RESULTS_FETCH_FAIL:
|
||||
case GIF_CATEGORIES_FETCH_FAIL:
|
||||
return state.withMutations(map => {
|
||||
map.set('error', !!action.error);
|
||||
map.set('loading', false);
|
||||
});
|
||||
case GIFS_CLEAR_RESULTS:
|
||||
return state.set('results', [])
|
||||
case GIF_SET_SELECTED:
|
||||
return state.set('chosenUrl', action.url)
|
||||
case GIF_CHANGE_SEARCH_TEXT:
|
||||
return state.set('searchText', action.text.trim());
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
|
@ -2,32 +2,32 @@ import {
|
|||
GROUP_FETCH_SUCCESS,
|
||||
GROUP_FETCH_FAIL,
|
||||
GROUPS_FETCH_SUCCESS,
|
||||
} from '../actions/groups';
|
||||
import { GROUP_UPDATE_SUCCESS } from '../actions/group_editor';
|
||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
} from '../actions/groups'
|
||||
import { GROUP_UPDATE_SUCCESS } from '../actions/group_editor'
|
||||
import { Map as ImmutableMap, fromJS } from 'immutable'
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
const initialState = ImmutableMap()
|
||||
|
||||
const normalizeGroup = (state, group) => state.set(group.id, fromJS(group));
|
||||
const normalizeGroup = (state, group) => state.set(group.id, fromJS(group))
|
||||
|
||||
const normalizeGroups = (state, groups) => {
|
||||
groups.forEach(group => {
|
||||
state = normalizeGroup(state, group);
|
||||
});
|
||||
state = normalizeGroup(state, group)
|
||||
})
|
||||
|
||||
return state;
|
||||
};
|
||||
return state
|
||||
}
|
||||
|
||||
export default function groups(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case GROUP_FETCH_SUCCESS:
|
||||
case GROUP_UPDATE_SUCCESS:
|
||||
return normalizeGroup(state, action.group);
|
||||
return normalizeGroup(state, action.group)
|
||||
case GROUPS_FETCH_SUCCESS:
|
||||
return normalizeGroups(state, action.groups);
|
||||
return normalizeGroups(state, action.groups)
|
||||
case GROUP_FETCH_FAIL:
|
||||
return state.set(action.id, false);
|
||||
return state.set(action.id, false)
|
||||
default:
|
||||
return state;
|
||||
return state
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,82 +1,84 @@
|
|||
import { combineReducers } from 'redux-immutable'
|
||||
import popover from './popover'
|
||||
import timelines from './timelines'
|
||||
import meta from './meta'
|
||||
import { loadingBarReducer } from 'react-redux-loading-bar'
|
||||
import modal from './modal'
|
||||
import user_lists from './user_lists'
|
||||
import domain_lists from './domain_lists'
|
||||
import accounts from './accounts'
|
||||
import accounts_counters from './accounts_counters'
|
||||
import statuses from './statuses'
|
||||
import relationships from './relationships'
|
||||
import settings from './settings'
|
||||
import push_notifications from './push_notifications'
|
||||
import status_lists from './status_lists'
|
||||
import mutes from './mutes'
|
||||
import reports from './reports'
|
||||
import contexts from './contexts'
|
||||
import compose from './compose'
|
||||
import search from './search'
|
||||
import media_attachments from './media_attachments'
|
||||
import notifications from './notifications'
|
||||
import height_cache from './height_cache'
|
||||
import custom_emojis from './custom_emojis'
|
||||
import lists from './lists'
|
||||
import listEditor from './list_editor'
|
||||
import listAdder from './list_adder'
|
||||
import filters from './filters'
|
||||
import contexts from './contexts'
|
||||
import conversations from './conversations'
|
||||
import suggestions from './suggestions'
|
||||
import polls from './polls'
|
||||
import identity_proofs from './identity_proofs'
|
||||
import hashtags from './hashtags'
|
||||
import custom_emojis from './custom_emojis'
|
||||
import domain_lists from './domain_lists'
|
||||
import filters from './filters'
|
||||
import groups from './groups'
|
||||
import group_relationships from './group_relationships'
|
||||
import group_lists from './group_lists'
|
||||
import group_editor from './group_editor'
|
||||
import group_lists from './group_lists'
|
||||
import group_relationships from './group_relationships'
|
||||
import hashtags from './hashtags'
|
||||
import height_cache from './height_cache'
|
||||
import identity_proofs from './identity_proofs'
|
||||
import lists from './lists'
|
||||
import listAdder from './list_adder'
|
||||
import listEditor from './list_editor'
|
||||
import media_attachments from './media_attachments'
|
||||
import meta from './meta'
|
||||
import modal from './modal'
|
||||
import mutes from './mutes'
|
||||
import notifications from './notifications'
|
||||
import polls from './polls'
|
||||
import popover from './popover'
|
||||
import push_notifications from './push_notifications'
|
||||
import relationships from './relationships'
|
||||
import reports from './reports'
|
||||
import search from './search'
|
||||
import settings from './settings'
|
||||
import sidebar from './sidebar'
|
||||
import statuses from './statuses'
|
||||
import status_lists from './status_lists'
|
||||
import status_revisions from './status_revisions'
|
||||
import suggestions from './suggestions'
|
||||
import tenor from './tenor'
|
||||
import timelines from './timelines'
|
||||
import user_lists from './user_lists'
|
||||
|
||||
const reducers = {
|
||||
popover,
|
||||
timelines,
|
||||
meta,
|
||||
loadingBar: loadingBarReducer,
|
||||
modal,
|
||||
user_lists,
|
||||
domain_lists,
|
||||
status_lists,
|
||||
accounts,
|
||||
accounts_counters,
|
||||
statuses,
|
||||
relationships,
|
||||
settings,
|
||||
push_notifications,
|
||||
mutes,
|
||||
reports,
|
||||
contexts,
|
||||
compose,
|
||||
search,
|
||||
media_attachments,
|
||||
notifications,
|
||||
height_cache,
|
||||
contexts,
|
||||
conversations,
|
||||
custom_emojis,
|
||||
domain_lists,
|
||||
filters,
|
||||
groups,
|
||||
group_editor,
|
||||
group_lists,
|
||||
group_relationships,
|
||||
hashtags,
|
||||
height_cache,
|
||||
identity_proofs,
|
||||
lists,
|
||||
listEditor,
|
||||
listAdder,
|
||||
filters,
|
||||
conversations,
|
||||
suggestions,
|
||||
listEditor,
|
||||
loadingBar: loadingBarReducer,
|
||||
media_attachments,
|
||||
meta,
|
||||
modal,
|
||||
mutes,
|
||||
notifications,
|
||||
polls,
|
||||
hashtags,
|
||||
groups,
|
||||
group_relationships,
|
||||
group_lists,
|
||||
group_editor,
|
||||
popover,
|
||||
push_notifications,
|
||||
relationships,
|
||||
reports,
|
||||
search,
|
||||
settings,
|
||||
sidebar,
|
||||
statuses,
|
||||
status_lists,
|
||||
status_revisions,
|
||||
suggestions,
|
||||
tenor,
|
||||
timelines,
|
||||
user_lists,
|
||||
}
|
||||
|
||||
export default combineReducers(reducers)
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import {
|
||||
GIFS_CLEAR_RESULTS,
|
||||
GIF_SET_SELECTED,
|
||||
GIF_CHANGE_SEARCH_TEXT,
|
||||
GIF_RESULTS_FETCH_REQUEST,
|
||||
GIF_RESULTS_FETCH_SUCCESS,
|
||||
GIF_RESULTS_FETCH_FAIL,
|
||||
GIF_CATEGORIES_FETCH_REQUEST,
|
||||
GIF_CATEGORIES_FETCH_SUCCESS,
|
||||
GIF_CATEGORIES_FETCH_FAIL
|
||||
} from '../actions/tenor'
|
||||
import { Map as ImmutableMap } from 'immutable'
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
categories: [],
|
||||
results: [],
|
||||
chosenUrl: '',
|
||||
searchText: '',
|
||||
loading: false,
|
||||
error: false,
|
||||
})
|
||||
|
||||
export default function (state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case GIF_RESULTS_FETCH_REQUEST:
|
||||
case GIF_CATEGORIES_FETCH_REQUEST:
|
||||
return state.set('loading', true)
|
||||
case GIF_RESULTS_FETCH_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('results', action.results);
|
||||
map.set('error', false);
|
||||
map.set('loading', false);
|
||||
});
|
||||
case GIF_CATEGORIES_FETCH_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('categories', action.categories);
|
||||
map.set('error', false);
|
||||
map.set('loading', false);
|
||||
});
|
||||
case GIF_RESULTS_FETCH_FAIL:
|
||||
case GIF_CATEGORIES_FETCH_FAIL:
|
||||
return state.withMutations(map => {
|
||||
map.set('error', !!action.error);
|
||||
map.set('loading', false);
|
||||
});
|
||||
case GIFS_CLEAR_RESULTS:
|
||||
return state.set('results', [])
|
||||
case GIF_SET_SELECTED:
|
||||
return state.set('chosenUrl', action.url)
|
||||
case GIF_CHANGE_SEARCH_TEXT:
|
||||
return state.set('searchText', action.text.trim());
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
|
@ -70,12 +70,13 @@ export const makeGetStatus = () => {
|
|||
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'quote_of_id'])]),
|
||||
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]),
|
||||
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
|
||||
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'quote_of_id']), 'account'])]),
|
||||
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
|
||||
(state, { username }) => username,
|
||||
getFilters,
|
||||
],
|
||||
|
||||
(statusBase, quotedStatus, statusRepost, accountBase, accountRepost, username, filters) => {
|
||||
(statusBase, quotedStatus, statusRepost, accountBase, accountQuoted, accountRepost, username, filters) => {
|
||||
if (!statusBase) {
|
||||
return null;
|
||||
}
|
||||
|
@ -92,6 +93,10 @@ export const makeGetStatus = () => {
|
|||
statusRepost = null;
|
||||
}
|
||||
|
||||
if (quotedStatus) {
|
||||
quotedStatus = quotedStatus.set('account', accountQuoted);
|
||||
}
|
||||
|
||||
const regex = (accountRepost || accountBase).get('id') !== me && regexFromFilters(filters);
|
||||
const filtered = regex && regex.test(statusBase.get('reblog') ? statusRepost.get('search_index') : statusBase.get('search_index'));
|
||||
|
||||
|
|
|
@ -248,6 +248,10 @@ body {
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.cursorText {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.cursorPointer {
|
||||
cursor: pointer
|
||||
}
|
||||
|
@ -472,6 +476,10 @@ body {
|
|||
min-height: 50vh;
|
||||
}
|
||||
|
||||
.heightMin50PX {
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.height100VH {
|
||||
height: 100vh;
|
||||
}
|
||||
|
@ -935,6 +943,11 @@ body {
|
|||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.px2 {
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.pb15 {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
@ -1017,3 +1030,26 @@ body {
|
|||
.visibilityHidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rich Text Editor
|
||||
*/
|
||||
.RTE :global(.RichEditor-hidePlaceholder .public-DraftEditorPlaceholder-root) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.RTE :global(.RichEditor-editor .RichEditor-blockquote) {
|
||||
border-left: 5px solid #eee;
|
||||
color: #666;
|
||||
font-family: 'Hoefler Text', 'Georgia', serif;
|
||||
font-style: italic;
|
||||
margin: 16px 0;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.RTE :global(.RichEditor-editor .public-DraftStyleDefault-pre) {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
font-family: 'Inconsolata', 'Menlo', 'Consolas', monospace;
|
||||
font-size: 16px;
|
||||
padding: 20px;
|
||||
}
|
|
@ -39,6 +39,7 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
store[:unread_count] = unread_count object.current_account
|
||||
store[:monthly_expenses_complete] = Redis.current.get("monthly_funding_ammount") || 0
|
||||
store[:favourites_count] = object.current_account.favourites.count.to_s
|
||||
store[:tenorkey] = "QHFJ0C5EWGBH"
|
||||
end
|
||||
|
||||
store
|
||||
|
|
|
@ -20,10 +20,10 @@ Rails.application.config.content_security_policy do |p|
|
|||
if Rails.env.development?
|
||||
webpacker_urls = %w(ws http).map { |protocol| "#{protocol}#{Webpacker.dev_server.https? ? 's' : ''}://#{Webpacker.dev_server.host_with_port}" }
|
||||
|
||||
p.connect_src :self, :blob, assets_host, Rails.configuration.x.streaming_api_base_url, *webpacker_urls, "https://*.gab.com"
|
||||
p.connect_src :self, :blob, assets_host, Rails.configuration.x.streaming_api_base_url, *webpacker_urls, "https://*.gab.com", "https://api.tenor.com"
|
||||
p.script_src :self, :unsafe_inline, :unsafe_eval, assets_host, "https://*.gab.com"
|
||||
else
|
||||
p.connect_src :self, :blob, assets_host, Rails.configuration.x.streaming_api_base_url, "https://*.gab.com"
|
||||
p.connect_src :self, :blob, assets_host, Rails.configuration.x.streaming_api_base_url, "https://*.gab.com", "https://api.tenor.com"
|
||||
p.script_src :self, assets_host, "https://*.gab.com"
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue