Notifications Initializer Migration to Pack tag (#19124)
Co-authored-by: Ridhwana <Ridhwana.Khan16@gmail.com> Co-authored-by: Lawrence S <lboogie@Lawrences-Air.attlocal.net> Co-authored-by: Rajat Talesra <rajat@forem.com>
This commit is contained in:
parent
2233f2865b
commit
56e10f1306
12 changed files with 285 additions and 73 deletions
|
|
@ -2,7 +2,7 @@
|
|||
global initializeLocalStorageRender, initializeBodyData,
|
||||
initializeAllTagEditButtons, initializeUserFollowButts,
|
||||
initializeCommentsPage,
|
||||
initializeArticleDate, initializeArticleReactions, initNotifications,
|
||||
initializeArticleDate, initializeArticleReactions,
|
||||
initializeSettings, initializeRuntimeBanner,
|
||||
initializeTimeFixer, initializeDashboardSort, initializeCreditsPage,
|
||||
initializeDrawerSliders, initializeHeroBannerClose,
|
||||
|
|
@ -17,7 +17,6 @@ function callInitializers() {
|
|||
initializeCommentsPage();
|
||||
initializeArticleDate();
|
||||
initializeArticleReactions();
|
||||
initNotifications();
|
||||
initializeSettings();
|
||||
initializeTimeFixer();
|
||||
initializeDashboardSort();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
import { initializeCommentDate } from './initializers/initializeCommentDate';
|
||||
import { initializeCommentPreview } from './initializers/initializeCommentPreview';
|
||||
import { initializeNotifications } from './initializers/initializeNotifications';
|
||||
import {
|
||||
showUserAlertModal,
|
||||
showModalAfterError,
|
||||
} from '@utilities/showUserAlertModal';
|
||||
|
||||
initializeCommentDate();
|
||||
initializeCommentPreview();
|
||||
initializeNotifications();
|
||||
|
||||
window.showUserAlertModal = showUserAlertModal;
|
||||
window.showModalAfterError = showModalAfterError;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,10 @@
|
|||
/* global checkUserLoggedIn, instantClick, InstantClick, sendHapticMessage, showModalAfterError */
|
||||
|
||||
function initNotifications() {
|
||||
fetchNotificationsCount();
|
||||
markNotificationsAsRead();
|
||||
initReactions();
|
||||
listenForNotificationsBellClick();
|
||||
initFilter();
|
||||
initPagination();
|
||||
initLoadMoreButton();
|
||||
}
|
||||
|
||||
import { sendHapticMessage } from '../../utilities/sendHapticMessage';
|
||||
import { checkUserLoggedIn } from '../../utilities/checkUserLoggedIn';
|
||||
import { showModalAfterError } from '../../utilities/showUserAlertModal';
|
||||
// eslint-disable-next-line no-redeclare
|
||||
/* global InstantClick, instantClick */
|
||||
function markNotificationsAsRead() {
|
||||
setTimeout(function () {
|
||||
setTimeout(() => {
|
||||
if (document.getElementById('notifications-container')) {
|
||||
getCsrfToken().then((csrfToken) => {
|
||||
const locationAsArray = window.location.pathname.split('/');
|
||||
|
|
@ -44,7 +37,7 @@ function fetchNotificationsCount() {
|
|||
// Prefetch notifications page
|
||||
if (instantClick) {
|
||||
InstantClick.removeExpiredKeys('force');
|
||||
setTimeout(function () {
|
||||
setTimeout(() => {
|
||||
InstantClick.preload(
|
||||
document.getElementById('notifications-link').href,
|
||||
'force',
|
||||
|
|
@ -55,18 +48,18 @@ function fetchNotificationsCount() {
|
|||
}
|
||||
|
||||
function initReactions() {
|
||||
setTimeout(function () {
|
||||
setTimeout(() => {
|
||||
if (document.getElementById('notifications-container')) {
|
||||
var butts = document.getElementsByClassName('reaction-button');
|
||||
let butts = document.getElementsByClassName('reaction-button');
|
||||
|
||||
for (var i = 0; i < butts.length; i++) {
|
||||
var butt = butts[i];
|
||||
for (let i = 0; i < butts.length; i++) {
|
||||
const butt = butts[i];
|
||||
butt.setAttribute('aria-pressed', butt.classList.contains('reacted'));
|
||||
|
||||
butt.onclick = function (event) {
|
||||
event.preventDefault();
|
||||
sendHapticMessage('medium');
|
||||
var thisButt = this;
|
||||
const thisButt = this;
|
||||
thisButt.classList.add('reacted');
|
||||
|
||||
function successCb(response) {
|
||||
|
|
@ -79,14 +72,14 @@ function initReactions() {
|
|||
}
|
||||
}
|
||||
|
||||
var formData = new FormData();
|
||||
const formData = new FormData();
|
||||
formData.append('reactable_type', thisButt.dataset.reactableType);
|
||||
formData.append('category', thisButt.dataset.category);
|
||||
formData.append('reactable_id', thisButt.dataset.reactableId);
|
||||
|
||||
getCsrfToken()
|
||||
.then(sendFetch('reaction-creation', formData))
|
||||
.then(function (response) {
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
response.json().then(successCb);
|
||||
} else {
|
||||
|
|
@ -108,16 +101,16 @@ function initReactions() {
|
|||
|
||||
butt.onclick = function (event) {
|
||||
event.preventDefault();
|
||||
var thisButt = this;
|
||||
const thisButt = this;
|
||||
document
|
||||
.getElementById('comment-form-for-' + thisButt.dataset.reactableId)
|
||||
.getElementById(`comment-form-for-${ thisButt.dataset.reactableId }`)
|
||||
.classList.remove('hidden');
|
||||
thisButt.classList.add('hidden');
|
||||
thisButt.classList.remove('inline-flex');
|
||||
setTimeout(function () {
|
||||
setTimeout(() => {
|
||||
document
|
||||
.getElementById(
|
||||
'comment-textarea-for-' + thisButt.dataset.reactableId,
|
||||
`comment-textarea-for-${ thisButt.dataset.reactableId }`,
|
||||
)
|
||||
.focus();
|
||||
}, 30);
|
||||
|
|
@ -128,9 +121,9 @@ function initReactions() {
|
|||
}
|
||||
|
||||
function listenForNotificationsBellClick() {
|
||||
var notificationsLink = document.getElementById('notifications-link');
|
||||
const notificationsLink = document.getElementById('notifications-link');
|
||||
if (notificationsLink) {
|
||||
setTimeout(function () {
|
||||
setTimeout(() => {
|
||||
notificationsLink.onclick = function () {
|
||||
document.getElementById('notifications-number').classList.add('hidden');
|
||||
};
|
||||
|
|
@ -162,9 +155,9 @@ function initPagination() {
|
|||
method: 'GET',
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
.then(function (response) {
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
response.text().then(function (html) {
|
||||
response.text().then((html) => {
|
||||
const markup = html.trim();
|
||||
|
||||
if (markup) {
|
||||
|
|
@ -198,3 +191,13 @@ function initLoadMoreButton() {
|
|||
button.addEventListener('click', initPagination);
|
||||
}
|
||||
}
|
||||
|
||||
export function initializeNotifications() {
|
||||
fetchNotificationsCount();
|
||||
markNotificationsAsRead();
|
||||
initReactions();
|
||||
listenForNotificationsBellClick();
|
||||
initFilter();
|
||||
initPagination();
|
||||
initLoadMoreButton();
|
||||
}
|
||||
14
app/javascript/utilities/__tests__/checkUserLoggedIn.test.js
Normal file
14
app/javascript/utilities/__tests__/checkUserLoggedIn.test.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { checkUserLoggedIn } from '@utilities/checkUserLoggedIn';
|
||||
|
||||
describe('CheckUserLoggedIn Utility', () => {
|
||||
it('should return false if no body', () => {
|
||||
const userLoggedIn = checkUserLoggedIn();
|
||||
expect(userLoggedIn).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return true if user has the logged in attribute', () => {
|
||||
document.body.setAttribute('data-user-status', 'logged-in');
|
||||
const userLoggedIn = checkUserLoggedIn();
|
||||
expect(userLoggedIn).toEqual(true);
|
||||
});
|
||||
});
|
||||
24
app/javascript/utilities/__tests__/sendHapticMessage.test.js
Normal file
24
app/javascript/utilities/__tests__/sendHapticMessage.test.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { sendHapticMessage } from '../sendHapticMessage';
|
||||
|
||||
describe('SendHapticMessage Utility', () => {
|
||||
it('should call postMessage', async () => {
|
||||
const mockPostMessage = jest.fn();
|
||||
global.window.webkit = {
|
||||
messageHandlers: {
|
||||
haptic: {
|
||||
postMessage: mockPostMessage,
|
||||
},
|
||||
},
|
||||
};
|
||||
await sendHapticMessage('sample message');
|
||||
|
||||
expect(mockPostMessage).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should log to console otherwise', async () => {
|
||||
global.window = {};
|
||||
global.console.log = jest.fn();
|
||||
await sendHapticMessage('sample message');
|
||||
expect(global.console.log).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import 'isomorphic-fetch';
|
||||
import {
|
||||
getModalHtml,
|
||||
showModalAfterError,
|
||||
} from '../../utilities/showUserAlertModal';
|
||||
|
||||
describe('ShowUserAlert Utility', () => {
|
||||
beforeEach(() => {
|
||||
const mockShowModal = jest.fn();
|
||||
global.window.Forem = { showModal: mockShowModal };
|
||||
});
|
||||
|
||||
it('should return modal html', () => {
|
||||
const modalHtml = getModalHtml('Sample text', 'Sample Confirm Text');
|
||||
expect(modalHtml).toContain('Sample text');
|
||||
});
|
||||
|
||||
test('shows rate limit modal if response status is 429', async () => {
|
||||
const response = new Response('', { status: 429 });
|
||||
const showRateLimitModal = jest.fn();
|
||||
const showUserAlertModal = jest.fn();
|
||||
|
||||
await showModalAfterError({
|
||||
response,
|
||||
element: 'post',
|
||||
action_ing: 'creating',
|
||||
action_past: 'created',
|
||||
timeframe: '5 minutes',
|
||||
showRateLimitModal,
|
||||
showUserAlertModal,
|
||||
});
|
||||
|
||||
expect(showUserAlertModal).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('shows user alert modal if response status is not 429', async () => {
|
||||
const response = new Response(
|
||||
JSON.stringify({ error: 'Something went wrong' }),
|
||||
);
|
||||
const showRateLimitModal = jest.fn();
|
||||
const showUserAlertModal = jest.fn();
|
||||
|
||||
await showModalAfterError({
|
||||
response,
|
||||
element: 'post',
|
||||
action_ing: 'creating',
|
||||
action_past: 'created',
|
||||
timeframe: '5 minutes',
|
||||
showRateLimitModal,
|
||||
showUserAlertModal,
|
||||
});
|
||||
|
||||
expect(showRateLimitModal).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('shows user alert modal if response cannot be parsed as JSON', async () => {
|
||||
const response = new Response('Something went wrong', { status: 500 });
|
||||
const showRateLimitModal = jest.fn();
|
||||
const showUserAlertModal = jest.fn();
|
||||
|
||||
await showModalAfterError({
|
||||
response,
|
||||
element: 'post',
|
||||
action_ing: 'creating',
|
||||
action_past: 'created',
|
||||
timeframe: '5 minutes',
|
||||
showRateLimitModal,
|
||||
showUserAlertModal,
|
||||
});
|
||||
|
||||
expect(showRateLimitModal).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
8
app/javascript/utilities/checkUserLoggedIn.js
Normal file
8
app/javascript/utilities/checkUserLoggedIn.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export function checkUserLoggedIn() {
|
||||
const { body } = document;
|
||||
if (!body) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return body.getAttribute('data-user-status') === 'logged-in';
|
||||
}
|
||||
14
app/javascript/utilities/sendHapticMessage.js
Normal file
14
app/javascript/utilities/sendHapticMessage.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
export function sendHapticMessage(message) {
|
||||
try {
|
||||
if (
|
||||
window &&
|
||||
window.webkit &&
|
||||
window.webkit.messageHandlers &&
|
||||
window.webkit.messageHandlers.haptic
|
||||
) {
|
||||
window.webkit.messageHandlers.haptic.postMessage(message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err.message); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ const modalId = 'user-alert-modal';
|
|||
* @example
|
||||
* showUserAlertModal('Warning', 'You must wait', 'OK', '/faq/why-must-i-wait', 'Why must I wait?');
|
||||
*/
|
||||
function showUserAlertModal(title, text, confirm_text) {
|
||||
export function showUserAlertModal(title, text, confirm_text) {
|
||||
buildModalDiv(text, confirm_text);
|
||||
window.Forem.showModal({
|
||||
title,
|
||||
|
|
@ -26,6 +26,7 @@ function showUserAlertModal(title, text, confirm_text) {
|
|||
overlay: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a user rate limit alert modal letting the user know what they did that exceeded a rate limit,
|
||||
* and gives them links to explain why they must wait
|
||||
|
|
@ -45,13 +46,13 @@ function showRateLimitModal({
|
|||
action_past,
|
||||
timeframe = 'a moment',
|
||||
}) {
|
||||
let rateLimitText = buildRateLimitText({
|
||||
const rateLimitText = buildRateLimitText({
|
||||
element,
|
||||
action_ing,
|
||||
action_past,
|
||||
timeframe,
|
||||
});
|
||||
let rateLimitLink = '/faq';
|
||||
const rateLimitLink = '/faq';
|
||||
showUserAlertModal(
|
||||
`Wait ${timeframe}...`,
|
||||
rateLimitText,
|
||||
|
|
@ -60,6 +61,7 @@ function showRateLimitModal({
|
|||
'Why do I have to wait?',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the corresponding modal after an error.
|
||||
*
|
||||
|
|
@ -73,7 +75,7 @@ function showRateLimitModal({
|
|||
* @example
|
||||
* showModalAfterError(response, 'made a comment', 'making another comment', 'a moment');
|
||||
*/
|
||||
function showModalAfterError({
|
||||
export function showModalAfterError({
|
||||
response,
|
||||
element,
|
||||
action_ing,
|
||||
|
|
@ -82,19 +84,23 @@ function showModalAfterError({
|
|||
}) {
|
||||
response
|
||||
.json()
|
||||
.then(function parseError(errorResponse) {
|
||||
.then((errorResponse) => {
|
||||
if (response.status === 429) {
|
||||
showRateLimitModal({ element, action_ing, action_past, timeframe });
|
||||
showRateLimitModal({
|
||||
element,
|
||||
action_ing,
|
||||
action_past,
|
||||
timeframe,
|
||||
});
|
||||
} else {
|
||||
showUserAlertModal(
|
||||
`Error ${action_ing} ${element}`,
|
||||
`Your ${element} could not be ${action_past} due to an error: ` +
|
||||
errorResponse.error,
|
||||
`Your ${element} could not be ${action_past} due to an error: ${errorResponse.error}`,
|
||||
'OK',
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(function parseError(error) {
|
||||
.catch(() => {
|
||||
showUserAlertModal(
|
||||
`Error ${action_ing} ${element}`,
|
||||
`Your ${element} could not be ${action_past} due to a server error`,
|
||||
|
|
@ -103,30 +109,6 @@ function showModalAfterError({
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML template for modal
|
||||
*
|
||||
* @private
|
||||
* @function getModalHtml
|
||||
*
|
||||
* @param {string} text The body text to be displayed
|
||||
* @param {string} confirm_text Text of the confirmation button
|
||||
*
|
||||
* @returns {string} HTML for the modal
|
||||
*/
|
||||
const getModalHtml = (text, confirm_text) => `
|
||||
<div id="${modalId}" hidden>
|
||||
<div class="flex flex-col">
|
||||
<p class="color-base-70">
|
||||
${text}
|
||||
</p>
|
||||
<button class="crayons-btn mt-4 ml-auto" type="button" onClick="window.Forem.closeModal()">
|
||||
${confirm_text}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
/**
|
||||
* Constructs wording for rate limit modals
|
||||
*
|
||||
|
|
@ -140,10 +122,39 @@ const getModalHtml = (text, confirm_text) => `
|
|||
*
|
||||
* @returns {string} Formatted body text for a rate limit modal
|
||||
*/
|
||||
function buildRateLimitText({ element, action_ing, action_past, timeframe }) {
|
||||
export function buildRateLimitText({
|
||||
element,
|
||||
action_ing,
|
||||
action_past,
|
||||
timeframe,
|
||||
}) {
|
||||
return `Since you recently ${action_past} a ${element}, you’ll need to wait ${timeframe} before ${action_ing} another ${element}.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML template for modal
|
||||
*
|
||||
* @private
|
||||
* @function getModalHtml
|
||||
*
|
||||
* @param {string} text The body text to be displayed
|
||||
* @param {string} confirm_text Text of the confirmation button
|
||||
*
|
||||
* @returns {string} HTML for the modal
|
||||
*/
|
||||
export const getModalHtml = (text, confirm_text) => `
|
||||
<div id="${modalId}" hidden>
|
||||
<div class="flex flex-col">
|
||||
<p class="color-base-70">
|
||||
${text}
|
||||
</p>
|
||||
<button class="crayons-btn mt-4 ml-auto" type="button" onClick="window.Forem.closeModal()">
|
||||
${confirm_text}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
/**
|
||||
* Checks for the alert modal, and if it's not present builds and inserts it in the DOM
|
||||
*
|
||||
|
|
@ -177,8 +188,8 @@ function buildModalDiv(text, confirm_text) {
|
|||
*
|
||||
* @returns {Element} DOM node of alert modal with formatted text
|
||||
*/
|
||||
function getModal(text, confirm_text) {
|
||||
let wrapper = document.createElement('div');
|
||||
export function getModal(text, confirm_text) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = getModalHtml(text, confirm_text);
|
||||
return wrapper;
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
describe('Notification initialization', () => {
|
||||
describe('Home page notifications', () => {
|
||||
beforeEach(() => {
|
||||
cy.testSetup();
|
||||
cy.fixture('users/notificationsUser.json').as('user');
|
||||
|
||||
cy.get('@user').then((user) => {
|
||||
cy.loginUser(user).then(() => {
|
||||
cy.visit('/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Shows the notification count', () => {
|
||||
cy.get('#notifications-link').find('span').as('pan');
|
||||
|
||||
cy.get('@pan').should('have.text', '2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Notifications page', () => {
|
||||
beforeEach(() => {
|
||||
cy.testSetup();
|
||||
cy.fixture('users/notificationsUser.json').as('user');
|
||||
|
||||
cy.get('@user').then((user) => {
|
||||
cy.loginAndVisit(user, '/notifications');
|
||||
});
|
||||
});
|
||||
|
||||
it('Shows the notifications marked as read', () => {
|
||||
cy.get('#notifications-link').find('span').as('pan');
|
||||
|
||||
cy.get('@pan').should('have.value', '');
|
||||
});
|
||||
|
||||
it('initializes reactions', () => {
|
||||
cy.get('.reaction-button').should('exist');
|
||||
cy.get('.reacted').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -82,6 +82,7 @@
|
|||
"eslint-plugin-no-only-tests": "^3.1.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"husky": "^8.0.3",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"jest": "28.1.3",
|
||||
"jest-axe": "^6.0.0",
|
||||
"jest-environment-jsdom": "^28.1.3",
|
||||
|
|
@ -131,6 +132,7 @@
|
|||
"he": "^1.2.0",
|
||||
"i18n-js": "^4.2.3",
|
||||
"intersection-observer": "^0.12.2",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"jest-junit": "^14.0.1",
|
||||
"linkstate": "^2.0.1",
|
||||
"lodash.debounce": "4.0.8",
|
||||
|
|
|
|||
25
yarn.lock
25
yarn.lock
|
|
@ -5463,14 +5463,14 @@ caniuse-api@^3.0.0:
|
|||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001304, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001426:
|
||||
version "1.0.30001434"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz#ec1ec1cfb0a93a34a0600d37903853030520a4e5"
|
||||
integrity sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==
|
||||
version "1.0.30001474"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001474.tgz#13b6fe301a831fe666cce8ca4ef89352334133d5"
|
||||
integrity sha512-iaIZ8gVrWfemh5DG3T9/YqarVZoYf0r188IjaGwx68j4Pf0SGY6CQkmJUIE+NZHkkecQGohzXmBGEwWDr9aM3Q==
|
||||
|
||||
canvas@^2.11.0:
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.0.tgz#7f0c3e9ae94cf469269b5d3a7963a7f3a9936434"
|
||||
integrity sha512-bdTjFexjKJEwtIo0oRx8eD4G2yWoUOXP9lj279jmQ2zMnTQhT8C3512OKz3s+ZOaQlLbE7TuVvRDYDB3Llyy5g==
|
||||
version "2.11.2"
|
||||
resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.2.tgz#553d87b1e0228c7ac0fc72887c3adbac4abbd860"
|
||||
integrity sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==
|
||||
dependencies:
|
||||
"@mapbox/node-pre-gyp" "^1.0.0"
|
||||
nan "^2.17.0"
|
||||
|
|
@ -9918,6 +9918,14 @@ isobject@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0"
|
||||
integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==
|
||||
|
||||
isomorphic-fetch@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4"
|
||||
integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==
|
||||
dependencies:
|
||||
node-fetch "^2.6.1"
|
||||
whatwg-fetch "^3.4.1"
|
||||
|
||||
isomorphic-unfetch@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f"
|
||||
|
|
@ -16840,6 +16848,11 @@ whatwg-encoding@^2.0.0:
|
|||
dependencies:
|
||||
iconv-lite "0.6.3"
|
||||
|
||||
whatwg-fetch@^3.4.1:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c"
|
||||
integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==
|
||||
|
||||
whatwg-mimetype@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue