Création d'une section /nouveautés

Cette nouvelle section s'accompagne d'un bandeau qui s'affiche quand une
nouvelle version est publiée sur GitHub.

Les données sont téléchargées depuis l'API GitHub en GraphQL au moment
du build du site puis persistées dans un fichier Json statique.
pull/842/head
Maxime Quandalle 2020-01-14 16:10:29 +01:00
parent 90e42a8b47
commit 54d45dbdb6
No known key found for this signature in database
GPG Key ID: 428641C03D29CA10
17 changed files with 508 additions and 82 deletions

2
.gitignore vendored
View File

@ -1,5 +1,7 @@
.tags*
.tmp
.env
source/data
node_modules/
dist/
.DS_Store

View File

@ -21,6 +21,7 @@
"dependencies": {
"@babel/polyfill": "^7.4.0",
"@babel/runtime": "^7.3.4",
"@rehooks/local-storage": "^2.1.1",
"classnames": "^2.2.5",
"color-convert": "^1.9.2",
"core-js": "^3.2.1",
@ -59,6 +60,7 @@
"regenerator-runtime": "^0.13.3",
"reselect": "^4.0.0",
"screenfull": "^3.3.2",
"swr": "^0.1.16",
"whatwg-fetch": "^3.0.0"
},
"scripts": {
@ -135,6 +137,7 @@
"csv-loader": "^2.1.1",
"daggy": "^1.3.0",
"dedent-js": "^1.0.1",
"dotenv": "=8.1.0",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"eslint": "^6.5.1",
@ -148,6 +151,7 @@
"http-server": "^0.11.1",
"intl": "^1.2.5",
"intl-locales-supported": "^1.0.0",
"isomorphic-fetch": "^2.2.1",
"isomorphic-style-loader": "^5.1.0",
"jest": "^24.9.0",
"jest-transform-nearley": "^1.0.0",

View File

@ -12,3 +12,29 @@
font-size: 1.4rem;
margin-bottom: 0.6rem !important;
}
.ui__.banner.news {
justify-content: space-between;
background: var(--lightestColor);
font-size: 0.9em;
width: auto;
max-width: 450px;
margin: auto;
padding: 4px 15px 0px;
border-radius: 15px;
}
.ui__.banner.news img {
vertical-align: middle !important;
top: 4px;
position: relative;
}
.ui__.close-button {
cursor: pointer;
font-size: 1.3em;
}
.ui__.close-button:hover {
color: black;
}

View File

@ -107,6 +107,7 @@ a {
font-size: inherit;
padding: none;
text-decoration: underline;
text-underline-offset: 4px;
color: rgb(41, 117, 209);
color: var(--color);
}

View File

@ -30,7 +30,7 @@ export const Markdown = ({
<ReactMarkdown
source={source}
className={`markdown ${className}`}
renderers={{ ...renderers, link: LinkRenderer, text: TextRenderer }}
renderers={{ link: LinkRenderer, text: TextRenderer, ...renderers }}
{...otherProps}
/>
)

View File

@ -0,0 +1,101 @@
// This script uses the GitHub API which requires an access token.
// https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line
// Once you have your access token you can put it in a `.env` file at the root
// of the project to enable it during development. For instance:
//
// GITHUB_API_SECRET=f4336c82cb1e494752d06e610614eab12b65f1d1
//
require('dotenv').config()
require('isomorphic-fetch')
const fs = require('fs')
const path = require('path')
// We use the GitHub API V4 in GraphQL to download the releases. A GraphQL
// explorer can be found here : https://developer.github.com/v4/explorer/
const githubAuthToken = process.env.GITHUB_API_SECRET
const cursorOfV1Release = 'Y3Vyc29yOnYyOpHOARHb8g=='
const query = `query {
repository(owner:"betagouv", name:"mon-entreprise") {
releases(after:"${cursorOfV1Release}", last:100) {
nodes {
name
description
}
}
}
}`
// In case we cannot fetch the release (the API is down or the Authorization
// token isn't valid) we fallback to some fake data -- it would be better to
// have a static ressource accessible without authentification.
const fakeData = [
{
name: 'Fake release',
descriptionHTML: `You are seing this fake release because you
didn't configure your GitHub access token and we weren't
able to fetch the real releases from GitHub.<br /><br />
See the script <pre>fetch-releases.js</pre> for more informations.`
},
{
name: 'Release 2',
descriptionHTML: 'blah blah blah'
},
{
name: 'Release 3',
descriptionHTML: 'blah blah blah'
}
]
// eslint-disable-next-line no-undef
const dataDir = path.resolve(__dirname, '../data/')
async function main() {
createDataDir()
writeReleasesInDataDir(await fetchReleases())
}
function createDataDir() {
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir)
}
}
async function fetchReleases() {
if (!githubAuthToken) {
return fakeData
}
try {
const response = await fetch('https://api.github.com/graphql', {
method: 'post',
headers: new Headers({ Authorization: `bearer ${githubAuthToken}` }),
body: JSON.stringify({ query })
})
const {
data: {
repository: {
releases: { nodes: releases }
}
}
} = await response.json()
return releases.filter(Boolean).reverse()
} catch (e) {
return fakeData
}
}
function writeReleasesInDataDir(releases) {
// The last release name is fetched on all pages (to display the banner)
// whereas the full release data is used only in the dedicated page, that why
// we deduplicate the releases data in two separated files that can be
// bundled/fetched separately.
fs.writeFileSync(
path.join(dataDir, 'releases.json'),
JSON.stringify(releases)
)
fs.writeFileSync(
path.join(dataDir, 'last-release.json'),
JSON.stringify({ lastRelease: releases[0].name })
)
}
main()

