Merge remote-tracking branch 'origin/main' into upgrade-deps
This commit is contained in:
commit
588b86eb84
|
@ -41,7 +41,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@akryum/flexsearch-es": "^0.7.32",
|
"@akryum/flexsearch-es": "^0.7.32",
|
||||||
"@emoji-mart/data": "^1.1.2",
|
"@emoji-mart/data": "^1.2.1",
|
||||||
"@floating-ui/react": "^0.26.0",
|
"@floating-ui/react": "^0.26.0",
|
||||||
"@fontsource/inter": "^5.0.0",
|
"@fontsource/inter": "^5.0.0",
|
||||||
"@fontsource/noto-sans-javanese": "^5.0.16",
|
"@fontsource/noto-sans-javanese": "^5.0.16",
|
||||||
|
@ -72,6 +72,7 @@
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@tanstack/react-query": "^5.59.13",
|
"@tanstack/react-query": "^5.59.13",
|
||||||
|
"@twemoji/svg": "^15.0.0",
|
||||||
"@types/escape-html": "^1.0.1",
|
"@types/escape-html": "^1.0.1",
|
||||||
"@types/http-link-header": "^1.0.3",
|
"@types/http-link-header": "^1.0.3",
|
||||||
"@types/leaflet": "^1.8.0",
|
"@types/leaflet": "^1.8.0",
|
||||||
|
@ -102,8 +103,8 @@
|
||||||
"cryptocurrency-icons": "^0.18.1",
|
"cryptocurrency-icons": "^0.18.1",
|
||||||
"cssnano": "^6.0.0",
|
"cssnano": "^6.0.0",
|
||||||
"detect-passive-events": "^2.0.0",
|
"detect-passive-events": "^2.0.0",
|
||||||
"emoji-datasource": "14.0.0",
|
"emoji-datasource": "15.0.1",
|
||||||
"emoji-mart": "^5.5.2",
|
"emoji-mart": "^5.6.0",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"eslint-plugin-formatjs": "^4.12.2",
|
"eslint-plugin-formatjs": "^4.12.2",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
|
@ -152,7 +153,6 @@
|
||||||
"sass": "^1.79.5",
|
"sass": "^1.79.5",
|
||||||
"semver": "^7.3.8",
|
"semver": "^7.3.8",
|
||||||
"stringz": "^2.0.0",
|
"stringz": "^2.0.0",
|
||||||
"twemoji": "https://github.com/twitter/twemoji#v14.0.2",
|
|
||||||
"type-fest": "^4.0.0",
|
"type-fest": "^4.0.0",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.2",
|
||||||
"vite": "^5.4.8",
|
"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';
|
/* eslint-disable compat/compat */
|
||||||
import { pushNotificationsSetting } from 'soapbox/settings';
|
import { HTTPError } from 'soapbox/api/HTTPError';
|
||||||
import { getVapidKey } from 'soapbox/utils/auth';
|
import { MastodonClient } from 'soapbox/api/MastodonClient';
|
||||||
import { decode as decodeBase64 } from 'soapbox/utils/base64';
|
import { WebPushSubscription, webPushSubscriptionSchema } from 'soapbox/schemas/web-push';
|
||||||
|
import { decodeBase64Url } 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));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload
|
// 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 supportsPushNotifications = ('serviceWorker' in navigator && 'PushManager' in window && 'getKey' in PushSubscription.prototype);
|
||||||
|
|
||||||
const register = () =>
|
/**
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
* Register web push notifications.
|
||||||
const me = getState().me;
|
* This function creates a subscription if one hasn't been created already, and syncronizes it with the backend.
|
||||||
const vapidKey = getVapidKey(getState());
|
*/
|
||||||
|
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) {
|
if (created) {
|
||||||
console.warn('Your browser does not support Web Push Notifications.');
|
await sendSubscriptionToBackend(api, subscription);
|
||||||
return;
|
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) {
|
/** Publish a new subscription to the backend. */
|
||||||
console.error('The VAPID public key is not set. You will not be able to receive Web Push Notifications.');
|
async function sendSubscriptionToBackend(api: MastodonClient, subscription: PushSubscription): Promise<WebPushSubscription> {
|
||||||
return;
|
const params = {
|
||||||
}
|
subscription: subscription.toJSON(),
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveSettings = () =>
|
const response = await api.post('/api/v1/push/subscription', params);
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
const data = await response.json();
|
||||||
const state = getState().push_notifications;
|
|
||||||
const alerts = state.alerts;
|
|
||||||
const data = { alerts };
|
|
||||||
const me = getState().me;
|
|
||||||
|
|
||||||
return dispatch(updatePushSubscription({ data })).then(() => {
|
return webPushSubscriptionSchema.parse(data);
|
||||||
if (me) {
|
}
|
||||||
pushNotificationsSetting.set(me, data);
|
|
||||||
}
|
|
||||||
}).catch(console.warn);
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
/** Check if the VAPID key and endpoint of the subscription match the data in the backend. */
|
||||||
register,
|
function subscriptionMatchesBackend(subscription: PushSubscription, backend: WebPushSubscription): boolean {
|
||||||
saveSettings,
|
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({
|
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.' },
|
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.' },
|
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.' },
|
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) {
|
if (!isSubmitDisabled && !createChatMessage.isPending) {
|
||||||
submitMessage();
|
submitMessage();
|
||||||
|
|
||||||
if (!chat.accepted) {
|
if (chat.accepted === false) {
|
||||||
acceptChat.mutate();
|
acceptChat.mutate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { CLEAR_EDITOR_COMMAND, TextNode, type LexicalEditor, $getRoot } from 'lexical';
|
import { CLEAR_EDITOR_COMMAND, TextNode, type LexicalEditor, $getRoot } from 'lexical';
|
||||||
import React, { Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
import React, { Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { Link, useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { length } from 'stringz';
|
import { length } from 'stringz';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -39,7 +39,6 @@ import SpoilerInput from './spoiler-input';
|
||||||
import TextCharacterCounter from './text-character-counter';
|
import TextCharacterCounter from './text-character-counter';
|
||||||
import UploadForm from './upload-form';
|
import UploadForm from './upload-form';
|
||||||
import VisualCharacterCounter from './visual-character-counter';
|
import VisualCharacterCounter from './visual-character-counter';
|
||||||
import Warning from './warning';
|
|
||||||
|
|
||||||
import type { Emoji } from 'soapbox/features/emoji';
|
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 compose = useCompose(id);
|
||||||
const showSearch = useAppSelector((state) => state.search.submitted && !state.search.hidden);
|
const showSearch = useAppSelector((state) => state.search.submitted && !state.search.hidden);
|
||||||
const maxTootChars = instance.configuration.statuses.max_characters;
|
const maxTootChars = instance.configuration.statuses.max_characters;
|
||||||
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
|
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -246,25 +244,6 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className='w-full' space={4} ref={formRef} onClick={handleClick} element='form' onSubmit={handleSubmit}>
|
<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} />
|
<WarningContainer composeId={id} />
|
||||||
|
|
||||||
{!shouldCondense && !event && !group && groupId && <ReplyGroupIndicator composeId={id} />}
|
{!shouldCondense && !event && !group && groupId && <ReplyGroupIndicator composeId={id} />}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
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 { selectOwnAccount } from 'soapbox/selectors';
|
||||||
|
|
||||||
import Warning from '../components/warning';
|
import Warning from '../components/warning';
|
||||||
|
@ -15,11 +15,61 @@ interface IWarningWrapper {
|
||||||
|
|
||||||
const WarningWrapper: React.FC<IWarningWrapper> = ({ composeId }) => {
|
const WarningWrapper: React.FC<IWarningWrapper> = ({ composeId }) => {
|
||||||
const compose = useCompose(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 needsLockWarning = useAppSelector((state) => compose.privacy === 'private' && !selectOwnAccount(state)!.locked);
|
||||||
const hashtagWarning = (compose.privacy !== 'public' && compose.privacy !== 'group') && APPROX_HASHTAG_RE.test(compose.text);
|
const hashtagWarning = (compose.privacy !== 'public' && compose.privacy !== 'group') && APPROX_HASHTAG_RE.test(compose.text);
|
||||||
const directMessageWarning = compose.privacy === 'direct';
|
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) {
|
if (needsLockWarning) {
|
||||||
return (
|
return (
|
||||||
<Warning
|
<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 {
|
export interface NativeEmoji {
|
||||||
unified: string;
|
unified: string;
|
||||||
|
|
|
@ -5,9 +5,8 @@ import { FormattedMessage } from 'react-intl';
|
||||||
import { fetchAccount } from 'soapbox/actions/accounts';
|
import { fetchAccount } from 'soapbox/actions/accounts';
|
||||||
import { logInNostr } from 'soapbox/actions/nostr';
|
import { logInNostr } from 'soapbox/actions/nostr';
|
||||||
import { closeSidebar } from 'soapbox/actions/sidebar';
|
import { closeSidebar } from 'soapbox/actions/sidebar';
|
||||||
import CopyableInput from 'soapbox/components/copyable-input';
|
|
||||||
import EmojiGraphic from 'soapbox/components/emoji-graphic';
|
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 { useNostr } from 'soapbox/contexts/nostr-context';
|
||||||
import { NKeys } from 'soapbox/features/nostr/keys';
|
import { NKeys } from 'soapbox/features/nostr/keys';
|
||||||
import { useAppDispatch, useInstance } from 'soapbox/hooks';
|
import { useAppDispatch, useInstance } from 'soapbox/hooks';
|
||||||
|
@ -43,8 +42,6 @@ const KeygenStep: React.FC<IKeygenStep> = ({ onClose }) => {
|
||||||
setDownloaded(true);
|
setDownloaded(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopy = () => setDownloaded(true);
|
|
||||||
|
|
||||||
const handleNext = async () => {
|
const handleNext = async () => {
|
||||||
const signer = NKeys.add(secretKey);
|
const signer = NKeys.add(secretKey);
|
||||||
const pubkey = await signer.getPublicKey();
|
const pubkey = await signer.getPublicKey();
|
||||||
|
@ -63,10 +60,10 @@ const KeygenStep: React.FC<IKeygenStep> = ({ onClose }) => {
|
||||||
await relay?.event(kind0);
|
await relay?.event(kind0);
|
||||||
await Promise.all(events.map((event) => relay?.event(event)));
|
await Promise.all(events.map((event) => relay?.event(event)));
|
||||||
|
|
||||||
await dispatch(logInNostr(pubkey));
|
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
|
|
||||||
|
await dispatch(logInNostr(pubkey));
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
dispatch(closeSidebar());
|
dispatch(closeSidebar());
|
||||||
}
|
}
|
||||||
|
@ -79,7 +76,13 @@ const KeygenStep: React.FC<IKeygenStep> = ({ onClose }) => {
|
||||||
<EmojiGraphic emoji='🔑' />
|
<EmojiGraphic emoji='🔑' />
|
||||||
|
|
||||||
<FormGroup labelText={<FormattedMessage id='nostr_signup.keygen.label_text' defaultMessage='Secret Key' />}>
|
<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>
|
</FormGroup>
|
||||||
|
|
||||||
<Stack className='rounded-xl bg-gray-100 p-4 dark:bg-gray-800'>
|
<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 { fetchFilters } from 'soapbox/actions/filters';
|
||||||
import { fetchMarker } from 'soapbox/actions/markers';
|
import { fetchMarker } from 'soapbox/actions/markers';
|
||||||
import { expandNotifications } from 'soapbox/actions/notifications';
|
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 { fetchScheduledStatuses } from 'soapbox/actions/scheduled-statuses';
|
||||||
import { fetchSuggestionsForTimeline } from 'soapbox/actions/suggestions';
|
import { fetchSuggestionsForTimeline } from 'soapbox/actions/suggestions';
|
||||||
import { expandHomeTimeline } from 'soapbox/actions/timelines';
|
import { expandHomeTimeline } from 'soapbox/actions/timelines';
|
||||||
|
@ -16,7 +16,7 @@ import { useUserStream } from 'soapbox/api/hooks';
|
||||||
import SidebarNavigation from 'soapbox/components/sidebar-navigation';
|
import SidebarNavigation from 'soapbox/components/sidebar-navigation';
|
||||||
import ThumbNavigation from 'soapbox/components/thumb-navigation';
|
import ThumbNavigation from 'soapbox/components/thumb-navigation';
|
||||||
import { Layout } from 'soapbox/components/ui';
|
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 AdminPage from 'soapbox/pages/admin-page';
|
||||||
import ChatsPage from 'soapbox/pages/chats-page';
|
import ChatsPage from 'soapbox/pages/chats-page';
|
||||||
import DefaultPage from 'soapbox/pages/default-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 RemoteInstancePage from 'soapbox/pages/remote-instance-page';
|
||||||
import SearchPage from 'soapbox/pages/search-page';
|
import SearchPage from 'soapbox/pages/search-page';
|
||||||
import StatusPage from 'soapbox/pages/status-page';
|
import StatusPage from 'soapbox/pages/status-page';
|
||||||
import { getVapidKey } from 'soapbox/utils/auth';
|
|
||||||
|
|
||||||
import BackgroundShapes from './components/background-shapes';
|
import BackgroundShapes from './components/background-shapes';
|
||||||
import FloatingActionButton from './components/floating-action-button';
|
import FloatingActionButton from './components/floating-action-button';
|
||||||
|
@ -381,6 +380,7 @@ interface IUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
const UI: React.FC<IUI> = ({ children }) => {
|
const UI: React.FC<IUI> = ({ children }) => {
|
||||||
|
const api = useApi();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const node = useRef<HTMLDivElement | null>(null);
|
const node = useRef<HTMLDivElement | null>(null);
|
||||||
|
@ -388,7 +388,7 @@ const UI: React.FC<IUI> = ({ children }) => {
|
||||||
const { account } = useOwnAccount();
|
const { account } = useOwnAccount();
|
||||||
const instance = useInstance();
|
const instance = useInstance();
|
||||||
const features = useFeatures();
|
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);
|
const dropdownMenuIsOpen = useAppSelector(state => state.dropdown_menu.isOpen);
|
||||||
|
|
||||||
|
@ -470,7 +470,9 @@ const UI: React.FC<IUI> = ({ children }) => {
|
||||||
}, [!!account]);
|
}, [!!account]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(registerPushNotifications());
|
if (vapidKey) {
|
||||||
|
registerPushNotifications(api, vapidKey).catch(console.warn);
|
||||||
|
}
|
||||||
}, [vapidKey]);
|
}, [vapidKey]);
|
||||||
|
|
||||||
const shouldHideFAB = (): boolean => {
|
const shouldHideFAB = (): boolean => {
|
||||||
|
|
|
@ -22,3 +22,4 @@ export { useSettings } from './useSettings';
|
||||||
export { useSoapboxConfig } from './useSoapboxConfig';
|
export { useSoapboxConfig } from './useSoapboxConfig';
|
||||||
export { useSystemTheme } from './useSystemTheme';
|
export { useSystemTheme } from './useSystemTheme';
|
||||||
export { useTheme } from './useTheme';
|
export { useTheme } from './useTheme';
|
||||||
|
export { useSettingsNotifications } from './useSettingsNotifications';
|
|
@ -479,6 +479,9 @@
|
||||||
"compose_form.markdown.marked": "Post markdown enabled",
|
"compose_form.markdown.marked": "Post markdown enabled",
|
||||||
"compose_form.markdown.unmarked": "Post markdown disabled",
|
"compose_form.markdown.unmarked": "Post markdown disabled",
|
||||||
"compose_form.message": "Message",
|
"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.placeholder": "What's on your mind?",
|
||||||
"compose_form.poll.add_option": "Add an answer",
|
"compose_form.poll.add_option": "Add an answer",
|
||||||
"compose_form.poll.duration": "Poll duration",
|
"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_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.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.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_message.wrong": "Oops! It looks like your captcha response was incorrect. Please try again.",
|
||||||
"nostr_signup.captcha_reset_button": "Reset puzzle",
|
"nostr_signup.captcha_reset_button": "Reset puzzle",
|
||||||
"nostr_signup.captcha_title": "Human Verification",
|
"nostr_signup.captcha_title": "Human Verification",
|
||||||
|
|
|
@ -478,6 +478,9 @@
|
||||||
"compose_form.markdown.marked": "Markdown do post ativado",
|
"compose_form.markdown.marked": "Markdown do post ativado",
|
||||||
"compose_form.markdown.unmarked": "Markdown do post desativado",
|
"compose_form.markdown.unmarked": "Markdown do post desativado",
|
||||||
"compose_form.message": "Mensagem",
|
"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.placeholder": "No que você está pensando?",
|
||||||
"compose_form.poll.add_option": "Adicionar uma resposta",
|
"compose_form.poll.add_option": "Adicionar uma resposta",
|
||||||
"compose_form.poll.duration": "Duração da enquete",
|
"compose_form.poll.duration": "Duração da enquete",
|
||||||
|
|
|
@ -294,8 +294,7 @@ const useChatActions = (chatId: string) => {
|
||||||
onError: (_error: any, variables, context: any) => {
|
onError: (_error: any, variables, context: any) => {
|
||||||
queryClient.setQueryData(['chats', 'messages', variables.chatId], context.prevChatMessages);
|
queryClient.setQueryData(['chats', 'messages', variables.chatId], context.prevChatMessages);
|
||||||
},
|
},
|
||||||
onSuccess: async (response, variables, context) => {
|
onSuccess: (data, variables, context) => {
|
||||||
const data = await response.json();
|
|
||||||
const nextChat = { ...chat, last_message: data };
|
const nextChat = { ...chat, last_message: data };
|
||||||
updatePageItem(ChatKeys.chatSearch(), nextChat, (o, n) => o.id === n.id);
|
updatePageItem(ChatKeys.chatSearch(), nextChat, (o, n) => o.id === n.id);
|
||||||
updatePageItem(
|
updatePageItem(
|
||||||
|
|
|
@ -39,7 +39,6 @@ import patron from './patron';
|
||||||
import pending_statuses from './pending-statuses';
|
import pending_statuses from './pending-statuses';
|
||||||
import polls from './polls';
|
import polls from './polls';
|
||||||
import profile_hover_card from './profile-hover-card';
|
import profile_hover_card from './profile-hover-card';
|
||||||
import push_notifications from './push-notifications';
|
|
||||||
import relationships from './relationships';
|
import relationships from './relationships';
|
||||||
import reports from './reports';
|
import reports from './reports';
|
||||||
import scheduled_statuses from './scheduled-statuses';
|
import scheduled_statuses from './scheduled-statuses';
|
||||||
|
@ -97,7 +96,6 @@ const reducers = {
|
||||||
pending_statuses,
|
pending_statuses,
|
||||||
polls,
|
polls,
|
||||||
profile_hover_card,
|
profile_hover_card,
|
||||||
push_notifications,
|
|
||||||
relationships,
|
relationships,
|
||||||
reports,
|
reports,
|
||||||
scheduled_statuses,
|
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 { z } from 'zod';
|
||||||
|
|
||||||
|
import { coerceObject } from './utils';
|
||||||
|
|
||||||
/** https://docs.joinmastodon.org/entities/WebPushSubscription/ */
|
/** https://docs.joinmastodon.org/entities/WebPushSubscription/ */
|
||||||
const webPushSubscriptionSchema = z.object({
|
const webPushSubscriptionSchema = z.object({
|
||||||
id: z.coerce.string(),
|
id: z.coerce.string(),
|
||||||
endpoint: z.string().url(),
|
endpoint: z.string().url(),
|
||||||
alerts: z.object({
|
alerts: coerceObject({
|
||||||
mention: z.boolean().optional(),
|
mention: z.boolean().optional(),
|
||||||
status: z.boolean().optional(),
|
status: z.boolean().optional(),
|
||||||
reblog: z.boolean().optional(),
|
reblog: z.boolean().optional(),
|
||||||
|
@ -15,7 +17,7 @@ const webPushSubscriptionSchema = z.object({
|
||||||
update: z.boolean().optional(),
|
update: z.boolean().optional(),
|
||||||
'admin.sign_up': z.boolean().optional(),
|
'admin.sign_up': z.boolean().optional(),
|
||||||
'admin.report': z.boolean().optional(),
|
'admin.report': z.boolean().optional(),
|
||||||
}).optional(),
|
}),
|
||||||
server_key: z.string(),
|
server_key: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -62,8 +62,4 @@ export const getAuthUserUrl = (state: RootState) => {
|
||||||
].filter(url => url)).find(isURL);
|
].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;
|
export const getMeUrl = (state: RootState) => selectOwnAccount(state)?.url;
|
|
@ -1,10 +1,8 @@
|
||||||
import * as base64 from './base64';
|
import { decodeBase64 } from './base64';
|
||||||
|
|
||||||
describe('base64', () => {
|
describe('decodeBase64', () => {
|
||||||
describe('decode', () => {
|
it('returns a uint8 array', () => {
|
||||||
it('returns a uint8 array', () => {
|
const arr = decodeBase64('dGVzdA==');
|
||||||
const arr = base64.decode('dGVzdA==');
|
expect(arr).toEqual(new Uint8Array([116, 101, 115, 116]));
|
||||||
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 rawData = window.atob(base64);
|
||||||
const outputArray = new Uint8Array(rawData.length);
|
const outputArray = new Uint8Array(rawData.length);
|
||||||
|
|
||||||
|
@ -7,4 +7,14 @@ export const decode = (base64: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return outputArray;
|
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({
|
viteStaticCopy({
|
||||||
targets: [{
|
targets: [{
|
||||||
src: './node_modules/twemoji/assets/svg/*',
|
src: './node_modules/@twemoji/svg/*',
|
||||||
dest: 'packs/emoji/',
|
dest: 'packs/emoji/',
|
||||||
}, {
|
}, {
|
||||||
src: './src/instance',
|
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"
|
resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#519c1549b0e147759e7825701ecffd25e5819f7b"
|
||||||
integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==
|
integrity sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==
|
||||||
|
|
||||||
"@emoji-mart/data@^1.1.2":
|
"@emoji-mart/data@^1.2.1":
|
||||||
version "1.1.2"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@emoji-mart/data/-/data-1.1.2.tgz#777c976f8f143df47cbb23a7077c9ca9fe5fc513"
|
resolved "https://registry.yarnpkg.com/@emoji-mart/data/-/data-1.2.1.tgz#0ad70c662e3bc603e23e7d98413bd1e64c4fcb6c"
|
||||||
integrity sha512-1HP8BxD2azjqWJvxIaWAMyTySeZY0Osr83ukYjltPVkNXeJvTz7yDrPLBtnrD5uqJ3tg4CcLuuBW09wahqL/fg==
|
integrity sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==
|
||||||
|
|
||||||
"@es-joy/jsdoccomment@~0.41.0":
|
"@es-joy/jsdoccomment@~0.41.0":
|
||||||
version "0.41.0"
|
version "0.41.0"
|
||||||
|
@ -2465,6 +2465,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
|
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
|
||||||
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
|
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":
|
"@types/aria-query@^5.0.1":
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc"
|
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"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.28.tgz#aee074e202c6ee8a0030a9c2ef0b3fe9f967d576"
|
||||||
integrity sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw==
|
integrity sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw==
|
||||||
|
|
||||||
emoji-datasource@14.0.0:
|
emoji-datasource@15.0.1:
|
||||||
version "14.0.0"
|
version "15.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-14.0.0.tgz#99529a62f3a86546fc670c09b672ddc9f24f3d44"
|
resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-15.0.1.tgz#6cc7676e4d48d7559c2e068ffcacf84ec653584c"
|
||||||
integrity sha512-SoOv0lSa+9/2X9ulSRDhu2u1zAOaOv5vtMY3OYUDcQCoReEh0/3eQAMuBM9LyD7Hy3G4K7mDPDqVeHUWvy7cow==
|
integrity sha512-aF5Q6LCKXzJzpG4K0ETiItuzz0xLYxNexR9qWw45/shuuEDWZkOIbeGHA23uopOSYA/LmeZIXIFsySCx+YKg2g==
|
||||||
|
|
||||||
emoji-mart@^5.5.2:
|
emoji-mart@^5.6.0:
|
||||||
version "5.5.2"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-5.5.2.tgz#3ddbaf053139cf4aa217650078bc1c50ca8381af"
|
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-5.6.0.tgz#71b3ed0091d3e8c68487b240d9d6d9a73c27f023"
|
||||||
integrity sha512-Sqc/nso4cjxhOwWJsp9xkVm8OF5c+mJLZJFoFfzRuKO+yWiN7K8c96xmtughYb0d/fZ8UC6cLIQ/p4BR6Pv3/A==
|
integrity sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==
|
||||||
|
|
||||||
emoji-regex@10.3.0, emoji-regex@^10.2.1:
|
emoji-regex@10.3.0, emoji-regex@^10.2.1:
|
||||||
version "10.3.0"
|
version "10.3.0"
|
||||||
|
@ -5027,15 +5032,6 @@ fs-extra@^11.1.0:
|
||||||
jsonfile "^6.0.1"
|
jsonfile "^6.0.1"
|
||||||
universalify "^2.0.0"
|
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:
|
fs-extra@^9.0.1:
|
||||||
version "9.1.0"
|
version "9.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
|
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"
|
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
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:
|
jsonfile@^6.0.1:
|
||||||
version "6.1.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
|
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"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
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:
|
type-check@^0.4.0, type-check@~0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||||
|
@ -8748,11 +8714,6 @@ unique-string@^2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
crypto-random-string "^2.0.0"
|
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:
|
universalify@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
||||||
|
|
Loading…
Reference in New Issue