diff --git a/core/dev-test/backends/github/config.yml b/core/dev-test/backends/github/config.yml index 9a489331..21c1ef09 100644 --- a/core/dev-test/backends/github/config.yml +++ b/core/dev-test/backends/github/config.yml @@ -1,10 +1,10 @@ backend: name: github - branch: master - repo: owner/repo + branch: main + repo: staticjscms/static-cms-github -media_folder: static/media -public_folder: /media +media_folder: assets/upload +public_folder: /assets/upload collections: - name: posts label: Posts diff --git a/core/package.json b/core/package.json index d6ed46ea..82eab2b3 100644 --- a/core/package.json +++ b/core/package.json @@ -102,7 +102,6 @@ "react-router-dom": "6.4.1", "react-scroll-sync": "0.9.0", "react-sortable-hoc": "2.0.0", - "react-split-pane": "0.1.92", "react-textarea-autosize": "8.3.4", "react-topbar-progress-indicator": "4.1.1", "react-virtualized-auto-sizer": "1.0.7", diff --git a/core/src/backend.ts b/core/src/backend.ts index 7de38136..4eb89b80 100644 --- a/core/src/backend.ts +++ b/core/src/backend.ts @@ -767,7 +767,8 @@ export class Backend { } return Object.assign(entry, { data: isError(data) ? {} : data }); } - return format.fromFile(entry); + + return entry; }; } @@ -967,7 +968,7 @@ export class Backend { const format = resolveFormat(collection, entry); const fieldsOrder = this.fieldsOrder(collection, entry); const fieldsComments = selectFieldsComments(collection, entry); - return format && format.toFile(entry.data, fieldsOrder, fieldsComments); + return format ? format.toFile(entry.data ?? {}, fieldsOrder, fieldsComments) : ''; } fieldsOrder(collection: Collection, entry: Entry) { diff --git a/core/src/components/App/App.tsx b/core/src/components/App/App.tsx index e781771c..23b14de6 100644 --- a/core/src/components/App/App.tsx +++ b/core/src/components/App/App.tsx @@ -4,11 +4,12 @@ import { styled } from '@mui/material/styles'; import React, { useCallback, useMemo } from 'react'; import { translate } from 'react-polyglot'; import { connect } from 'react-redux'; -import { Navigate, Route, Routes, useParams } from 'react-router-dom'; +import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom'; import { ScrollSync } from 'react-scroll-sync'; import TopBarProgress from 'react-topbar-progress-indicator'; import { loginUser as loginUserAction } from '../../actions/auth'; +import { discardDraft as discardDraftAction } from '../../actions/entries'; import { currentBackend } from '../../backend'; import { colors, GlobalStyles } from '../../components/UI/styles'; import { history } from '../../routing/history'; @@ -26,6 +27,7 @@ import type { ComponentType } from 'react'; import type { ConnectedProps } from 'react-redux'; import type { Collections, Credentials, TranslatedProps } from '../../interface'; import type { RootState } from '../../store'; + TopBarProgress.config({ barColors: { 0: colors.active, @@ -91,6 +93,7 @@ const App = ({ useMediaLibrary, t, scrollSyncEnabled, + discardDraft, }: TranslatedProps) => { const configError = useCallback( (error?: string) => { @@ -157,6 +160,14 @@ const App = ({ const defaultPath = useMemo(() => getDefaultPath(collections), [collections]); + const { pathname } = useLocation(); + React.useEffect(() => { + if (!/\/collections\/[a-zA-Z0-9_-]+\/entries\/[a-zA-Z0-9_-]+/g.test(pathname)) { + discardDraft(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [pathname]); + const content = useMemo(() => { if (!user) { return authenticationPage; @@ -264,6 +275,7 @@ function mapStateToProps(state: RootState) { const mapDispatchToProps = { loginUser: loginUserAction, + discardDraft: discardDraftAction, }; const connector = connect(mapStateToProps, mapDispatchToProps); diff --git a/core/src/components/Editor/Editor.tsx b/core/src/components/Editor/Editor.tsx index 11fd85ca..135a9dd2 100644 --- a/core/src/components/Editor/Editor.tsx +++ b/core/src/components/Editor/Editor.tsx @@ -75,7 +75,8 @@ const Editor = ({ debounce(function (entry: Entry, collection: Collection) { persistLocalBackup(entry, collection); }, 2000), - [persistLocalBackup], + // eslint-disable-next-line react-hooks/exhaustive-deps + [], ); const deleteBackup = useCallback(() => { @@ -84,7 +85,8 @@ const Editor = ({ deleteLocalBackup(collection, slug); } deleteDraftLocalBackup(); - }, [collection, createBackup, deleteDraftLocalBackup, deleteLocalBackup, slug]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [collection, createBackup, slug]); const [submitted, setSubmitted] = useState(false); const handlePersistEntry = useCallback( @@ -195,14 +197,12 @@ const Editor = ({ useEffect(() => { if (hasChanged && entryDraft.entry) { createBackup(entryDraft.entry, collection); - } else if (localBackup) { - deleteBackup(); } return () => { createBackup.flush(); }; - }, [collection, createBackup, deleteBackup, entryDraft.entry, hasChanged, localBackup]); + }, [collection, createBackup, entryDraft.entry, hasChanged]); const [prevCollection, setPrevCollection] = useState(null); const [preSlug, setPrevSlug] = useState(null); @@ -280,11 +280,6 @@ const Editor = ({ }; }, [collection.name, deleteBackup, discardDraft, navigationBlocker]); - // TODO Is this needed? - // if (!collectionEntriesLoaded) { - // loadEntries(collection); - // } - if (entry && entry.error) { return (
diff --git a/core/src/components/Editor/EditorControlPane/EditorControl.tsx b/core/src/components/Editor/EditorControlPane/EditorControl.tsx index ec555f35..3c2fefe5 100644 --- a/core/src/components/Editor/EditorControlPane/EditorControl.tsx +++ b/core/src/components/Editor/EditorControlPane/EditorControl.tsx @@ -117,7 +117,7 @@ interface ControlHintProps { $error: boolean; } -export const ControlHint = styled( +const ControlHint = styled( 'p', transientOptions, )( diff --git a/core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.tsx b/core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.tsx index 7579fa36..d9ae5ace 100644 --- a/core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.tsx +++ b/core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.tsx @@ -94,6 +94,7 @@ function getWidgetFor( getAsset: GetAssetFunction, widgetFields: Field[] = fields, values: EntryData = entry.data, + idx: number | null = null, ): ReactNode { // We retrieve the field by name so that this function can also be used in // custom preview templates, where the field object can't be passed in. @@ -157,7 +158,7 @@ function getWidgetFor( value.toString().length < 50 ) { renderedValue = ( -
+
<> {field.label ?? field.name}: {value} @@ -165,7 +166,7 @@ function getWidgetFor( ); } return renderedValue - ? getWidget(config, fieldWithWidgets, collection, renderedValue, entry, getAsset) + ? getWidget(config, fieldWithWidgets, collection, renderedValue, entry, getAsset, idx) : null; } @@ -237,6 +238,7 @@ function widgetsForNestedFields( getAsset: GetAssetFunction, widgetFields: Field[], values: EntryData, + idx: number | null = null, ) { return widgetFields .map(field => @@ -250,6 +252,7 @@ function widgetsForNestedFields( getAsset, widgetFields, values, + idx, ), ) .filter(widget => Boolean(widget)) as JSX.Element[]; @@ -283,6 +286,7 @@ function getTypedNestedWidgets( getAsset, itemType.fields, value, + index, ); }) .filter(Boolean); diff --git a/core/src/formats/FileFormatter.ts b/core/src/formats/FileFormatter.ts new file mode 100644 index 00000000..5adea6f3 --- /dev/null +++ b/core/src/formats/FileFormatter.ts @@ -0,0 +1,8 @@ +export abstract class FileFormatter { + abstract fromFile(content: string): object; + abstract toFile( + data: object, + sortedKeys?: string[], + comments?: Record, + ): string; +} diff --git a/core/src/formats/JsonFormatter.ts b/core/src/formats/JsonFormatter.ts new file mode 100644 index 00000000..4c684a0d --- /dev/null +++ b/core/src/formats/JsonFormatter.ts @@ -0,0 +1,13 @@ +import { FileFormatter } from './FileFormatter'; + +class JsonFormatter extends FileFormatter { + fromFile(content: string) { + return JSON.parse(content); + } + + toFile(data: object) { + return JSON.stringify(data, null, 2); + } +} + +export default new JsonFormatter(); diff --git a/core/src/formats/toml.ts b/core/src/formats/TomlFormatter.ts similarity index 72% rename from core/src/formats/toml.ts rename to core/src/formats/TomlFormatter.ts index a1b8a141..6d05bfb9 100644 --- a/core/src/formats/toml.ts +++ b/core/src/formats/TomlFormatter.ts @@ -1,9 +1,10 @@ import toml from '@iarna/toml'; -import tomlify from 'tomlify-j0.4'; import moment from 'moment'; +import tomlify from 'tomlify-j0.4'; import AssetProxy from '../valueObjects/AssetProxy'; import { sortKeys } from './helpers'; +import { FileFormatter } from './FileFormatter'; function outputReplacer(_key: string, value: unknown) { if (moment.isMoment(value)) { @@ -22,12 +23,14 @@ function outputReplacer(_key: string, value: unknown) { return false; } -export default { +class TomlFormatter extends FileFormatter { fromFile(content: string) { return toml.parse(content); - }, + } - toFile(data: object, sortedKeys: string[] = []) { - return tomlify.toToml(data, { replace: outputReplacer, sort: sortKeys(sortedKeys) }); - }, -}; + toFile(data: object, sortedKeys: string[] = []): string { + return tomlify.toToml(data as object, { replace: outputReplacer, sort: sortKeys(sortedKeys) }); + } +} + +export default new TomlFormatter(); diff --git a/core/src/formats/yaml.ts b/core/src/formats/YamlFormatter.ts similarity index 89% rename from core/src/formats/yaml.ts rename to core/src/formats/YamlFormatter.ts index 3d6004f3..086597a2 100644 --- a/core/src/formats/yaml.ts +++ b/core/src/formats/YamlFormatter.ts @@ -1,6 +1,7 @@ import yaml from 'yaml'; import { sortKeys } from './helpers'; +import { FileFormatter } from './FileFormatter'; import type { Pair, YAMLMap, YAMLSeq } from 'yaml/types'; @@ -20,13 +21,13 @@ function addComments(items: Array, comments: Record, prefi }); } -export default { +class YamlFormatter extends FileFormatter { fromFile(content: string) { if (content && content.trim().endsWith('---')) { content = content.trim().slice(0, -3); } return yaml.parse(content); - }, + } toFile(data: object, sortedKeys: string[] = [], comments: Record = {}) { const contents = yaml.createNode(data) as YAMLMap | YAMLSeq; @@ -38,5 +39,7 @@ export default { doc.contents = contents; return doc.toString(); - }, -}; + } +} + +export default new YamlFormatter(); diff --git a/core/src/formats/formats.ts b/core/src/formats/formats.ts index 8202675b..d64acc05 100644 --- a/core/src/formats/formats.ts +++ b/core/src/formats/formats.ts @@ -1,12 +1,11 @@ -import get from 'lodash/get'; - -import yamlFormatter from './yaml'; -import tomlFormatter from './toml'; -import jsonFormatter from './json'; +import YamlFormatter from './YamlFormatter'; +import TomlFormatter from './TomlFormatter'; +import JsonFormatter from './JsonFormatter'; import { FrontmatterInfer, frontmatterJSON, frontmatterTOML, frontmatterYAML } from './frontmatter'; import type { Delimiter } from './frontmatter'; import type { Collection, Entry, Format } from '../interface'; +import type { FileFormatter } from './FileFormatter'; export const frontmatterFormats = ['yaml-frontmatter', 'toml-frontmatter', 'json-frontmatter']; @@ -21,30 +20,32 @@ export const formatExtensions = { 'yaml-frontmatter': 'md', }; -export const extensionFormatters = { - yml: yamlFormatter, - yaml: yamlFormatter, - toml: tomlFormatter, - json: jsonFormatter, +export const extensionFormatters: Record = { + yml: YamlFormatter, + yaml: YamlFormatter, + toml: TomlFormatter, + json: JsonFormatter, md: FrontmatterInfer, markdown: FrontmatterInfer, html: FrontmatterInfer, }; -function formatByName(name: Format, customDelimiter?: Delimiter) { - return { - yml: yamlFormatter, - yaml: yamlFormatter, - toml: tomlFormatter, - json: jsonFormatter, +function formatByName(name: Format, customDelimiter?: Delimiter): FileFormatter { + const fileFormatter: Record = { + yml: YamlFormatter, + yaml: YamlFormatter, + toml: TomlFormatter, + json: JsonFormatter, frontmatter: FrontmatterInfer, 'json-frontmatter': frontmatterJSON(customDelimiter), 'toml-frontmatter': frontmatterTOML(customDelimiter), 'yaml-frontmatter': frontmatterYAML(customDelimiter), - }[name]; + }; + + return fileFormatter[name]; } -export function resolveFormat(collection: Collection, entry: Entry) { +export function resolveFormat(collection: Collection, entry: Entry): FileFormatter | undefined { // Check for custom delimiter const frontmatter_delimiter = collection.frontmatter_delimiter; @@ -59,7 +60,7 @@ export function resolveFormat(collection: Collection, entry: Entry) { if (filePath) { const fileExtension = filePath.split('.').pop(); if (fileExtension) { - return get(extensionFormatters, fileExtension); + return extensionFormatters[fileExtension]; } } @@ -67,7 +68,7 @@ export function resolveFormat(collection: Collection, entry: Entry) { // collection config, infer the format from that extension. const extension = collection.extension; if (extension) { - return get(extensionFormatters, extension); + return extensionFormatters[extension]; } // If no format is specified and it cannot be inferred, return the default. diff --git a/core/src/formats/frontmatter.ts b/core/src/formats/frontmatter.ts index 5f9fa775..71c7f3ae 100644 --- a/core/src/formats/frontmatter.ts +++ b/core/src/formats/frontmatter.ts @@ -1,8 +1,9 @@ import matter from 'gray-matter'; -import tomlFormatter from './toml'; -import yamlFormatter from './yaml'; -import jsonFormatter from './json'; +import TomlFormatter from './TomlFormatter'; +import YamlFormatter from './YamlFormatter'; +import JsonFormatter from './JsonFormatter'; +import { FileFormatter } from './FileFormatter'; const Languages = { YAML: 'yaml', @@ -17,10 +18,10 @@ type Format = { language: Language; delimiters: Delimiter }; const parsers = { toml: { - parse: (input: string) => tomlFormatter.fromFile(input), + parse: (input: string) => TomlFormatter.fromFile(input), stringify: (metadata: object, opts?: { sortedKeys?: string[] }) => { const { sortedKeys } = opts || {}; - return tomlFormatter.toFile(metadata, sortedKeys); + return TomlFormatter.toFile(metadata, sortedKeys); }, }, json: { @@ -30,10 +31,10 @@ const parsers = { if (JSONinput.slice(0, 1) !== '{') { JSONinput = '{' + JSONinput + '}'; } - return jsonFormatter.fromFile(JSONinput); + return JsonFormatter.fromFile(JSONinput); }, stringify: (metadata: object) => { - let JSONoutput = jsonFormatter.toFile(metadata).trim(); + let JSONoutput = JsonFormatter.toFile(metadata).trim(); // Trim leading and trailing brackets. if (JSONoutput.slice(0, 1) === '{' && JSONoutput.slice(-1) === '}') { JSONoutput = JSONoutput.slice(1, -1); @@ -42,13 +43,13 @@ const parsers = { }, }, yaml: { - parse: (input: string) => yamlFormatter.fromFile(input), + parse: (input: string) => YamlFormatter.fromFile(input), stringify: ( metadata: object, opts?: { sortedKeys?: string[]; comments?: Record }, ) => { const { sortedKeys, comments } = opts || {}; - return yamlFormatter.toFile(metadata, sortedKeys, comments); + return YamlFormatter.toFile(metadata, sortedKeys, comments); }, }, }; @@ -91,10 +92,11 @@ export function getFormatOpts(format?: Language, customDelimiter?: Delimiter) { }; } -export class FrontmatterFormatter { +export class FrontmatterFormatter extends FileFormatter { format?: Format; constructor(format?: Language, customDelimiter?: Delimiter) { + super(); this.format = getFormatOpts(format, customDelimiter); } diff --git a/core/src/formats/json.ts b/core/src/formats/json.ts deleted file mode 100644 index 518f85d4..00000000 --- a/core/src/formats/json.ts +++ /dev/null @@ -1,9 +0,0 @@ -export default { - fromFile(content: string) { - return JSON.parse(content); - }, - - toFile(data: object) { - return JSON.stringify(data, null, 2); - }, -}; diff --git a/core/src/interface.ts b/core/src/interface.ts index d3462974..176078fd 100644 --- a/core/src/interface.ts +++ b/core/src/interface.ts @@ -75,13 +75,13 @@ export type ValueOrNestedValue = export type EntryData = ObjectValue | undefined | null; -export interface Entry { +export interface Entry { collection: string; slug: string; path: string; partial: boolean; raw: string; - data: EntryData; + data: T | undefined | null; label: string | null; isModification: boolean | null; mediaFiles: MediaFile[]; @@ -277,10 +277,10 @@ export type WidgetPreviewComponent = | React.ReactElement> | ComponentType>; -export interface TemplatePreviewProps { +export interface TemplatePreviewProps { collection: Collection; fields: Field[]; - entry: Entry; + entry: Entry; document: Document | undefined | null; window: Window | undefined | null; getAsset: GetAssetFunction; diff --git a/core/src/reducers/entryDraft.ts b/core/src/reducers/entryDraft.ts index 924eff20..5fe2f74d 100644 --- a/core/src/reducers/entryDraft.ts +++ b/core/src/reducers/entryDraft.ts @@ -49,7 +49,6 @@ function entryDraftReducer( switch (action.type) { case DRAFT_CREATE_FROM_ENTRY: { const newState = { ...state }; - delete newState.localBackup; // Existing Entry return { diff --git a/core/src/widgets/markdown/MarkdownControl.tsx b/core/src/widgets/markdown/MarkdownControl.tsx index febc5544..e0faebd1 100644 --- a/core/src/widgets/markdown/MarkdownControl.tsx +++ b/core/src/widgets/markdown/MarkdownControl.tsx @@ -66,9 +66,11 @@ const MarkdownControl = ({ const handleOnChange = useCallback(() => { const newValue = editorRef.current?.getInstance().getMarkdown() ?? ''; - setInternalValue(newValue); - onChange(newValue); - }, [editorRef, onChange]); + if (newValue !== internalValue) { + setInternalValue(newValue); + onChange(newValue); + } + }, [editorRef, internalValue, onChange]); const handleLabelClick = useCallback(() => { editorRef.current?.getInstance().focus(); @@ -151,7 +153,7 @@ const MarkdownControl = ({ addMedia(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [field, mediaPath]); + }, [mediaPath]); const { initialEditType, height, ...markdownEditorOptions } = useEditorOptions(); diff --git a/core/src/widgets/markdown/MarkdownPreview.tsx b/core/src/widgets/markdown/MarkdownPreview.tsx index faffcea0..e73c9e02 100644 --- a/core/src/widgets/markdown/MarkdownPreview.tsx +++ b/core/src/widgets/markdown/MarkdownPreview.tsx @@ -19,8 +19,11 @@ const MarkdownPreview = ({ const mediaHolder = useMemo(() => new MediaHolder(), []); const media = useMedia({ value, getAsset, field }); + const viewer = useRef(null); + useEffect(() => { mediaHolder.setBulkMedia(media); + viewer.current?.getInstance().setMarkdown(value ?? ''); // eslint-disable-next-line react-hooks/exhaustive-deps }, [media]); @@ -31,12 +34,6 @@ const MarkdownPreview = ({ mode: 'preview', }); - const viewer = useRef(null); - - useEffect(() => { - viewer.current?.getInstance().setMarkdown(value ?? ''); - }, [value, media]); - return useMemo(() => { if (!value) { return null; diff --git a/core/src/widgets/markdown/hooks/useMedia.ts b/core/src/widgets/markdown/hooks/useMedia.ts index 0335b335..370990f6 100644 --- a/core/src/widgets/markdown/hooks/useMedia.ts +++ b/core/src/widgets/markdown/hooks/useMedia.ts @@ -48,7 +48,7 @@ const useMedia = ({ value, getAsset, field }: UseMediaProps) => { } const uniqueMediaToLoad = mediaToLoad.filter( - (value, index, self) => self.indexOf(value) === index, + (value, index, self) => self.indexOf(value) === index && !(value in media), ); for (const url of uniqueMediaToLoad) { diff --git a/core/src/widgets/relation/RelationControl.tsx b/core/src/widgets/relation/RelationControl.tsx index dc78f1bd..d20b23cd 100644 --- a/core/src/widgets/relation/RelationControl.tsx +++ b/core/src/widgets/relation/RelationControl.tsx @@ -113,6 +113,7 @@ const RelationControl = ({ query, locale, label, + hasErrors }: WidgetControlProps) => { const [internalValue, setInternalValue] = useState(value); const [initialOptions, setInitialOptions] = useState([]); @@ -227,6 +228,7 @@ const RelationControl = ({ key="relation-control-input" {...params} label={label} + error={hasErrors} InputProps={{ ...params.InputProps, endAdornment: ( diff --git a/core/yarn.lock b/core/yarn.lock index e714f7c7..e097eb12 100644 --- a/core/yarn.lock +++ b/core/yarn.lock @@ -7207,7 +7207,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -7458,11 +7458,6 @@ react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-lifecycles-compat@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== - react-polyglot@0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/react-polyglot/-/react-polyglot-0.7.2.tgz#b0277688c6d26eaff3312774738859cac53a227c" @@ -7519,22 +7514,6 @@ react-sortable-hoc@2.0.0: invariant "^2.2.4" prop-types "^15.5.7" -react-split-pane@0.1.92: - version "0.1.92" - resolved "https://registry.yarnpkg.com/react-split-pane/-/react-split-pane-0.1.92.tgz#68242f72138aed95dd5910eeb9d99822c4fc3a41" - integrity sha512-GfXP1xSzLMcLJI5BM36Vh7GgZBpy+U/X0no+VM3fxayv+p1Jly5HpMofZJraeaMl73b3hvlr+N9zJKvLB/uz9w== - dependencies: - prop-types "^15.7.2" - react-lifecycles-compat "^3.0.4" - react-style-proptype "^3.2.2" - -react-style-proptype@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/react-style-proptype/-/react-style-proptype-3.2.2.tgz#d8e998e62ce79ec35b087252b90f19f1c33968a0" - integrity sha512-ywYLSjNkxKHiZOqNlso9PZByNEY+FTyh3C+7uuziK0xFXu9xzdyfHwg4S9iyiRRoPCR4k2LqaBBsWVmSBwCWYQ== - dependencies: - prop-types "^15.5.4" - react-svg-core@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/react-svg-core/-/react-svg-core-3.0.3.tgz#5d856efeaa4d089b0afeebe885b20b8c9500d162"