View File

@ -1 +1,2 @@
require('./dottednames.js')
require('./fetch-releases.js')

View File

@ -13,7 +13,6 @@ body,
min-height: 100%;
display: flex;
flex-direction: column;
overflow: auto;
}
@media (min-width: 500px) {
@ -22,7 +21,6 @@ body,
flex: 1;
}
.app-container {
overflow: auto;
min-height: 100vh;
height: auto;
}

View File

@ -1,37 +1,44 @@
import Route404 from 'Components/Route404';
import { SitePathsContext } from 'Components/utils/withSitePaths';
import { rules as baseRulesEn, rulesFr as baseRulesFr } from 'Engine/rules';
import 'iframe-resizer';
import createRavenMiddleware from 'raven-for-redux';
import Raven from 'raven-js';
import React, { useContext, useEffect } from 'react';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import { Route, Switch } from 'react-router-dom';
import 'Ui/index.css';
import Provider from '../../Provider';
import { persistEverything, retrievePersistedState } from '../../storage/persistEverything';
import { persistSimulation, retrievePersistedSimulation } from '../../storage/persistSimulation';
import Tracker, { devTracker } from '../../Tracker';
import { getSessionStorage, inIframe } from '../../utils';
import './App.css';
import Footer from './layout/Footer/Footer';
import Header from './layout/Header';
import trackSimulatorActions from './middlewares/trackSimulatorActions';
import Créer from './pages/Créer';
import Couleur from './pages/Dev/Couleur';
import IntegrationTest from './pages/Dev/IntegrationTest';
import Personas from './pages/Dev/Personas';
import Sitemap from './pages/Dev/Sitemap';
import Documentation from './pages/Documentation';
import Gérer from './pages/Gérer';
import Iframes from './pages/Iframes';
import Integration from './pages/integration/index';
import Landing from './pages/Landing/Landing';
import Simulateurs from './pages/Simulateurs';
import ÉconomieCollaborative from './pages/ÉconomieCollaborative';
import redirects from './redirects';
import { constructLocalizedSitePath } from './sitePaths';
import Route404 from 'Components/Route404'
import { SitePathsContext } from 'Components/utils/withSitePaths'
import { rules as baseRulesEn, rulesFr as baseRulesFr } from 'Engine/rules'
import 'iframe-resizer'
import createRavenMiddleware from 'raven-for-redux'
import Raven from 'raven-js'
import React, { useContext, useEffect } from 'react'
import { Helmet } from 'react-helmet'
import { useTranslation } from 'react-i18next'
import { Route, Switch } from 'react-router-dom'
import 'Ui/index.css'
import Provider from '../../Provider'
import {
persistEverything,
retrievePersistedState
} from '../../storage/persistEverything'
import {
persistSimulation,
retrievePersistedSimulation
} from '../../storage/persistSimulation'
import Tracker, { devTracker } from '../../Tracker'
import { getSessionStorage, inIframe } from '../../utils'
import './App.css'
import Footer from './layout/Footer/Footer'
import Header from './layout/Header'
import trackSimulatorActions from './middlewares/trackSimulatorActions'
import Créer from './pages/Créer'
import Couleur from './pages/Dev/Couleur'
import IntegrationTest from './pages/Dev/IntegrationTest'
import Personas from './pages/Dev/Personas'
import Sitemap from './pages/Dev/Sitemap'
import Documentation from './pages/Documentation'
import Gérer from './pages/Gérer'
import Iframes from './pages/Iframes'
import Integration from './pages/integration/index'
import Landing from './pages/Landing/Landing'
import Nouveautés from './pages/Nouveautés/Nouveautés'
import Simulateurs from './pages/Simulateurs'
import ÉconomieCollaborative from './pages/ÉconomieCollaborative'
import redirects from './redirects'
import { constructLocalizedSitePath } from './sitePaths'
if (process.env.NODE_ENV === 'production') {
Raven.config(
@ -70,7 +77,8 @@ function InFranceRoute({ basename, language }) {
...retrievePersistedState(),
previousSimulation: retrievePersistedSimulation(),
rules
}}>
}}
>
<RouterSwitch />
</Provider>
)
@ -113,6 +121,7 @@ const App = () => {
component={Documentation}
/>
<Route path={sitePaths.integration.index} component={Integration} />
<Route path={sitePaths.nouveautés} component={Nouveautés} />
<Route exact path="/dev/sitemap" component={Sitemap} />
<Route
exact

View File

@ -84,13 +84,15 @@ const Footer = () => {
<LegalNotice />
{' • '}
<Privacy />
{' • '}
<a href="https://github.com/betagouv/mon-entreprise/releases">
Nouveautés
</a>
{i18n.language === 'fr' && (
<>
{' • '}
<Link to={sitePaths.nouveautés}>Nouveautés</Link>
</>
)}
{' • '}
<a href="https://mon-entreprise.fr/stats">Stats</a>
{' • '}{' '}
{' • '}
<Link to={sitePaths.integration.index}>
Intégrer nos simulateurs
</Link>

View File

@ -6,47 +6,58 @@ import urssafSvg from 'Images/urssaf.svg'
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import NewsBanner from './NewsBanner'
export default function Header() {
const sitePaths = useContext(SitePathsContext)
const { language } = useTranslation().i18n
return (
<div
className="ui__ container"
style={{
display: 'flex',
alignItems: 'center'
}}>
<Link style={{ height: '4rem' }} to={sitePaths.index}>
<img
alt="logo mon-entreprise.fr"
style={{
padding: '0.5rem 0',
height: '100%'
}}
src={language === 'fr' ? logoSvg : logoEnSvg}
/>
</Link>
<div style={{ flex: 1 }} />
<a
href="https://beta.gouv.fr"
target="_blank"
<>
<div
className="ui__ container"
style={{
height: '4rem',
padding: '1rem'
}}>
<img alt="logo marianne" style={{ height: '100%' }} src={marianneSvg} />
</a>
<a
href="https://www.urssaf.fr"
target="_blank"
style={{
height: '4rem',
padding: '1rem'
display: 'flex',
alignItems: 'center'
}}
className="landing-header__institutional-logo">
<img alt="logo urssaf" style={{ height: '100%' }} src={urssafSvg} />
</a>
</div>
>
<Link style={{ height: '4rem' }} to={sitePaths.index}>
<img
alt="logo mon-entreprise.fr"
style={{
padding: '0.5rem 0',
height: '100%'
}}
src={language === 'fr' ? logoSvg : logoEnSvg}
/>
</Link>
<div style={{ flex: 1 }} />
<a
href="https://beta.gouv.fr"
target="_blank"
style={{
height: '4rem',
padding: '1rem'
}}
>
<img
alt="logo marianne"
style={{ height: '100%' }}
src={marianneSvg}
/>
</a>
<a
href="https://www.urssaf.fr"
target="_blank"
style={{
height: '4rem',
padding: '1rem'
}}
className="landing-header__institutional-logo"
>
<img alt="logo urssaf" style={{ height: '100%' }} src={urssafSvg} />
</a>
</div>
<NewsBanner />
</>
)
}

View File

@ -0,0 +1,39 @@
import { useLocalStorage, writeStorage } from '@rehooks/local-storage'
import { SitePathsContext } from 'Components/utils/withSitePaths'
import React, { useContext } from 'react'
import emoji from 'react-easy-emoji'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import { lastRelease } from '../../../data/last-release.json'
import { inIframe } from '../../../utils'
const localStorageKey = 'last-viewed-release'
export const hideNewsBanner = () => writeStorage(localStorageKey, lastRelease)
export default function NewsBanner() {
const [lastViewedRelease] = useLocalStorage(localStorageKey)
const sitePaths = useContext(SitePathsContext)
const { i18n } = useTranslation()
const showBanner =
lastViewedRelease !== lastRelease && i18n.language === 'fr' && !inIframe()
// We only want to show the banner to returning visitors, so we initiate the
// local storage value with the last release.
if (showBanner && lastViewedRelease === undefined) {
hideNewsBanner()
}
return showBanner ? (
<div className="ui__ banner news">
<span>
{emoji('✨')} Découvrez les nouveautés de{' '}
<Link to={sitePaths.nouveautés}>{lastRelease}</Link>
</span>
<span onClick={hideNewsBanner} className="ui__ close-button">
&times;
</span>
</div>
) : null
}

View File

@ -0,0 +1,186 @@
import { Markdown } from 'Components/utils/markdown'
import { SitePathsContext } from 'Components/utils/withSitePaths'
import React, { useContext, useEffect } from 'react'
import emoji from 'react-easy-emoji'
import { Redirect, useHistory, useRouteMatch } from 'react-router'
import { Link, NavLink } from 'react-router-dom'
import styled from 'styled-components'
import useSWR from 'swr'
import { hideNewsBanner } from '../../layout/NewsBanner'
const fetcher = (url: RequestInfo) => fetch(url).then(r => r.json())
const slugify = (name: string) => name.toLowerCase().replace(' ', '-')
export default function Nouveautés() {
// The release.json file may be big, we don't want to include it in the main
// bundle, that's why we only fetch it on this page. Alternatively we could
// use import("data/release.json") and configure code splitting with Webpack.
const { data } = useSWR('/data/releases.json', fetcher)
const history = useHistory()
const sitePaths = useContext(SitePathsContext)
const slug = useRouteMatch<{ slug: string }>(`${sitePaths.nouveautés}/:slug`)
?.params?.slug
const selectedRelease = data?.findIndex(({ name }) => slugify(name) === slug)
useEffect(hideNewsBanner, [])
useEffect(() => {
window.scrollTo({ top: 0 })
}, [selectedRelease])
const getPath = (index: number) =>
`${sitePaths.nouveautés}/${slugify(data[index].name)}`
if (!data) {
return null
} else if (!slug || selectedRelease === -1) {
return <Redirect to={getPath(0)} />
}
return (
<>
<h1>Les nouveautés {emoji('✨')}</h1>
<p>
Nous améliorons le site en continu à partir de vos retours. Découvrez
les{' '}
{selectedRelease === 0
? 'dernières nouveautés'
: `nouveautés de ${data[selectedRelease].name.toLowerCase()}`}{' '}
:
</p>
<SmallScreenSelect
onChange={evt => {
history.push(getPath(Number(evt.target.value)))
}}
>
{data.map(({ name }, index) => (
<option
key={index}
value={index}
selected={index === selectedRelease}
>
{name}
</option>
))}
</SmallScreenSelect>
<NewsSection>
<Sidebar>
{data.map(({ name }, index) => (
<li key={name}>
<NavLink activeClassName="active" to={getPath(index)}>
{name}
</NavLink>
</li>
))}
</Sidebar>
<MainBlock>
<Markdown
source={data[selectedRelease].description}
escapeHtml={false}
renderers={{ text: TextRenderer }}
/>
<NavigationButtons>
{selectedRelease + 1 < data.length ? (
<Link to={getPath(selectedRelease + 1)}>
{data[selectedRelease + 1].name}
</Link>
) : (
<span /> // For spacing
)}
{selectedRelease > 0 && (
<Link to={getPath(selectedRelease - 1)}>
{data[selectedRelease - 1].name}
</Link>
)}
</NavigationButtons>
</MainBlock>
</NewsSection>
</>
)
}
const removeGithubIssuesReferences = (text: string) =>
text.replace(/#[0-9]{1,5}/g, '')
const TextRenderer = ({ children }) => (
<>{emoji(removeGithubIssuesReferences(children))}</>
)
const NewsSection = styled.section`
display: flex;
justify-content: space-between;
align-items: flex-start;
@media (min-width: 1250px) {
margin-left: -175px;
}
`
const Sidebar = styled.ul`
display: flex;
flex-direction: column;
position: sticky;
top: 20px;
margin-right: 25px;
padding-left: 0;
font-size: 0.9em;
border-right: 1px solid var(--lighterColor);
@media (max-width: 700px) {
display: none;
}
li {
list-style-type: none;
list-style-position: inside;
width: 150px;
padding: 0;
margin: 0;
a {
display: block;
color: inherit;
text-decoration: none;
padding: 4px 10px;
margin: 0;
&:hover,
&.active {
background: var(--lightestColor);
}
&.active {
font-weight: bold;
}
}
}
`
const SmallScreenSelect = styled.select`
display: none;
@media (max-width: 700px) {
display: initial;
}
`
const MainBlock = styled.div`
flex: 1;
> h1:first-child,
h2:first-child,
h3:first-child {
margin-top: 0px;
}
`
const NavigationButtons = styled.div`
display: flex;
justify-content: space-between;
margin-top: 40px;
a {
cursor: pointer;
background: var(--lightestColor);
padding: 20px 30px;
}
`

View File

@ -111,6 +111,7 @@ export const constructLocalizedSitePath = (language: string) => {
'/votre-situation'
)
},
nouveautés: t('path.nouveautés', '/nouveautés'),
documentation: {
exemples: t('path.documentation.exemples', '/exemples'),
index: t('path.documentation.index', '/documentation')

View File

@ -4,5 +4,6 @@ declare module NodeJS {
FR_SITE: string
NODE_ENV: 'development' | 'production'
MASTER: boolean
GITHUB_API_SECRET: string
}
}

View File

@ -55,6 +55,10 @@ module.exports.default = {
from: './source/images',
to: 'images'
},
{
from: './source/data',
to: 'data'
},
{
from: './source/sites/mon-entreprise.fr/favicon',
to: 'favicon'

View File

@ -1056,6 +1056,11 @@
promise-limit "^2.5.0"
puppeteer "^1.7.0"
"@rehooks/local-storage@^2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@rehooks/local-storage/-/local-storage-2.1.1.tgz#e9d9a36a17308667f2aee9b01cbec9a29d53d341"
integrity sha512-w+7kwnFn3tdRreDG+Pi610eqozTyuJnmYIqwa0GojNpoLMjnYDSCF02eyWQOkVPUj1UHf+Zeqnjg+jzqg3OcuQ==
"@sinonjs/commons@^1", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.7.0":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.0.tgz#f90ffc52a2e519f018b13b6c4da03cbff36ebed6"
@ -3757,6 +3762,11 @@ domutils@^2.0.0:
domelementtype "^2.0.1"
domhandler "^3.0.0"
dotenv@=8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.1.0.tgz#d811e178652bfb8a1e593c6dd704ec7e90d85ea2"
integrity sha512-GUE3gqcDCaMltj2++g6bRQ5rBJWtkWTmqmD0fo1RnnMuUqHNCt2oTPeDnS9n6fKYvlhn7AeBkb38lymBtWBQdA==
duplexify@^3.4.2, duplexify@^3.6.0:
version "3.7.1"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
@ -3841,6 +3851,13 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
encoding@^0.1.11:
version "0.1.12"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
dependencies:
iconv-lite "~0.4.13"
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@ -4401,7 +4418,7 @@ extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
fast-deep-equal@^2.0.1:
fast-deep-equal@2.0.1, fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
@ -5294,7 +5311,7 @@ i18next@^18.0.1:
dependencies:
"@babel/runtime" "^7.3.1"
iconv-lite@0.4.24, iconv-lite@^0.4.24:
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@ -5728,7 +5745,7 @@ is-regexp@^1.0.0:
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk=
is-stream@^1.1.0:
is-stream@^1.0.1, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
@ -5821,6 +5838,14 @@ isobject@^3.0.0, isobject@^3.0.1:
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
isomorphic-fetch@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
dependencies:
node-fetch "^1.0.1"
whatwg-fetch ">=0.10.0"
isomorphic-style-loader@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/isomorphic-style-loader/-/isomorphic-style-loader-5.1.0.tgz#4845f90bb9828f3dfecc82d0574c9ed01bbaba2d"
@ -7257,6 +7282,14 @@ no-case@^2.2.0:
dependencies:
lower-case "^1.1.1"
node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
dependencies:
encoding "^0.1.11"
is-stream "^1.0.1"
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@ -10077,6 +10110,13 @@ svgo@^0.7.0:
sax "~1.2.1"
whet.extend "~0.9.9"
swr@^0.1.16:
version "0.1.16"
resolved "https://registry.yarnpkg.com/swr/-/swr-0.1.16.tgz#e1667f2260ac091fb4c2cc6255ca1b8ff83b0acd"
integrity sha512-E+i0pJOCCPRxnwQMFKf3RYr4os+B4swNuwnHo+pBCNxwfuJiN0ZalwP9u4OqjoeMl9eZFgcQYUPHD8KVyTqM1g==
dependencies:
fast-deep-equal "2.0.1"
symbol-observable@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4"
@ -10853,7 +10893,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
dependencies:
iconv-lite "0.4.24"
whatwg-fetch@^3.0.0:
whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==