Merge commit '6aeb162927e6f9bbfd597632a10d82d9656c2385' into glitch-soc/merge-upstream

Conflicts:
- `.github/dependabot.yml`:
  We deleted it.
  Kept it removed.
- `app/javascript/packs/public.jsx`:
  Upstream changed an import, we have slightly different ones.
  Ported upstream changes.
This commit is contained in:
Claire
2023-05-09 23:12:48 +02:00
22 changed files with 216 additions and 206 deletions

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Provider } from 'react-redux';
import PropTypes from 'prop-types';
import { store } from '../store/configureStore';
import { store } from '../store';
import { hydrateStore } from '../actions/store';
import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from '../locales';

View File

@ -5,7 +5,7 @@ import { IntlProvider, addLocaleData } from 'react-intl';
import { Provider as ReduxProvider } from 'react-redux';
import { BrowserRouter, Route } from 'react-router-dom';
import { ScrollContext } from 'react-router-scroll-4';
import { store } from 'mastodon/store/configureStore';
import { store } from 'mastodon/store';
import UI from 'mastodon/features/ui';
import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
import { hydrateStore } from 'mastodon/actions/store';

View File

@ -16,10 +16,6 @@ export const layoutFromWindow = (): LayoutType => {
}
};
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && window.MSStream != null;
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
let userTouching = false;
@ -33,5 +29,3 @@ const touchListener = () => {
window.addEventListener('touchstart', touchListener, listenerOptions);
export const isUserTouching = () => userTouching;
export const isIOS = () => iOS;

View File

@ -2,7 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { setupBrowserNotifications } from 'mastodon/actions/notifications';
import Mastodon from 'mastodon/containers/mastodon';
import { store } from 'mastodon/store/configureStore';
import { store } from 'mastodon/store';
import { me } from 'mastodon/initial_state';
import ready from 'mastodon/ready';
import * as perf from 'mastodon/performance';

View File

@ -1,26 +1,16 @@
import 'intl';
import 'intl/locale-data/jsonp/en';
import 'es6-symbol/implement';
import assign from 'object-assign';
import values from 'object.values';
import { decode as decodeBase64 } from './utils/base64';
import promiseFinally from 'promise.prototype.finally';
if (!Object.assign) {
Object.assign = assign;
}
if (!Object.values) {
values.shim();
}
promiseFinally.shim();
import 'core-js/features/object/assign';
import 'core-js/features/object/values';
import 'core-js/features/symbol';
import 'core-js/features/promise/finally';
import { decode as decodeBase64 } from '../utils/base64';
if (!HTMLCanvasElement.prototype.toBlob) {
const BASE64_MARKER = ';base64,';
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value(callback, type = 'image/png', quality) {
value(callback: BlobCallback, type = 'image/png', quality: any) {
const dataURL = this.toDataURL(type, quality);
let data;

View File

@ -10,14 +10,14 @@ function importExtraPolyfills() {
return import(/* webpackChunkName: "extra_polyfills" */ './extra_polyfills');
}
function loadPolyfills() {
export function loadPolyfills() {
const needsBasePolyfills = !(
HTMLCanvasElement.prototype.toBlob &&
window.Intl &&
Object.assign &&
Object.values &&
window.Symbol &&
Promise.prototype.finally
'toBlob' in HTMLCanvasElement.prototype &&
'Intl' in window &&
'assign' in Object &&
'values' in Object &&
'Symbol' in window &&
'finally' in Promise.prototype
);
// Latest version of Firefox and Safari do not have IntersectionObserver.
@ -36,5 +36,3 @@ function loadPolyfills() {
needsExtraPolyfills && importExtraPolyfills(),
]);
}
export default loadPolyfills;

View File

@ -87,4 +87,6 @@ const reducers = {
followed_tags,
};
export default combineReducers(reducers);
const rootReducer = combineReducers(reducers);
export { rootReducer };

View File

@ -1,16 +0,0 @@
import { configureStore } from '@reduxjs/toolkit';
import thunk from 'redux-thunk';
import appReducer from '../reducers';
import loadingBarMiddleware from '../middleware/loading_bar';
import errorsMiddleware from '../middleware/errors';
import soundsMiddleware from '../middleware/sounds';
export const store = configureStore({
reducer: appReducer,
middleware: [
thunk,
loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }),
errorsMiddleware(),
soundsMiddleware(),
],
});

View File

@ -0,0 +1,23 @@
import { configureStore } from '@reduxjs/toolkit';
import { rootReducer } from '../reducers';
import { loadingBarMiddleware } from './middlewares/loading_bar';
import { errorsMiddleware } from './middlewares/errors';
import { soundsMiddleware } from './middlewares/sounds';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
export const store = configureStore({
reducer: rootReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware().concat(
loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }))
.concat(errorsMiddleware)
.concat(soundsMiddleware()),
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof rootReducer>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

View File

@ -1,9 +1,11 @@
import { showAlertForError } from '../actions/alerts';
import { Middleware } from 'redux';
import { showAlertForError } from '../../actions/alerts';
import { RootState } from '..';
const defaultFailSuffix = 'FAIL';
export default function errorsMiddleware() {
return ({ dispatch }) => next => action => {
export const errorsMiddleware: Middleware<Record<string, never>, RootState> =
({ dispatch }) => next => action => {
if (action.type && !action.skipAlert) {
const isFail = new RegExp(`${defaultFailSuffix}$`, 'g');
@ -14,4 +16,3 @@ export default function errorsMiddleware() {
return next(action);
};
}

View File

@ -1,8 +1,14 @@
import { showLoading, hideLoading } from 'react-redux-loading-bar';
import { Middleware } from 'redux';
import { RootState } from '..';
const defaultTypeSuffixes = ['PENDING', 'FULFILLED', 'REJECTED'];
interface Config {
promiseTypeSuffixes?: string[]
}
export default function loadingBarMiddleware(config = {}) {
const defaultTypeSuffixes: Config['promiseTypeSuffixes'] = ['PENDING', 'FULFILLED', 'REJECTED'];
export const loadingBarMiddleware = (config: Config = {}): Middleware<Record<string, never>, RootState> => {
const promiseTypeSuffixes = config.promiseTypeSuffixes || defaultTypeSuffixes;
return ({ dispatch }) => next => (action) => {
@ -22,4 +28,4 @@ export default function loadingBarMiddleware(config = {}) {
return next(action);
};
}
};

View File

@ -1,4 +1,12 @@
const createAudio = sources => {
import { Middleware, AnyAction } from 'redux';
import { RootState } from '..';
interface AudioSource {
src: string
type: string
}
const createAudio = (sources: AudioSource[]) => {
const audio = new Audio();
sources.forEach(({ type, src }) => {
const source = document.createElement('source');
@ -9,7 +17,7 @@ const createAudio = sources => {
return audio;
};
const play = audio => {
const play = (audio: HTMLAudioElement) => {
if (!audio.paused) {
audio.pause();
if (typeof audio.fastSeek === 'function') {
@ -22,8 +30,8 @@ const play = audio => {
audio.play();
};
export default function soundsMiddleware() {
const soundCache = {
export const soundsMiddleware = (): Middleware<Record<string, never>, RootState> => {
const soundCache: {[key: string]: HTMLAudioElement} = {
boop: createAudio([
{
src: '/sounds/boop.ogg',
@ -36,11 +44,13 @@ export default function soundsMiddleware() {
]),
};
return () => next => action => {
if (action.meta && action.meta.sound && soundCache[action.meta.sound]) {
play(soundCache[action.meta.sound]);
return () => next => (action: AnyAction) => {
const sound = action?.meta?.sound;
if (sound && soundCache[sound]) {
play(soundCache[sound]);
}
return next(action);
};
}
};

View File

@ -1,5 +1,5 @@
import './public-path';
import loadPolyfills from '../mastodon/load_polyfills';
import { loadPolyfills } from '../mastodon/polyfills';
import { start } from '../mastodon/common';
start();

View File

@ -1,8 +1,9 @@
import './public-path';
import loadPolyfills from '../mastodon/load_polyfills';
import { loadPolyfills } from '../mastodon/polyfills';
import ready from '../mastodon/ready';
import { start } from '../mastodon/common';
import ready from '../mastodon/ready';
import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions';
import 'cocoon-js-vanilla';
import axios from 'axios';
@ -11,7 +12,7 @@ import { defineMessages } from 'react-intl';
import * as IntlMessageFormat from 'intl-messageformat';
import { timeAgoString } from '../mastodon/components/relative_timestamp';
import { delegate } from '@rails/ujs';
import * as emojify from '../mastodon/features/emoji/emoji';
import emojify from '../mastodon/features/emoji/emoji';
import { getLocale } from '../mastodon/locales';
import React from 'react';
import ReactDOM from 'react-dom';

View File

@ -1,5 +1,5 @@
import './public-path';
import loadPolyfills from '../mastodon/load_polyfills';
import { loadPolyfills } from '../mastodon/polyfills';
import { start } from '../mastodon/common';
import ready from '../mastodon/ready';
import ComposeContainer from '../mastodon/containers/compose_container';

View File

@ -1,10 +1,54 @@
import type { Record } from 'immutable';
type AccountValues = {
id: number;
type CustomEmoji = Record<{
shortcode: string;
static_url: string;
url: string;
}>;
type AccountField = Record<{
name: string;
value: string;
verified_at: string | null;
}>;
type AccountApiResponseValues = {
acct: string;
avatar: string;
avatar_static: string;
[key: string]: any;
bot: boolean;
created_at: string;
discoverable: boolean;
display_name: string;
emojis: CustomEmoji[];
fields: AccountField[];
followers_count: number;
following_count: number;
group: boolean;
header: string;
header_static: string;
id: string;
last_status_at: string;
locked: boolean;
note: string;
statuses_count: number;
url: string;
username: string;
};
export type Account = Record<AccountValues>;
type NormalizedAccountField = Record<{
name_emojified: string;
value_emojified: string;
value_plain: string;
}>;
type NormalizedAccountValues = {
display_name_html: string;
fields: NormalizedAccountField[];
note_emojified: string;
note_plain: string;
};
export type Account = Record<
AccountApiResponseValues & NormalizedAccountValues
>;