Merge remote-tracking branch 'origin/main' into upgrade-deps
This commit is contained in:
commit
588b86eb84
|
@ -41,7 +41,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@akryum/flexsearch-es": "^0.7.32",
|
||||
"@emoji-mart/data": "^1.1.2",
|
||||
"@emoji-mart/data": "^1.2.1",
|
||||
"@floating-ui/react": "^0.26.0",
|
||||
"@fontsource/inter": "^5.0.0",
|
||||
"@fontsource/noto-sans-javanese": "^5.0.16",
|
||||
|
@ -72,6 +72,7 @@
|
|||
"@tailwindcss/forms": "^0.5.9",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tanstack/react-query": "^5.59.13",
|
||||
"@twemoji/svg": "^15.0.0",
|
||||
"@types/escape-html": "^1.0.1",
|
||||
"@types/http-link-header": "^1.0.3",
|
||||
"@types/leaflet": "^1.8.0",
|
||||
|
@ -102,8 +103,8 @@
|
|||
"cryptocurrency-icons": "^0.18.1",
|
||||
"cssnano": "^6.0.0",
|
||||
"detect-passive-events": "^2.0.0",
|
||||
"emoji-datasource": "14.0.0",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"emoji-datasource": "15.0.1",
|
||||
"emoji-mart": "^5.6.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"eslint-plugin-formatjs": "^4.12.2",
|
||||
"exifr": "^7.1.3",
|
||||
|
@ -152,7 +153,6 @@
|
|||
"sass": "^1.79.5",
|
||||
"semver": "^7.3.8",
|
||||
"stringz": "^2.0.0",
|
||||
"twemoji": "https://github.com/twitter/twemoji#v14.0.2",
|
||||
"type-fest": "^4.0.0",
|
||||
"typescript": "^5.6.2",
|
||||
"vite": "^5.4.8",
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import { register, saveSettings } from './registerer';
|
||||
import {
|
||||
SET_BROWSER_SUPPORT,
|
||||
SET_SUBSCRIPTION,
|
||||
CLEAR_SUBSCRIPTION,
|
||||
SET_ALERTS,
|
||||
setAlerts,
|
||||
} from './setter';
|
||||
|
||||
import type { AppDispatch } from 'soapbox/store';
|
||||
|
||||
export {
|
||||
SET_BROWSER_SUPPORT,
|
||||
SET_SUBSCRIPTION,
|
||||
CLEAR_SUBSCRIPTION,
|
||||
SET_ALERTS,
|
||||
register,
|
||||
changeAlerts,
|
||||
};
|
||||
|
||||
const changeAlerts = (path: Array<string>, value: any) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
dispatch(setAlerts(path, value));
|
||||
dispatch(saveSettings() as any);
|
||||
};
|
|
@ -1,159 +1,108 @@
|
|||
import { createPushSubscription, updatePushSubscription } from 'soapbox/actions/push-subscriptions';
|
||||
import { pushNotificationsSetting } from 'soapbox/settings';
|
||||
import { getVapidKey } from 'soapbox/utils/auth';
|
||||
import { decode as decodeBase64 } from 'soapbox/utils/base64';
|
||||
|
||||
import { setBrowserSupport, setSubscription, clearSubscription } from './setter';
|
||||
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { Me } from 'soapbox/types/soapbox';
|
||||
|
||||
// Taken from https://www.npmjs.com/package/web-push
|
||||
const urlBase64ToUint8Array = (base64String: string) => {
|
||||
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||
const base64 = (base64String + padding)
|
||||
.replace(/-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
|
||||
return decodeBase64(base64);
|
||||
};
|
||||
|
||||
const getRegistration = () => {
|
||||
if (navigator.serviceWorker) {
|
||||
return navigator.serviceWorker.ready;
|
||||
} else {
|
||||
throw 'Your browser does not support Service Workers.';
|
||||
}
|
||||
};
|
||||
|
||||
const getPushSubscription = (registration: ServiceWorkerRegistration) =>
|
||||
registration.pushManager.getSubscription()
|
||||
.then(subscription => ({ registration, subscription }));
|
||||
|
||||
const subscribe = (registration: ServiceWorkerRegistration, getState: () => RootState) =>
|
||||
registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(getVapidKey(getState())),
|
||||
});
|
||||
|
||||
const unsubscribe = ({ registration, subscription }: {
|
||||
registration: ServiceWorkerRegistration;
|
||||
subscription: PushSubscription | null;
|
||||
}) =>
|
||||
subscription ? subscription.unsubscribe().then(() => registration) : new Promise<ServiceWorkerRegistration>(r => r(registration));
|
||||
|
||||
const sendSubscriptionToBackend = (subscription: PushSubscription, me: Me) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const alerts = getState().push_notifications.alerts.toJS();
|
||||
const params = { subscription: subscription.toJSON(), data: { alerts } };
|
||||
|
||||
if (me) {
|
||||
const data = pushNotificationsSetting.get(me);
|
||||
if (data) {
|
||||
params.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
return dispatch(createPushSubscription(params));
|
||||
};
|
||||
/* eslint-disable compat/compat */
|
||||
import { HTTPError } from 'soapbox/api/HTTPError';
|
||||
import { MastodonClient } from 'soapbox/api/MastodonClient';
|
||||
import { WebPushSubscription, webPushSubscriptionSchema } from 'soapbox/schemas/web-push';
|
||||
import { decodeBase64Url } from 'soapbox/utils/base64';
|
||||
|
||||
// Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload
|
||||
// eslint-disable-next-line compat/compat
|
||||
const supportsPushNotifications = ('serviceWorker' in navigator && 'PushManager' in window && 'getKey' in PushSubscription.prototype);
|
||||
|
||||
const register = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const me = getState().me;
|
||||
const vapidKey = getVapidKey(getState());
|
||||
/**
|
||||
* Register web push notifications.
|
||||
* This function creates a subscription if one hasn't been created already, and syncronizes it with the backend.
|
||||
*/
|
||||
export async function registerPushNotifications(api: MastodonClient, vapidKey: string) {
|
||||
if (!supportsPushNotifications) {
|
||||
console.warn('Your browser does not support Web Push Notifications.');
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(setBrowserSupport(supportsPushNotifications));
|
||||
const { subscription, created } = await getOrCreateSubscription(vapidKey);
|
||||
|
||||
if (!supportsPushNotifications) {
|
||||
console.warn('Your browser does not support Web Push Notifications.');
|
||||
return;
|
||||
if (created) {
|
||||
await sendSubscriptionToBackend(api, subscription);
|
||||
return;
|
||||
}
|
||||
|
||||
// We have a subscription, check if it is still valid.
|
||||
const backend = await getBackendSubscription(api);
|
||||
|
||||
// If the VAPID public key did not change and the endpoint corresponds
|
||||
// to the endpoint saved in the backend, the subscription is valid.
|
||||
if (backend && subscriptionMatchesBackend(subscription, backend)) {
|
||||
return;
|
||||
} else {
|
||||
// Something went wrong, try to subscribe again.
|
||||
await subscription.unsubscribe();
|
||||
const newSubscription = await createSubscription(vapidKey);
|
||||
await sendSubscriptionToBackend(api, newSubscription);
|
||||
}
|
||||
}
|
||||
|
||||
/** Get an existing subscription object from the browser if it exists, or ask the browser to create one. */
|
||||
async function getOrCreateSubscription(vapidKey: string): Promise<{ subscription: PushSubscription; created: boolean }> {
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
const subscription = await registration.pushManager.getSubscription();
|
||||
|
||||
if (subscription) {
|
||||
return { subscription, created: false };
|
||||
} else {
|
||||
const subscription = await createSubscription(vapidKey);
|
||||
return { subscription, created: true };
|
||||
}
|
||||
}
|
||||
|
||||
/** Request a subscription object from the web browser. */
|
||||
async function createSubscription(vapidKey: string): Promise<PushSubscription> {
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
|
||||
return registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: decodeBase64Url(vapidKey),
|
||||
});
|
||||
}
|
||||
|
||||
/** Fetch the API for an existing subscription saved in the backend, if any. */
|
||||
async function getBackendSubscription(api: MastodonClient): Promise<WebPushSubscription | null> {
|
||||
try {
|
||||
const response = await api.get('/api/v1/push/subscription');
|
||||
const data = await response.json();
|
||||
return webPushSubscriptionSchema.parse(data);
|
||||
} catch (e) {
|
||||
if (e instanceof HTTPError && e.response.status === 404) {
|
||||
return null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!vapidKey) {
|
||||
console.error('The VAPID public key is not set. You will not be able to receive Web Push Notifications.');
|
||||
return;
|
||||
}
|
||||
|
||||
getRegistration()
|
||||
.then(getPushSubscription)
|
||||
// @ts-ignore
|
||||
.then(({ registration, subscription }: {
|
||||
registration: ServiceWorkerRegistration;
|
||||
subscription: PushSubscription | null;
|
||||
}) => {
|
||||
if (subscription !== null) {
|
||||
// We have a subscription, check if it is still valid
|
||||
const currentServerKey = (new Uint8Array(subscription.options.applicationServerKey!)).toString();
|
||||
const subscriptionServerKey = urlBase64ToUint8Array(vapidKey).toString();
|
||||
const serverEndpoint = getState().push_notifications.subscription?.endpoint;
|
||||
|
||||
// If the VAPID public key did not change and the endpoint corresponds
|
||||
// to the endpoint saved in the backend, the subscription is valid
|
||||
if (subscriptionServerKey === currentServerKey && subscription.endpoint === serverEndpoint) {
|
||||
return { subscription };
|
||||
} else {
|
||||
// Something went wrong, try to subscribe again
|
||||
return unsubscribe({ registration, subscription }).then((registration: ServiceWorkerRegistration) => {
|
||||
return subscribe(registration, getState);
|
||||
}).then(
|
||||
(subscription: PushSubscription) => dispatch(sendSubscriptionToBackend(subscription, me) as any));
|
||||
}
|
||||
}
|
||||
|
||||
// No subscription, try to subscribe
|
||||
return subscribe(registration, getState)
|
||||
.then(subscription => dispatch(sendSubscriptionToBackend(subscription, me) as any));
|
||||
})
|
||||
.then(({ subscription }: { subscription: PushSubscription | Record<string, any> }) => {
|
||||
// If we got a PushSubscription (and not a subscription object from the backend)
|
||||
// it means that the backend subscription is valid (and was set during hydration)
|
||||
if (!(subscription instanceof PushSubscription)) {
|
||||
dispatch(setSubscription(subscription as PushSubscription));
|
||||
if (me) {
|
||||
pushNotificationsSetting.set(me, { alerts: subscription.alerts });
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.code === 20 && error.name === 'AbortError') {
|
||||
console.warn('Your browser supports Web Push Notifications, but does not seem to implement the VAPID protocol.');
|
||||
} else if (error.code === 5 && error.name === 'InvalidCharacterError') {
|
||||
console.error('The VAPID public key seems to be invalid:', vapidKey);
|
||||
}
|
||||
|
||||
// Clear alerts and hide UI settings
|
||||
dispatch(clearSubscription());
|
||||
|
||||
if (me) {
|
||||
pushNotificationsSetting.remove(me);
|
||||
}
|
||||
|
||||
return getRegistration()
|
||||
.then(getPushSubscription)
|
||||
.then(unsubscribe);
|
||||
})
|
||||
.catch(console.warn);
|
||||
/** Publish a new subscription to the backend. */
|
||||
async function sendSubscriptionToBackend(api: MastodonClient, subscription: PushSubscription): Promise<WebPushSubscription> {
|
||||
const params = {
|
||||
subscription: subscription.toJSON(),
|
||||
};
|
||||
|
||||
const saveSettings = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState().push_notifications;
|
||||
const alerts = state.alerts;
|
||||
const data = { alerts };
|
||||
const me = getState().me;
|
||||
const response = await api.post('/api/v1/push/subscription', params);
|
||||
const data = await response.json();
|
||||
|
||||
return dispatch(updatePushSubscription({ data })).then(() => {
|
||||
if (me) {
|
||||
pushNotificationsSetting.set(me, data);
|
||||
}
|
||||
}).catch(console.warn);
|
||||
};
|
||||
return webPushSubscriptionSchema.parse(data);
|
||||
}
|
||||
|
||||
export {
|
||||
register,
|
||||
saveSettings,
|
||||
};
|
||||
/** Check if the VAPID key and endpoint of the subscription match the data in the backend. */
|
||||
function subscriptionMatchesBackend(subscription: PushSubscription, backend: WebPushSubscription): boolean {
|
||||
const { applicationServerKey } = subscription.options;
|
||||
|
||||
if (subscription.endpoint !== backend.endpoint) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!applicationServerKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const backendKeyBytes: Uint8Array = decodeBase64Url(backend.server_key);
|
||||
const subscriptionKeyBytes: Uint8Array = new Uint8Array(applicationServerKey);
|
||||
|
||||
return backendKeyBytes.toString() === subscriptionKeyBytes.toString();
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import type { AnyAction } from 'redux';
|
||||
|
||||
const SET_BROWSER_SUPPORT = 'PUSH_NOTIFICATIONS_SET_BROWSER_SUPPORT';
|
||||
const SET_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_SET_SUBSCRIPTION';
|
||||
const CLEAR_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_CLEAR_SUBSCRIPTION';
|
||||
const SET_ALERTS = 'PUSH_NOTIFICATIONS_SET_ALERTS';
|
||||
|
||||
const setBrowserSupport = (value: boolean) => ({
|
||||
type: SET_BROWSER_SUPPORT,
|
||||
value,
|
||||
});
|
||||
|
||||
const setSubscription = (subscription: PushSubscription) => ({
|
||||
type: SET_SUBSCRIPTION,
|
||||
subscription,
|
||||
});
|
||||
|
||||
const clearSubscription = () => ({
|
||||
type: CLEAR_SUBSCRIPTION,
|
||||
});
|
||||
|
||||
const setAlerts = (path: Array<string>, value: any) =>
|
||||
(dispatch: React.Dispatch<AnyAction>) =>
|
||||
dispatch({
|
||||
type: SET_ALERTS,
|
||||
path,
|
||||
value,
|
||||
});
|
||||
|
||||
export {
|
||||
SET_BROWSER_SUPPORT,
|
||||
SET_SUBSCRIPTION,
|
||||
CLEAR_SUBSCRIPTION,
|
||||
SET_ALERTS,
|
||||
setBrowserSupport,
|
||||
setSubscription,
|
||||
clearSubscription,
|
||||
setAlerts,
|
||||
};
|
|
@ -1,86 +0,0 @@
|
|||
import api from '../api';
|
||||
|
||||
const PUSH_SUBSCRIPTION_CREATE_REQUEST = 'PUSH_SUBSCRIPTION_CREATE_REQUEST';
|
||||
const PUSH_SUBSCRIPTION_CREATE_SUCCESS = 'PUSH_SUBSCRIPTION_CREATE_SUCCESS';
|
||||
const PUSH_SUBSCRIPTION_CREATE_FAIL = 'PUSH_SUBSCRIPTION_CREATE_FAIL';
|
||||
|
||||
const PUSH_SUBSCRIPTION_FETCH_REQUEST = 'PUSH_SUBSCRIPTION_FETCH_REQUEST';
|
||||
const PUSH_SUBSCRIPTION_FETCH_SUCCESS = 'PUSH_SUBSCRIPTION_FETCH_SUCCESS';
|
||||
const PUSH_SUBSCRIPTION_FETCH_FAIL = 'PUSH_SUBSCRIPTION_FETCH_FAIL';
|
||||
|
||||
const PUSH_SUBSCRIPTION_UPDATE_REQUEST = 'PUSH_SUBSCRIPTION_UPDATE_REQUEST';
|
||||
const PUSH_SUBSCRIPTION_UPDATE_SUCCESS = 'PUSH_SUBSCRIPTION_UPDATE_SUCCESS';
|
||||
const PUSH_SUBSCRIPTION_UPDATE_FAIL = 'PUSH_SUBSCRIPTION_UPDATE_FAIL';
|
||||
|
||||
const PUSH_SUBSCRIPTION_DELETE_REQUEST = 'PUSH_SUBSCRIPTION_DELETE_REQUEST';
|
||||
const PUSH_SUBSCRIPTION_DELETE_SUCCESS = 'PUSH_SUBSCRIPTION_DELETE_SUCCESS';
|
||||
const PUSH_SUBSCRIPTION_DELETE_FAIL = 'PUSH_SUBSCRIPTION_DELETE_FAIL';
|
||||
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
|
||||
interface CreatePushSubscriptionParams {
|
||||
subscription: PushSubscriptionJSON;
|
||||
data?: {
|
||||
alerts?: Record<string, boolean>;
|
||||
policy?: 'all' | 'followed' | 'follower' | 'none';
|
||||
};
|
||||
}
|
||||
|
||||
const createPushSubscription = (params: CreatePushSubscriptionParams) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: PUSH_SUBSCRIPTION_CREATE_REQUEST, params });
|
||||
return api(getState).post('/api/v1/push/subscription', params).then(({ data: subscription }) =>
|
||||
dispatch({ type: PUSH_SUBSCRIPTION_CREATE_SUCCESS, params, subscription }),
|
||||
).catch(error =>
|
||||
dispatch({ type: PUSH_SUBSCRIPTION_CREATE_FAIL, params, error }),
|
||||
);
|
||||
};
|
||||
|
||||
const fetchPushSubscription = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: PUSH_SUBSCRIPTION_FETCH_REQUEST });
|
||||
return api(getState).get('/api/v1/push/subscription').then(({ data: subscription }) =>
|
||||
dispatch({ type: PUSH_SUBSCRIPTION_FETCH_SUCCESS, subscription }),
|
||||
).catch(error =>
|
||||
dispatch({ type: PUSH_SUBSCRIPTION_FETCH_FAIL, error }),
|
||||
);
|
||||
};
|
||||
|
||||
const updatePushSubscription = (params: Record<string, any>) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: PUSH_SUBSCRIPTION_UPDATE_REQUEST, params });
|
||||
return api(getState).put('/api/v1/push/subscription', params).then(({ data: subscription }) =>
|
||||
dispatch({ type: PUSH_SUBSCRIPTION_UPDATE_SUCCESS, params, subscription }),
|
||||
).catch(error =>
|
||||
dispatch({ type: PUSH_SUBSCRIPTION_UPDATE_FAIL, params, error }),
|
||||
);
|
||||
};
|
||||
|
||||
const deletePushSubscription = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: PUSH_SUBSCRIPTION_DELETE_REQUEST });
|
||||
return api(getState).delete('/api/v1/push/subscription').then(() =>
|
||||
dispatch({ type: PUSH_SUBSCRIPTION_DELETE_SUCCESS }),
|
||||
).catch(error =>
|
||||
dispatch({ type: PUSH_SUBSCRIPTION_DELETE_FAIL, error }),
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
PUSH_SUBSCRIPTION_CREATE_REQUEST,
|
||||
PUSH_SUBSCRIPTION_CREATE_SUCCESS,
|
||||
PUSH_SUBSCRIPTION_CREATE_FAIL,
|
||||
PUSH_SUBSCRIPTION_FETCH_REQUEST,
|
||||
PUSH_SUBSCRIPTION_FETCH_SUCCESS,
|
||||
PUSH_SUBSCRIPTION_FETCH_FAIL,
|
||||
PUSH_SUBSCRIPTION_UPDATE_REQUEST,
|
||||
PUSH_SUBSCRIPTION_UPDATE_SUCCESS,
|
||||
PUSH_SUBSCRIPTION_UPDATE_FAIL,
|
||||
PUSH_SUBSCRIPTION_DELETE_REQUEST,
|
||||
PUSH_SUBSCRIPTION_DELETE_SUCCESS,
|
||||
PUSH_SUBSCRIPTION_DELETE_FAIL,
|
||||
createPushSubscription,
|
||||
fetchPushSubscription,
|
||||
updatePushSubscription,
|
||||
deletePushSubscription,
|
||||
};
|
|
@ -10,7 +10,7 @@ import toast from 'soapbox/toast';
|
|||
|
||||
|
||||
const messages = defineMessages({
|
||||
sucessMessage: { id: 'nostr_signup.captcha_message.sucess', defaultMessage: 'Incredible! You\'ve successfully completed the captcha. Let\'s move on to the next step!' },
|
||||
sucessMessage: { id: 'nostr_signup.captcha_message.sucess', defaultMessage: 'Incredible! You\'ve successfully completed the captcha.' },
|
||||
wrongMessage: { id: 'nostr_signup.captcha_message.wrong', defaultMessage: 'Oops! It looks like your captcha response was incorrect. Please try again.' },
|
||||
errorMessage: { id: 'nostr_signup.captcha_message.error', defaultMessage: 'It seems an error has occurred. Please try again. If the problem persists, please contact us.' },
|
||||
misbehavingMessage: { id: 'nostr_signup.captcha_message.misbehaving', defaultMessage: 'It looks like we\'re experiencing issues with the {instance}. Please try again. If the error persists, try again later.' },
|
||||
|
|
|
@ -96,7 +96,7 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
|||
if (!isSubmitDisabled && !createChatMessage.isPending) {
|
||||
submitMessage();
|
||||
|
||||
if (!chat.accepted) {
|
||||
if (chat.accepted === false) {
|
||||
acceptChat.mutate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import clsx from 'clsx';
|
||||
import { CLEAR_EDITOR_COMMAND, TextNode, type LexicalEditor, $getRoot } from 'lexical';
|
||||
import React, { Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { length } from 'stringz';
|
||||
|
||||
import {
|
||||
|
@ -39,7 +39,6 @@ import SpoilerInput from './spoiler-input';
|
|||
import TextCharacterCounter from './text-character-counter';
|
||||
import UploadForm from './upload-form';
|
||||
import VisualCharacterCounter from './visual-character-counter';
|
||||
import Warning from './warning';
|
||||
|
||||
import type { Emoji } from 'soapbox/features/emoji';
|
||||
|
||||
|
@ -73,7 +72,6 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
|
|||
const compose = useCompose(id);
|
||||
const showSearch = useAppSelector((state) => state.search.submitted && !state.search.hidden);
|
||||
const maxTootChars = instance.configuration.statuses.max_characters;
|
||||
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
|
||||
const features = useFeatures();
|
||||
|
||||
const {
|
||||
|
@ -246,25 +244,6 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
|
|||
|
||||
return (
|
||||
<Stack className='w-full' space={4} ref={formRef} onClick={handleClick} element='form' onSubmit={handleSubmit}>
|
||||
{scheduledStatusCount > 0 && !event && !group && (
|
||||
<Warning
|
||||
message={(
|
||||
<FormattedMessage
|
||||
id='compose_form.scheduled_statuses.message'
|
||||
defaultMessage='You have scheduled posts. {click_here} to see them.'
|
||||
values={{ click_here: (
|
||||
<Link to='/scheduled_statuses'>
|
||||
<FormattedMessage
|
||||
id='compose_form.scheduled_statuses.click_here'
|
||||
defaultMessage='Click here'
|
||||
/>
|
||||
</Link>
|
||||
) }}
|
||||
/>)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<WarningContainer composeId={id} />
|
||||
|
||||
{!shouldCondense && !event && !group && groupId && <ReplyGroupIndicator composeId={id} />}
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useAppSelector, useCompose } from 'soapbox/hooks';
|
||||
import { useAppSelector, useCompose, useFeatures, useOwnAccount, useSettingsNotifications } from 'soapbox/hooks';
|
||||
import { selectOwnAccount } from 'soapbox/selectors';
|
||||
|
||||
import Warning from '../components/warning';
|
||||
|
@ -15,11 +15,61 @@ interface IWarningWrapper {
|
|||
|
||||
const WarningWrapper: React.FC<IWarningWrapper> = ({ composeId }) => {
|
||||
const compose = useCompose(composeId);
|
||||
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
|
||||
const { account } = useOwnAccount();
|
||||
const settingsNotifications = useSettingsNotifications();
|
||||
const features = useFeatures();
|
||||
|
||||
const needsLockWarning = useAppSelector((state) => compose.privacy === 'private' && !selectOwnAccount(state)!.locked);
|
||||
const hashtagWarning = (compose.privacy !== 'public' && compose.privacy !== 'group') && APPROX_HASHTAG_RE.test(compose.text);
|
||||
const directMessageWarning = compose.privacy === 'direct';
|
||||
|
||||
if (scheduledStatusCount > 0) {
|
||||
return (
|
||||
<Warning
|
||||
message={(
|
||||
<FormattedMessage
|
||||
id='compose_form.scheduled_statuses.message'
|
||||
defaultMessage='You have scheduled posts. {click_here} to see them.'
|
||||
values={{ click_here: (
|
||||
<Link to='/scheduled_statuses'>
|
||||
<FormattedMessage
|
||||
id='compose_form.scheduled_statuses.click_here'
|
||||
defaultMessage='Click here'
|
||||
/>
|
||||
</Link>
|
||||
) }}
|
||||
/>)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (features.nostr && account?.source?.nostr?.nip05 === undefined) {
|
||||
return (
|
||||
<Warning
|
||||
message={(settingsNotifications.has('needsNip05')) ? (
|
||||
<FormattedMessage
|
||||
id='compose_form.nip05.warning'
|
||||
defaultMessage={'You don\'t have a username configured. {click} to set one up.'}
|
||||
values={{
|
||||
click: (
|
||||
<Link to='/settings/identity'>
|
||||
<FormattedMessage id='compose_form.nip05.warning.click' defaultMessage='Click here' />
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='compose_form.nip05.pending'
|
||||
defaultMessage='Your username request is under review.'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (needsLockWarning) {
|
||||
return (
|
||||
<Warning
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import data from '@emoji-mart/data/sets/14/twitter.json';
|
||||
import data from '@emoji-mart/data/sets/15/twitter.json';
|
||||
|
||||
export interface NativeEmoji {
|
||||
unified: string;
|
||||
|
|
|
@ -5,9 +5,8 @@ import { FormattedMessage } from 'react-intl';
|
|||
import { fetchAccount } from 'soapbox/actions/accounts';
|
||||
import { logInNostr } from 'soapbox/actions/nostr';
|
||||
import { closeSidebar } from 'soapbox/actions/sidebar';
|
||||
import CopyableInput from 'soapbox/components/copyable-input';
|
||||
import EmojiGraphic from 'soapbox/components/emoji-graphic';
|
||||
import { Button, Stack, Modal, FormGroup, Text, Tooltip, HStack } from 'soapbox/components/ui';
|
||||
import { Button, Stack, Modal, FormGroup, Text, Tooltip, HStack, Input } from 'soapbox/components/ui';
|
||||
import { useNostr } from 'soapbox/contexts/nostr-context';
|
||||
import { NKeys } from 'soapbox/features/nostr/keys';
|
||||
import { useAppDispatch, useInstance } from 'soapbox/hooks';
|
||||
|
@ -43,8 +42,6 @@ const KeygenStep: React.FC<IKeygenStep> = ({ onClose }) => {
|
|||
setDownloaded(true);
|
||||
};
|
||||
|
||||
const handleCopy = () => setDownloaded(true);
|
||||
|
||||
const handleNext = async () => {
|
||||
const signer = NKeys.add(secretKey);
|
||||
const pubkey = await signer.getPublicKey();
|
||||
|
@ -63,10 +60,10 @@ const KeygenStep: React.FC<IKeygenStep> = ({ onClose }) => {
|
|||
await relay?.event(kind0);
|
||||
await Promise.all(events.map((event) => relay?.event(event)));
|
||||
|
||||
await dispatch(logInNostr(pubkey));
|
||||
|
||||
onClose();
|
||||
|
||||
await dispatch(logInNostr(pubkey));
|
||||
|
||||
if (isMobile) {
|
||||
dispatch(closeSidebar());
|
||||
}
|
||||
|
@ -79,7 +76,13 @@ const KeygenStep: React.FC<IKeygenStep> = ({ onClose }) => {
|
|||
<EmojiGraphic emoji='🔑' />
|
||||
|
||||
<FormGroup labelText={<FormattedMessage id='nostr_signup.keygen.label_text' defaultMessage='Secret Key' />}>
|
||||
<CopyableInput value={nsec} type='password' onCopy={handleCopy} />
|
||||
<Input
|
||||
type={'password'}
|
||||
value={nsec}
|
||||
className='rounded-lg'
|
||||
outerClassName='grow'
|
||||
readOnly
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<Stack className='rounded-xl bg-gray-100 p-4 dark:bg-gray-800'>
|
||||
|
|
|
@ -8,7 +8,7 @@ import { fetchCustomEmojis } from 'soapbox/actions/custom-emojis';
|
|||
import { fetchFilters } from 'soapbox/actions/filters';
|
||||
import { fetchMarker } from 'soapbox/actions/markers';
|
||||
import { expandNotifications } from 'soapbox/actions/notifications';
|
||||
import { register as registerPushNotifications } from 'soapbox/actions/push-notifications';
|
||||
import { registerPushNotifications } from 'soapbox/actions/push-notifications/registerer';
|
||||
import { fetchScheduledStatuses } from 'soapbox/actions/scheduled-statuses';
|
||||
import { fetchSuggestionsForTimeline } from 'soapbox/actions/suggestions';
|
||||
import { expandHomeTimeline } from 'soapbox/actions/timelines';
|
||||
|
@ -16,7 +16,7 @@ import { useUserStream } from 'soapbox/api/hooks';
|
|||
import SidebarNavigation from 'soapbox/components/sidebar-navigation';
|
||||
import ThumbNavigation from 'soapbox/components/thumb-navigation';
|
||||
import { Layout } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures, useDraggedFiles, useInstance, useLoggedIn } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures, useDraggedFiles, useInstance, useLoggedIn, useApi } from 'soapbox/hooks';
|
||||
import AdminPage from 'soapbox/pages/admin-page';
|
||||
import ChatsPage from 'soapbox/pages/chats-page';
|
||||
import DefaultPage from 'soapbox/pages/default-page';
|
||||
|
@ -34,7 +34,6 @@ import ProfilePage from 'soapbox/pages/profile-page';
|
|||
import RemoteInstancePage from 'soapbox/pages/remote-instance-page';
|
||||
import SearchPage from 'soapbox/pages/search-page';
|
||||
import StatusPage from 'soapbox/pages/status-page';
|
||||
import { getVapidKey } from 'soapbox/utils/auth';
|
||||
|
||||
import BackgroundShapes from './components/background-shapes';
|
||||
import FloatingActionButton from './components/floating-action-button';
|
||||
|
@ -381,6 +380,7 @@ interface IUI {
|
|||
}
|
||||
|
||||
const UI: React.FC<IUI> = ({ children }) => {
|
||||
const api = useApi();
|
||||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
const node = useRef<HTMLDivElement | null>(null);
|
||||
|
@ -388,7 +388,7 @@ const UI: React.FC<IUI> = ({ children }) => {
|
|||
const { account } = useOwnAccount();
|
||||
const instance = useInstance();
|
||||
const features = useFeatures();
|
||||
const vapidKey = useAppSelector(state => getVapidKey(state));
|
||||
const vapidKey = instance.instance.configuration.vapid.public_key;
|
||||
|
||||
const dropdownMenuIsOpen = useAppSelector(state => state.dropdown_menu.isOpen);
|
||||
|
||||
|
@ -470,7 +470,9 @@ const UI: React.FC<IUI> = ({ children }) => {
|
|||
}, [!!account]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(registerPushNotifications());
|
||||
if (vapidKey) {
|
||||
registerPushNotifications(api, vapidKey).catch(console.warn);
|
||||
}
|
||||
}, [vapidKey]);
|
||||
|
||||
const shouldHideFAB = (): boolean => {
|
||||
|
|
|
@ -22,3 +22,4 @@ export { useSettings } from './useSettings';
|
|||
export { useSoapboxConfig } from './useSoapboxConfig';
|
||||
export { useSystemTheme } from './useSystemTheme';
|
||||
export { useTheme } from './useTheme';
|
||||
export { useSettingsNotifications } from './useSettingsNotifications';
|
|
@ -479,6 +479,9 @@
|
|||
"compose_form.markdown.marked": "Post markdown enabled",
|
||||
"compose_form.markdown.unmarked": "Post markdown disabled",
|
||||
"compose_form.message": "Message",
|
||||
"compose_form.nip05.pending": "Your username request is under review.",
|
||||
"compose_form.nip05.warning": "You don't have a username configured. {click} to set one up.",
|
||||
"compose_form.nip05.warning.click": "Click here",
|
||||
"compose_form.placeholder": "What's on your mind?",
|
||||
"compose_form.poll.add_option": "Add an answer",
|
||||
"compose_form.poll.duration": "Poll duration",
|
||||
|
@ -1181,7 +1184,7 @@
|
|||
"nostr_signup.captcha_instruction": "Complete the puzzle by dragging the puzzle piece to the correct position.",
|
||||
"nostr_signup.captcha_message.error": "It seems an error has occurred. Please try again. If the problem persists, please contact us.",
|
||||
"nostr_signup.captcha_message.misbehaving": "It looks like we're experiencing issues with the {instance}. Please try again. If the error persists, try again later.",
|
||||
"nostr_signup.captcha_message.sucess": "Incredible! You've successfully completed the captcha. Let's move on to the next step!",
|
||||
"nostr_signup.captcha_message.sucess": "Incredible! You've successfully completed the captcha.",
|
||||
"nostr_signup.captcha_message.wrong": "Oops! It looks like your captcha response was incorrect. Please try again.",
|
||||
"nostr_signup.captcha_reset_button": "Reset puzzle",
|
||||
"nostr_signup.captcha_title": "Human Verification",
|
||||
|
|
|
@ -478,6 +478,9 @@
|
|||
"compose_form.markdown.marked": "Markdown do post ativado",
|
||||
"compose_form.markdown.unmarked": "Markdown do post desativado",
|
||||
"compose_form.message": "Mensagem",
|
||||
"compose_form.nip05.pending": "Seu pedido de nome de usuário está em revisão.",
|
||||
"compose_form.nip05.warning": "Você não tem um nome de usuário configurado. {click} para configurá-lo.",
|
||||
"compose_form.nip05.warning.click": "Clique aqui",
|
||||
"compose_form.placeholder": "No que você está pensando?",
|
||||
"compose_form.poll.add_option": "Adicionar uma resposta",
|
||||
"compose_form.poll.duration": "Duração da enquete",
|
||||
|
|
|
@ -294,8 +294,7 @@ const useChatActions = (chatId: string) => {
|
|||
onError: (_error: any, variables, context: any) => {
|
||||
queryClient.setQueryData(['chats', 'messages', variables.chatId], context.prevChatMessages);
|
||||
},
|
||||
onSuccess: async (response, variables, context) => {
|
||||
const data = await response.json();
|
||||
onSuccess: (data, variables, context) => {
|
||||
const nextChat = { ...chat, last_message: data };
|
||||
updatePageItem(ChatKeys.chatSearch(), nextChat, (o, n) => o.id === n.id);
|
||||
updatePageItem(
|
||||
|
|
|
@ -39,7 +39,6 @@ import patron from './patron';
|
|||
import pending_statuses from './pending-statuses';
|
||||
import polls from './polls';
|
||||
import profile_hover_card from './profile-hover-card';
|
||||
import push_notifications from './push-notifications';
|
||||
import relationships from './relationships';
|
||||
import reports from './reports';
|
||||
import scheduled_statuses from './scheduled-statuses';
|
||||
|
@ -97,7 +96,6 @@ const reducers = {
|
|||
pending_statuses,
|
||||
polls,
|
||||
profile_hover_card,
|
||||
push_notifications,
|
||||
relationships,
|
||||
reports,
|
||||
scheduled_statuses,
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import reducer from './push-notifications';
|
||||
|
||||
describe('push_notifications reducer', () => {
|
||||
it('should return the initial state', () => {
|
||||
expect(reducer(undefined, {} as any).toJS()).toEqual({
|
||||
subscription: null,
|
||||
alerts: {
|
||||
follow: true,
|
||||
follow_request: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true,
|
||||
poll: true,
|
||||
},
|
||||
isSubscribed: false,
|
||||
browserSupport: false,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,47 +0,0 @@
|
|||
import { Map as ImmutableMap, Record as ImmutableRecord } from 'immutable';
|
||||
|
||||
import { SET_BROWSER_SUPPORT, SET_SUBSCRIPTION, CLEAR_SUBSCRIPTION, SET_ALERTS } from '../actions/push-notifications';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
|
||||
const SubscriptionRecord = ImmutableRecord({
|
||||
id: '',
|
||||
endpoint: '',
|
||||
});
|
||||
|
||||
const ReducerRecord = ImmutableRecord({
|
||||
subscription: null as Subscription | null,
|
||||
alerts: ImmutableMap<string, boolean>({
|
||||
follow: true,
|
||||
follow_request: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true,
|
||||
poll: true,
|
||||
}),
|
||||
isSubscribed: false,
|
||||
browserSupport: false,
|
||||
});
|
||||
|
||||
type Subscription = ReturnType<typeof SubscriptionRecord>;
|
||||
|
||||
export default function push_subscriptions(state = ReducerRecord(), action: AnyAction) {
|
||||
switch (action.type) {
|
||||
case SET_SUBSCRIPTION:
|
||||
return state
|
||||
.set('subscription', SubscriptionRecord({
|
||||
id: action.subscription.id,
|
||||
endpoint: action.subscription.endpoint,
|
||||
}))
|
||||
.set('alerts', ImmutableMap(action.subscription.alerts))
|
||||
.set('isSubscribed', true);
|
||||
case SET_BROWSER_SUPPORT:
|
||||
return state.set('browserSupport', action.value);
|
||||
case CLEAR_SUBSCRIPTION:
|
||||
return ReducerRecord();
|
||||
case SET_ALERTS:
|
||||
return state.setIn(action.path, action.value);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { coerceObject } from './utils';
|
||||
|
||||
/** https://docs.joinmastodon.org/entities/WebPushSubscription/ */
|
||||
const webPushSubscriptionSchema = z.object({
|
||||
id: z.coerce.string(),
|
||||
endpoint: z.string().url(),
|
||||
alerts: z.object({
|
||||
alerts: coerceObject({
|
||||
mention: z.boolean().optional(),
|
||||
status: z.boolean().optional(),
|
||||
reblog: z.boolean().optional(),
|
||||
|
@ -15,7 +17,7 @@ const webPushSubscriptionSchema = z.object({
|
|||
update: z.boolean().optional(),
|
||||
'admin.sign_up': z.boolean().optional(),
|
||||
'admin.report': z.boolean().optional(),
|
||||
}).optional(),
|
||||
}),
|
||||
server_key: z.string(),
|
||||
});
|
||||
|
||||
|
|
|
@ -62,8 +62,4 @@ export const getAuthUserUrl = (state: RootState) => {
|
|||
].filter(url => url)).find(isURL);
|
||||
};
|
||||
|
||||
/** Get the VAPID public key. */
|
||||
export const getVapidKey = (state: RootState) =>
|
||||
state.auth.app.vapid_key || state.instance.pleroma.vapid_public_key;
|
||||
|
||||
export const getMeUrl = (state: RootState) => selectOwnAccount(state)?.url;
|
|
@ -1,10 +1,8 @@
|
|||
import * as base64 from './base64';
|
||||
import { decodeBase64 } from './base64';
|
||||
|
||||
describe('base64', () => {
|
||||
describe('decode', () => {
|
||||
it('returns a uint8 array', () => {
|
||||
const arr = base64.decode('dGVzdA==');
|
||||
expect(arr).toEqual(new Uint8Array([116, 101, 115, 116]));
|
||||
});
|
||||
describe('decodeBase64', () => {
|
||||
it('returns a uint8 array', () => {
|
||||
const arr = decodeBase64('dGVzdA==');
|
||||
expect(arr).toEqual(new Uint8Array([116, 101, 115, 116]));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const decode = (base64: string) => {
|
||||
export function decodeBase64(base64: string): Uint8Array {
|
||||
const rawData = window.atob(base64);
|
||||
const outputArray = new Uint8Array(rawData.length);
|
||||
|
||||
|
@ -7,4 +7,14 @@ export const decode = (base64: string) => {
|
|||
}
|
||||
|
||||
return outputArray;
|
||||
};
|
||||
}
|
||||
|
||||
/** Taken from https://www.npmjs.com/package/web-push */
|
||||
export function decodeBase64Url(base64String: string): Uint8Array {
|
||||
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||
const base64 = (base64String + padding)
|
||||
.replace(/-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
|
||||
return decodeBase64(base64);
|
||||
}
|
|
@ -76,7 +76,7 @@ export default defineConfig(({ command }) => ({
|
|||
}),
|
||||
viteStaticCopy({
|
||||
targets: [{
|
||||
src: './node_modules/twemoji/assets/svg/*',
|
||||
src: './node_modules/@twemoji/svg/*',
|
||||
dest: 'packs/emoji/',
|
||||
}, {
|
||||
src: './src/instance',
|
||||
|
|
73
yarn.lock
73
yarn.lock
|
@ -1009,10 +1009,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#519c1549b0e147759e7825701ecffd25e5819f7b"
|
||||
integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==
|
||||
|
||||
"@emoji-mart/data@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@emoji-mart/data/-/data-1.1.2.tgz#777c976f8f143df47cbb23a7077c9ca9fe5fc513"
|
||||
integrity sha512-1HP8BxD2azjqWJvxIaWAMyTySeZY0Osr83ukYjltPVkNXeJvTz7yDrPLBtnrD5uqJ3tg4CcLuuBW09wahqL/fg==
|
||||
"@emoji-mart/data@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@emoji-mart/data/-/data-1.2.1.tgz#0ad70c662e3bc603e23e7d98413bd1e64c4fcb6c"
|
||||
integrity sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==
|
||||
|
||||
"@es-joy/jsdoccomment@~0.41.0":
|
||||
version "0.41.0"
|
||||
|
@ -2465,6 +2465,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
|
||||
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
|
||||
|
||||
"@twemoji/svg@^15.0.0":
|
||||
version "15.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@twemoji/svg/-/svg-15.0.0.tgz#0e3828c654726f1848fe11f31ef4e8a75854cc7f"
|
||||
integrity sha512-ZSPef2B6nBaYnfgdTbAy4jgW95o7pi2xPGwGCU+WMTxo7J6B1lMPTWwSq/wTuiMq+N0khQ90CcvYp1wFoQpo/w==
|
||||
|
||||
"@types/aria-query@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc"
|
||||
|
@ -4322,15 +4327,15 @@ electron-to-chromium@^1.5.28:
|
|||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.28.tgz#aee074e202c6ee8a0030a9c2ef0b3fe9f967d576"
|
||||
integrity sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw==
|
||||
|
||||
emoji-datasource@14.0.0:
|
||||
version "14.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-14.0.0.tgz#99529a62f3a86546fc670c09b672ddc9f24f3d44"
|
||||
integrity sha512-SoOv0lSa+9/2X9ulSRDhu2u1zAOaOv5vtMY3OYUDcQCoReEh0/3eQAMuBM9LyD7Hy3G4K7mDPDqVeHUWvy7cow==
|
||||
emoji-datasource@15.0.1:
|
||||
version "15.0.1"
|
||||
resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-15.0.1.tgz#6cc7676e4d48d7559c2e068ffcacf84ec653584c"
|
||||
integrity sha512-aF5Q6LCKXzJzpG4K0ETiItuzz0xLYxNexR9qWw45/shuuEDWZkOIbeGHA23uopOSYA/LmeZIXIFsySCx+YKg2g==
|
||||
|
||||
emoji-mart@^5.5.2:
|
||||
version "5.5.2"
|
||||
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-5.5.2.tgz#3ddbaf053139cf4aa217650078bc1c50ca8381af"
|
||||
integrity sha512-Sqc/nso4cjxhOwWJsp9xkVm8OF5c+mJLZJFoFfzRuKO+yWiN7K8c96xmtughYb0d/fZ8UC6cLIQ/p4BR6Pv3/A==
|
||||
emoji-mart@^5.6.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-5.6.0.tgz#71b3ed0091d3e8c68487b240d9d6d9a73c27f023"
|
||||
integrity sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==
|
||||
|
||||
emoji-regex@10.3.0, emoji-regex@^10.2.1:
|
||||
version "10.3.0"
|
||||
|
@ -5027,15 +5032,6 @@ fs-extra@^11.1.0:
|
|||
jsonfile "^6.0.1"
|
||||
universalify "^2.0.0"
|
||||
|
||||
fs-extra@^8.0.1:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
||||
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
fs-extra@^9.0.1:
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
|
||||
|
@ -5978,22 +5974,6 @@ json5@^2.1.2, json5@^2.2.0, json5@^2.2.3:
|
|||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
|
||||
jsonfile@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsonfile@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-5.0.0.tgz#e6b718f73da420d612823996fdf14a03f6ff6922"
|
||||
integrity sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==
|
||||
dependencies:
|
||||
universalify "^0.1.2"
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsonfile@^6.0.1:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
|
||||
|
@ -8608,20 +8588,6 @@ tslib@^1.9.0:
|
|||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
twemoji-parser@14.0.0:
|
||||
version "14.0.0"
|
||||
resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-14.0.0.tgz#13dabcb6d3a261d9efbf58a1666b182033bf2b62"
|
||||
integrity sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA==
|
||||
|
||||
"twemoji@https://github.com/twitter/twemoji#v14.0.2":
|
||||
version "14.0.2"
|
||||
resolved "https://github.com/twitter/twemoji#7a3dad4a4da30497093dab22eafba135f02308e1"
|
||||
dependencies:
|
||||
fs-extra "^8.0.1"
|
||||
jsonfile "^5.0.0"
|
||||
twemoji-parser "14.0.0"
|
||||
universalify "^0.1.2"
|
||||
|
||||
type-check@^0.4.0, type-check@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||
|
@ -8748,11 +8714,6 @@ unique-string@^2.0.0:
|
|||
dependencies:
|
||||
crypto-random-string "^2.0.0"
|
||||
|
||||
universalify@^0.1.0, universalify@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||
|
||||
universalify@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
||||
|
|
Loading…
Reference in New Issue