Merge remote-tracking branch 'origin/develop' into thread-cta
22
.eslintrc.js
|
@ -21,6 +21,7 @@ module.exports = {
|
|||
|
||||
plugins: [
|
||||
'react',
|
||||
'jsdoc',
|
||||
'jsx-a11y',
|
||||
'import',
|
||||
'promise',
|
||||
|
@ -65,11 +66,14 @@ module.exports = {
|
|||
],
|
||||
'comma-style': ['warn', 'last'],
|
||||
'space-before-function-paren': ['error', 'never'],
|
||||
'space-infix-ops': 'error',
|
||||
'space-in-parens': ['error', 'never'],
|
||||
'keyword-spacing': 'error',
|
||||
'consistent-return': 'error',
|
||||
'dot-notation': 'error',
|
||||
eqeqeq: 'error',
|
||||
indent: ['error', 2, {
|
||||
SwitchCase: 1, // https://stackoverflow.com/a/53055584/8811886
|
||||
ignoredNodes: ['TemplateLiteral'],
|
||||
}],
|
||||
'jsx-quotes': ['error', 'prefer-single'],
|
||||
|
@ -267,5 +271,23 @@ module.exports = {
|
|||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
},
|
||||
{
|
||||
// Only enforce JSDoc comments on UI components for now.
|
||||
// https://www.npmjs.com/package/eslint-plugin-jsdoc
|
||||
files: ['app/soapbox/components/ui/**/*'],
|
||||
rules: {
|
||||
'jsdoc/require-jsdoc': ['error', {
|
||||
publicOnly: true,
|
||||
require: {
|
||||
ArrowFunctionExpression: true,
|
||||
ClassDeclaration: true,
|
||||
ClassExpression: true,
|
||||
FunctionDeclaration: true,
|
||||
FunctionExpression: true,
|
||||
MethodDefinition: true,
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# Custom icons
|
||||
|
||||
- dashboard-filled.svg - Modified from Tabler icons, MIT
|
||||
- fediverse.svg - Modified from Wikipedia, CC0
|
||||
- home-squared.svg - Modified from Tabler icons, MIT
|
||||
- pen-plus.svg - Modified from Tabler icons, MIT
|
||||
- verified.svg - Created by Alex Gleason. CC0
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<svg viewBox="0 0 24 24" stroke="currentColor" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 14v6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h6M19 9a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
Before Width: | Height: | Size: 267 B |
|
@ -1 +0,0 @@
|
|||
<svg fill="white" stroke="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="M8.21 4.175V5.86h1.685a.842.842 0 0 1 0 1.684H8.21v1.684a.842.842 0 1 1-1.685 0V7.544H4.842a.842.842 0 1 1 0-1.684h1.684V4.175a.842.842 0 1 1 1.685 0Zm12.87 3.523a.814.814 0 0 1 0 1.18l-1.43 1.6-3.2-3.2 1.515-1.517a.814.814 0 0 1 1.179 0l1.937 1.937ZM6.573 18.364a5 5 0 0 1 1.392-2.686l7.559-7.559 3.116 3.2-7.47 7.544a5 5 0 0 1-2.704 1.409l-2.29.395.397-2.303Z"/></svg>
|
Before Width: | Height: | Size: 487 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler" width="24" height="24" stroke-width="2" stroke="currentColor" fill="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M11.969 3.955A9 9 0 0 0 6.4 20H17.6a9 9 0 0 0-5.631-16.045zM15.529 8.5a1 1 0 0 1 .678 1.707l-1.51 1.51c.189.391.303.823.303 1.283 0 1.645-1.355 3-3 3s-3-1.355-3-3 1.355-3 3-3c.46 0 .892.114 1.283.303l1.51-1.51a1 1 0 0 1 .736-.293zM12 12c-.564 0-1 .436-1 1s.436 1 1 1 1-.436 1-1-.436-1-1-1z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 506 B |
|
@ -1 +0,0 @@
|
|||
<svg viewBox="0 0 24 24" stroke="currentColor" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M22 3H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1ZM1 15h22M1 21h22" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
Before Width: | Height: | Size: 264 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler" viewBox="0 0 24 24" width="24" height="24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m 12,3 -9,7 v 11 c 1.9540513,0 3.8465823,0 6,0 v -6 c 0,-1.104569 0.8954305,-2 2,-2 h 2 c 1.104569,0 2,0.895431 2,2 v 6 c 2,0 4,0 6,0 V 10 Z" />
|
||||
</svg>
|
Before Width: | Height: | Size: 370 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0zm-4 7a7 7 0 0 0-7 7h14a7 7 0 0 0-7-7z"/></svg>
|
Before Width: | Height: | Size: 247 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 95 95" width="100" height="100"><path d="M94.909 19.374C94.909 8.674 86.235 0 75.534 0c-10.647 0-19.28 8.591-19.365 19.217l-15.631 2.09c-1.961-6.007-7.598-10.35-14.258-10.35-8.284 0-15.002 6.716-15.002 15.002 0 6.642 4.321 12.267 10.303 14.24l-2.205 16.056c-10.66.049-19.285 8.7-19.285 19.37C.091 86.325 8.765 95 19.466 95c10.677 0 19.332-8.638 19.37-19.304l18.093-2.501c1.979 5.972 7.598 10.285 14.234 10.285 8.284 0 15.002-6.716 15.002-15.002 0-6.891-4.652-12.682-10.983-14.441l1.365-15.339c10.229-.53 18.363-8.966 18.363-19.324zM56.194 67.8l-18.116 2.505a19.39 19.39 0 0 0-13.312-13.3l2.205-16.077a14.98 14.98 0 0 0 14.27-14.222l15.655-2.094c1.894 6.757 7.351 12.009 14.225 13.612l-1.365 15.322c-7.4.688-13.224 6.753-13.562 14.254z" fill="#ffffff"/></svg>
|
After Width: | Height: | Size: 812 B |
|
@ -515,15 +515,15 @@ const fetchComposeSuggestionsTags = (dispatch, getState, token) => {
|
|||
export function fetchComposeSuggestions(token) {
|
||||
return (dispatch, getState) => {
|
||||
switch (token[0]) {
|
||||
case ':':
|
||||
fetchComposeSuggestionsEmojis(dispatch, getState, token);
|
||||
break;
|
||||
case '#':
|
||||
fetchComposeSuggestionsTags(dispatch, getState, token);
|
||||
break;
|
||||
default:
|
||||
fetchComposeSuggestionsAccounts(dispatch, getState, token);
|
||||
break;
|
||||
case ':':
|
||||
fetchComposeSuggestionsEmojis(dispatch, getState, token);
|
||||
break;
|
||||
case '#':
|
||||
fetchComposeSuggestionsTags(dispatch, getState, token);
|
||||
break;
|
||||
default:
|
||||
fetchComposeSuggestionsAccounts(dispatch, getState, token);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ export function createFilter(intl, phrase, expires_at, context, whole_word, irre
|
|||
export function deleteFilter(intl, id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: FILTERS_DELETE_REQUEST });
|
||||
return api(getState).delete('/api/v1/filters/'+id).then(response => {
|
||||
return api(getState).delete('/api/v1/filters/' + id).then(response => {
|
||||
dispatch({ type: FILTERS_DELETE_SUCCESS, filter: response.data });
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.removed)));
|
||||
}).catch(error => {
|
||||
|
|
|
@ -46,7 +46,7 @@ export const fetchInstance = createAsyncThunk<void, void, { state: RootState }>(
|
|||
dispatch(fetchNodeinfo());
|
||||
}
|
||||
return instance;
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
return rejectWithValue(e);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -51,47 +51,47 @@ export function connectTimelineStream(timelineId, path, pollingRefresh = null, a
|
|||
},
|
||||
|
||||
onReceive(data) {
|
||||
switch(data.event) {
|
||||
case 'update':
|
||||
dispatch(processTimelineUpdate(timelineId, JSON.parse(data.payload), accept));
|
||||
break;
|
||||
case 'status.update':
|
||||
dispatch(updateStatus(JSON.parse(data.payload)));
|
||||
break;
|
||||
case 'delete':
|
||||
dispatch(deleteFromTimelines(data.payload));
|
||||
break;
|
||||
case 'notification':
|
||||
messages[locale]().then(messages => {
|
||||
dispatch(updateNotificationsQueue(JSON.parse(data.payload), messages, locale, window.location.pathname));
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
break;
|
||||
case 'conversation':
|
||||
dispatch(updateConversations(JSON.parse(data.payload)));
|
||||
break;
|
||||
case 'filters_changed':
|
||||
dispatch(fetchFilters());
|
||||
break;
|
||||
case 'pleroma:chat_update':
|
||||
dispatch((dispatch, getState) => {
|
||||
const chat = JSON.parse(data.payload);
|
||||
const me = getState().get('me');
|
||||
const messageOwned = !(chat.last_message && chat.last_message.account_id !== me);
|
||||
|
||||
dispatch({
|
||||
type: STREAMING_CHAT_UPDATE,
|
||||
chat,
|
||||
me,
|
||||
// Only play sounds for recipient messages
|
||||
meta: !messageOwned && getSettings(getState()).getIn(['chats', 'sound']) && { sound: 'chat' },
|
||||
switch (data.event) {
|
||||
case 'update':
|
||||
dispatch(processTimelineUpdate(timelineId, JSON.parse(data.payload), accept));
|
||||
break;
|
||||
case 'status.update':
|
||||
dispatch(updateStatus(JSON.parse(data.payload)));
|
||||
break;
|
||||
case 'delete':
|
||||
dispatch(deleteFromTimelines(data.payload));
|
||||
break;
|
||||
case 'notification':
|
||||
messages[locale]().then(messages => {
|
||||
dispatch(updateNotificationsQueue(JSON.parse(data.payload), messages, locale, window.location.pathname));
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'pleroma:follow_relationships_update':
|
||||
dispatch(updateFollowRelationships(JSON.parse(data.payload)));
|
||||
break;
|
||||
break;
|
||||
case 'conversation':
|
||||
dispatch(updateConversations(JSON.parse(data.payload)));
|
||||
break;
|
||||
case 'filters_changed':
|
||||
dispatch(fetchFilters());
|
||||
break;
|
||||
case 'pleroma:chat_update':
|
||||
dispatch((dispatch, getState) => {
|
||||
const chat = JSON.parse(data.payload);
|
||||
const me = getState().get('me');
|
||||
const messageOwned = !(chat.last_message && chat.last_message.account_id !== me);
|
||||
|
||||
dispatch({
|
||||
type: STREAMING_CHAT_UPDATE,
|
||||
chat,
|
||||
me,
|
||||
// Only play sounds for recipient messages
|
||||
meta: !messageOwned && getSettings(getState()).getIn(['chats', 'sound']) && { sound: 'chat' },
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'pleroma:follow_relationships_update':
|
||||
dispatch(updateFollowRelationships(JSON.parse(data.payload)));
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -108,47 +108,47 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
switch(e.key) {
|
||||
case 'Escape':
|
||||
if (suggestions.size === 0 || suggestionsHidden) {
|
||||
document.querySelector('.ui').parentElement.focus();
|
||||
} else {
|
||||
e.preventDefault();
|
||||
this.setState({ suggestionsHidden: true });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
if (!suggestionsHidden && (suggestions.size > 0 || menu)) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, lastIndex) });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
if (!suggestionsHidden && (suggestions.size > 0 || menu)) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, firstIndex) });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Enter':
|
||||
case 'Tab':
|
||||
// Select suggestion
|
||||
if (!suggestionsHidden && selectedSuggestion > -1 && (suggestions.size > 0 || menu)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.setState({ selectedSuggestion: firstIndex });
|
||||
|
||||
if (selectedSuggestion < suggestions.size) {
|
||||
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
if (suggestions.size === 0 || suggestionsHidden) {
|
||||
document.querySelector('.ui').parentElement.focus();
|
||||
} else {
|
||||
const item = menu[selectedSuggestion - suggestions.size];
|
||||
this.handleMenuItemAction(item);
|
||||
e.preventDefault();
|
||||
this.setState({ suggestionsHidden: true });
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
if (!suggestionsHidden && (suggestions.size > 0 || menu)) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, lastIndex) });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
if (!suggestionsHidden && (suggestions.size > 0 || menu)) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, firstIndex) });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Enter':
|
||||
case 'Tab':
|
||||
// Select suggestion
|
||||
if (!suggestionsHidden && selectedSuggestion > -1 && (suggestions.size > 0 || menu)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.setState({ selectedSuggestion: firstIndex });
|
||||
|
||||
if (selectedSuggestion < suggestions.size) {
|
||||
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
|
||||
} else {
|
||||
const item = menu[selectedSuggestion - suggestions.size];
|
||||
this.handleMenuItemAction(item);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (e.defaultPrevented || !this.props.onKeyDown) {
|
||||
|
|
|
@ -97,40 +97,40 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
switch(e.key) {
|
||||
case 'Escape':
|
||||
if (suggestions.size === 0 || suggestionsHidden) {
|
||||
document.querySelector('.ui').parentElement.focus();
|
||||
} else {
|
||||
e.preventDefault();
|
||||
this.setState({ suggestionsHidden: true });
|
||||
}
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
if (suggestions.size === 0 || suggestionsHidden) {
|
||||
document.querySelector('.ui').parentElement.focus();
|
||||
} else {
|
||||
e.preventDefault();
|
||||
this.setState({ suggestionsHidden: true });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
|
||||
}
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
|
||||
}
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Enter':
|
||||
case 'Tab':
|
||||
break;
|
||||
case 'Enter':
|
||||
case 'Tab':
|
||||
// Select suggestion
|
||||
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
|
||||
}
|
||||
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
|
||||
}
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
if (e.defaultPrevented || !this.props.onKeyDown) {
|
||||
|
|
|
@ -95,29 +95,29 @@ class DropdownMenu extends React.PureComponent<IDropdownMenu, IDropdownMenuState
|
|||
const index = items.indexOf(document.activeElement as any);
|
||||
let element = null;
|
||||
|
||||
switch(e.key) {
|
||||
case 'ArrowDown':
|
||||
element = items[index+1] || items[0];
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
element = items[index-1] || items[items.length-1];
|
||||
break;
|
||||
case 'Tab':
|
||||
if (e.shiftKey) {
|
||||
element = items[index-1] || items[items.length-1];
|
||||
} else {
|
||||
element = items[index+1] || items[0];
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
element = items[0];
|
||||
break;
|
||||
case 'End':
|
||||
element = items[items.length-1];
|
||||
break;
|
||||
case 'Escape':
|
||||
this.props.onClose();
|
||||
break;
|
||||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
element = items[index + 1] || items[0];
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
element = items[index - 1] || items[items.length - 1];
|
||||
break;
|
||||
case 'Tab':
|
||||
if (e.shiftKey) {
|
||||
element = items[index - 1] || items[items.length - 1];
|
||||
} else {
|
||||
element = items[index + 1] || items[0];
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
element = items[0];
|
||||
break;
|
||||
case 'End':
|
||||
element = items[items.length - 1];
|
||||
break;
|
||||
case 'Escape':
|
||||
this.props.onClose();
|
||||
break;
|
||||
}
|
||||
|
||||
if (element) {
|
||||
|
@ -312,22 +312,22 @@ class Dropdown extends React.PureComponent<IDropdown, IDropdownState> {
|
|||
}
|
||||
|
||||
handleButtonKeyDown: React.EventHandler<React.KeyboardEvent> = (e) => {
|
||||
switch(e.key) {
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
this.handleMouseDown(e);
|
||||
break;
|
||||
switch (e.key) {
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
this.handleMouseDown(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyPress: React.EventHandler<React.KeyboardEvent<HTMLButtonElement>> = (e) => {
|
||||
switch(e.key) {
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
this.handleClick(e);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
switch (e.key) {
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
this.handleClick(e);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,22 +68,22 @@ class EmojiSelector extends ImmutablePureComponent<IEmojiSelector> {
|
|||
const { onUnfocus } = this.props;
|
||||
|
||||
switch (e.key) {
|
||||
case 'Tab':
|
||||
e.preventDefault();
|
||||
if (e.shiftKey) this._selectPreviousEmoji(i);
|
||||
else this._selectNextEmoji(i);
|
||||
break;
|
||||
case 'Left':
|
||||
case 'ArrowLeft':
|
||||
this._selectPreviousEmoji(i);
|
||||
break;
|
||||
case 'Right':
|
||||
case 'ArrowRight':
|
||||
this._selectNextEmoji(i);
|
||||
break;
|
||||
case 'Escape':
|
||||
onUnfocus();
|
||||
break;
|
||||
case 'Tab':
|
||||
e.preventDefault();
|
||||
if (e.shiftKey) this._selectPreviousEmoji(i);
|
||||
else this._selectNextEmoji(i);
|
||||
break;
|
||||
case 'Left':
|
||||
case 'ArrowLeft':
|
||||
this._selectPreviousEmoji(i);
|
||||
break;
|
||||
case 'Right':
|
||||
case 'ArrowRight':
|
||||
this._selectNextEmoji(i);
|
||||
break;
|
||||
case 'Escape':
|
||||
onUnfocus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,13 +56,13 @@ class FilterBar extends React.PureComponent {
|
|||
const index = items.indexOf(document.activeElement);
|
||||
let element = null;
|
||||
|
||||
switch(e.key) {
|
||||
case 'ArrowRight':
|
||||
element = items[index+1] || items[0];
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
element = items[index-1] || items[items.length-1];
|
||||
break;
|
||||
switch (e.key) {
|
||||
case 'ArrowRight':
|
||||
element = items[index + 1] || items[0];
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
element = items[index - 1] || items[items.length - 1];
|
||||
break;
|
||||
}
|
||||
|
||||
if (element) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import { Helmet as ReactHelmet } from'react-helmet';
|
||||
import { Helmet as ReactHelmet } from 'react-helmet';
|
||||
|
||||
import { useAppSelector, useSettings } from 'soapbox/hooks';
|
||||
import FaviconService from 'soapbox/utils/favicon_service';
|
||||
|
|
|
@ -337,7 +337,7 @@ class MediaGallery extends React.PureComponent {
|
|||
const aspectRatio = media.getIn([0, 'meta', 'original', 'aspect']);
|
||||
|
||||
const getHeight = () => {
|
||||
if (!aspectRatio) return width*9/16;
|
||||
if (!aspectRatio) return width * 9 / 16;
|
||||
if (isPanoramic(aspectRatio)) return Math.floor(width / maximumAspectRatio);
|
||||
if (isPortrait(aspectRatio)) return Math.floor(width / minimumAspectRatio);
|
||||
return Math.floor(width / aspectRatio);
|
||||
|
|
|
@ -208,7 +208,12 @@ class ModalRoot extends React.PureComponent {
|
|||
})}
|
||||
style={{ opacity: revealed ? 1 : 0 }}
|
||||
>
|
||||
<div role='presentation' id='modal-overlay' className='fixed inset-0 bg-gray-600 bg-opacity-90' onClick={this.handleOnClose} />
|
||||
<div
|
||||
role='presentation'
|
||||
id='modal-overlay'
|
||||
className='fixed inset-0 bg-gray-600 bg-opacity-90'
|
||||
onClick={this.handleOnClose}
|
||||
/>
|
||||
|
||||
<div
|
||||
role='dialog'
|
||||
|
|
|
@ -54,16 +54,16 @@ const selectUnits = delta => {
|
|||
|
||||
const getUnitDelay = units => {
|
||||
switch (units) {
|
||||
case 'second':
|
||||
return SECOND;
|
||||
case 'minute':
|
||||
return MINUTE;
|
||||
case 'hour':
|
||||
return HOUR;
|
||||
case 'day':
|
||||
return DAY;
|
||||
default:
|
||||
return MAX_DELAY;
|
||||
case 'second':
|
||||
return SECOND;
|
||||
case 'minute':
|
||||
return MINUTE;
|
||||
case 'hour':
|
||||
return HOUR;
|
||||
case 'day':
|
||||
return DAY;
|
||||
default:
|
||||
return MAX_DELAY;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -35,8 +35,7 @@ const SidebarNavigation = () => {
|
|||
to: '/follow_requests',
|
||||
text: <FormattedMessage id='navigation_bar.follow_requests' defaultMessage='Follow requests' />,
|
||||
icon: require('@tabler/icons/icons/user-plus.svg'),
|
||||
// TODO: let menu items have a counter
|
||||
// count: followRequestsCount,
|
||||
count: followRequestsCount,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -138,7 +137,7 @@ const SidebarNavigation = () => {
|
|||
<div className='flex flex-col space-y-2'>
|
||||
<SidebarNavigationLink
|
||||
to='/'
|
||||
icon={require('icons/feed.svg')}
|
||||
icon={require('@tabler/icons/icons/home.svg')}
|
||||
text={<FormattedMessage id='tabs_bar.home' defaultMessage='Home' />}
|
||||
/>
|
||||
|
||||
|
@ -152,13 +151,13 @@ const SidebarNavigation = () => {
|
|||
<>
|
||||
<SidebarNavigationLink
|
||||
to={`/@${account.acct}`}
|
||||
icon={require('icons/user.svg')}
|
||||
icon={require('@tabler/icons/icons/user.svg')}
|
||||
text={<FormattedMessage id='tabs_bar.profile' defaultMessage='Profile' />}
|
||||
/>
|
||||
|
||||
<SidebarNavigationLink
|
||||
to='/notifications'
|
||||
icon={require('icons/alert.svg')}
|
||||
icon={require('@tabler/icons/icons/bell.svg')}
|
||||
count={notificationCount}
|
||||
text={<FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' />}
|
||||
/>
|
||||
|
|
|
@ -9,9 +9,10 @@ import { fetchOwnAccounts } from 'soapbox/actions/auth';
|
|||
import { getSettings } from 'soapbox/actions/settings';
|
||||
import { closeSidebar } from 'soapbox/actions/sidebar';
|
||||
import Account from 'soapbox/components/account';
|
||||
import SiteLogo from 'soapbox/components/site-logo';
|
||||
import { Stack } from 'soapbox/components/ui';
|
||||
import ProfileStats from 'soapbox/features/ui/components/profile_stats';
|
||||
import { useAppSelector, useSoapboxConfig, useFeatures } from 'soapbox/hooks';
|
||||
import { useAppSelector, useFeatures } from 'soapbox/hooks';
|
||||
import { makeGetAccount, makeGetOtherAccounts } from 'soapbox/selectors';
|
||||
import { getBaseURL } from 'soapbox/utils/accounts';
|
||||
|
||||
|
@ -66,7 +67,6 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
|
|||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { logo } = useSoapboxConfig();
|
||||
const features = useFeatures();
|
||||
const getAccount = makeGetAccount();
|
||||
const instance = useAppSelector((state) => state.instance);
|
||||
|
@ -141,15 +141,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
|
|||
<Stack space={4}>
|
||||
<HStack alignItems='center' justifyContent='between'>
|
||||
<Link to='/' onClick={onClose}>
|
||||
{logo ? (
|
||||
<img alt='Logo' src={logo} className='h-5 w-auto cursor-pointer' />
|
||||
): (
|
||||
<Icon
|
||||
alt='Logo'
|
||||
src={require('@tabler/icons/icons/home.svg')}
|
||||
className='h-6 w-6 text-gray-400 hover:text-gray-600 dark:text-gray-200 cursor-pointer'
|
||||
/>
|
||||
)}
|
||||
<SiteLogo alt='Logo' className='h-5 w-auto cursor-pointer' />
|
||||
</Link>
|
||||
|
||||
<IconButton
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
|
||||
import { useSoapboxConfig, useSettings, useSystemTheme } from 'soapbox/hooks';
|
||||
|
||||
/** Display the most appropriate site logo based on the theme and configuration. */
|
||||
const SiteLogo: React.FC<React.ComponentProps<'img'>> = (props) => {
|
||||
const { logo, logoDarkMode } = useSoapboxConfig();
|
||||
const settings = useSettings();
|
||||
|
||||
const systemTheme = useSystemTheme();
|
||||
const userTheme = settings.get('themeMode');
|
||||
const darkMode = userTheme === 'dark' || (userTheme === 'system' && systemTheme === 'dark');
|
||||
|
||||
/** Soapbox logo. */
|
||||
const soapboxLogo = darkMode
|
||||
? require('images/soapbox-logo-white.svg')
|
||||
: require('images/soapbox-logo.svg');
|
||||
|
||||
// Use the right logo if provided, then use fallbacks.
|
||||
const getSrc = () => {
|
||||
// In demo mode, use the Soapbox logo.
|
||||
if (settings.get('demo')) return soapboxLogo;
|
||||
|
||||
return (darkMode && logoDarkMode)
|
||||
? logoDarkMode
|
||||
: logo || logoDarkMode || soapboxLogo;
|
||||
};
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/alt-text
|
||||
<img src={getSrc()} {...props} />
|
||||
);
|
||||
};
|
||||
|
||||
export default SiteLogo;
|
|
@ -673,7 +673,7 @@ class StatusActionBar extends ImmutablePureComponent<IStatusActionBar, IStatusAc
|
|||
count={emojiReactCount}
|
||||
/>
|
||||
</EmojiButtonWrapper>
|
||||
): (
|
||||
) : (
|
||||
<StatusActionButton
|
||||
title={intl.formatMessage(messages.favourite)}
|
||||
icon={require('@tabler/icons/icons/heart.svg')}
|
||||
|
|
|
@ -43,7 +43,7 @@ const ThumbNavigation: React.FC = (): JSX.Element => {
|
|||
return (
|
||||
<div className='thumb-navigation'>
|
||||
<ThumbNavigationLink
|
||||
src={require('icons/feed.svg')}
|
||||
src={require('@tabler/icons/icons/home.svg')}
|
||||
text={<FormattedMessage id='navigation.home' defaultMessage='Home' />}
|
||||
to='/'
|
||||
exact
|
||||
|
|
|
@ -10,6 +10,7 @@ type IButtonStyles = {
|
|||
size: ButtonSizes
|
||||
}
|
||||
|
||||
/** Provides class names for the <Button> component. */
|
||||
const useButtonStyles = ({
|
||||
theme,
|
||||
block,
|
||||
|
|
|
@ -13,7 +13,7 @@ interface ISvgIcon {
|
|||
}
|
||||
|
||||
/** Renders an inline SVG with an empty frame loading state */
|
||||
const SvgIcon: React.FC<ISvgIcon> = ({ src, alt, size = 24, className }): JSX.Element => {
|
||||
const SvgIcon: React.FC<ISvgIcon> = ({ src, alt, size = 24, className, ...filteredProps }): JSX.Element => {
|
||||
const loader = (
|
||||
<svg
|
||||
className={className}
|
||||
|
@ -33,6 +33,7 @@ const SvgIcon: React.FC<ISvgIcon> = ({ src, alt, size = 24, className }): JSX.El
|
|||
height={size}
|
||||
loader={loader}
|
||||
data-testid='svg-icon'
|
||||
{...filteredProps}
|
||||
>
|
||||
{/* If the fetch fails, fall back to displaying the loader */}
|
||||
{loader}
|
||||
|
|
|
@ -4,6 +4,7 @@ interface IProgressBar {
|
|||
progress: number,
|
||||
}
|
||||
|
||||
/** A horizontal meter filled to the given percentage. */
|
||||
const ProgressBar: React.FC<IProgressBar> = ({ progress }) => (
|
||||
<div className='h-2.5 w-full rounded-full bg-gray-100 dark:bg-slate-900/50 overflow-hidden'>
|
||||
<div className='h-full bg-accent-500' style={{ width: `${Math.floor(progress * 100)}%` }} />
|
||||
|
|
|
@ -20,7 +20,7 @@ import PublicLayout from 'soapbox/features/public_layout';
|
|||
import NotificationsContainer from 'soapbox/features/ui/containers/notifications_container';
|
||||
import WaitlistPage from 'soapbox/features/verification/waitlist_page';
|
||||
import { createGlobals } from 'soapbox/globals';
|
||||
import { useAppSelector, useAppDispatch, useOwnAccount, useFeatures, useSoapboxConfig, useSettings } from 'soapbox/hooks';
|
||||
import { useAppSelector, useAppDispatch, useOwnAccount, useFeatures, useSoapboxConfig, useSettings, useSystemTheme } from 'soapbox/hooks';
|
||||
import MESSAGES from 'soapbox/locales/messages';
|
||||
import { generateThemeCss } from 'soapbox/utils/theme';
|
||||
|
||||
|
@ -82,18 +82,13 @@ const SoapboxMount = () => {
|
|||
const [localeLoading, setLocaleLoading] = useState(true);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
const colorSchemeQueryList = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const [isSystemDarkMode, setSystemDarkMode] = useState(colorSchemeQueryList.matches);
|
||||
const systemTheme = useSystemTheme();
|
||||
const userTheme = settings.get('themeMode');
|
||||
const darkMode = userTheme === 'dark' || (userTheme === 'system' && isSystemDarkMode);
|
||||
const darkMode = userTheme === 'dark' || (userTheme === 'system' && systemTheme === 'dark');
|
||||
const pepeEnabled = soapboxConfig.getIn(['extensions', 'pepe', 'enabled']) === true;
|
||||
|
||||
const themeCss = generateThemeCss(soapboxConfig);
|
||||
|
||||
const handleSystemModeChange = (event: MediaQueryListEvent) => {
|
||||
setSystemDarkMode(event.matches);
|
||||
};
|
||||
|
||||
// Load the user's locale
|
||||
useEffect(() => {
|
||||
MESSAGES[locale]().then(messages => {
|
||||
|
@ -111,12 +106,6 @@ const SoapboxMount = () => {
|
|||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
colorSchemeQueryList.addEventListener('change', handleSystemModeChange);
|
||||
|
||||
return () => colorSchemeQueryList.removeEventListener('change', handleSystemModeChange);
|
||||
}, []);
|
||||
|
||||
// @ts-ignore: I don't actually know what these should be, lol
|
||||
const shouldUpdateScroll = (prevRouterProps, { location }) => {
|
||||
return !(location.state?.soapboxModalKey && location.state?.soapboxModalKey !== prevRouterProps?.location?.state?.soapboxModalKey);
|
||||
|
|
|
@ -85,7 +85,7 @@ class Audio extends React.PureComponent {
|
|||
|
||||
_setDimensions() {
|
||||
const width = this.player.offsetWidth;
|
||||
const height = this.props.fullscreen ? this.player.offsetHeight : (width / (16/9));
|
||||
const height = this.props.fullscreen ? this.player.offsetHeight : (width / (16 / 9));
|
||||
|
||||
if (this.props.cacheWidth) {
|
||||
this.props.cacheWidth(width);
|
||||
|
@ -412,27 +412,27 @@ class Audio extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleKeyDown = e => {
|
||||
switch(e.key) {
|
||||
case 'k':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.togglePlay();
|
||||
break;
|
||||
case 'm':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.toggleMute();
|
||||
break;
|
||||
case 'j':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.seekBy(-10);
|
||||
break;
|
||||
case 'l':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.seekBy(10);
|
||||
break;
|
||||
switch (e.key) {
|
||||
case 'k':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.togglePlay();
|
||||
break;
|
||||
case 'm':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.toggleMute();
|
||||
break;
|
||||
case 'j':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.seekBy(-10);
|
||||
break;
|
||||
case 'l':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.seekBy(10);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ export default class Visualizer {
|
|||
getTickPoints(count) {
|
||||
const coords = [];
|
||||
|
||||
for(let i = 0; i < count; i++) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
const rad = Math.PI * 2 * i / count;
|
||||
coords.push({ x: Math.cos(rad), y: -Math.sin(rad) });
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@ import React from 'react';
|
|||
import { Link, Redirect, Route, Switch } from 'react-router-dom';
|
||||
|
||||
import LandingGradient from 'soapbox/components/landing-gradient';
|
||||
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
|
||||
import SiteLogo from 'soapbox/components/site-logo';
|
||||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||
import { NotificationsContainer } from 'soapbox/features/ui/util/async-components';
|
||||
import { useAppSelector, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
import { Card, CardBody } from '../../components/ui';
|
||||
import LoginPage from '../auth_login/components/login_page';
|
||||
|
@ -18,7 +18,6 @@ import Verification from '../verification';
|
|||
import EmailPassthru from '../verification/email_passthru';
|
||||
|
||||
const AuthLayout = () => {
|
||||
const { logo } = useSoapboxConfig();
|
||||
const siteTitle = useAppSelector(state => state.instance.title);
|
||||
|
||||
return (
|
||||
|
@ -29,15 +28,7 @@ const AuthLayout = () => {
|
|||
<div className='w-full sm:max-w-lg md:max-w-2xl space-y-8'>
|
||||
<header className='flex justify-center relative'>
|
||||
<Link to='/' className='cursor-pointer'>
|
||||
{logo ? (
|
||||
<img src={logo} alt={siteTitle} className='h-7' />
|
||||
) : (
|
||||
<SvgIcon
|
||||
className='w-7 h-7 dark:text-white'
|
||||
alt={siteTitle}
|
||||
src={require('@tabler/icons/icons/home.svg')}
|
||||
/>
|
||||
)}
|
||||
<SiteLogo alt={siteTitle} className='h-7' />
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ const CaptchaField: React.FC<ICaptchaField> = ({
|
|||
onFetch = noOp,
|
||||
onFetchFail = noOp,
|
||||
onClick = noOp,
|
||||
refreshInterval = 5*60*1000, // 5 minutes, Pleroma default
|
||||
refreshInterval = 5 * 60 * 1000, // 5 minutes, Pleroma default
|
||||
idempotencyKey,
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
@ -71,26 +71,26 @@ const CaptchaField: React.FC<ICaptchaField> = ({
|
|||
};
|
||||
}, [idempotencyKey]);
|
||||
|
||||
switch(captcha.get('type')) {
|
||||
case 'native':
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
<FormattedMessage id='registration.captcha.hint' defaultMessage='Click the image to get a new captcha' />
|
||||
</Text>
|
||||
switch (captcha.get('type')) {
|
||||
case 'native':
|
||||
return (
|
||||
<div>
|
||||
<Text>
|
||||
<FormattedMessage id='registration.captcha.hint' defaultMessage='Click the image to get a new captcha' />
|
||||
</Text>
|
||||
|
||||
<NativeCaptchaField
|
||||
captcha={captcha}
|
||||
onChange={onChange}
|
||||
onClick={onClick}
|
||||
name={name}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case 'none':
|
||||
default:
|
||||
return null;
|
||||
<NativeCaptchaField
|
||||
captcha={captcha}
|
||||
onChange={onChange}
|
||||
onClick={onClick}
|
||||
name={name}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case 'none':
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ class ChatBox extends ImmutablePureComponent {
|
|||
|
||||
onUploadProgress = (e) => {
|
||||
const { loaded, total } = e;
|
||||
this.setState({ uploadProgress: loaded/total });
|
||||
this.setState({ uploadProgress: loaded / total });
|
||||
}
|
||||
|
||||
handleFiles = (files) => {
|
||||
|
@ -193,7 +193,7 @@ class ChatBox extends ImmutablePureComponent {
|
|||
<div className='chat-box' onMouseOver={this.handleHover}>
|
||||
<ChatMessageList chatMessageIds={chatMessageIds} chatId={chatId} />
|
||||
{this.renderAttachment()}
|
||||
<UploadProgress active={isUploading} progress={uploadProgress*100} />
|
||||
<UploadProgress active={isUploading} progress={uploadProgress * 100} />
|
||||
<div className='chat-box__actions simple_form'>
|
||||
<div className='chat-box__send'>
|
||||
{this.renderActionButton()}
|
||||
|
|
|
@ -314,17 +314,17 @@ class ChatMessageList extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='chat-messages' ref={this.setRef}>
|
||||
{chatMessages.reduce((acc, curr, idx) => {
|
||||
const lastMessage = chatMessages.get(idx-1);
|
||||
const lastMessage = chatMessages.get(idx - 1);
|
||||
|
||||
if (lastMessage) {
|
||||
const key = `${curr.get('id')}_divider`;
|
||||
switch(timeChange(lastMessage, curr)) {
|
||||
case 'today':
|
||||
acc.push(this.renderDivider(key, intl.formatMessage(messages.today)));
|
||||
break;
|
||||
case 'date':
|
||||
acc.push(this.renderDivider(key, new Date(curr.get('created_at')).toDateString()));
|
||||
break;
|
||||
switch (timeChange(lastMessage, curr)) {
|
||||
case 'today':
|
||||
acc.push(this.renderDivider(key, intl.formatMessage(messages.today)));
|
||||
break;
|
||||
case 'date':
|
||||
acc.push(this.renderDivider(key, new Date(curr.get('created_at')).toDateString()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -229,8 +229,8 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
const spoilerUpdated = this.props.spoiler !== prevProps.spoiler;
|
||||
if (spoilerUpdated) {
|
||||
switch (this.props.spoiler) {
|
||||
case true: this.focusSpoilerInput(); break;
|
||||
case false: this.focusTextarea(); break;
|
||||
case true: this.focusSpoilerInput(); break;
|
||||
case false: this.focusTextarea(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,32 +55,32 @@ class PrivacyDropdownMenu extends React.PureComponent {
|
|||
});
|
||||
let element = null;
|
||||
|
||||
switch(e.key) {
|
||||
case 'Escape':
|
||||
this.props.onClose();
|
||||
break;
|
||||
case 'Enter':
|
||||
this.handleClick(e);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
element = this.node.childNodes[index + 1] || this.node.firstChild;
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
element = this.node.childNodes[index - 1] || this.node.lastChild;
|
||||
break;
|
||||
case 'Tab':
|
||||
if (e.shiftKey) {
|
||||
element = this.node.childNodes[index - 1] || this.node.lastChild;
|
||||
} else {
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
this.props.onClose();
|
||||
break;
|
||||
case 'Enter':
|
||||
this.handleClick(e);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
element = this.node.childNodes[index + 1] || this.node.firstChild;
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
element = this.node.firstChild;
|
||||
break;
|
||||
case 'End':
|
||||
element = this.node.lastChild;
|
||||
break;
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
element = this.node.childNodes[index - 1] || this.node.lastChild;
|
||||
break;
|
||||
case 'Tab':
|
||||
if (e.shiftKey) {
|
||||
element = this.node.childNodes[index - 1] || this.node.lastChild;
|
||||
} else {
|
||||
element = this.node.childNodes[index + 1] || this.node.firstChild;
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
element = this.node.firstChild;
|
||||
break;
|
||||
case 'End':
|
||||
element = this.node.lastChild;
|
||||
break;
|
||||
}
|
||||
|
||||
if (element) {
|
||||
|
@ -211,10 +211,10 @@ class PrivacyDropdown extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleKeyDown = e => {
|
||||
switch(e.key) {
|
||||
case 'Escape':
|
||||
this.handleClose();
|
||||
break;
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
this.handleClose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,11 +225,11 @@ class PrivacyDropdown extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleButtonKeyDown = (e) => {
|
||||
switch(e.key) {
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
this.handleMouseDown();
|
||||
break;
|
||||
switch (e.key) {
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
this.handleMouseDown();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,129 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import IconButton from 'soapbox/components/icon_button';
|
||||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||
import { DatePicker } from 'soapbox/features/ui/util/async-components';
|
||||
|
||||
import { setSchedule, removeSchedule } from '../../../actions/compose';
|
||||
|
||||
const messages = defineMessages({
|
||||
schedule: { id: 'schedule.post_time', defaultMessage: 'Post Date/Time' },
|
||||
remove: { id: 'schedule.remove', defaultMessage: 'Remove schedule' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
active: state.getIn(['compose', 'schedule']) ? true : false,
|
||||
scheduledAt: state.getIn(['compose', 'schedule']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onSchedule(date) {
|
||||
dispatch(setSchedule(date));
|
||||
},
|
||||
|
||||
onRemoveSchedule(date) {
|
||||
dispatch(removeSchedule());
|
||||
},
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class ScheduleForm extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
scheduledAt: PropTypes.instanceOf(Date),
|
||||
intl: PropTypes.object.isRequired,
|
||||
onSchedule: PropTypes.func.isRequired,
|
||||
onRemoveSchedule: PropTypes.func.isRequired,
|
||||
dispatch: PropTypes.func,
|
||||
active: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
initialized: false,
|
||||
}
|
||||
|
||||
setSchedule = date => {
|
||||
this.props.onSchedule(date);
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.datePicker = c;
|
||||
}
|
||||
|
||||
openDatePicker = () => {
|
||||
if (!this.datePicker) return;
|
||||
this.datePicker.setOpen(true);
|
||||
}
|
||||
|
||||
isCurrentOrFutureDate(date) {
|
||||
return date && new Date().setHours(0, 0, 0, 0) <= new Date(date).setHours(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
isFiveMinutesFromNow(time) {
|
||||
const fiveMinutesFromNow = new Date(new Date().getTime() + 300000); // now, plus five minutes (Pleroma won't schedule posts )
|
||||
const selectedDate = new Date(time);
|
||||
|
||||
return fiveMinutesFromNow.getTime() < selectedDate.getTime();
|
||||
}
|
||||
|
||||
handleRemove = e => {
|
||||
this.props.onRemoveSchedule();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
initialize = () => {
|
||||
const { initialized } = this.state;
|
||||
|
||||
if (!initialized && this.datePicker) {
|
||||
this.openDatePicker();
|
||||
this.setState({ initialized: true });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.active) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { intl, scheduledAt } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classNames('datepicker', { 'datepicker--error': !this.isFiveMinutesFromNow(scheduledAt) })}>
|
||||
<div className='datepicker__hint'>
|
||||
<FormattedMessage id='datepicker.hint' defaultMessage='Scheduled to post at…' />
|
||||
</div>
|
||||
<div className='datepicker__input'>
|
||||
<BundleContainer fetchComponent={DatePicker}>
|
||||
{Component => (<Component
|
||||
selected={scheduledAt}
|
||||
showTimeSelect
|
||||
dateFormat='MMMM d, yyyy h:mm aa'
|
||||
timeIntervals={15}
|
||||
wrapperClassName='react-datepicker-wrapper'
|
||||
onChange={this.setSchedule}
|
||||
placeholderText={this.props.intl.formatMessage(messages.schedule)}
|
||||
filterDate={this.isCurrentOrFutureDate}
|
||||
filterTime={this.isFiveMinutesFromNow}
|
||||
ref={this.setRef}
|
||||
/>)}
|
||||
</BundleContainer>
|
||||
<div className='datepicker__cancel'>
|
||||
<IconButton title={intl.formatMessage(messages.remove)} src={require('@tabler/icons/icons/x.svg')} onClick={this.handleRemove} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
'use strict';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { setSchedule, removeSchedule } from 'soapbox/actions/compose';
|
||||
import IconButton from 'soapbox/components/icon_button';
|
||||
import { HStack, Stack, Text } from 'soapbox/components/ui';
|
||||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||
import { DatePicker } from 'soapbox/features/ui/util/async-components';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
const isCurrentOrFutureDate = (date: Date) => {
|
||||
return date && new Date().setHours(0, 0, 0, 0) <= new Date(date).setHours(0, 0, 0, 0);
|
||||
};
|
||||
|
||||
const isFiveMinutesFromNow = (time: Date) => {
|
||||
const fiveMinutesFromNow = new Date(new Date().getTime() + 300000); // now, plus five minutes (Pleroma won't schedule posts )
|
||||
const selectedDate = new Date(time);
|
||||
|
||||
return fiveMinutesFromNow.getTime() < selectedDate.getTime();
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
schedule: { id: 'schedule.post_time', defaultMessage: 'Post Date/Time' },
|
||||
remove: { id: 'schedule.remove', defaultMessage: 'Remove schedule' },
|
||||
});
|
||||
|
||||
const ScheduleForm: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const scheduledAt = useAppSelector((state) => state.compose.get('schedule'));
|
||||
const active = !!scheduledAt;
|
||||
|
||||
const onSchedule = (date: Date) => {
|
||||
dispatch(setSchedule(date));
|
||||
};
|
||||
|
||||
const handleRemove = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
dispatch(removeSchedule());
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
if (!active) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack className='mb-2' space={1}>
|
||||
<Text theme='muted'>
|
||||
<FormattedMessage id='datepicker.hint' defaultMessage='Scheduled to post at…' />
|
||||
</Text>
|
||||
<HStack space={2} alignItems='center'>
|
||||
<BundleContainer fetchComponent={DatePicker}>
|
||||
{Component => (<Component
|
||||
selected={scheduledAt}
|
||||
showTimeSelect
|
||||
dateFormat='MMMM d, yyyy h:mm aa'
|
||||
timeIntervals={15}
|
||||
wrapperClassName='react-datepicker-wrapper'
|
||||
onChange={onSchedule}
|
||||
placeholderText={intl.formatMessage(messages.schedule)}
|
||||
filterDate={isCurrentOrFutureDate}
|
||||
filterTime={isFiveMinutesFromNow}
|
||||
className={classNames({
|
||||
'has-error': !isFiveMinutesFromNow(scheduledAt),
|
||||
})}
|
||||
/>)}
|
||||
</BundleContainer>
|
||||
<IconButton
|
||||
iconClassName='w-4 h-4'
|
||||
className='bg-transparent text-gray-400 hover:text-gray-600'
|
||||
src={require('@tabler/icons/icons/x.svg')}
|
||||
onClick={handleRemove}
|
||||
title={intl.formatMessage(messages.remove)}
|
||||
/>
|
||||
</HStack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScheduleForm;
|
|
@ -158,7 +158,7 @@ class Upload extends ImmutablePureComponent {
|
|||
className={classNames('compose-form__upload-thumbnail', `${mediaType}`)}
|
||||
style={{
|
||||
transform: `scale(${scale})`,
|
||||
backgroundImage: mediaType === 'image' ? `url(${media.get('preview_url')})`: null,
|
||||
backgroundImage: mediaType === 'image' ? `url(${media.get('preview_url')})` : null,
|
||||
backgroundPosition: `${x}% ${y}%` }}
|
||||
>
|
||||
<div className={classNames('compose-form__upload__actions', { active })}>
|
||||
|
|
|
@ -177,8 +177,8 @@ export const urlRegex = (function() {
|
|||
'(?:' +
|
||||
'#{validGeneralUrlPathChars}*' +
|
||||
'(?:#{validUrlBalancedParens}#{validGeneralUrlPathChars}*)*' +
|
||||
'#{validUrlPathEndingChars}'+
|
||||
')|(?:@#{validGeneralUrlPathChars}+\/)'+
|
||||
'#{validUrlPathEndingChars}' +
|
||||
')|(?:@#{validGeneralUrlPathChars}+\/)' +
|
||||
')', 'i');
|
||||
regexen.validUrlQueryChars = /[a-z0-9!?\*'@\(\);:&=\+\$\/%#\[\]\-_\.,~|]/i;
|
||||
regexen.validUrlQueryEndingChars = /[a-z0-9_&=#\/]/i;
|
||||
|
|
|
@ -48,7 +48,7 @@ const Developers = () => {
|
|||
</Link>
|
||||
|
||||
<Link to='/developers/timeline' className='bg-gray-200 dark:bg-gray-600 p-4 rounded flex flex-col items-center justify-center space-y-2 hover:-translate-y-1 transition-transform'>
|
||||
<SvgIcon src={require('icons/feed.svg')} className='dark:text-gray-100' />
|
||||
<SvgIcon src={require('@tabler/icons/icons/home.svg')} className='dark:text-gray-100' />
|
||||
|
||||
<Text>
|
||||
<FormattedMessage id='developers.navigation.test_timeline_label' defaultMessage='Test timeline' />
|
||||
|
|
|
@ -409,7 +409,7 @@ const EditProfile: React.FC = () => {
|
|||
hint={<FormattedMessage id='edit_profile.hints.hide_network' defaultMessage='Who you follow and who follows you will not be shown on your profile' />}
|
||||
>
|
||||
<Toggle
|
||||
checked={account ? hidesNetwork(account): false}
|
||||
checked={account ? hidesNetwork(account) : false}
|
||||
onChange={handleHideNetworkChange}
|
||||
/>
|
||||
</ListItem>
|
||||
|
|
|
@ -132,12 +132,12 @@ class Introduction extends React.PureComponent {
|
|||
|
||||
handleKeyUp = ({ key }) => {
|
||||
switch (key) {
|
||||
case 'ArrowLeft':
|
||||
this.handlePrev();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
this.handleNext();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
this.handlePrev();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
this.handleNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -214,46 +214,46 @@ const Notification: React.FC<INotificaton> = (props) => {
|
|||
|
||||
const renderContent = () => {
|
||||
switch (type) {
|
||||
case 'follow':
|
||||
case 'follow_request':
|
||||
return account && typeof account === 'object' ? (
|
||||
<AccountContainer
|
||||
id={account.id}
|
||||
hidden={hidden}
|
||||
avatarSize={48}
|
||||
/>
|
||||
) : null;
|
||||
case 'move':
|
||||
return account && typeof account === 'object' && notification.target && typeof notification.target === 'object' ? (
|
||||
<AccountContainer
|
||||
id={notification.target.id}
|
||||
hidden={hidden}
|
||||
avatarSize={48}
|
||||
/>
|
||||
) : null;
|
||||
case 'favourite':
|
||||
case 'mention':
|
||||
case 'reblog':
|
||||
case 'status':
|
||||
case 'poll':
|
||||
case 'pleroma:emoji_reaction':
|
||||
return status && typeof status === 'object' ? (
|
||||
<StatusContainer
|
||||
case 'follow':
|
||||
case 'follow_request':
|
||||
return account && typeof account === 'object' ? (
|
||||
<AccountContainer
|
||||
id={account.id}
|
||||
hidden={hidden}
|
||||
avatarSize={48}
|
||||
/>
|
||||
) : null;
|
||||
case 'move':
|
||||
return account && typeof account === 'object' && notification.target && typeof notification.target === 'object' ? (
|
||||
<AccountContainer
|
||||
id={notification.target.id}
|
||||
hidden={hidden}
|
||||
avatarSize={48}
|
||||
/>
|
||||
) : null;
|
||||
case 'favourite':
|
||||
case 'mention':
|
||||
case 'reblog':
|
||||
case 'status':
|
||||
case 'poll':
|
||||
case 'pleroma:emoji_reaction':
|
||||
return status && typeof status === 'object' ? (
|
||||
<StatusContainer
|
||||
// @ts-ignore
|
||||
id={status.id}
|
||||
withDismiss
|
||||
hidden={hidden}
|
||||
onMoveDown={handleMoveDown}
|
||||
onMoveUp={handleMoveUp}
|
||||
contextType='notifications'
|
||||
getScrollPosition={props.getScrollPosition}
|
||||
updateScrollBottom={props.updateScrollBottom}
|
||||
cachedMediaWidth={props.cachedMediaWidth}
|
||||
cacheMediaWidth={props.cacheMediaWidth}
|
||||
/>
|
||||
) : null;
|
||||
default:
|
||||
return null;
|
||||
id={status.id}
|
||||
withDismiss
|
||||
hidden={hidden}
|
||||
onMoveDown={handleMoveDown}
|
||||
onMoveUp={handleMoveUp}
|
||||
contextType='notifications'
|
||||
getScrollPosition={props.getScrollPosition}
|
||||
updateScrollBottom={props.updateScrollBottom}
|
||||
cachedMediaWidth={props.cachedMediaWidth}
|
||||
cacheMediaWidth={props.cacheMediaWidth}
|
||||
/>
|
||||
) : null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -46,12 +46,12 @@ const OnboardingWizard = () => {
|
|||
|
||||
const handleKeyUp = ({ key }: KeyboardEvent): void => {
|
||||
switch (key) {
|
||||
case 'ArrowLeft':
|
||||
handlePreviousStep();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
handleNextStep();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
handlePreviousStep();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
handleNextStep();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
@ -6,6 +5,7 @@ import { Link, Redirect } from 'react-router-dom';
|
|||
|
||||
import { logIn, verifyCredentials } from 'soapbox/actions/auth';
|
||||
import { fetchInstance } from 'soapbox/actions/instance';
|
||||
import SiteLogo from 'soapbox/components/site-logo';
|
||||
import { useAppSelector, useFeatures, useSoapboxConfig } from 'soapbox/hooks';
|
||||
|
||||
import { openModal } from '../../../actions/modals';
|
||||
|
@ -31,7 +31,6 @@ const Header = () => {
|
|||
const soapboxConfig = useSoapboxConfig();
|
||||
const pepeEnabled = soapboxConfig.getIn(['extensions', 'pepe', 'enabled']) === true;
|
||||
|
||||
const { logo, logoDarkMode } = soapboxConfig;
|
||||
const features = useFeatures();
|
||||
const instance = useAppSelector((state) => state.instance);
|
||||
const isOpen = features.accountCreation && instance.registrations;
|
||||
|
@ -80,11 +79,7 @@ const Header = () => {
|
|||
<Sonar />
|
||||
</div>
|
||||
<Link to='/' className='z-10'>
|
||||
<img alt='Logo' src={logo} className={classNames('h-6 w-auto cursor-pointer', { 'dark:hidden': logoDarkMode })} />
|
||||
{logoDarkMode && (
|
||||
<img alt='Logo' src={logoDarkMode} className='h-6 w-auto cursor-pointer hidden dark:block' />
|
||||
)}
|
||||
|
||||
<SiteLogo alt='Logo' className='h-6 w-auto cursor-pointer' />
|
||||
<span className='hidden'>{intl.formatMessage(messages.home)}</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import React from 'react';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
instance: state.get('instance'),
|
||||
soapbox: getSoapboxConfig(state),
|
||||
});
|
||||
|
||||
class SiteBanner extends ImmutablePureComponent {
|
||||
|
||||
render() {
|
||||
const { instance, soapbox } = this.props;
|
||||
const logos = {
|
||||
imgLogo: (<img alt={instance.get('title')} src={soapbox.get('banner')} />),
|
||||
textLogo: (<h1>{instance.get('title')}</h1>),
|
||||
};
|
||||
return soapbox.getIn(['banner']) ? logos.imgLogo : logos.textLogo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(SiteBanner);
|
|
@ -1,25 +0,0 @@
|
|||
import React from 'react';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
instance: state.get('instance'),
|
||||
soapbox: getSoapboxConfig(state),
|
||||
});
|
||||
|
||||
class SiteLogo extends ImmutablePureComponent {
|
||||
|
||||
render() {
|
||||
const { instance, soapbox } = this.props;
|
||||
const logos = {
|
||||
imgLogo: (<img alt={instance.get('title')} src={soapbox.get('logo')} />),
|
||||
textLogo: (<h1>{instance.get('title')}</h1>),
|
||||
};
|
||||
return soapbox.getIn(['logo']) ? logos.imgLogo : logos.textLogo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(SiteLogo);
|
|
@ -1,69 +0,0 @@
|
|||
import React from 'react';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
import { Switch, Route, Redirect } from 'react-router-dom';
|
||||
|
||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||
import LandingGradient from 'soapbox/components/landing-gradient';
|
||||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||
import {
|
||||
NotificationsContainer,
|
||||
ModalContainer,
|
||||
} from 'soapbox/features/ui/util/async-components';
|
||||
import { isStandalone } from 'soapbox/utils/state';
|
||||
|
||||
import AboutPage from '../about';
|
||||
import LandingPage from '../landing_page';
|
||||
import MobilePage from '../mobile';
|
||||
|
||||
import Footer from './components/footer';
|
||||
import Header from './components/header';
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
soapbox: getSoapboxConfig(state),
|
||||
standalone: isStandalone(state),
|
||||
});
|
||||
|
||||
class PublicLayout extends ImmutablePureComponent {
|
||||
|
||||
render() {
|
||||
const { standalone } = this.props;
|
||||
|
||||
if (standalone) {
|
||||
return <Redirect to='/login/external' />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='h-full'>
|
||||
<LandingGradient />
|
||||
|
||||
<div className='flex flex-col h-screen'>
|
||||
<div className='flex-shrink-0'>
|
||||
<Header />
|
||||
|
||||
<div className='relative'>
|
||||
<Switch>
|
||||
<Route exact path='/' component={LandingPage} />
|
||||
<Route exact path='/about/:slug?' component={AboutPage} />
|
||||
<Route exact path='/mobile/:slug?' component={MobilePage} />
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
|
||||
<BundleContainer fetchComponent={NotificationsContainer}>
|
||||
{(Component) => <Component />}
|
||||
</BundleContainer>
|
||||
|
||||
<BundleContainer fetchComponent={ModalContainer}>
|
||||
{(Component) => <Component />}
|
||||
</BundleContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(PublicLayout);
|
|
@ -0,0 +1,58 @@
|
|||
import React from 'react';
|
||||
import { Switch, Route, Redirect } from 'react-router-dom';
|
||||
|
||||
import LandingGradient from 'soapbox/components/landing-gradient';
|
||||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||
import {
|
||||
NotificationsContainer,
|
||||
ModalContainer,
|
||||
} from 'soapbox/features/ui/util/async-components';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { isStandalone } from 'soapbox/utils/state';
|
||||
|
||||
import AboutPage from '../about';
|
||||
import LandingPage from '../landing_page';
|
||||
import MobilePage from '../mobile';
|
||||
|
||||
import Footer from './components/footer';
|
||||
import Header from './components/header';
|
||||
|
||||
const PublicLayout = () => {
|
||||
const standalone = useAppSelector((state) => isStandalone(state));
|
||||
|
||||
if (standalone) {
|
||||
return <Redirect to='/login/external' />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='h-full'>
|
||||
<LandingGradient />
|
||||
|
||||
<div className='flex flex-col h-screen'>
|
||||
<div className='flex-shrink-0'>
|
||||
<Header />
|
||||
|
||||
<div className='relative'>
|
||||
<Switch>
|
||||
<Route exact path='/' component={LandingPage} />
|
||||
<Route exact path='/about/:slug?' component={AboutPage} />
|
||||
<Route exact path='/mobile/:slug?' component={MobilePage} />
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
|
||||
<BundleContainer fetchComponent={NotificationsContainer}>
|
||||
{(Component) => <Component />}
|
||||
</BundleContainer>
|
||||
|
||||
<BundleContainer fetchComponent={ModalContainer}>
|
||||
{(Component) => <Component />}
|
||||
</BundleContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PublicLayout;
|
|
@ -4,6 +4,7 @@ import { FormattedMessage } from 'react-intl';
|
|||
|
||||
import { defaultSettings } from 'soapbox/actions/settings';
|
||||
import BackgroundShapes from 'soapbox/features/ui/components/background_shapes';
|
||||
import { useSystemTheme } from 'soapbox/hooks';
|
||||
import { normalizeSoapboxConfig } from 'soapbox/normalizers';
|
||||
import { generateThemeCss } from 'soapbox/utils/theme';
|
||||
|
||||
|
@ -17,7 +18,10 @@ const SitePreview: React.FC<ISitePreview> = ({ soapbox }) => {
|
|||
const soapboxConfig = useMemo(() => normalizeSoapboxConfig(soapbox), [soapbox]);
|
||||
const settings = defaultSettings.mergeDeep(soapboxConfig.defaultSettings);
|
||||
|
||||
const dark = settings.get('themeMode') === 'dark';
|
||||
const userTheme = settings.get('themeMode');
|
||||
const systemTheme = useSystemTheme();
|
||||
|
||||
const dark = userTheme === 'dark' || (userTheme === 'system' && systemTheme === 'dark');
|
||||
|
||||
const bodyClass = classNames(
|
||||
'site-preview',
|
||||
|
|
|
@ -11,7 +11,7 @@ const ComposeButton = () => {
|
|||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<Button icon={require('icons/compose.svg')} block size='lg' onClick={onOpenCompose}>
|
||||
<Button icon={require('icons/pen-plus.svg')} block size='lg' onClick={onOpenCompose}>
|
||||
<span><FormattedMessage id='navigation.compose' defaultMessage='Compose' /></span>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -22,7 +22,7 @@ const moneyFormat = (amount: number): string => (
|
|||
currency: 'usd',
|
||||
notation: 'compact',
|
||||
})
|
||||
.format(amount/100)
|
||||
.format(amount / 100)
|
||||
);
|
||||
|
||||
const FundingPanel: React.FC = () => {
|
||||
|
@ -59,7 +59,7 @@ const FundingPanel: React.FC = () => {
|
|||
<div className='funding-panel__ratio'>
|
||||
<Text>{ratioText}</Text>
|
||||
</div>
|
||||
<ProgressBar progress={amount/goal} />
|
||||
<ProgressBar progress={amount / goal} />
|
||||
<div className='funding-panel__description'>
|
||||
<Text>{goalText}</Text>
|
||||
</div>
|
||||
|
|
|
@ -57,17 +57,17 @@ class MediaModal extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
switch(e.key) {
|
||||
case 'ArrowLeft':
|
||||
this.handlePrevClick();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
this.handleNextClick();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
break;
|
||||
switch (e.key) {
|
||||
case 'ArrowLeft':
|
||||
this.handlePrevClick();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
this.handleNextClick();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import classNames from 'classnames';
|
|||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import SiteLogo from 'soapbox/components/site-logo';
|
||||
import { Button } from 'soapbox/components/ui';
|
||||
import { Modal } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useFeatures, useSoapboxConfig } from 'soapbox/hooks';
|
||||
|
@ -23,7 +24,6 @@ const LandingPageModal: React.FC<ILandingPageModal> = ({ onClose }) => {
|
|||
const soapboxConfig = useSoapboxConfig();
|
||||
const pepeEnabled = soapboxConfig.getIn(['extensions', 'pepe', 'enabled']) === true;
|
||||
|
||||
const { logo } = soapboxConfig;
|
||||
const instance = useAppSelector((state) => state.instance);
|
||||
const features = useFeatures();
|
||||
|
||||
|
@ -32,7 +32,7 @@ const LandingPageModal: React.FC<ILandingPageModal> = ({ onClose }) => {
|
|||
|
||||
return (
|
||||
<Modal
|
||||
title={<img alt='Logo' src={logo} className='h-4 w-auto' />}
|
||||
title={<SiteLogo alt='Logo' className='h-4 w-auto' />}
|
||||
onClose={() => onClose('LANDING_PAGE')}
|
||||
>
|
||||
<div className='mt-4 divide-y divide-solid divide-gray-200 dark:divide-slate-700'>
|
||||
|
|
|
@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { blockAccount } from 'soapbox/actions/accounts';
|
||||
import { submitReport, cancelReport, submitReportSuccess, submitReportFail } from 'soapbox/actions/reports';
|
||||
import { submitReport, submitReportSuccess, submitReportFail } from 'soapbox/actions/reports';
|
||||
import { expandAccountTimeline } from 'soapbox/actions/timelines';
|
||||
import AttachmentThumbs from 'soapbox/components/attachment_thumbs';
|
||||
import StatusContent from 'soapbox/components/status_content';
|
||||
|
@ -87,6 +87,7 @@ const ReportModal = ({ onClose }: IReportModal) => {
|
|||
const ruleIds = useAppSelector((state) => state.reports.getIn(['new', 'rule_ids']) as ImmutableSet<string>);
|
||||
const selectedStatusIds = useAppSelector((state) => state.reports.getIn(['new', 'status_ids']) as ImmutableSet<string>);
|
||||
|
||||
const isReportingAccount = useMemo(() => selectedStatusIds.size === 0, []);
|
||||
const shouldRequireRule = rules.length > 0;
|
||||
|
||||
const [currentStep, setCurrentStep] = useState<Steps>(Steps.ONE);
|
||||
|
@ -101,49 +102,44 @@ const ReportModal = ({ onClose }: IReportModal) => {
|
|||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
dispatch(cancelReport());
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleNextStep = () => {
|
||||
switch (currentStep) {
|
||||
case Steps.ONE:
|
||||
setCurrentStep(Steps.TWO);
|
||||
break;
|
||||
case Steps.TWO:
|
||||
handleSubmit();
|
||||
break;
|
||||
case Steps.THREE:
|
||||
dispatch(submitReportSuccess());
|
||||
onClose();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case Steps.ONE:
|
||||
setCurrentStep(Steps.TWO);
|
||||
break;
|
||||
case Steps.TWO:
|
||||
handleSubmit();
|
||||
break;
|
||||
case Steps.THREE:
|
||||
dispatch(submitReportSuccess());
|
||||
onClose();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const renderSelectedStatuses = useCallback(() => {
|
||||
switch (selectedStatusIds.size) {
|
||||
case 0:
|
||||
return (
|
||||
<div className='bg-gray-100 dark:bg-slate-700 p-4 rounded-lg flex items-center justify-center w-full'>
|
||||
<Text theme='muted'>{intl.formatMessage(messages.blankslate)}</Text>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return <SelectedStatus statusId={selectedStatusIds.first()} />;
|
||||
case 0:
|
||||
return (
|
||||
<div className='bg-gray-100 dark:bg-slate-700 p-4 rounded-lg flex items-center justify-center w-full'>
|
||||
<Text theme='muted'>{intl.formatMessage(messages.blankslate)}</Text>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return <SelectedStatus statusId={selectedStatusIds.first()} />;
|
||||
}
|
||||
}, [selectedStatusIds.size]);
|
||||
|
||||
const confirmationText = useMemo(() => {
|
||||
switch (currentStep) {
|
||||
case Steps.TWO:
|
||||
return intl.formatMessage(messages.submit);
|
||||
case Steps.THREE:
|
||||
return intl.formatMessage(messages.done);
|
||||
default:
|
||||
return intl.formatMessage(messages.next);
|
||||
case Steps.TWO:
|
||||
return intl.formatMessage(messages.submit);
|
||||
case Steps.THREE:
|
||||
return intl.formatMessage(messages.done);
|
||||
default:
|
||||
return intl.formatMessage(messages.next);
|
||||
}
|
||||
}, [currentStep]);
|
||||
|
||||
|
@ -152,19 +148,19 @@ const ReportModal = ({ onClose }: IReportModal) => {
|
|||
return false;
|
||||
}
|
||||
|
||||
return isSubmitting || (shouldRequireRule && ruleIds.isEmpty()) || selectedStatusIds.size === 0;
|
||||
}, [currentStep, isSubmitting, shouldRequireRule, ruleIds, selectedStatusIds.size]);
|
||||
return isSubmitting || (shouldRequireRule && ruleIds.isEmpty()) || (!isReportingAccount && selectedStatusIds.size === 0);
|
||||
}, [currentStep, isSubmitting, shouldRequireRule, ruleIds, selectedStatusIds.size, isReportingAccount]);
|
||||
|
||||
const calculateProgress = useCallback(() => {
|
||||
switch (currentStep) {
|
||||
case Steps.ONE:
|
||||
return 0.33;
|
||||
case Steps.TWO:
|
||||
return 0.66;
|
||||
case Steps.THREE:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
case Steps.ONE:
|
||||
return 0.33;
|
||||
case Steps.TWO:
|
||||
return 0.66;
|
||||
case Steps.THREE:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}, [currentStep]);
|
||||
|
||||
|
@ -183,7 +179,7 @@ const ReportModal = ({ onClose }: IReportModal) => {
|
|||
return (
|
||||
<Modal
|
||||
title={<FormattedMessage id='report.target' defaultMessage='Reporting {target}' values={{ target: <strong>@{account.acct}</strong> }} />}
|
||||
onClose={handleClose}
|
||||
onClose={onClose}
|
||||
cancelAction={currentStep === Steps.THREE ? undefined : onClose}
|
||||
confirmationAction={handleNextStep}
|
||||
confirmationText={confirmationText}
|
||||
|
@ -193,7 +189,7 @@ const ReportModal = ({ onClose }: IReportModal) => {
|
|||
<Stack space={4}>
|
||||
<ProgressBar progress={calculateProgress()} />
|
||||
|
||||
{currentStep !== Steps.THREE && renderSelectedStatuses()}
|
||||
{(currentStep !== Steps.THREE && !isReportingAccount) && renderSelectedStatuses()}
|
||||
|
||||
<StepToRender account={account} />
|
||||
</Stack>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import classNames from 'classnames';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
|
@ -36,6 +36,9 @@ const ReasonStep = (_props: IReasonStep) => {
|
|||
const ruleIds = useAppSelector((state) => state.reports.getIn(['new', 'rule_ids']) as ImmutableSet<string>);
|
||||
const shouldRequireRule = rules.length > 0;
|
||||
|
||||
const selectedStatusIds = useAppSelector((state) => state.reports.getIn(['new', 'status_ids']) as ImmutableSet<string>);
|
||||
const isReportingAccount = useMemo(() => selectedStatusIds.size === 0, []);
|
||||
|
||||
const handleCommentChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
dispatch(changeReportComment(event.target.value));
|
||||
};
|
||||
|
@ -58,6 +61,16 @@ const ReasonStep = (_props: IReasonStep) => {
|
|||
}
|
||||
};
|
||||
|
||||
const filterRuleType = (rule: any) => {
|
||||
const ruleTypeToFilter = isReportingAccount ? 'account' : 'content';
|
||||
|
||||
if (rule.rule_type) {
|
||||
return rule.rule_type === ruleTypeToFilter;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchRules());
|
||||
}, []);
|
||||
|
@ -87,7 +100,7 @@ const ReasonStep = (_props: IReasonStep) => {
|
|||
onScroll={handleRulesScrolling}
|
||||
ref={rulesListRef}
|
||||
>
|
||||
{rules.map((rule, idx) => {
|
||||
{rules.filter(filterRuleType).map((rule, idx) => {
|
||||
const isSelected = ruleIds.includes(String(rule.id));
|
||||
|
||||
return (
|
||||
|
|
|
@ -4,9 +4,10 @@ import { FormattedMessage } from 'react-intl';
|
|||
import { useDispatch } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Avatar, Button, Icon } from 'soapbox/components/ui';
|
||||
import SiteLogo from 'soapbox/components/site-logo';
|
||||
import { Avatar, Button } from 'soapbox/components/ui';
|
||||
import Search from 'soapbox/features/compose/components/search';
|
||||
import { useOwnAccount, useSoapboxConfig, useSettings } from 'soapbox/hooks';
|
||||
import { useOwnAccount, useSoapboxConfig } from 'soapbox/hooks';
|
||||
|
||||
import { openSidebar } from '../../../actions/sidebar';
|
||||
|
||||
|
@ -17,14 +18,9 @@ const Navbar = () => {
|
|||
const node = React.useRef(null);
|
||||
|
||||
const account = useOwnAccount();
|
||||
const settings = useSettings();
|
||||
const soapboxConfig = useSoapboxConfig();
|
||||
const singleUserMode = soapboxConfig.get('singleUserMode');
|
||||
|
||||
// In demo mode, use the Soapbox logo
|
||||
const logo = settings.get('demo') ? require('images/soapbox-logo.svg') : soapboxConfig.logo;
|
||||
const logoDarkMode = soapboxConfig.logoDarkMode;
|
||||
|
||||
const onOpenSidebar = () => dispatch(openSidebar());
|
||||
|
||||
return (
|
||||
|
@ -46,21 +42,10 @@ const Navbar = () => {
|
|||
'justify-start': !account,
|
||||
})}
|
||||
>
|
||||
{logo ? (
|
||||
<Link key='logo' to='/' data-preview-title-id='column.home' className='flex-shrink-0 flex items-center'>
|
||||
<img alt='Logo' src={logo} className={classNames('h-5 lg:h-6 w-auto cursor-pointer', { 'dark:hidden': logoDarkMode })} />
|
||||
{logoDarkMode && (
|
||||
<img alt='Logo' src={logoDarkMode} className='h-5 lg:h-6 w-auto cursor-pointer hidden dark:block' />
|
||||
)}
|
||||
|
||||
<span className='hidden'><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></span>
|
||||
</Link>
|
||||
) : (
|
||||
<Link key='logo' to='/' data-preview-title-id='column.home' className='flex-shrink-0 flex items-center'>
|
||||
<Icon alt='Logo' src={require('@tabler/icons/icons/home.svg')} className='h-5 lg:h-6 w-auto text-primary-700' />
|
||||
<span className='hidden'><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></span>
|
||||
</Link>
|
||||
)}
|
||||
<Link key='logo' to='/' data-preview-title-id='column.home' className='flex-shrink-0 flex items-center'>
|
||||
<SiteLogo alt='Logo' className='h-5 lg:h-6 w-auto cursor-pointer' />
|
||||
<span className='hidden'><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></span>
|
||||
</Link>
|
||||
|
||||
{account && (
|
||||
<div className='flex-1 hidden lg:flex justify-center px-2 lg:ml-6 lg:justify-start items-center'>
|
||||
|
|
|
@ -20,14 +20,14 @@ const ThemeSelector: React.FC<IThemeSelector> = ({ value, onChange }) => {
|
|||
|
||||
const themeIconSrc = useMemo(() => {
|
||||
switch (value) {
|
||||
case 'system':
|
||||
return require('@tabler/icons/icons/device-desktop.svg');
|
||||
case 'light':
|
||||
return require('@tabler/icons/icons/sun.svg');
|
||||
case 'dark':
|
||||
return require('@tabler/icons/icons/moon.svg');
|
||||
default:
|
||||
return null;
|
||||
case 'system':
|
||||
return require('@tabler/icons/icons/device-desktop.svg');
|
||||
case 'light':
|
||||
return require('@tabler/icons/icons/sun.svg');
|
||||
case 'dark':
|
||||
return require('@tabler/icons/icons/moon.svg');
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
|
|
|
@ -20,12 +20,12 @@ const UploadArea: React.FC<IUploadArea> = ({ active, onClose }) => {
|
|||
const keyCode = e.keyCode;
|
||||
|
||||
if (active) {
|
||||
switch(keyCode) {
|
||||
case 27:
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onClose();
|
||||
break;
|
||||
switch (keyCode) {
|
||||
case 27:
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onClose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { cancelReport } from 'soapbox/actions/reports';
|
||||
|
||||
import { cancelReplyCompose } from '../../../actions/compose';
|
||||
import { closeModal } from '../../../actions/modals';
|
||||
import ModalRoot from '../components/modal_root';
|
||||
|
@ -18,8 +20,15 @@ const mapStateToProps = state => {
|
|||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onClose(type) {
|
||||
if (type === 'COMPOSE') {
|
||||
dispatch(cancelReplyCompose());
|
||||
switch (type) {
|
||||
case 'COMPOSE':
|
||||
dispatch(cancelReplyCompose());
|
||||
break;
|
||||
case 'REPORT':
|
||||
dispatch(cancelReport());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
dispatch(closeModal(type));
|
||||
|
|
|
@ -115,24 +115,24 @@ const EmailPassThru = ({ match }) => {
|
|||
|
||||
if (errorKey) {
|
||||
switch (errorKey) {
|
||||
case 'token_expired':
|
||||
message = intl.formatMessage({
|
||||
id: 'email_passthru.fail.expired',
|
||||
defaultMessage: 'Your email token has expired.',
|
||||
});
|
||||
setStatus(Statuses.TOKEN_EXPIRED);
|
||||
break;
|
||||
case 'token_not_found':
|
||||
message = intl.formatMessage({
|
||||
id: 'email_passthru.fail.not_found',
|
||||
defaultMessage: 'Your email token is invalid.',
|
||||
});
|
||||
message = 'Your token is invalid';
|
||||
setStatus(Statuses.TOKEN_NOT_FOUND);
|
||||
break;
|
||||
default:
|
||||
setStatus(Statuses.GENERIC_FAIL);
|
||||
break;
|
||||
case 'token_expired':
|
||||
message = intl.formatMessage({
|
||||
id: 'email_passthru.fail.expired',
|
||||
defaultMessage: 'Your email token has expired.',
|
||||
});
|
||||
setStatus(Statuses.TOKEN_EXPIRED);
|
||||
break;
|
||||
case 'token_not_found':
|
||||
message = intl.formatMessage({
|
||||
id: 'email_passthru.fail.not_found',
|
||||
defaultMessage: 'Your email token is invalid.',
|
||||
});
|
||||
message = 'Your token is invalid';
|
||||
setStatus(Statuses.TOKEN_NOT_FOUND);
|
||||
break;
|
||||
default:
|
||||
setStatus(Statuses.GENERIC_FAIL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,16 +142,16 @@ const EmailPassThru = ({ match }) => {
|
|||
}, [token]);
|
||||
|
||||
switch (status) {
|
||||
case Statuses.SUCCESS:
|
||||
return <Success />;
|
||||
case Statuses.TOKEN_EXPIRED:
|
||||
return <TokenExpired />;
|
||||
case Statuses.TOKEN_NOT_FOUND:
|
||||
return <TokenNotFound />;
|
||||
case Statuses.GENERIC_FAIL:
|
||||
return <GenericFail />;
|
||||
default:
|
||||
return <Spinner />;
|
||||
case Statuses.SUCCESS:
|
||||
return <Success />;
|
||||
case Statuses.TOKEN_EXPIRED:
|
||||
return <TokenExpired />;
|
||||
case Statuses.TOKEN_NOT_FOUND:
|
||||
return <TokenNotFound />;
|
||||
case Statuses.GENERIC_FAIL:
|
||||
return <GenericFail />;
|
||||
default:
|
||||
return <Spinner />;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||
import LandingGradient from 'soapbox/components/landing-gradient';
|
||||
import SiteLogo from 'soapbox/components/site-logo';
|
||||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||
import { NotificationsContainer } from 'soapbox/features/ui/util/async-components';
|
||||
|
||||
|
@ -16,8 +16,6 @@ const WaitlistPage = ({ account }) => {
|
|||
const dispatch = useDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const logo = useSelector((state) => getSoapboxConfig(state).get('logo'));
|
||||
|
||||
const onClickLogOut = (event) => {
|
||||
event.preventDefault();
|
||||
dispatch(logOut(intl));
|
||||
|
@ -31,7 +29,7 @@ const WaitlistPage = ({ account }) => {
|
|||
<header className='relative flex justify-between h-16'>
|
||||
<div className='flex-1 flex items-stretch justify-center relative'>
|
||||
<Link to='/' className='cursor-pointer flex-shrink-0 flex items-center'>
|
||||
<img alt='Logo' src={logo} className='h-7' />
|
||||
<SiteLogo alt='Logo' className='h-7' />
|
||||
</Link>
|
||||
|
||||
<div className='absolute inset-y-0 right-0 flex items-center pr-2 space-x-3'>
|
||||
|
|
|
@ -290,42 +290,42 @@ class Video extends React.PureComponent {
|
|||
handleKeyDown = e => {
|
||||
const frameTime = 1 / 25;
|
||||
|
||||
switch(e.key) {
|
||||
case 'k':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.togglePlay();
|
||||
break;
|
||||
case 'm':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.toggleMute();
|
||||
break;
|
||||
case 'f':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.toggleFullscreen();
|
||||
break;
|
||||
case 'j':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.seekBy(-10);
|
||||
break;
|
||||
case 'l':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.seekBy(10);
|
||||
break;
|
||||
case ',':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.seekBy(-frameTime);
|
||||
break;
|
||||
case '.':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.seekBy(frameTime);
|
||||
break;
|
||||
switch (e.key) {
|
||||
case 'k':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.togglePlay();
|
||||
break;
|
||||
case 'm':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.toggleMute();
|
||||
break;
|
||||
case 'f':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.toggleFullscreen();
|
||||
break;
|
||||
case 'j':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.seekBy(-10);
|
||||
break;
|
||||
case 'l':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.seekBy(10);
|
||||
break;
|
||||
case ',':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.seekBy(-frameTime);
|
||||
break;
|
||||
case '.':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.seekBy(frameTime);
|
||||
break;
|
||||
}
|
||||
|
||||
// If we are in fullscreen mode, we don't want any hotkeys
|
||||
|
@ -505,7 +505,7 @@ class Video extends React.PureComponent {
|
|||
|
||||
if (inline && containerWidth) {
|
||||
width = containerWidth;
|
||||
const minSize = containerWidth / (16/9);
|
||||
const minSize = containerWidth / (16 / 9);
|
||||
|
||||
if (isPanoramic(aspectRatio)) {
|
||||
height = Math.max(Math.floor(containerWidth / maximumAspectRatio), minSize);
|
||||
|
|
|
@ -6,3 +6,4 @@ export { useOnScreen } from './useOnScreen';
|
|||
export { useOwnAccount } from './useOwnAccount';
|
||||
export { useSettings } from './useSettings';
|
||||
export { useSoapboxConfig } from './useSoapboxConfig';
|
||||
export { useSystemTheme } from './useSystemTheme';
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
|
||||
type SystemTheme = 'light' | 'dark';
|
||||
|
||||
/** Get the system color scheme of the system. */
|
||||
export const useSystemTheme = (): SystemTheme => {
|
||||
const query = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const [dark, setDark] = useState(query.matches);
|
||||
|
||||
const handleChange = (event: MediaQueryListEvent) => {
|
||||
setDark(event.matches);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
query.addEventListener('change', handleChange);
|
||||
|
||||
return () => query.removeEventListener('change', handleChange);
|
||||
}, []);
|
||||
|
||||
return dark ? 'dark' : 'light';
|
||||
};
|
|
@ -23,20 +23,20 @@ type State = ReturnType<typeof ReducerRecord>;
|
|||
|
||||
export default function account_notes(state: State = ReducerRecord(), action: AnyAction) {
|
||||
switch (action.type) {
|
||||
case ACCOUNT_NOTE_INIT_MODAL:
|
||||
return state.withMutations((state) => {
|
||||
state.setIn(['edit', 'isSubmitting'], false);
|
||||
state.setIn(['edit', 'account_id'], action.account.get('id'));
|
||||
state.setIn(['edit', 'comment'], action.comment);
|
||||
});
|
||||
case ACCOUNT_NOTE_CHANGE_COMMENT:
|
||||
return state.setIn(['edit', 'comment'], action.comment);
|
||||
case ACCOUNT_NOTE_SUBMIT_REQUEST:
|
||||
return state.setIn(['edit', 'isSubmitting'], true);
|
||||
case ACCOUNT_NOTE_SUBMIT_FAIL:
|
||||
case ACCOUNT_NOTE_SUBMIT_SUCCESS:
|
||||
return state.setIn(['edit', 'isSubmitting'], false);
|
||||
default:
|
||||
return state;
|
||||
case ACCOUNT_NOTE_INIT_MODAL:
|
||||
return state.withMutations((state) => {
|
||||
state.setIn(['edit', 'isSubmitting'], false);
|
||||
state.setIn(['edit', 'account_id'], action.account.get('id'));
|
||||
state.setIn(['edit', 'comment'], action.comment);
|
||||
});
|
||||
case ACCOUNT_NOTE_CHANGE_COMMENT:
|
||||
return state.setIn(['edit', 'comment'], action.comment);
|
||||
case ACCOUNT_NOTE_SUBMIT_REQUEST:
|
||||
return state.setIn(['edit', 'isSubmitting'], true);
|
||||
case ACCOUNT_NOTE_SUBMIT_FAIL:
|
||||
case ACCOUNT_NOTE_SUBMIT_SUCCESS:
|
||||
return state.setIn(['edit', 'isSubmitting'], false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -93,13 +93,13 @@ const addTags = (
|
|||
);
|
||||
|
||||
tags.forEach(tag => {
|
||||
switch(tag) {
|
||||
case 'verified':
|
||||
state.setIn([id, 'verified'], true);
|
||||
break;
|
||||
case 'donor':
|
||||
state.setIn([id, 'donor'], true);
|
||||
break;
|
||||
switch (tag) {
|
||||
case 'verified':
|
||||
state.setIn([id, 'verified'], true);
|
||||
break;
|
||||
case 'donor':
|
||||
state.setIn([id, 'donor'], true);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -118,13 +118,13 @@ const removeTags = (
|
|||
);
|
||||
|
||||
tags.forEach(tag => {
|
||||
switch(tag) {
|
||||
case 'verified':
|
||||
state.setIn([id, 'verified'], false);
|
||||
break;
|
||||
case 'donor':
|
||||
state.setIn([id, 'donor'], false);
|
||||
break;
|
||||
switch (tag) {
|
||||
case 'verified':
|
||||
state.setIn([id, 'verified'], false);
|
||||
break;
|
||||
case 'donor':
|
||||
state.setIn([id, 'donor'], false);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -243,50 +243,50 @@ const setSuggested = (state: State, accountIds: Array<string>, isSuggested: bool
|
|||
};
|
||||
|
||||
export default function accounts(state: State = initialState, action: AnyAction): State {
|
||||
switch(action.type) {
|
||||
case ACCOUNT_IMPORT:
|
||||
return fixAccount(state, action.account);
|
||||
case ACCOUNTS_IMPORT:
|
||||
return normalizeAccounts(state, action.accounts);
|
||||
case ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP:
|
||||
return fixAccount(state, { id: -1, username: action.username });
|
||||
case CHATS_FETCH_SUCCESS:
|
||||
case CHATS_EXPAND_SUCCESS:
|
||||
return importAccountsFromChats(state, action.chats);
|
||||
case CHAT_FETCH_SUCCESS:
|
||||
case STREAMING_CHAT_UPDATE:
|
||||
return importAccountsFromChats(state, [action.chat]);
|
||||
case ADMIN_USERS_TAG_REQUEST:
|
||||
case ADMIN_USERS_TAG_SUCCESS:
|
||||
case ADMIN_USERS_UNTAG_FAIL:
|
||||
return addTags(state, action.accountIds, action.tags);
|
||||
case ADMIN_USERS_UNTAG_REQUEST:
|
||||
case ADMIN_USERS_UNTAG_SUCCESS:
|
||||
case ADMIN_USERS_TAG_FAIL:
|
||||
return removeTags(state, action.accountIds, action.tags);
|
||||
case ADMIN_ADD_PERMISSION_GROUP_REQUEST:
|
||||
case ADMIN_ADD_PERMISSION_GROUP_SUCCESS:
|
||||
case ADMIN_REMOVE_PERMISSION_GROUP_FAIL:
|
||||
return addPermission(state, action.accountIds, action.permissionGroup);
|
||||
case ADMIN_REMOVE_PERMISSION_GROUP_REQUEST:
|
||||
case ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS:
|
||||
case ADMIN_ADD_PERMISSION_GROUP_FAIL:
|
||||
return removePermission(state, action.accountIds, action.permissionGroup);
|
||||
case ADMIN_USERS_DELETE_REQUEST:
|
||||
case ADMIN_USERS_DEACTIVATE_REQUEST:
|
||||
return setActive(state, action.accountIds, false);
|
||||
case ADMIN_USERS_DELETE_FAIL:
|
||||
case ADMIN_USERS_DEACTIVATE_FAIL:
|
||||
return setActive(state, action.accountIds, true);
|
||||
case ADMIN_USERS_FETCH_SUCCESS:
|
||||
return importAdminUsers(state, action.users);
|
||||
case ADMIN_USERS_SUGGEST_REQUEST:
|
||||
case ADMIN_USERS_UNSUGGEST_FAIL:
|
||||
return setSuggested(state, action.accountIds, true);
|
||||
case ADMIN_USERS_UNSUGGEST_REQUEST:
|
||||
case ADMIN_USERS_SUGGEST_FAIL:
|
||||
return setSuggested(state, action.accountIds, false);
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case ACCOUNT_IMPORT:
|
||||
return fixAccount(state, action.account);
|
||||
case ACCOUNTS_IMPORT:
|
||||
return normalizeAccounts(state, action.accounts);
|
||||
case ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP:
|
||||
return fixAccount(state, { id: -1, username: action.username });
|
||||
case CHATS_FETCH_SUCCESS:
|
||||
case CHATS_EXPAND_SUCCESS:
|
||||
return importAccountsFromChats(state, action.chats);
|
||||
case CHAT_FETCH_SUCCESS:
|
||||
case STREAMING_CHAT_UPDATE:
|
||||
return importAccountsFromChats(state, [action.chat]);
|
||||
case ADMIN_USERS_TAG_REQUEST:
|
||||
case ADMIN_USERS_TAG_SUCCESS:
|
||||
case ADMIN_USERS_UNTAG_FAIL:
|
||||
return addTags(state, action.accountIds, action.tags);
|
||||
case ADMIN_USERS_UNTAG_REQUEST:
|
||||
case ADMIN_USERS_UNTAG_SUCCESS:
|
||||
case ADMIN_USERS_TAG_FAIL:
|
||||
return removeTags(state, action.accountIds, action.tags);
|
||||
case ADMIN_ADD_PERMISSION_GROUP_REQUEST:
|
||||
case ADMIN_ADD_PERMISSION_GROUP_SUCCESS:
|
||||
case ADMIN_REMOVE_PERMISSION_GROUP_FAIL:
|
||||
return addPermission(state, action.accountIds, action.permissionGroup);
|
||||
case ADMIN_REMOVE_PERMISSION_GROUP_REQUEST:
|
||||
case ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS:
|
||||
case ADMIN_ADD_PERMISSION_GROUP_FAIL:
|
||||
return removePermission(state, action.accountIds, action.permissionGroup);
|
||||
case ADMIN_USERS_DELETE_REQUEST:
|
||||
case ADMIN_USERS_DEACTIVATE_REQUEST:
|
||||
return setActive(state, action.accountIds, false);
|
||||
case ADMIN_USERS_DELETE_FAIL:
|
||||
case ADMIN_USERS_DEACTIVATE_FAIL:
|
||||
return setActive(state, action.accountIds, true);
|
||||
case ADMIN_USERS_FETCH_SUCCESS:
|
||||
return importAdminUsers(state, action.users);
|
||||
case ADMIN_USERS_SUGGEST_REQUEST:
|
||||
case ADMIN_USERS_UNSUGGEST_FAIL:
|
||||
return setSuggested(state, action.accountIds, true);
|
||||
case ADMIN_USERS_UNSUGGEST_REQUEST:
|
||||
case ADMIN_USERS_SUGGEST_FAIL:
|
||||
return setSuggested(state, action.accountIds, false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,19 +36,19 @@ const updateFollowCounters = (state, counterUpdates) => {
|
|||
const initialState = ImmutableMap();
|
||||
|
||||
export default function accountsCounters(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case ACCOUNT_IMPORT:
|
||||
return normalizeAccount(state, action.account);
|
||||
case ACCOUNTS_IMPORT:
|
||||
return normalizeAccounts(state, action.accounts);
|
||||
case ACCOUNT_FOLLOW_SUCCESS:
|
||||
return action.alreadyFollowing ? state :
|
||||
state.updateIn([action.relationship.id, 'followers_count'], num => num + 1);
|
||||
case ACCOUNT_UNFOLLOW_SUCCESS:
|
||||
return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1));
|
||||
case STREAMING_FOLLOW_RELATIONSHIPS_UPDATE:
|
||||
return updateFollowCounters(state, [action.follower, action.following]);
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case ACCOUNT_IMPORT:
|
||||
return normalizeAccount(state, action.account);
|
||||
case ACCOUNTS_IMPORT:
|
||||
return normalizeAccounts(state, action.accounts);
|
||||
case ACCOUNT_FOLLOW_SUCCESS:
|
||||
return action.alreadyFollowing ? state :
|
||||
state.updateIn([action.relationship.id, 'followers_count'], num => num + 1);
|
||||
case ACCOUNT_UNFOLLOW_SUCCESS:
|
||||
return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1));
|
||||
case STREAMING_FOLLOW_RELATIONSHIPS_UPDATE:
|
||||
return updateFollowCounters(state, [action.follower, action.following]);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,14 +20,14 @@ const importAccount = (state, account) => {
|
|||
};
|
||||
|
||||
export default function accounts_meta(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case ME_FETCH_SUCCESS:
|
||||
case ME_PATCH_SUCCESS:
|
||||
return importAccount(state, fromJS(action.me));
|
||||
case VERIFY_CREDENTIALS_SUCCESS:
|
||||
case AUTH_ACCOUNT_REMEMBER_SUCCESS:
|
||||
return importAccount(state, fromJS(action.account));
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case ME_FETCH_SUCCESS:
|
||||
case ME_PATCH_SUCCESS:
|
||||
return importAccount(state, fromJS(action.me));
|
||||
case VERIFY_CREDENTIALS_SUCCESS:
|
||||
case AUTH_ACCOUNT_REMEMBER_SUCCESS:
|
||||
return importAccount(state, fromJS(action.account));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,12 +137,12 @@ function handleReportDiffs(state: State, reports: APIReport[]) {
|
|||
// hence the need for a new function.
|
||||
return state.withMutations(state => {
|
||||
reports.forEach(report => {
|
||||
switch(report.state) {
|
||||
case 'open':
|
||||
state.update('openReports', orderedSet => orderedSet.add(report.id));
|
||||
break;
|
||||
default:
|
||||
state.update('openReports', orderedSet => orderedSet.delete(report.id));
|
||||
switch (report.state) {
|
||||
case 'open':
|
||||
state.update('openReports', orderedSet => orderedSet.add(report.id));
|
||||
break;
|
||||
default:
|
||||
state.update('openReports', orderedSet => orderedSet.delete(report.id));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -159,25 +159,25 @@ const importConfigs = (state: State, configs: any): State => {
|
|||
};
|
||||
|
||||
export default function admin(state: State = ReducerRecord(), action: AnyAction): State {
|
||||
switch(action.type) {
|
||||
case ADMIN_CONFIG_FETCH_SUCCESS:
|
||||
case ADMIN_CONFIG_UPDATE_SUCCESS:
|
||||
return importConfigs(state, action.configs);
|
||||
case ADMIN_REPORTS_FETCH_SUCCESS:
|
||||
return importReports(state, action.reports);
|
||||
case ADMIN_REPORTS_PATCH_REQUEST:
|
||||
case ADMIN_REPORTS_PATCH_SUCCESS:
|
||||
return handleReportDiffs(state, action.reports);
|
||||
case ADMIN_USERS_FETCH_SUCCESS:
|
||||
return importUsers(state, action.users, action.filters, action.page);
|
||||
case ADMIN_USERS_DELETE_REQUEST:
|
||||
case ADMIN_USERS_DELETE_SUCCESS:
|
||||
return deleteUsers(state, action.accountIds);
|
||||
case ADMIN_USERS_APPROVE_REQUEST:
|
||||
return state.update('awaitingApproval', set => set.subtract(action.accountIds));
|
||||
case ADMIN_USERS_APPROVE_SUCCESS:
|
||||
return approveUsers(state, action.users);
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case ADMIN_CONFIG_FETCH_SUCCESS:
|
||||
case ADMIN_CONFIG_UPDATE_SUCCESS:
|
||||
return importConfigs(state, action.configs);
|
||||
case ADMIN_REPORTS_FETCH_SUCCESS:
|
||||
return importReports(state, action.reports);
|
||||
case ADMIN_REPORTS_PATCH_REQUEST:
|
||||
case ADMIN_REPORTS_PATCH_SUCCESS:
|
||||
return handleReportDiffs(state, action.reports);
|
||||
case ADMIN_USERS_FETCH_SUCCESS:
|
||||
return importUsers(state, action.users, action.filters, action.page);
|
||||
case ADMIN_USERS_DELETE_REQUEST:
|
||||
case ADMIN_USERS_DELETE_SUCCESS:
|
||||
return deleteUsers(state, action.accountIds);
|
||||
case ADMIN_USERS_APPROVE_REQUEST:
|
||||
return state.update('awaitingApproval', set => set.subtract(action.accountIds));
|
||||
case ADMIN_USERS_APPROVE_SUCCESS:
|
||||
return approveUsers(state, action.users);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,10 +36,10 @@ const importItems = (state, items, total) => {
|
|||
};
|
||||
|
||||
export default function admin_log(state = ReducerRecord(), action) {
|
||||
switch(action.type) {
|
||||
case ADMIN_LOG_FETCH_SUCCESS:
|
||||
return importItems(state, action.items, action.total);
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case ADMIN_LOG_FETCH_SUCCESS:
|
||||
return importItems(state, action.items, action.total);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,14 +40,14 @@ const deleteAlert = (state: State, alert: PlainAlert): State => {
|
|||
};
|
||||
|
||||
export default function alerts(state: State = ImmutableList<Alert>(), action: AnyAction): State {
|
||||
switch(action.type) {
|
||||
case ALERT_SHOW:
|
||||
return importAlert(state, action);
|
||||
case ALERT_DISMISS:
|
||||
return deleteAlert(state, action.alert);
|
||||
case ALERT_CLEAR:
|
||||
return state.clear();
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case ALERT_SHOW:
|
||||
return importAlert(state, action);
|
||||
case ALERT_DISMISS:
|
||||
return deleteAlert(state, action.alert);
|
||||
case ALERT_CLEAR:
|
||||
return state.clear();
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,25 +20,25 @@ const initialState = ImmutableMap({
|
|||
});
|
||||
|
||||
export default function aliasesReducer(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case ALIASES_FETCH_SUCCESS:
|
||||
return state
|
||||
.setIn(['aliases', 'items'], action.value);
|
||||
case ALIASES_SUGGESTIONS_CHANGE:
|
||||
return state
|
||||
.setIn(['suggestions', 'value'], action.value)
|
||||
.setIn(['suggestions', 'loaded'], false);
|
||||
case ALIASES_SUGGESTIONS_READY:
|
||||
return state
|
||||
.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map(item => item.id)))
|
||||
.setIn(['suggestions', 'loaded'], true);
|
||||
case ALIASES_SUGGESTIONS_CLEAR:
|
||||
return state.update('suggestions', suggestions => suggestions.withMutations(map => {
|
||||
map.set('items', ImmutableList());
|
||||
map.set('value', '');
|
||||
map.set('loaded', false);
|
||||
}));
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case ALIASES_FETCH_SUCCESS:
|
||||
return state
|
||||
.setIn(['aliases', 'items'], action.value);
|
||||
case ALIASES_SUGGESTIONS_CHANGE:
|
||||
return state
|
||||
.setIn(['suggestions', 'value'], action.value)
|
||||
.setIn(['suggestions', 'loaded'], false);
|
||||
case ALIASES_SUGGESTIONS_READY:
|
||||
return state
|
||||
.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map(item => item.id)))
|
||||
.setIn(['suggestions', 'loaded'], true);
|
||||
case ALIASES_SUGGESTIONS_CLEAR:
|
||||
return state.update('suggestions', suggestions => suggestions.withMutations(map => {
|
||||
map.set('items', ImmutableList());
|
||||
map.set('value', '');
|
||||
map.set('loaded', false);
|
||||
}));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -282,28 +282,28 @@ const deleteForbiddenToken = (state, error, token) => {
|
|||
};
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch(action.type) {
|
||||
case AUTH_APP_CREATED:
|
||||
return state.set('app', fromJS(action.app));
|
||||
case AUTH_APP_AUTHORIZED:
|
||||
return state.update('app', ImmutableMap(), app => app.merge(fromJS(action.token)));
|
||||
case AUTH_LOGGED_IN:
|
||||
return importToken(state, action.token);
|
||||
case AUTH_LOGGED_OUT:
|
||||
return deleteUser(state, action.account);
|
||||
case VERIFY_CREDENTIALS_SUCCESS:
|
||||
persistAuthAccount(action.account);
|
||||
return importCredentials(state, action.token, action.account);
|
||||
case VERIFY_CREDENTIALS_FAIL:
|
||||
return deleteForbiddenToken(state, action.error, action.token);
|
||||
case SWITCH_ACCOUNT:
|
||||
return state.set('me', action.account.get('url'));
|
||||
case ME_FETCH_SKIP:
|
||||
return state.set('me', null);
|
||||
case MASTODON_PRELOAD_IMPORT:
|
||||
return importMastodonPreload(state, fromJS(action.data));
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case AUTH_APP_CREATED:
|
||||
return state.set('app', fromJS(action.app));
|
||||
case AUTH_APP_AUTHORIZED:
|
||||
return state.update('app', ImmutableMap(), app => app.merge(fromJS(action.token)));
|
||||
case AUTH_LOGGED_IN:
|
||||
return importToken(state, action.token);
|
||||
case AUTH_LOGGED_OUT:
|
||||
return deleteUser(state, action.account);
|
||||
case VERIFY_CREDENTIALS_SUCCESS:
|
||||
persistAuthAccount(action.account);
|
||||
return importCredentials(state, action.token, action.account);
|
||||
case VERIFY_CREDENTIALS_FAIL:
|
||||
return deleteForbiddenToken(state, action.error, action.token);
|
||||
case SWITCH_ACCOUNT:
|
||||
return state.set('me', action.account.get('url'));
|
||||
case ME_FETCH_SKIP:
|
||||
return state.set('me', null);
|
||||
case MASTODON_PRELOAD_IMPORT:
|
||||
return importMastodonPreload(state, fromJS(action.data));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -18,11 +18,11 @@ const importBackups = (state, backups) => {
|
|||
};
|
||||
|
||||
export default function backups(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case BACKUPS_FETCH_SUCCESS:
|
||||
case BACKUPS_CREATE_SUCCESS:
|
||||
return importBackups(state, fromJS(action.backups));
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case BACKUPS_FETCH_SUCCESS:
|
||||
case BACKUPS_CREATE_SUCCESS:
|
||||
return importBackups(state, fromJS(action.backups));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,25 +51,25 @@ const replaceMessage = (state: State, chatId: string, oldId: string, newId: stri
|
|||
};
|
||||
|
||||
export default function chatMessageLists(state = initialState, action: AnyAction) {
|
||||
switch(action.type) {
|
||||
case CHAT_MESSAGE_SEND_REQUEST:
|
||||
return updateList(state, action.chatId, [action.uuid]);
|
||||
case CHATS_FETCH_SUCCESS:
|
||||
case CHATS_EXPAND_SUCCESS:
|
||||
return importLastMessages(state, action.chats);
|
||||
case STREAMING_CHAT_UPDATE:
|
||||
if (action.chat.last_message &&
|
||||
switch (action.type) {
|
||||
case CHAT_MESSAGE_SEND_REQUEST:
|
||||
return updateList(state, action.chatId, [action.uuid]);
|
||||
case CHATS_FETCH_SUCCESS:
|
||||
case CHATS_EXPAND_SUCCESS:
|
||||
return importLastMessages(state, action.chats);
|
||||
case STREAMING_CHAT_UPDATE:
|
||||
if (action.chat.last_message &&
|
||||
action.chat.last_message.account_id !== action.me)
|
||||
return importMessages(state, [action.chat.last_message]);
|
||||
else
|
||||
return importMessages(state, [action.chat.last_message]);
|
||||
else
|
||||
return state;
|
||||
case CHAT_MESSAGES_FETCH_SUCCESS:
|
||||
return updateList(state, action.chatId, action.chatMessages.map((chat: APIEntity) => chat.id));
|
||||
case CHAT_MESSAGE_SEND_SUCCESS:
|
||||
return replaceMessage(state, action.chatId, action.uuid, action.chatMessage.id);
|
||||
case CHAT_MESSAGE_DELETE_SUCCESS:
|
||||
return state.update(action.chatId, chat => chat!.delete(action.messageId));
|
||||
default:
|
||||
return state;
|
||||
case CHAT_MESSAGES_FETCH_SUCCESS:
|
||||
return updateList(state, action.chatId, action.chatMessages.map((chat: APIEntity) => chat.id));
|
||||
case CHAT_MESSAGE_SEND_SUCCESS:
|
||||
return replaceMessage(state, action.chatId, action.uuid, action.chatMessage.id);
|
||||
case CHAT_MESSAGE_DELETE_SUCCESS:
|
||||
return state.update(action.chatId, chat => chat!.delete(action.messageId));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,31 +37,31 @@ const importLastMessages = (state: State, chats: APIEntities) =>
|
|||
const initialState: State = ImmutableMap();
|
||||
|
||||
export default function chatMessages(state = initialState, action: AnyAction) {
|
||||
switch(action.type) {
|
||||
case CHAT_MESSAGE_SEND_REQUEST:
|
||||
return importMessage(state, fromJS({
|
||||
id: action.uuid, // Make fake message to get overriden later
|
||||
chat_id: action.chatId,
|
||||
account_id: action.me,
|
||||
content: action.params.content,
|
||||
created_at: (new Date()).toISOString(),
|
||||
pending: true,
|
||||
}));
|
||||
case CHATS_FETCH_SUCCESS:
|
||||
case CHATS_EXPAND_SUCCESS:
|
||||
return importLastMessages(state, action.chats);
|
||||
case CHAT_MESSAGES_FETCH_SUCCESS:
|
||||
return importMessages(state, action.chatMessages);
|
||||
case CHAT_MESSAGE_SEND_SUCCESS:
|
||||
return importMessage(state, fromJS(action.chatMessage)).delete(action.uuid);
|
||||
case STREAMING_CHAT_UPDATE:
|
||||
return importLastMessages(state, [action.chat]);
|
||||
case CHAT_MESSAGE_DELETE_REQUEST:
|
||||
return state.update(action.messageId, chatMessage =>
|
||||
switch (action.type) {
|
||||
case CHAT_MESSAGE_SEND_REQUEST:
|
||||
return importMessage(state, fromJS({
|
||||
id: action.uuid, // Make fake message to get overriden later
|
||||
chat_id: action.chatId,
|
||||
account_id: action.me,
|
||||
content: action.params.content,
|
||||
created_at: (new Date()).toISOString(),
|
||||
pending: true,
|
||||
}));
|
||||
case CHATS_FETCH_SUCCESS:
|
||||
case CHATS_EXPAND_SUCCESS:
|
||||
return importLastMessages(state, action.chats);
|
||||
case CHAT_MESSAGES_FETCH_SUCCESS:
|
||||
return importMessages(state, action.chatMessages);
|
||||
case CHAT_MESSAGE_SEND_SUCCESS:
|
||||
return importMessage(state, fromJS(action.chatMessage)).delete(action.uuid);
|
||||
case STREAMING_CHAT_UPDATE:
|
||||
return importLastMessages(state, [action.chat]);
|
||||
case CHAT_MESSAGE_DELETE_REQUEST:
|
||||
return state.update(action.messageId, chatMessage =>
|
||||
chatMessage!.set('pending', true).set('deleting', true));
|
||||
case CHAT_MESSAGE_DELETE_SUCCESS:
|
||||
return state.delete(action.messageId);
|
||||
default:
|
||||
return state;
|
||||
case CHAT_MESSAGE_DELETE_SUCCESS:
|
||||
return state.delete(action.messageId);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,22 +55,22 @@ const importChats = (state: State, chats: APIEntities, next?: string) =>
|
|||
});
|
||||
|
||||
export default function chats(state: State = ReducerRecord(), action: AnyAction): State {
|
||||
switch(action.type) {
|
||||
case CHATS_FETCH_REQUEST:
|
||||
case CHATS_EXPAND_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
case CHATS_FETCH_SUCCESS:
|
||||
case CHATS_EXPAND_SUCCESS:
|
||||
return importChats(state, action.chats, action.next);
|
||||
case STREAMING_CHAT_UPDATE:
|
||||
return importChats(state, [action.chat]);
|
||||
case CHAT_FETCH_SUCCESS:
|
||||
return importChats(state, [action.chat]);
|
||||
case CHAT_READ_REQUEST:
|
||||
return state.setIn([action.chatId, 'unread'], 0);
|
||||
case CHAT_READ_SUCCESS:
|
||||
return importChats(state, [action.chat]);
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case CHATS_FETCH_REQUEST:
|
||||
case CHATS_EXPAND_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
case CHATS_FETCH_SUCCESS:
|
||||
case CHATS_EXPAND_SUCCESS:
|
||||
return importChats(state, action.chats, action.next);
|
||||
case STREAMING_CHAT_UPDATE:
|
||||
return importChats(state, [action.chat]);
|
||||
case CHAT_FETCH_SUCCESS:
|
||||
return importChats(state, [action.chat]);
|
||||
case CHAT_READ_REQUEST:
|
||||
return state.setIn([action.chatId, 'unread'], 0);
|
||||
case CHAT_READ_SUCCESS:
|
||||
return importChats(state, [action.chat]);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -273,223 +273,223 @@ const updateAccount = (state, account) => {
|
|||
const updateSetting = (state, path, value) => {
|
||||
const pathString = path.join(',');
|
||||
switch (pathString) {
|
||||
case 'defaultPrivacy':
|
||||
return state.set('default_privacy', value).set('privacy', value);
|
||||
case 'defaultContentType':
|
||||
return state.set('default_content_type', value).set('content_type', value);
|
||||
default:
|
||||
return state;
|
||||
case 'defaultPrivacy':
|
||||
return state.set('default_privacy', value).set('privacy', value);
|
||||
case 'defaultContentType':
|
||||
return state.set('default_content_type', value).set('content_type', value);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default function compose(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case COMPOSE_MOUNT:
|
||||
return state.set('mounted', state.get('mounted') + 1);
|
||||
case COMPOSE_UNMOUNT:
|
||||
return state
|
||||
.set('mounted', Math.max(state.get('mounted') - 1, 0))
|
||||
.set('is_composing', false);
|
||||
case COMPOSE_SENSITIVITY_CHANGE:
|
||||
return state.withMutations(map => {
|
||||
if (!state.get('spoiler')) {
|
||||
map.set('sensitive', !state.get('sensitive'));
|
||||
}
|
||||
|
||||
map.set('idempotencyKey', uuid());
|
||||
});
|
||||
case COMPOSE_TYPE_CHANGE:
|
||||
return state.withMutations(map => {
|
||||
map.set('content_type', action.value);
|
||||
map.set('idempotencyKey', uuid());
|
||||
});
|
||||
case COMPOSE_SPOILERNESS_CHANGE:
|
||||
return state.withMutations(map => {
|
||||
map.set('spoiler_text', '');
|
||||
map.set('spoiler', !state.get('spoiler'));
|
||||
map.set('idempotencyKey', uuid());
|
||||
|
||||
if (!state.get('sensitive') && state.get('media_attachments').size >= 1) {
|
||||
map.set('sensitive', true);
|
||||
}
|
||||
});
|
||||
case COMPOSE_SPOILER_TEXT_CHANGE:
|
||||
return state
|
||||
.set('spoiler_text', action.text)
|
||||
.set('idempotencyKey', uuid());
|
||||
case COMPOSE_VISIBILITY_CHANGE:
|
||||
return state
|
||||
.set('privacy', action.value)
|
||||
.set('idempotencyKey', uuid());
|
||||
case COMPOSE_CHANGE:
|
||||
return state
|
||||
.set('text', action.text)
|
||||
.set('idempotencyKey', uuid());
|
||||
case COMPOSE_COMPOSING_CHANGE:
|
||||
return state.set('is_composing', action.value);
|
||||
case COMPOSE_REPLY:
|
||||
return state.withMutations(map => {
|
||||
map.set('in_reply_to', action.status.get('id'));
|
||||
map.set('to', action.explicitAddressing ? statusToMentionsArray(action.status, action.account) : ImmutableOrderedSet());
|
||||
map.set('text', !action.explicitAddressing ? statusToTextMentions(state, action.status, action.account) : '');
|
||||
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
|
||||
map.set('focusDate', new Date());
|
||||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
map.set('content_type', state.get('default_content_type'));
|
||||
|
||||
if (action.status.get('spoiler_text', '').length > 0) {
|
||||
map.set('spoiler', true);
|
||||
map.set('spoiler_text', action.status.get('spoiler_text'));
|
||||
} else {
|
||||
map.set('spoiler', false);
|
||||
map.set('spoiler_text', '');
|
||||
}
|
||||
});
|
||||
case COMPOSE_QUOTE:
|
||||
return state.withMutations(map => {
|
||||
map.set('quote', action.status.get('id'));
|
||||
map.set('to', undefined);
|
||||
map.set('text', '');
|
||||
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
|
||||
map.set('focusDate', new Date());
|
||||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
map.set('content_type', state.get('default_content_type'));
|
||||
map.set('spoiler', false);
|
||||
map.set('spoiler_text', '');
|
||||
});
|
||||
case COMPOSE_SUBMIT_REQUEST:
|
||||
return state.set('is_submitting', true);
|
||||
case COMPOSE_UPLOAD_CHANGE_REQUEST:
|
||||
return state.set('is_changing_upload', true);
|
||||
case COMPOSE_REPLY_CANCEL:
|
||||
case COMPOSE_QUOTE_CANCEL:
|
||||
case COMPOSE_RESET:
|
||||
case COMPOSE_SUBMIT_SUCCESS:
|
||||
return clearAll(state);
|
||||
case COMPOSE_SUBMIT_FAIL:
|
||||
return state.set('is_submitting', false);
|
||||
case COMPOSE_UPLOAD_CHANGE_FAIL:
|
||||
return state.set('is_changing_upload', false);
|
||||
case COMPOSE_UPLOAD_REQUEST:
|
||||
return state.set('is_uploading', true);
|
||||
case COMPOSE_UPLOAD_SUCCESS:
|
||||
return appendMedia(state, fromJS(action.media));
|
||||
case COMPOSE_UPLOAD_FAIL:
|
||||
return state.set('is_uploading', false);
|
||||
case COMPOSE_UPLOAD_UNDO:
|
||||
return removeMedia(state, action.media_id);
|
||||
case COMPOSE_UPLOAD_PROGRESS:
|
||||
return state.set('progress', Math.round((action.loaded / action.total) * 100));
|
||||
case COMPOSE_MENTION:
|
||||
return state.withMutations(map => {
|
||||
map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' '));
|
||||
map.set('focusDate', new Date());
|
||||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
});
|
||||
case COMPOSE_DIRECT:
|
||||
return state.withMutations(map => {
|
||||
map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' '));
|
||||
map.set('privacy', 'direct');
|
||||
map.set('focusDate', new Date());
|
||||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
});
|
||||
case COMPOSE_SUGGESTIONS_CLEAR:
|
||||
return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null);
|
||||
case COMPOSE_SUGGESTIONS_READY:
|
||||
return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).set('suggestion_token', action.token);
|
||||
case COMPOSE_SUGGESTION_SELECT:
|
||||
return insertSuggestion(state, action.position, action.token, action.completion, action.path);
|
||||
case COMPOSE_SUGGESTION_TAGS_UPDATE:
|
||||
return updateSuggestionTags(state, action.token);
|
||||
case COMPOSE_TAG_HISTORY_UPDATE:
|
||||
return state.set('tagHistory', fromJS(action.tags));
|
||||
case TIMELINE_DELETE:
|
||||
if (action.id === state.get('in_reply_to')) {
|
||||
return state.set('in_reply_to', null);
|
||||
} if (action.id === state.get('quote')) {
|
||||
return state.set('quote', null);
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
case COMPOSE_EMOJI_INSERT:
|
||||
return insertEmoji(state, action.position, action.emoji, action.needsSpace);
|
||||
case COMPOSE_UPLOAD_CHANGE_SUCCESS:
|
||||
return state
|
||||
.set('is_changing_upload', false)
|
||||
.update('media_attachments', list => list.map(item => {
|
||||
if (item.get('id') === action.media.id) {
|
||||
return fromJS(action.media);
|
||||
switch (action.type) {
|
||||
case COMPOSE_MOUNT:
|
||||
return state.set('mounted', state.get('mounted') + 1);
|
||||
case COMPOSE_UNMOUNT:
|
||||
return state
|
||||
.set('mounted', Math.max(state.get('mounted') - 1, 0))
|
||||
.set('is_composing', false);
|
||||
case COMPOSE_SENSITIVITY_CHANGE:
|
||||
return state.withMutations(map => {
|
||||
if (!state.get('spoiler')) {
|
||||
map.set('sensitive', !state.get('sensitive'));
|
||||
}
|
||||
|
||||
return item;
|
||||
}));
|
||||
case COMPOSE_SET_STATUS:
|
||||
return state.withMutations(map => {
|
||||
map.set('id', action.status.get('id'));
|
||||
map.set('text', action.rawText || unescapeHTML(expandMentions(action.status)));
|
||||
map.set('to', action.explicitAddressing ? getExplicitMentions(action.status.get('account', 'id'), action.status) : ImmutableOrderedSet());
|
||||
map.set('in_reply_to', action.status.get('in_reply_to_id'));
|
||||
map.set('privacy', action.status.get('visibility'));
|
||||
map.set('focusDate', new Date());
|
||||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
map.set('content_type', action.contentType || 'text/plain');
|
||||
map.set('idempotencyKey', uuid());
|
||||
});
|
||||
case COMPOSE_TYPE_CHANGE:
|
||||
return state.withMutations(map => {
|
||||
map.set('content_type', action.value);
|
||||
map.set('idempotencyKey', uuid());
|
||||
});
|
||||
case COMPOSE_SPOILERNESS_CHANGE:
|
||||
return state.withMutations(map => {
|
||||
map.set('spoiler_text', '');
|
||||
map.set('spoiler', !state.get('spoiler'));
|
||||
map.set('idempotencyKey', uuid());
|
||||
|
||||
if (action.v?.software === PLEROMA && hasIntegerMediaIds(action.status)) {
|
||||
map.set('media_attachments', ImmutableList());
|
||||
} else {
|
||||
map.set('media_attachments', action.status.get('media_attachments'));
|
||||
}
|
||||
if (!state.get('sensitive') && state.get('media_attachments').size >= 1) {
|
||||
map.set('sensitive', true);
|
||||
}
|
||||
});
|
||||
case COMPOSE_SPOILER_TEXT_CHANGE:
|
||||
return state
|
||||
.set('spoiler_text', action.text)
|
||||
.set('idempotencyKey', uuid());
|
||||
case COMPOSE_VISIBILITY_CHANGE:
|
||||
return state
|
||||
.set('privacy', action.value)
|
||||
.set('idempotencyKey', uuid());
|
||||
case COMPOSE_CHANGE:
|
||||
return state
|
||||
.set('text', action.text)
|
||||
.set('idempotencyKey', uuid());
|
||||
case COMPOSE_COMPOSING_CHANGE:
|
||||
return state.set('is_composing', action.value);
|
||||
case COMPOSE_REPLY:
|
||||
return state.withMutations(map => {
|
||||
map.set('in_reply_to', action.status.get('id'));
|
||||
map.set('to', action.explicitAddressing ? statusToMentionsArray(action.status, action.account) : ImmutableOrderedSet());
|
||||
map.set('text', !action.explicitAddressing ? statusToTextMentions(state, action.status, action.account) : '');
|
||||
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
|
||||
map.set('focusDate', new Date());
|
||||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
map.set('content_type', state.get('default_content_type'));
|
||||
|
||||
if (action.status.get('spoiler_text').length > 0) {
|
||||
map.set('spoiler', true);
|
||||
map.set('spoiler_text', action.status.get('spoiler_text'));
|
||||
} else {
|
||||
if (action.status.get('spoiler_text', '').length > 0) {
|
||||
map.set('spoiler', true);
|
||||
map.set('spoiler_text', action.status.get('spoiler_text'));
|
||||
} else {
|
||||
map.set('spoiler', false);
|
||||
map.set('spoiler_text', '');
|
||||
}
|
||||
});
|
||||
case COMPOSE_QUOTE:
|
||||
return state.withMutations(map => {
|
||||
map.set('quote', action.status.get('id'));
|
||||
map.set('to', undefined);
|
||||
map.set('text', '');
|
||||
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
|
||||
map.set('focusDate', new Date());
|
||||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
map.set('content_type', state.get('default_content_type'));
|
||||
map.set('spoiler', false);
|
||||
map.set('spoiler_text', '');
|
||||
});
|
||||
case COMPOSE_SUBMIT_REQUEST:
|
||||
return state.set('is_submitting', true);
|
||||
case COMPOSE_UPLOAD_CHANGE_REQUEST:
|
||||
return state.set('is_changing_upload', true);
|
||||
case COMPOSE_REPLY_CANCEL:
|
||||
case COMPOSE_QUOTE_CANCEL:
|
||||
case COMPOSE_RESET:
|
||||
case COMPOSE_SUBMIT_SUCCESS:
|
||||
return clearAll(state);
|
||||
case COMPOSE_SUBMIT_FAIL:
|
||||
return state.set('is_submitting', false);
|
||||
case COMPOSE_UPLOAD_CHANGE_FAIL:
|
||||
return state.set('is_changing_upload', false);
|
||||
case COMPOSE_UPLOAD_REQUEST:
|
||||
return state.set('is_uploading', true);
|
||||
case COMPOSE_UPLOAD_SUCCESS:
|
||||
return appendMedia(state, fromJS(action.media));
|
||||
case COMPOSE_UPLOAD_FAIL:
|
||||
return state.set('is_uploading', false);
|
||||
case COMPOSE_UPLOAD_UNDO:
|
||||
return removeMedia(state, action.media_id);
|
||||
case COMPOSE_UPLOAD_PROGRESS:
|
||||
return state.set('progress', Math.round((action.loaded / action.total) * 100));
|
||||
case COMPOSE_MENTION:
|
||||
return state.withMutations(map => {
|
||||
map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' '));
|
||||
map.set('focusDate', new Date());
|
||||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
});
|
||||
case COMPOSE_DIRECT:
|
||||
return state.withMutations(map => {
|
||||
map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' '));
|
||||
map.set('privacy', 'direct');
|
||||
map.set('focusDate', new Date());
|
||||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
});
|
||||
case COMPOSE_SUGGESTIONS_CLEAR:
|
||||
return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null);
|
||||
case COMPOSE_SUGGESTIONS_READY:
|
||||
return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).set('suggestion_token', action.token);
|
||||
case COMPOSE_SUGGESTION_SELECT:
|
||||
return insertSuggestion(state, action.position, action.token, action.completion, action.path);
|
||||
case COMPOSE_SUGGESTION_TAGS_UPDATE:
|
||||
return updateSuggestionTags(state, action.token);
|
||||
case COMPOSE_TAG_HISTORY_UPDATE:
|
||||
return state.set('tagHistory', fromJS(action.tags));
|
||||
case TIMELINE_DELETE:
|
||||
if (action.id === state.get('in_reply_to')) {
|
||||
return state.set('in_reply_to', null);
|
||||
} if (action.id === state.get('quote')) {
|
||||
return state.set('quote', null);
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
case COMPOSE_EMOJI_INSERT:
|
||||
return insertEmoji(state, action.position, action.emoji, action.needsSpace);
|
||||
case COMPOSE_UPLOAD_CHANGE_SUCCESS:
|
||||
return state
|
||||
.set('is_changing_upload', false)
|
||||
.update('media_attachments', list => list.map(item => {
|
||||
if (item.get('id') === action.media.id) {
|
||||
return fromJS(action.media);
|
||||
}
|
||||
|
||||
if (action.status.get('poll')) {
|
||||
map.set('poll', ImmutableMap({
|
||||
options: action.status.getIn(['poll', 'options']).map(x => x.get('title')),
|
||||
multiple: action.status.getIn(['poll', 'multiple']),
|
||||
expires_in: 24 * 3600,
|
||||
return item;
|
||||
}));
|
||||
}
|
||||
});
|
||||
case COMPOSE_POLL_ADD:
|
||||
return state.set('poll', initialPoll);
|
||||
case COMPOSE_POLL_REMOVE:
|
||||
return state.set('poll', null);
|
||||
case COMPOSE_SCHEDULE_ADD:
|
||||
return state.set('schedule', new Date());
|
||||
case COMPOSE_SCHEDULE_SET:
|
||||
return state.set('schedule', action.date);
|
||||
case COMPOSE_SCHEDULE_REMOVE:
|
||||
return state.set('schedule', null);
|
||||
case COMPOSE_POLL_OPTION_ADD:
|
||||
return state.updateIn(['poll', 'options'], options => options.push(action.title));
|
||||
case COMPOSE_POLL_OPTION_CHANGE:
|
||||
return state.setIn(['poll', 'options', action.index], action.title);
|
||||
case COMPOSE_POLL_OPTION_REMOVE:
|
||||
return state.updateIn(['poll', 'options'], options => options.delete(action.index));
|
||||
case COMPOSE_POLL_SETTINGS_CHANGE:
|
||||
return state.update('poll', poll => poll.set('expires_in', action.expiresIn).set('multiple', action.isMultiple));
|
||||
case COMPOSE_ADD_TO_MENTIONS:
|
||||
return state.update('to', mentions => mentions.add(action.account));
|
||||
case COMPOSE_REMOVE_FROM_MENTIONS:
|
||||
return state.update('to', mentions => mentions.delete(action.account));
|
||||
case ME_FETCH_SUCCESS:
|
||||
return importAccount(state, action.me);
|
||||
case ME_PATCH_SUCCESS:
|
||||
return updateAccount(state, action.me);
|
||||
case SETTING_CHANGE:
|
||||
return updateSetting(state, action.path, action.value);
|
||||
default:
|
||||
return state;
|
||||
case COMPOSE_SET_STATUS:
|
||||
return state.withMutations(map => {
|
||||
map.set('id', action.status.get('id'));
|
||||
map.set('text', action.rawText || unescapeHTML(expandMentions(action.status)));
|
||||
map.set('to', action.explicitAddressing ? getExplicitMentions(action.status.get('account', 'id'), action.status) : ImmutableOrderedSet());
|
||||
map.set('in_reply_to', action.status.get('in_reply_to_id'));
|
||||
map.set('privacy', action.status.get('visibility'));
|
||||
map.set('focusDate', new Date());
|
||||
map.set('caretPosition', null);
|
||||
map.set('idempotencyKey', uuid());
|
||||
map.set('content_type', action.contentType || 'text/plain');
|
||||
|
||||
if (action.v?.software === PLEROMA && hasIntegerMediaIds(action.status)) {
|
||||
map.set('media_attachments', ImmutableList());
|
||||
} else {
|
||||
map.set('media_attachments', action.status.get('media_attachments'));
|
||||
}
|
||||
|
||||
if (action.status.get('spoiler_text').length > 0) {
|
||||
map.set('spoiler', true);
|
||||
map.set('spoiler_text', action.status.get('spoiler_text'));
|
||||
} else {
|
||||
map.set('spoiler', false);
|
||||
map.set('spoiler_text', '');
|
||||
}
|
||||
|
||||
if (action.status.get('poll')) {
|
||||
map.set('poll', ImmutableMap({
|
||||
options: action.status.getIn(['poll', 'options']).map(x => x.get('title')),
|
||||
multiple: action.status.getIn(['poll', 'multiple']),
|
||||
expires_in: 24 * 3600,
|
||||
}));
|
||||
}
|
||||
});
|
||||
case COMPOSE_POLL_ADD:
|
||||
return state.set('poll', initialPoll);
|
||||
case COMPOSE_POLL_REMOVE:
|
||||
return state.set('poll', null);
|
||||
case COMPOSE_SCHEDULE_ADD:
|
||||
return state.set('schedule', new Date());
|
||||
case COMPOSE_SCHEDULE_SET:
|
||||
return state.set('schedule', action.date);
|
||||
case COMPOSE_SCHEDULE_REMOVE:
|
||||
return state.set('schedule', null);
|
||||
case COMPOSE_POLL_OPTION_ADD:
|
||||
return state.updateIn(['poll', 'options'], options => options.push(action.title));
|
||||
case COMPOSE_POLL_OPTION_CHANGE:
|
||||
return state.setIn(['poll', 'options', action.index], action.title);
|
||||
case COMPOSE_POLL_OPTION_REMOVE:
|
||||
return state.updateIn(['poll', 'options'], options => options.delete(action.index));
|
||||
case COMPOSE_POLL_SETTINGS_CHANGE:
|
||||
return state.update('poll', poll => poll.set('expires_in', action.expiresIn).set('multiple', action.isMultiple));
|
||||
case COMPOSE_ADD_TO_MENTIONS:
|
||||
return state.update('to', mentions => mentions.add(action.account));
|
||||
case COMPOSE_REMOVE_FROM_MENTIONS:
|
||||
return state.update('to', mentions => mentions.delete(action.account));
|
||||
case ME_FETCH_SUCCESS:
|
||||
return importAccount(state, action.me);
|
||||
case ME_PATCH_SUCCESS:
|
||||
return updateAccount(state, action.me);
|
||||
case SETTING_CHANGE:
|
||||
return updateSetting(state, action.path, action.value);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,23 +141,23 @@ const deletePendingStatus = (state, { in_reply_to_id }, idempotencyKey) => {
|
|||
};
|
||||
|
||||
export default function replies(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
case ACCOUNT_MUTE_SUCCESS:
|
||||
return filterContexts(state, action.relationship, action.statuses);
|
||||
case CONTEXT_FETCH_SUCCESS:
|
||||
return normalizeContext(state, action.id, action.ancestors, action.descendants);
|
||||
case TIMELINE_DELETE:
|
||||
return deleteStatuses(state, [action.id]);
|
||||
case STATUS_CREATE_REQUEST:
|
||||
return importPendingStatus(state, action.params, action.idempotencyKey);
|
||||
case STATUS_CREATE_SUCCESS:
|
||||
return deletePendingStatus(state, action.status, action.idempotencyKey);
|
||||
case STATUS_IMPORT:
|
||||
return importStatus(state, action.status, action.idempotencyKey);
|
||||
case STATUSES_IMPORT:
|
||||
return importStatuses(state, action.statuses);
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
case ACCOUNT_MUTE_SUCCESS:
|
||||
return filterContexts(state, action.relationship, action.statuses);
|
||||
case CONTEXT_FETCH_SUCCESS:
|
||||
return normalizeContext(state, action.id, action.ancestors, action.descendants);
|
||||
case TIMELINE_DELETE:
|
||||
return deleteStatuses(state, [action.id]);
|
||||
case STATUS_CREATE_REQUEST:
|
||||
return importPendingStatus(state, action.params, action.idempotencyKey);
|
||||
case STATUS_CREATE_SUCCESS:
|
||||
return deletePendingStatus(state, action.status, action.idempotencyKey);
|
||||
case STATUS_IMPORT:
|
||||
return importStatus(state, action.status, action.idempotencyKey);
|
||||
case STATUSES_IMPORT:
|
||||
return importStatuses(state, action.statuses);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,27 +77,27 @@ const expandNormalizedConversations = (state, conversations, next, isLoadingRece
|
|||
|
||||
export default function conversations(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case CONVERSATIONS_FETCH_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
case CONVERSATIONS_FETCH_FAIL:
|
||||
return state.set('isLoading', false);
|
||||
case CONVERSATIONS_FETCH_SUCCESS:
|
||||
return expandNormalizedConversations(state, action.conversations, action.next, action.isLoadingRecent);
|
||||
case CONVERSATIONS_UPDATE:
|
||||
return updateConversation(state, action.conversation);
|
||||
case CONVERSATIONS_MOUNT:
|
||||
return state.update('mounted', count => count + 1);
|
||||
case CONVERSATIONS_UNMOUNT:
|
||||
return state.update('mounted', count => count - 1);
|
||||
case CONVERSATIONS_READ:
|
||||
return state.update('items', list => list.map(item => {
|
||||
if (item.get('id') === action.id) {
|
||||
return item.set('unread', false);
|
||||
}
|
||||
case CONVERSATIONS_FETCH_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
case CONVERSATIONS_FETCH_FAIL:
|
||||
return state.set('isLoading', false);
|
||||
case CONVERSATIONS_FETCH_SUCCESS:
|
||||
return expandNormalizedConversations(state, action.conversations, action.next, action.isLoadingRecent);
|
||||
case CONVERSATIONS_UPDATE:
|
||||
return updateConversation(state, action.conversation);
|
||||
case CONVERSATIONS_MOUNT:
|
||||
return state.update('mounted', count => count + 1);
|
||||
case CONVERSATIONS_UNMOUNT:
|
||||
return state.update('mounted', count => count - 1);
|
||||
case CONVERSATIONS_READ:
|
||||
return state.update('items', list => list.map(item => {
|
||||
if (item.get('id') === action.id) {
|
||||
return item.set('unread', false);
|
||||
}
|
||||
|
||||
return item;
|
||||
}));
|
||||
default:
|
||||
return state;
|
||||
return item;
|
||||
}));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,14 +13,14 @@ const initialState = ImmutableMap({
|
|||
});
|
||||
|
||||
export default function domainLists(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case DOMAIN_BLOCKS_FETCH_SUCCESS:
|
||||
return state.setIn(['blocks', 'items'], ImmutableOrderedSet(action.domains)).setIn(['blocks', 'next'], action.next);
|
||||
case DOMAIN_BLOCKS_EXPAND_SUCCESS:
|
||||
return state.updateIn(['blocks', 'items'], set => set.union(action.domains)).setIn(['blocks', 'next'], action.next);
|
||||
case DOMAIN_UNBLOCK_SUCCESS:
|
||||
return state.updateIn(['blocks', 'items'], set => set.delete(action.domain));
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case DOMAIN_BLOCKS_FETCH_SUCCESS:
|
||||
return state.setIn(['blocks', 'items'], ImmutableOrderedSet(action.domains)).setIn(['blocks', 'next'], action.next);
|
||||
case DOMAIN_BLOCKS_EXPAND_SUCCESS:
|
||||
return state.updateIn(['blocks', 'items'], set => set.union(action.domains)).setIn(['blocks', 'next'], action.next);
|
||||
case DOMAIN_UNBLOCK_SUCCESS:
|
||||
return state.updateIn(['blocks', 'items'], set => set.delete(action.domain));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,11 @@ const initialState = ImmutableMap({ openId: null, placement: null, keyboard: fal
|
|||
|
||||
export default function dropdownMenu(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case DROPDOWN_MENU_OPEN:
|
||||
return state.merge({ openId: action.id, placement: action.placement, keyboard: action.keyboard });
|
||||
case DROPDOWN_MENU_CLOSE:
|
||||
return state.get('openId') === action.id ? state.set('openId', null) : state;
|
||||
default:
|
||||
return state;
|
||||
case DROPDOWN_MENU_OPEN:
|
||||
return state.merge({ openId: action.id, placement: action.placement, keyboard: action.keyboard });
|
||||
case DROPDOWN_MENU_CLOSE:
|
||||
return state.get('openId') === action.id ? state.set('openId', null) : state;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,10 @@ const importFilters = (_state: State, filters: unknown): State => {
|
|||
};
|
||||
|
||||
export default function filters(state: State = ImmutableList<Filter>(), action: AnyAction): State {
|
||||
switch(action.type) {
|
||||
case FILTERS_FETCH_SUCCESS:
|
||||
return importFilters(state, action.filters);
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case FILTERS_FETCH_SUCCESS:
|
||||
return importFilters(state, action.filters);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,37 +22,37 @@ const initialState = ImmutableMap({
|
|||
});
|
||||
|
||||
export default function groupEditorReducer(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case GROUP_EDITOR_RESET:
|
||||
return initialState;
|
||||
case GROUP_EDITOR_SETUP:
|
||||
return state.withMutations(map => {
|
||||
map.set('groupId', action.group.get('id'));
|
||||
map.set('title', action.group.get('title'));
|
||||
map.set('description', action.group.get('description'));
|
||||
map.set('isSubmitting', false);
|
||||
});
|
||||
case GROUP_EDITOR_VALUE_CHANGE:
|
||||
return state.withMutations(map => {
|
||||
map.set(action.field, action.value);
|
||||
map.set('isChanged', true);
|
||||
});
|
||||
case GROUP_CREATE_REQUEST:
|
||||
case GROUP_UPDATE_REQUEST:
|
||||
return state.withMutations(map => {
|
||||
map.set('isSubmitting', true);
|
||||
map.set('isChanged', false);
|
||||
});
|
||||
case GROUP_CREATE_FAIL:
|
||||
case GROUP_UPDATE_FAIL:
|
||||
return state.set('isSubmitting', false);
|
||||
case GROUP_CREATE_SUCCESS:
|
||||
case GROUP_UPDATE_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('isSubmitting', false);
|
||||
map.set('groupId', action.group.id);
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case GROUP_EDITOR_RESET:
|
||||
return initialState;
|
||||
case GROUP_EDITOR_SETUP:
|
||||
return state.withMutations(map => {
|
||||
map.set('groupId', action.group.get('id'));
|
||||
map.set('title', action.group.get('title'));
|
||||
map.set('description', action.group.get('description'));
|
||||
map.set('isSubmitting', false);
|
||||
});
|
||||
case GROUP_EDITOR_VALUE_CHANGE:
|
||||
return state.withMutations(map => {
|
||||
map.set(action.field, action.value);
|
||||
map.set('isChanged', true);
|
||||
});
|
||||
case GROUP_CREATE_REQUEST:
|
||||
case GROUP_UPDATE_REQUEST:
|
||||
return state.withMutations(map => {
|
||||
map.set('isSubmitting', true);
|
||||
map.set('isChanged', false);
|
||||
});
|
||||
case GROUP_CREATE_FAIL:
|
||||
case GROUP_UPDATE_FAIL:
|
||||
return state.set('isSubmitting', false);
|
||||
case GROUP_CREATE_SUCCESS:
|
||||
case GROUP_UPDATE_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('isSubmitting', false);
|
||||
map.set('groupId', action.group.id);
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,10 +13,10 @@ const normalizeList = (state, type, id, groups) => {
|
|||
};
|
||||
|
||||
export default function groupLists(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case GROUPS_FETCH_SUCCESS:
|
||||
return normalizeList(state, action.tab, action.id, action.groups);
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case GROUPS_FETCH_SUCCESS:
|
||||
return normalizeList(state, action.tab, action.id, action.groups);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,13 +15,13 @@ const normalizeRelationships = (state, relationships) => {
|
|||
const initialState = ImmutableMap();
|
||||
|
||||
export default function group_relationships(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case GROUP_JOIN_SUCCESS:
|
||||
case GROUP_LEAVE_SUCCESS:
|
||||
return normalizeRelationship(state, action.relationship);
|
||||
case GROUP_RELATIONSHIPS_FETCH_SUCCESS:
|
||||
return normalizeRelationships(state, action.relationships);
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case GROUP_JOIN_SUCCESS:
|
||||
case GROUP_LEAVE_SUCCESS:
|
||||
return normalizeRelationship(state, action.relationship);
|
||||
case GROUP_RELATIONSHIPS_FETCH_SUCCESS:
|
||||
return normalizeRelationships(state, action.relationships);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,15 +20,15 @@ const normalizeGroups = (state, groups) => {
|
|||
};
|
||||
|
||||
export default function groups(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case GROUP_FETCH_SUCCESS:
|
||||
case GROUP_UPDATE_SUCCESS:
|
||||
return normalizeGroup(state, action.group);
|
||||
case GROUPS_FETCH_SUCCESS:
|
||||
return normalizeGroups(state, action.groups);
|
||||
case GROUP_FETCH_FAIL:
|
||||
return state.set(action.id, false);
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case GROUP_FETCH_SUCCESS:
|
||||
case GROUP_UPDATE_SUCCESS:
|
||||
return normalizeGroup(state, action.group);
|
||||
case GROUPS_FETCH_SUCCESS:
|
||||
return normalizeGroups(state, action.groups);
|
||||
case GROUP_FETCH_FAIL:
|
||||
return state.set(action.id, false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,20 +16,20 @@ type State = ImmutableMap<string, ReturnType<typeof HistoryRecord>>;
|
|||
const initialState: State = ImmutableMap();
|
||||
|
||||
export default function history(state: State = initialState, action: AnyAction) {
|
||||
switch(action.type) {
|
||||
case HISTORY_FETCH_REQUEST:
|
||||
return state.update(action.statusId, HistoryRecord(), history => history!.withMutations(map => {
|
||||
map.set('loading', true);
|
||||
map.set('items', ImmutableList());
|
||||
}));
|
||||
case HISTORY_FETCH_SUCCESS:
|
||||
return state.update(action.statusId, HistoryRecord(), history => history!.withMutations(map => {
|
||||
map.set('loading', false);
|
||||
map.set('items', ImmutableList(action.history.map((x: any, i: number) => ({ ...x, account: x.account.id, original: i === 0 })).reverse().map(normalizeStatusEdit)));
|
||||
}));
|
||||
case HISTORY_FETCH_FAIL:
|
||||
return state.update(action.statusId, HistoryRecord(), history => history!.set('loading', false));
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case HISTORY_FETCH_REQUEST:
|
||||
return state.update(action.statusId, HistoryRecord(), history => history!.withMutations(map => {
|
||||
map.set('loading', true);
|
||||
map.set('items', ImmutableList());
|
||||
}));
|
||||
case HISTORY_FETCH_SUCCESS:
|
||||
return state.update(action.statusId, HistoryRecord(), history => history!.withMutations(map => {
|
||||
map.set('loading', false);
|
||||
map.set('items', ImmutableList(action.history.map((x: any, i: number) => ({ ...x, account: x.account.id, original: i === 0 })).reverse().map(normalizeStatusEdit)));
|
||||
}));
|
||||
case HISTORY_FETCH_FAIL:
|
||||
return state.update(action.statusId, HistoryRecord(), history => history!.set('loading', false));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -9,18 +9,18 @@ import {
|
|||
const initialState = ImmutableMap();
|
||||
|
||||
export default function identityProofsReducer(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
case IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL:
|
||||
return state.set('isLoading', false);
|
||||
case IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS:
|
||||
return state.update(identity_proofs => identity_proofs.withMutations(map => {
|
||||
map.set('isLoading', false);
|
||||
map.set('loaded', true);
|
||||
map.set(action.accountId, fromJS(action.identity_proofs));
|
||||
}));
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
case IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL:
|
||||
return state.set('isLoading', false);
|
||||
case IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS:
|
||||
return state.update(identity_proofs => identity_proofs.withMutations(map => {
|
||||
map.set('isLoading', false);
|
||||
map.set('loaded', true);
|
||||
map.set(action.accountId, fromJS(action.identity_proofs));
|
||||
}));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,11 +152,11 @@ const logOut = (state: any = StateRecord()): ReturnType<typeof appReducer> => {
|
|||
};
|
||||
|
||||
const rootReducer: typeof appReducer = (state, action) => {
|
||||
switch(action.type) {
|
||||
case AUTH_LOGGED_OUT:
|
||||
return appReducer(logOut(state), action);
|
||||
default:
|
||||
return appReducer(state, action);
|
||||
switch (action.type) {
|
||||
case AUTH_LOGGED_OUT:
|
||||
return appReducer(logOut(state), action);
|
||||
default:
|
||||
return appReducer(state, action);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -111,22 +111,22 @@ const handleInstanceFetchFail = (state: typeof initialState, error: Record<strin
|
|||
};
|
||||
|
||||
export default function instance(state = initialState, action: AnyAction) {
|
||||
switch(action.type) {
|
||||
case PLEROMA_PRELOAD_IMPORT:
|
||||
return preloadImport(state, action, '/api/v1/instance');
|
||||
case rememberInstance.fulfilled.type:
|
||||
return importInstance(state, ImmutableMap(fromJS(action.payload)));
|
||||
case fetchInstance.fulfilled.type:
|
||||
persistInstance(action.payload);
|
||||
return importInstance(state, ImmutableMap(fromJS(action.payload)));
|
||||
case fetchInstance.rejected.type:
|
||||
return handleInstanceFetchFail(state, action.error);
|
||||
case fetchNodeinfo.fulfilled.type:
|
||||
return importNodeinfo(state, ImmutableMap(fromJS(action.payload)));
|
||||
case ADMIN_CONFIG_UPDATE_REQUEST:
|
||||
case ADMIN_CONFIG_UPDATE_SUCCESS:
|
||||
return importConfigs(state, ImmutableList(fromJS(action.configs)));
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case PLEROMA_PRELOAD_IMPORT:
|
||||
return preloadImport(state, action, '/api/v1/instance');
|
||||
case rememberInstance.fulfilled.type:
|
||||
return importInstance(state, ImmutableMap(fromJS(action.payload)));
|
||||
case fetchInstance.fulfilled.type:
|
||||
persistInstance(action.payload);
|
||||
return importInstance(state, ImmutableMap(fromJS(action.payload)));
|
||||
case fetchInstance.rejected.type:
|
||||
return handleInstanceFetchFail(state, action.error);
|
||||
case fetchNodeinfo.fulfilled.type:
|
||||
return importNodeinfo(state, ImmutableMap(fromJS(action.payload)));
|
||||
case ADMIN_CONFIG_UPDATE_REQUEST:
|
||||
case ADMIN_CONFIG_UPDATE_SUCCESS:
|
||||
return importConfigs(state, ImmutableList(fromJS(action.configs)));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,28 +26,28 @@ const ReducerRecord = ImmutableRecord({
|
|||
type State = ReturnType<typeof ReducerRecord>;
|
||||
|
||||
export default function listAdderReducer(state: State = ReducerRecord(), action: AnyAction) {
|
||||
switch(action.type) {
|
||||
case LIST_ADDER_RESET:
|
||||
return ReducerRecord();
|
||||
case LIST_ADDER_SETUP:
|
||||
return state.withMutations(map => {
|
||||
map.set('accountId', action.account.get('id'));
|
||||
});
|
||||
case LIST_ADDER_LISTS_FETCH_REQUEST:
|
||||
return state.setIn(['lists', 'isLoading'], true);
|
||||
case LIST_ADDER_LISTS_FETCH_FAIL:
|
||||
return state.setIn(['lists', 'isLoading'], false);
|
||||
case LIST_ADDER_LISTS_FETCH_SUCCESS:
|
||||
return state.update('lists', lists => lists.withMutations(map => {
|
||||
map.set('isLoading', false);
|
||||
map.set('loaded', true);
|
||||
map.set('items', ImmutableList(action.lists.map((item: { id: string }) => item.id)));
|
||||
}));
|
||||
case LIST_EDITOR_ADD_SUCCESS:
|
||||
return state.updateIn(['lists', 'items'], list => (list as ImmutableList<string>).unshift(action.listId));
|
||||
case LIST_EDITOR_REMOVE_SUCCESS:
|
||||
return state.updateIn(['lists', 'items'], list => (list as ImmutableList<string>).filterNot(item => item === action.listId));
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case LIST_ADDER_RESET:
|
||||
return ReducerRecord();
|
||||
case LIST_ADDER_SETUP:
|
||||
return state.withMutations(map => {
|
||||
map.set('accountId', action.account.get('id'));
|
||||
});
|
||||
case LIST_ADDER_LISTS_FETCH_REQUEST:
|
||||
return state.setIn(['lists', 'isLoading'], true);
|
||||
case LIST_ADDER_LISTS_FETCH_FAIL:
|
||||
return state.setIn(['lists', 'isLoading'], false);
|
||||
case LIST_ADDER_LISTS_FETCH_SUCCESS:
|
||||
return state.update('lists', lists => lists.withMutations(map => {
|
||||
map.set('isLoading', false);
|
||||
map.set('loaded', true);
|
||||
map.set('items', ImmutableList(action.lists.map((item: { id: string }) => item.id)));
|
||||
}));
|
||||
case LIST_EDITOR_ADD_SUCCESS:
|
||||
return state.updateIn(['lists', 'items'], list => (list as ImmutableList<string>).unshift(action.listId));
|
||||
case LIST_EDITOR_REMOVE_SUCCESS:
|
||||
return state.updateIn(['lists', 'items'], list => (list as ImmutableList<string>).filterNot(item => item === action.listId));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,59 +46,59 @@ const ReducerRecord = ImmutableRecord({
|
|||
type State = ReturnType<typeof ReducerRecord>;
|
||||
|
||||
export default function listEditorReducer(state: State = ReducerRecord(), action: AnyAction) {
|
||||
switch(action.type) {
|
||||
case LIST_EDITOR_RESET:
|
||||
return ReducerRecord();
|
||||
case LIST_EDITOR_SETUP:
|
||||
return state.withMutations(map => {
|
||||
map.set('listId', action.list.get('id'));
|
||||
map.set('title', action.list.get('title'));
|
||||
map.set('isSubmitting', false);
|
||||
});
|
||||
case LIST_EDITOR_TITLE_CHANGE:
|
||||
return state.withMutations(map => {
|
||||
map.set('title', action.value);
|
||||
map.set('isChanged', true);
|
||||
});
|
||||
case LIST_CREATE_REQUEST:
|
||||
case LIST_UPDATE_REQUEST:
|
||||
return state.withMutations(map => {
|
||||
map.set('isSubmitting', true);
|
||||
map.set('isChanged', false);
|
||||
});
|
||||
case LIST_CREATE_FAIL:
|
||||
case LIST_UPDATE_FAIL:
|
||||
return state.set('isSubmitting', false);
|
||||
case LIST_CREATE_SUCCESS:
|
||||
case LIST_UPDATE_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('isSubmitting', false);
|
||||
map.set('listId', action.list.id);
|
||||
});
|
||||
case LIST_ACCOUNTS_FETCH_REQUEST:
|
||||
return state.setIn(['accounts', 'isLoading'], true);
|
||||
case LIST_ACCOUNTS_FETCH_FAIL:
|
||||
return state.setIn(['accounts', 'isLoading'], false);
|
||||
case LIST_ACCOUNTS_FETCH_SUCCESS:
|
||||
return state.update('accounts', accounts => accounts.withMutations(map => {
|
||||
map.set('isLoading', false);
|
||||
map.set('loaded', true);
|
||||
map.set('items', ImmutableList(action.accounts.map((item: { id: string }) => item.id)));
|
||||
}));
|
||||
case LIST_EDITOR_SUGGESTIONS_CHANGE:
|
||||
return state.setIn(['suggestions', 'value'], action.value);
|
||||
case LIST_EDITOR_SUGGESTIONS_READY:
|
||||
return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map((item: { id: string }) => item.id)));
|
||||
case LIST_EDITOR_SUGGESTIONS_CLEAR:
|
||||
return state.update('suggestions', suggestions => suggestions.withMutations(map => {
|
||||
map.set('items', ImmutableList());
|
||||
map.set('value', '');
|
||||
}));
|
||||
case LIST_EDITOR_ADD_SUCCESS:
|
||||
return state.updateIn(['accounts', 'items'], list => (list as ImmutableList<string>).unshift(action.accountId));
|
||||
case LIST_EDITOR_REMOVE_SUCCESS:
|
||||
return state.updateIn(['accounts', 'items'], list => (list as ImmutableList<string>).filterNot((item) => item === action.accountId));
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case LIST_EDITOR_RESET:
|
||||
return ReducerRecord();
|
||||
case LIST_EDITOR_SETUP:
|
||||
return state.withMutations(map => {
|
||||
map.set('listId', action.list.get('id'));
|
||||
map.set('title', action.list.get('title'));
|
||||
map.set('isSubmitting', false);
|
||||
});
|
||||
case LIST_EDITOR_TITLE_CHANGE:
|
||||
return state.withMutations(map => {
|
||||
map.set('title', action.value);
|
||||
map.set('isChanged', true);
|
||||
});
|
||||
case LIST_CREATE_REQUEST:
|
||||
case LIST_UPDATE_REQUEST:
|
||||
return state.withMutations(map => {
|
||||
map.set('isSubmitting', true);
|
||||
map.set('isChanged', false);
|
||||
});
|
||||
case LIST_CREATE_FAIL:
|
||||
case LIST_UPDATE_FAIL:
|
||||
return state.set('isSubmitting', false);
|
||||
case LIST_CREATE_SUCCESS:
|
||||
case LIST_UPDATE_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('isSubmitting', false);
|
||||
map.set('listId', action.list.id);
|
||||
});
|
||||
case LIST_ACCOUNTS_FETCH_REQUEST:
|
||||
return state.setIn(['accounts', 'isLoading'], true);
|
||||
case LIST_ACCOUNTS_FETCH_FAIL:
|
||||
return state.setIn(['accounts', 'isLoading'], false);
|
||||
case LIST_ACCOUNTS_FETCH_SUCCESS:
|
||||
return state.update('accounts', accounts => accounts.withMutations(map => {
|
||||
map.set('isLoading', false);
|
||||
map.set('loaded', true);
|
||||
map.set('items', ImmutableList(action.accounts.map((item: { id: string }) => item.id)));
|
||||
}));
|
||||
case LIST_EDITOR_SUGGESTIONS_CHANGE:
|
||||
return state.setIn(['suggestions', 'value'], action.value);
|
||||
case LIST_EDITOR_SUGGESTIONS_READY:
|
||||
return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map((item: { id: string }) => item.id)));
|
||||
case LIST_EDITOR_SUGGESTIONS_CLEAR:
|
||||
return state.update('suggestions', suggestions => suggestions.withMutations(map => {
|
||||
map.set('items', ImmutableList());
|
||||
map.set('value', '');
|
||||
}));
|
||||
case LIST_EDITOR_ADD_SUCCESS:
|
||||
return state.updateIn(['accounts', 'items'], list => (list as ImmutableList<string>).unshift(action.accountId));
|
||||
case LIST_EDITOR_REMOVE_SUCCESS:
|
||||
return state.updateIn(['accounts', 'items'], list => (list as ImmutableList<string>).filterNot((item) => item === action.accountId));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,17 +30,17 @@ const importLists = (state: State, lists: APIEntities) => {
|
|||
};
|
||||
|
||||
export default function lists(state: State = initialState, action: AnyAction) {
|
||||
switch(action.type) {
|
||||
case LIST_FETCH_SUCCESS:
|
||||
case LIST_CREATE_SUCCESS:
|
||||
case LIST_UPDATE_SUCCESS:
|
||||
return importList(state, action.list);
|
||||
case LISTS_FETCH_SUCCESS:
|
||||
return importLists(state, action.lists);
|
||||
case LIST_DELETE_SUCCESS:
|
||||
case LIST_FETCH_FAIL:
|
||||
return state.set(action.id, false);
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case LIST_FETCH_SUCCESS:
|
||||
case LIST_CREATE_SUCCESS:
|
||||
case LIST_UPDATE_SUCCESS:
|
||||
return importList(state, action.list);
|
||||
case LISTS_FETCH_SUCCESS:
|
||||
return importLists(state, action.lists);
|
||||
case LIST_DELETE_SUCCESS:
|
||||
case LIST_FETCH_FAIL:
|
||||
return state.set(action.id, false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,19 +25,19 @@ const handleForbidden = (state: Me, error: AxiosError) => {
|
|||
};
|
||||
|
||||
export default function me(state: Me = initialState, action: AnyAction): Me {
|
||||
switch(action.type) {
|
||||
case ME_FETCH_SUCCESS:
|
||||
case ME_PATCH_SUCCESS:
|
||||
return action.me.id;
|
||||
case VERIFY_CREDENTIALS_SUCCESS:
|
||||
case AUTH_ACCOUNT_REMEMBER_SUCCESS:
|
||||
return state || action.account.id;
|
||||
case ME_FETCH_SKIP:
|
||||
case AUTH_LOGGED_OUT:
|
||||
return false;
|
||||
case ME_FETCH_FAIL:
|
||||
return handleForbidden(state, action.error);
|
||||
default:
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case ME_FETCH_SUCCESS:
|
||||
case ME_PATCH_SUCCESS:
|
||||
return action.me.id;
|
||||
case VERIFY_CREDENTIALS_SUCCESS:
|
||||
case AUTH_ACCOUNT_REMEMBER_SUCCESS:
|
||||
return state || action.account.id;
|
||||
case ME_FETCH_SKIP:
|
||||
case AUTH_LOGGED_OUT:
|
||||
return false;
|
||||
case ME_FETCH_FAIL:
|
||||
return handleForbidden(state, action.error);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|