feat: upgrade to Emotion 10 (#2166)

This commit is contained in:
Shawn Erquhart 2019-03-15 10:19:57 -04:00 committed by GitHub
parent 7d6992e464
commit ccef446d72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
109 changed files with 4672 additions and 3875 deletions

View File

@ -17,6 +17,19 @@
}, },
"rules": { "rules": {
"no-console": [0], "no-console": [0],
"react/prop-types": [1] "react/prop-types": [1],
} "no-duplicate-imports": "error",
"emotion/jsx-import": "error",
"emotion/no-vanilla": "error",
"emotion/import-from-emotion": "error",
"emotion/styled-import": "error"
},
"plugins": [
"emotion",
],
"settings": {
"react": {
"version": "detect",
},
},
} }

View File

@ -1,14 +1,23 @@
{ {
"processors": ["stylelint-processor-styled-components"], "processors": [
["stylelint-processor-styled-components", {
"parserPlugins": [
"jsx",
"objectRestSpread",
"exportDefaultFrom",
"classProperties",
],
}],
],
"extends": [ "extends": [
"stylelint-config-recommended", "stylelint-config-recommended",
"stylelint-config-styled-components" "stylelint-config-styled-components",
], ],
"rules": { "rules": {
"block-no-empty": null, "block-no-empty": null,
"no-duplicate-selectors": null, "no-duplicate-selectors": null,
"selector-type-no-unknown": [true, { "selector-type-no-unknown": [true, {
"ignoreTypes": ["$dummyValue"] "ignoreTypes": ["$dummyValue"],
}] }],
} },
} }

View File

@ -1,5 +1,5 @@
module.exports = { module.exports = {
setupTestFrameworkScriptFile: '<rootDir>/setupTestFramework.js', setupFilesAfterEnv: ['<rootDir>/setupTestFramework.js'],
transform: { transform: {
'\\.js$': '<rootDir>/custom-preprocessor.js', '\\.js$': '<rootDir>/custom-preprocessor.js',
}, },

View File

@ -62,50 +62,54 @@
"last 2 ChromeAndroid versions" "last 2 ChromeAndroid versions"
], ],
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.1.2", "@babel/cli": "^7.2.3",
"@babel/core": "^7.1.2", "@babel/core": "^7.3.4",
"@babel/plugin-proposal-class-properties": "^7.1.0", "@babel/plugin-proposal-class-properties": "^7.3.4",
"@babel/plugin-proposal-export-default-from": "^7.0.0", "@babel/plugin-proposal-export-default-from": "^7.2.0",
"@babel/plugin-proposal-object-rest-spread": "^7.0.0", "@babel/plugin-proposal-object-rest-spread": "^7.3.4",
"@babel/preset-env": "^7.1.0", "@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"all-contributors-cli": "^4.4.0", "all-contributors-cli": "^4.4.0",
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.1",
"babel-jest": "^23.6.0", "babel-jest": "^24.5.0",
"babel-loader": "^8.0.4", "babel-loader": "^8.0.5",
"babel-plugin-emotion": "^9.2.11", "babel-plugin-emotion": "^10.0.9",
"babel-plugin-inline-svg": "^1.0.0", "babel-plugin-inline-svg": "^1.0.0",
"babel-plugin-lodash": "^3.3.4", "babel-plugin-lodash": "^3.3.4",
"babel-plugin-module-resolver": "^3.1.1", "babel-plugin-module-resolver": "^3.2.0",
"babel-plugin-transform-builtin-extend": "^1.1.2", "babel-plugin-transform-builtin-extend": "^1.1.2",
"babel-plugin-transform-export-extensions": "^6.22.0", "babel-plugin-transform-export-extensions": "^6.22.0",
"babel-plugin-transform-inline-environment-variables": "^0.4.3", "babel-plugin-transform-inline-environment-variables": "^0.4.3",
"cache-me-outside": "^0.0.4", "cache-me-outside": "^0.0.4",
"cross-env": "^5.1.4", "cross-env": "^5.1.4",
"cypress": "^3.0.3", "cypress": "^3.1.5",
"dom-testing-library": "^3.13.0", "dom-testing-library": "^3.17.1",
"eslint": "^5.3.0", "eslint": "^5.15.1",
"eslint-plugin-react": "^7.10.0", "eslint-plugin-emotion": "^10.0.7",
"eslint-plugin-react": "^7.12.4",
"friendly-errors-webpack-plugin": "^1.7.0", "friendly-errors-webpack-plugin": "^1.7.0",
"http-server": "^0.11.1", "http-server": "^0.11.1",
"jest": "^23.4.0", "jest": "^24.5.0",
"jest-cli": "^23.4.0", "jest-cli": "^24.5.0",
"jest-emotion": "^9.2.7", "jest-emotion": "^10.0.9",
"lerna": "^3.4.0", "lerna": "^3.13.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "1.14.0", "prettier": "1.16.4",
"react-test-renderer": "^16.0.0", "react-test-renderer": "^16.8.4",
"rimraf": "^2.6.2", "rimraf": "^2.6.3",
"start-server-and-test": "^1.7.0", "start-server-and-test": "^1.7.11",
"stylelint": "^9.4.0", "stylelint": "^9.10.1",
"stylelint-config-recommended": "^2.1.0", "stylelint-config-recommended": "^2.1.0",
"stylelint-config-styled-components": "^0.1.1", "stylelint-config-styled-components": "^0.1.1",
"stylelint-processor-styled-components": "^1.3.1", "stylelint-processor-styled-components": "^1.5.2",
"svg-inline-loader": "^0.8.0" "svg-inline-loader": "^0.8.0"
}, },
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
], ],
"private": true "private": true,
"dependencies": {
"emotion": "^10.0.9"
}
} }

View File

@ -19,23 +19,23 @@
"build": "cross-env NODE_ENV=production webpack" "build": "cross-env NODE_ENV=production webpack"
}, },
"dependencies": { "dependencies": {
"js-base64": "^2.4.8", "js-base64": "^2.5.1",
"semaphore": "^1.1.0" "semaphore": "^1.1.0"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.9",
"immutable": "^3.7.6", "immutable": "^3.7.6",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"netlify-cms-lib-auth": "^2.0.4", "netlify-cms-lib-auth": "^2.0.4",
"netlify-cms-lib-util": "^2.1.0", "netlify-cms-lib-util": "^2.1.0",
"netlify-cms-ui-default": "^2.0.6", "netlify-cms-ui-default": "^2.0.6",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.4.1", "react": "^16.4.1"
"react-emotion": "^9.2.6"
} }
} }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { NetlifyAuthenticator } from 'netlify-cms-lib-auth'; import { NetlifyAuthenticator } from 'netlify-cms-lib-auth';
import { AuthenticationPage, Icon } from 'netlify-cms-ui-default'; import { AuthenticationPage, Icon } from 'netlify-cms-ui-default';

View File

@ -20,18 +20,19 @@
"build": "cross-env NODE_ENV=production webpack" "build": "cross-env NODE_ENV=production webpack"
}, },
"dependencies": { "dependencies": {
"gotrue-js": "^0.9.22", "gotrue-js": "^0.9.24",
"ini": "^1.3.5", "ini": "^1.3.5",
"jwt-decode": "^2.2.0", "jwt-decode": "^2.2.0",
"minimatch": "^3.0.4" "minimatch": "^3.0.4"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.9",
"immutable": "^3.7.6", "immutable": "^3.7.6",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"netlify-cms-backend-bitbucket": "^2.0.0", "netlify-cms-backend-bitbucket": "^2.0.0",
@ -41,7 +42,6 @@
"netlify-cms-lib-util": "^2.0.0", "netlify-cms-lib-util": "^2.0.0",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.4.1", "react": "^16.4.1"
"react-emotion": "^9.2.6"
} }
} }

View File

@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import React from 'react'; import React from 'react';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { partial } from 'lodash'; import { partial } from 'lodash';
import { import {
AuthenticationPage, AuthenticationPage,

View File

@ -20,22 +20,22 @@
}, },
"dependencies": { "dependencies": {
"common-tags": "^1.8.0", "common-tags": "^1.8.0",
"js-base64": "^2.4.8", "js-base64": "^2.5.1",
"semaphore": "^1.1.0" "semaphore": "^1.1.0"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.9",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"netlify-cms-lib-auth": "^2.0.0", "netlify-cms-lib-auth": "^2.0.0",
"netlify-cms-lib-util": "^2.0.0", "netlify-cms-lib-util": "^2.0.0",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.4.1", "react": "^16.4.1"
"react-emotion": "^9.2.6"
} }
} }

View File

@ -1,8 +1,12 @@
import { localForage } from 'netlify-cms-lib-util';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
import { uniq, initial, last, get, find, hasIn, partial, result } from 'lodash'; import { uniq, initial, last, get, find, hasIn, partial, result } from 'lodash';
import { filterPromises, resolvePromiseProperties } from 'netlify-cms-lib-util'; import {
import { APIError, EditorialWorkflowError } from 'netlify-cms-lib-util'; localForage,
filterPromises,
resolvePromiseProperties,
APIError,
EditorialWorkflowError,
} from 'netlify-cms-lib-util';
const CMS_BRANCH_PREFIX = 'cms/'; const CMS_BRANCH_PREFIX = 'cms/';
@ -222,8 +226,8 @@ export default class API {
} }
readUnpublishedBranchFile(contentKey) { readUnpublishedBranchFile(contentKey) {
const metaDataPromise = this.retrieveMetadata(contentKey).then( const metaDataPromise = this.retrieveMetadata(contentKey).then(data =>
data => (data.objects.entry.path ? data : Promise.reject(null)), data.objects.entry.path ? data : Promise.reject(null),
); );
return resolvePromiseProperties({ return resolvePromiseProperties({
metaData: metaDataPromise, metaData: metaDataPromise,

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { NetlifyAuthenticator } from 'netlify-cms-lib-auth'; import { NetlifyAuthenticator } from 'netlify-cms-lib-auth';
import { AuthenticationPage, Icon } from 'netlify-cms-ui-default'; import { AuthenticationPage, Icon } from 'netlify-cms-ui-default';

View File

@ -19,23 +19,23 @@
"build": "cross-env NODE_ENV=production webpack" "build": "cross-env NODE_ENV=production webpack"
}, },
"dependencies": { "dependencies": {
"js-base64": "^2.4.8", "js-base64": "^2.5.1",
"semaphore": "^1.1.0" "semaphore": "^1.1.0"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.9",
"immutable": "^3.7.6", "immutable": "^3.7.6",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"netlify-cms-lib-auth": "^2.0.0", "netlify-cms-lib-auth": "^2.0.0",
"netlify-cms-lib-util": "^2.0.0", "netlify-cms-lib-util": "^2.0.0",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.4.1", "react": "^16.4.1"
"react-emotion": "^9.2.6"
} }
} }

View File

@ -129,14 +129,13 @@ export default class API {
.update(list => Map(list)); .update(list => Map(list));
const actions = links const actions = links
.keySeq() .keySeq()
.flatMap( .flatMap(key =>
key => (key === 'prev' && index > 0) ||
(key === 'prev' && index > 0) || (key === 'next' && index < pageCount) ||
(key === 'next' && index < pageCount) || (key === 'first' && index > 0) ||
(key === 'first' && index > 0) || (key === 'last' && index < pageCount)
(key === 'last' && index < pageCount) ? [key]
? [key] : [],
: [],
); );
return Cursor.create({ return Cursor.create({
actions, actions,

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { NetlifyAuthenticator, ImplicitAuthenticator } from 'netlify-cms-lib-auth'; import { NetlifyAuthenticator, ImplicitAuthenticator } from 'netlify-cms-lib-auth';
import { AuthenticationPage, Icon } from 'netlify-cms-ui-default'; import { AuthenticationPage, Icon } from 'netlify-cms-ui-default';

View File

@ -22,18 +22,18 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.9",
"immutable": "^3.8.2", "immutable": "^3.8.2",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"netlify-cms-lib-util": "^2.0.0", "netlify-cms-lib-util": "^2.0.0",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.4.1", "react": "^16.4.1",
"react-emotion": "^9.2.6",
"react-immutable-proptypes": "^2.1.0" "react-immutable-proptypes": "^2.1.0"
} }
} }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { Icon, buttons, shadows } from 'netlify-cms-ui-default'; import { Icon, buttons, shadows } from 'netlify-cms-ui-default';
const StyledAuthenticationPage = styled.section` const StyledAuthenticationPage = styled.section`

View File

@ -1,7 +1,6 @@
import { attempt, isError, take } from 'lodash'; import { attempt, isError, take } from 'lodash';
import uuid from 'uuid/v4'; import uuid from 'uuid/v4';
import { EditorialWorkflowError } from 'netlify-cms-lib-util'; import { EditorialWorkflowError, Cursor, CURSOR_COMPATIBILITY_SYMBOL } from 'netlify-cms-lib-util';
import { Cursor, CURSOR_COMPATIBILITY_SYMBOL } from 'netlify-cms-lib-util';
import AuthenticationPage from './AuthenticationPage'; import AuthenticationPage from './AuthenticationPage';
window.repoFiles = window.repoFiles || {}; window.repoFiles = window.repoFiles || {};

View File

@ -19,44 +19,44 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ajv": "^6.4.0", "@emotion/core": "^10.0.9",
"ajv-errors": "^1.0.0", "@emotion/styled": "^10.0.9",
"copy-text-to-clipboard": "^1.0.4", "ajv": "^6.10.0",
"ajv-errors": "^1.0.1",
"copy-text-to-clipboard": "^2.0.0",
"diacritics": "^1.3.0", "diacritics": "^1.3.0",
"emotion": "^9.2.6",
"fuzzy": "^0.1.1", "fuzzy": "^0.1.1",
"gotrue-js": "^0.9.15", "gotrue-js": "^0.9.24",
"gray-matter": "^4.0.1", "gray-matter": "^4.0.2",
"history": "^4.7.2", "history": "^4.7.2",
"immutable": "^3.7.6", "immutable": "^3.7.6",
"js-base64": "^2.1.9", "js-base64": "^2.5.1",
"js-yaml": "^3.10.0", "js-yaml": "^3.12.2",
"jwt-decode": "^2.1.0", "jwt-decode": "^2.1.0",
"lodash": "^4.17.10", "lodash": "^4.17.11",
"moment": "^2.11.2", "moment": "^2.24.0",
"netlify-cms-editor-component-image": "^2.2.0", "netlify-cms-editor-component-image": "^2.2.0",
"netlify-cms-lib-auth": "^2.0.5", "netlify-cms-lib-auth": "^2.0.5",
"netlify-cms-lib-util": "^2.1.2", "netlify-cms-lib-util": "^2.1.2",
"netlify-cms-ui-default": "^2.4.1-alpha.0", "netlify-cms-ui-default": "^2.4.1-alpha.0",
"node-polyglot": "^2.3.0", "node-polyglot": "^2.3.0",
"prop-types": "^15.5.10", "prop-types": "^15.7.2",
"react": "^16.8.1", "react": "^16.8.4",
"react-dnd": "^7.0.0", "react-dnd": "^7.3.2",
"react-dnd-html5-backend": "^7.0.0", "react-dnd-html5-backend": "^7.2.0",
"react-dom": "^16.8.1", "react-dom": "^16.8.4",
"react-emotion": "^9.2.5", "react-frame-component": "^4.1.0",
"react-frame-component": "^4.0.2", "react-hot-loader": "^4.8.0",
"react-hot-loader": "^4.0.0",
"react-immutable-proptypes": "^2.1.0", "react-immutable-proptypes": "^2.1.0",
"react-is": "16.3.1", "react-is": "16.8.4",
"react-modal": "^3.1.5", "react-modal": "^3.8.1",
"react-polyglot": "^0.2.6", "react-polyglot": "^0.2.6",
"react-redux": "^5.1.1", "react-redux": "^5.1.1",
"react-router-dom": "^4.2.2", "react-router-dom": "^4.2.2",
"react-router-redux": "^5.0.0-alpha.8", "react-router-redux": "^5.0.0-alpha.8",
"react-scroll-sync": "^0.6.0", "react-scroll-sync": "^0.6.0",
"react-sortable-hoc": "^0.6.8", "react-sortable-hoc": "^0.6.8",
"react-split-pane": "^0.1.82", "react-split-pane": "^0.1.85",
"react-topbar-progress-indicator": "^2.0.0", "react-topbar-progress-indicator": "^2.0.0",
"react-waypoint": "^8.1.0", "react-waypoint": "^8.1.0",
"redux": "^4.0.1", "redux": "^4.0.1",
@ -68,13 +68,13 @@
"toml-j0.4": "^1.1.1", "toml-j0.4": "^1.1.1",
"tomlify-j0.4": "^3.0.0-alpha.0", "tomlify-j0.4": "^3.0.0-alpha.0",
"url": "^0.11.0", "url": "^0.11.0",
"what-input": "^5.0.3" "what-input": "^5.1.4"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"css-loader": "^1.0.0", "css-loader": "^2.1.1",
"to-string-loader": "^1.1.5", "to-string-loader": "^1.1.5",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
} }
} }

View File

@ -3,7 +3,6 @@ import { Map } from 'immutable';
import { stripIndent } from 'common-tags'; import { stripIndent } from 'common-tags';
import moment from 'moment'; import moment from 'moment';
import fuzzy from 'fuzzy'; import fuzzy from 'fuzzy';
import { localForage } from 'netlify-cms-lib-util';
import { resolveFormat } from 'Formats/formats'; import { resolveFormat } from 'Formats/formats';
import { selectIntegration } from 'Reducers/integrations'; import { selectIntegration } from 'Reducers/integrations';
import { import {
@ -19,7 +18,7 @@ import {
import { createEntry } from 'ValueObjects/Entry'; import { createEntry } from 'ValueObjects/Entry';
import { sanitizeSlug } from 'Lib/urlHelper'; import { sanitizeSlug } from 'Lib/urlHelper';
import { getBackend } from 'Lib/registry'; import { getBackend } from 'Lib/registry';
import { Cursor, CURSOR_COMPATIBILITY_SYMBOL } from 'netlify-cms-lib-util'; import { localForage, Cursor, CURSOR_COMPATIBILITY_SYMBOL } from 'netlify-cms-lib-util';
import { EDITORIAL_WORKFLOW, status } from 'Constants/publishModes'; import { EDITORIAL_WORKFLOW, status } from 'Constants/publishModes';
class LocalStorageAuthStore { class LocalStorageAuthStore {

View File

@ -8,6 +8,7 @@ import store from 'Redux';
import { mergeConfig } from 'Actions/config'; import { mergeConfig } from 'Actions/config';
import { getPhrases } from 'Constants/defaultPhrases'; import { getPhrases } from 'Constants/defaultPhrases';
import { I18n } from 'react-polyglot'; import { I18n } from 'react-polyglot';
import { GlobalStyles } from 'netlify-cms-ui-default';
import { ErrorBoundary } from 'UI'; import { ErrorBoundary } from 'UI';
import App from 'App/App'; import App from 'App/App';
import 'EditorWidgets'; import 'EditorWidgets';
@ -62,15 +63,18 @@ function bootstrap(opts = {}) {
* Create connected root component. * Create connected root component.
*/ */
const Root = () => ( const Root = () => (
<I18n locale={'en'} messages={getPhrases()}> <>
<ErrorBoundary showBackup> <GlobalStyles />
<Provider store={store}> <I18n locale={'en'} messages={getPhrases()}>
<ConnectedRouter history={history}> <ErrorBoundary showBackup>
<Route component={App} /> <Provider store={store}>
</ConnectedRouter> <ConnectedRouter history={history}>
</Provider> <Route component={App} />
</ErrorBoundary> </ConnectedRouter>
</I18n> </Provider>
</ErrorBoundary>
</I18n>
</>
); );
/** /**

View File

@ -3,7 +3,7 @@ import React from 'react';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Route, Switch, Redirect } from 'react-router-dom'; import { Route, Switch, Redirect } from 'react-router-dom';
import { Notifs } from 'redux-notifications'; import { Notifs } from 'redux-notifications';

View File

@ -1,7 +1,9 @@
/** @jsx jsx */
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled, { css } from 'react-emotion'; import styled from '@emotion/styled';
import { jsx, css } from '@emotion/core';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { import {
@ -22,15 +24,20 @@ const styles = {
`, `,
}; };
const AppHeader = styled.header` const AppHeader = props => (
${shadows.dropMain}; <header
position: sticky; css={css`
width: 100%; ${shadows.dropMain};
top: 0; position: sticky;
background-color: ${colors.foreground}; width: 100%;
z-index: 300; top: 0;
height: ${lengths.topBarHeight}; background-color: ${colors.foreground};
`; z-index: 300;
height: ${lengths.topBarHeight};
`}
{...props}
/>
);
const AppHeaderContent = styled.div` const AppHeaderContent = styled.div`
display: flex; display: flex;

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import { lengths } from 'netlify-cms-ui-default'; import { lengths } from 'netlify-cms-ui-default';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { lengths } from 'netlify-cms-ui-default'; import { lengths } from 'netlify-cms-ui-default';
import { getNewEntryUrl } from 'Lib/urlHelper'; import { getNewEntryUrl } from 'Lib/urlHelper';

View File

@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Icon, components, buttons, shadows, colors } from 'netlify-cms-ui-default'; import { Icon, components, buttons, shadows, colors } from 'netlify-cms-ui-default';

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { resolvePath } from 'netlify-cms-lib-util'; import { resolvePath } from 'netlify-cms-lib-util';
import { colors, colorsRaw, components, lengths } from 'netlify-cms-ui-default'; import { colors, colorsRaw, components, lengths } from 'netlify-cms-ui-default';

View File

@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import Waypoint from 'react-waypoint'; import Waypoint from 'react-waypoint';
import { Map } from 'immutable'; import { Map } from 'immutable';
import { Cursor } from 'netlify-cms-lib-util'; import { Cursor } from 'netlify-cms-lib-util';

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled, { css } from 'react-emotion'; import styled from '@emotion/styled';
import { css } from '@emotion/core';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { Icon, components, colors, colorsRaw, lengths } from 'netlify-cms-ui-default'; import { Icon, components, colors, colorsRaw, lengths } from 'netlify-cms-ui-default';

View File

@ -31,8 +31,7 @@ import { loadDeployPreview } from 'Actions/deploys';
import { deserializeValues } from 'Lib/serializeEntryValues'; import { deserializeValues } from 'Lib/serializeEntryValues';
import { selectEntry, selectUnpublishedEntry, selectDeployPreview, getAsset } from 'Reducers'; import { selectEntry, selectUnpublishedEntry, selectDeployPreview, getAsset } from 'Reducers';
import { selectFields } from 'Reducers/collections'; import { selectFields } from 'Reducers/collections';
import { status } from 'Constants/publishModes'; import { status, EDITORIAL_WORKFLOW } from 'Constants/publishModes';
import { EDITORIAL_WORKFLOW } from 'Constants/publishModes';
import EditorInterface from './EditorInterface'; import EditorInterface from './EditorInterface';
import withWorkflow from './withWorkflow'; import withWorkflow from './withWorkflow';

View File

@ -1,16 +1,17 @@
/** @jsx jsx */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import styled, { css, cx } from 'react-emotion'; import { jsx, ClassNames, Global, css as coreCss } from '@emotion/core';
import styled from '@emotion/styled';
import { partial, uniqueId } from 'lodash'; import { partial, uniqueId } from 'lodash';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { colors, colorsRaw, transitions, lengths, borders } from 'netlify-cms-ui-default'; import { colors, colorsRaw, transitions, lengths, borders } from 'netlify-cms-ui-default';
import { resolveWidget, getEditorComponents } from 'Lib/registry'; import { resolveWidget, getEditorComponents } from 'Lib/registry';
import { clearFieldErrors } from 'Actions/entries'; import { clearFieldErrors, loadEntry } from 'Actions/entries';
import { addAsset } from 'Actions/media'; import { addAsset } from 'Actions/media';
import { query, clearSearch } from 'Actions/search'; import { query, clearSearch } from 'Actions/search';
import { loadEntry } from 'Actions/entries';
import { import {
openMediaLibrary, openMediaLibrary,
removeInsertedMedia, removeInsertedMedia,
@ -20,8 +21,13 @@ import {
import { getAsset } from 'Reducers'; import { getAsset } from 'Reducers';
import Widget from './Widget'; import Widget from './Widget';
const styles = { /**
label: css` * This is a necessary bridge as we are still passing classnames to widgets
* for styling. Once that changes we can stop storing raw style strings like
* this.
*/
const styleStrings = {
label: `
color: ${colors.controlLabel}; color: ${colors.controlLabel};
background-color: ${colors.textFieldBorder}; background-color: ${colors.textFieldBorder};
display: inline-block; display: inline-block;
@ -55,15 +61,15 @@ const styles = {
background-color: #fff; background-color: #fff;
} }
`, `,
labelActive: css` labelActive: `
background-color: ${colors.active}; background-color: ${colors.active};
color: ${colors.textLight}; color: ${colors.textLight};
`, `,
labelError: css` labelError: `
background-color: ${colors.errorText}; background-color: ${colors.errorText};
color: ${colorsRaw.white}; color: ${colorsRaw.white};
`, `,
widget: css` widget: `
display: block; display: block;
width: 100%; width: 100%;
padding: ${lengths.inputPadding}; padding: ${lengths.inputPadding};
@ -85,10 +91,10 @@ const styles = {
height: 58px; height: 58px;
} }
`, `,
widgetActive: css` widgetActive: `
border-color: ${colors.active}; border-color: ${colors.active};
`, `,
widgetError: css` widgetError: `
border-color: ${colors.errorText}; border-color: ${colors.errorText};
`, `,
}; };
@ -96,7 +102,7 @@ const styles = {
const ControlContainer = styled.div` const ControlContainer = styled.div`
margin-top: 16px; margin-top: 16px;
&:first-child { &:first-of-type {
margin-top: 36px; margin-top: 36px;
} }
`; `;
@ -117,8 +123,8 @@ export const ControlHint = styled.p`
margin-bottom: 0; margin-bottom: 0;
padding: 3px 0; padding: 3px 0;
font-size: 12px; font-size: 12px;
color: ${({ active, error }) => color: ${props =>
error ? colors.errorText : active ? colors.active : colors.controlLabel}; props.error ? colors.errorText : props.active ? colors.active : colors.controlLabel};
transition: color ${transitions.main}; transition: color ${transitions.main};
`; `;
@ -191,75 +197,108 @@ class EditorControl extends React.Component {
const metadata = fieldsMetaData && fieldsMetaData.get(fieldName); const metadata = fieldsMetaData && fieldsMetaData.get(fieldName);
const errors = fieldsErrors && fieldsErrors.get(this.uniqueFieldId); const errors = fieldsErrors && fieldsErrors.get(this.uniqueFieldId);
return ( return (
<ControlContainer> <ClassNames>
<ControlErrorsList> {({ css, cx }) => (
{errors && <ControlContainer>
errors.map( {widget.globalStyles && <Global styles={coreCss`${widget.globalStyles}`} />}
error => <ControlErrorsList>
error.message && {errors &&
typeof error.message === 'string' && ( errors.map(
<li key={error.message.trim().replace(/[^a-z0-9]+/gi, '-')}>{error.message}</li> error =>
), error.message &&
typeof error.message === 'string' && (
<li key={error.message.trim().replace(/[^a-z0-9]+/gi, '-')}>
{error.message}
</li>
),
)}
</ControlErrorsList>
<label
className={cx(
css`
${styleStrings.label};
`,
this.state.styleActive &&
css`
${styleStrings.labelActive};
`,
!!errors &&
css`
${styleStrings.labelError};
`,
)}
htmlFor={this.uniqueFieldId}
>
{`${field.get('label', field.get('name'))}${isFieldOptional ? ' (optional)' : ''}`}
</label>
<Widget
classNameWrapper={cx(
css`
${styleStrings.widget};
`,
{
[css`
${styleStrings.widgetActive};
`]: this.state.styleActive,
},
{
[css`
${styleStrings.widgetError};
`]: !!errors,
},
)}
classNameWidget={css`
${styleStrings.widget};
`}
classNameWidgetActive={css`
${styleStrings.widgetActive};
`}
classNameLabel={css`
${styleStrings.label};
`}
classNameLabelActive={css`
${styleStrings.labelActive};
`}
controlComponent={widget.control}
field={field}
uniqueFieldId={this.uniqueFieldId}
value={value}
mediaPaths={mediaPaths}
metadata={metadata}
onChange={(newValue, newMetadata) => onChange(fieldName, newValue, newMetadata)}
onValidate={onValidate && partial(onValidate, this.uniqueFieldId)}
onOpenMediaLibrary={openMediaLibrary}
onClearMediaControl={clearMediaControl}
onRemoveMediaControl={removeMediaControl}
onRemoveInsertedMedia={removeInsertedMedia}
onAddAsset={addAsset}
getAsset={boundGetAsset}
hasActiveStyle={this.state.styleActive}
setActiveStyle={() => this.setState({ styleActive: true })}
setInactiveStyle={() => this.setState({ styleActive: false })}
resolveWidget={resolveWidget}
getEditorComponents={getEditorComponents}
ref={processControlRef && partial(processControlRef, field)}
controlRef={controlRef}
editorControl={ConnectedEditorControl}
query={query}
loadEntry={loadEntry}
queryHits={queryHits}
clearSearch={clearSearch}
clearFieldErrors={clearFieldErrors}
isFetching={isFetching}
fieldsErrors={fieldsErrors}
onValidateObject={onValidateObject}
t={t}
/>
{fieldHint && (
<ControlHint active={this.state.styleActive} error={!!errors}>
{fieldHint}
</ControlHint>
)} )}
</ControlErrorsList> </ControlContainer>
<label
className={cx(
styles.label,
{ [styles.labelActive]: this.state.styleActive },
{ [styles.labelError]: !!errors },
)}
htmlFor={this.uniqueFieldId}
>
{`${field.get('label', field.get('name'))}${isFieldOptional ? ' (optional)' : ''}`}
</label>
<Widget
classNameWrapper={cx(
styles.widget,
{ [styles.widgetActive]: this.state.styleActive },
{ [styles.widgetError]: !!errors },
)}
classNameWidget={styles.widget}
classNameWidgetActive={styles.widgetActive}
classNameLabel={styles.label}
classNameLabelActive={styles.labelActive}
controlComponent={widget.control}
field={field}
uniqueFieldId={this.uniqueFieldId}
value={value}
mediaPaths={mediaPaths}
metadata={metadata}
onChange={(newValue, newMetadata) => onChange(fieldName, newValue, newMetadata)}
onValidate={onValidate && partial(onValidate, this.uniqueFieldId)}
onOpenMediaLibrary={openMediaLibrary}
onClearMediaControl={clearMediaControl}
onRemoveMediaControl={removeMediaControl}
onRemoveInsertedMedia={removeInsertedMedia}
onAddAsset={addAsset}
getAsset={boundGetAsset}
hasActiveStyle={this.state.styleActive}
setActiveStyle={() => this.setState({ styleActive: true })}
setInactiveStyle={() => this.setState({ styleActive: false })}
resolveWidget={resolveWidget}
getEditorComponents={getEditorComponents}
ref={processControlRef && partial(processControlRef, field)}
controlRef={controlRef}
editorControl={ConnectedEditorControl}
query={query}
loadEntry={loadEntry}
queryHits={queryHits}
clearSearch={clearSearch}
clearFieldErrors={clearFieldErrors}
isFetching={isFetching}
fieldsErrors={fieldsErrors}
onValidateObject={onValidateObject}
t={t}
/>
{fieldHint && (
<ControlHint active={this.state.styleActive} error={!!errors}>
{fieldHint}
</ControlHint>
)} )}
</ControlContainer> </ClassNames>
); );
} }
} }

View File

@ -1,17 +1,14 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import EditorControl, { ControlHint } from './EditorControl'; import EditorControl from './EditorControl';
const ControlPaneContainer = styled.div` const ControlPaneContainer = styled.div`
max-width: 800px; max-width: 800px;
margin: 0 auto; margin: 0 auto;
padding-bottom: 16px; padding-bottom: 16px;
font-size: 16px;
p:not(${ControlHint}) {
font-size: 16px;
}
`; `;
export default class ControlPane extends React.Component { export default class ControlPane extends React.Component {
@ -56,21 +53,20 @@ export default class ControlPane extends React.Component {
return ( return (
<ControlPaneContainer> <ControlPaneContainer>
{fields.map( {fields.map((field, i) =>
(field, i) => field.get('widget') === 'hidden' ? null : (
field.get('widget') === 'hidden' ? null : ( <EditorControl
<EditorControl key={i}
key={i} field={field}
field={field} value={entry.getIn(['data', field.get('name')])}
value={entry.getIn(['data', field.get('name')])} fieldsMetaData={fieldsMetaData}
fieldsMetaData={fieldsMetaData} fieldsErrors={fieldsErrors}
fieldsErrors={fieldsErrors} onChange={onChange}
onChange={onChange} onValidate={onValidate}
onValidate={onValidate} processControlRef={this.controlRef.bind(this)}
processControlRef={this.controlRef.bind(this)} controlRef={this.controlRef}
controlRef={this.controlRef} />
/> ),
),
)} )}
</ControlPaneContainer> </ControlPaneContainer>
); );

View File

@ -1,7 +1,8 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled, { css, injectGlobal } from 'react-emotion'; import { css, Global } from '@emotion/core';
import styled from '@emotion/styled';
import SplitPane from 'react-split-pane'; import SplitPane from 'react-split-pane';
import { colors, colorsRaw, components, transitions } from 'netlify-cms-ui-default'; import { colors, colorsRaw, components, transitions } from 'netlify-cms-ui-default';
import { ScrollSync, ScrollSyncPane } from 'react-scroll-sync'; import { ScrollSync, ScrollSyncPane } from 'react-scroll-sync';
@ -25,33 +26,33 @@ const styles = {
`, `,
}; };
injectGlobal` const ReactSplitPaneGlobalStyles = () => (
/** <Global
* React Split Pane styles={css`
*/ .Resizer.vertical {
.Resizer.vertical { width: 21px;
width: 21px; cursor: col-resize;
cursor: col-resize; position: relative;
position: relative; transition: background-color ${transitions.main};
transition: background-color ${transitions.main};
&:before { &:before {
content: ''; content: '';
width: 1px; width: 1px;
height: 100%; height: 100%;
position: relative; position: relative;
left: 10px; left: 10px;
background-color: ${colors.textFieldBorder}; background-color: ${colors.textFieldBorder};
display: block; display: block;
} }
&:hover, &:hover,
&:active { &:active {
background-color: ${colorsRaw.GrayLight}; background-color: ${colorsRaw.GrayLight};
} }
} }
`}
`; />
);
const StyledSplitPane = styled(SplitPane)` const StyledSplitPane = styled(SplitPane)`
${styles.splitPane}; ${styles.splitPane};
@ -195,6 +196,7 @@ class EditorInterface extends Component {
const editorWithPreview = ( const editorWithPreview = (
<ScrollSync enabled={this.state.scrollSyncEnabled}> <ScrollSync enabled={this.state.scrollSyncEnabled}>
<div> <div>
<ReactSplitPaneGlobalStyles />
<StyledSplitPane <StyledSplitPane
maxSize={-100} maxSize={-100}
defaultSize="50%" defaultSize="50%"

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from 'react-emotion'; import styled from '@emotion/styled';
function isVisible(field) { function isVisible(field) {
return field.get('widget') !== 'hidden'; return field.get('widget') !== 'hidden';

View File

@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { List, Map } from 'immutable'; import { List, Map } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import Frame from 'react-frame-component'; import Frame from 'react-frame-component';

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { Icon, colors, colorsRaw, shadows, buttons } from 'netlify-cms-ui-default'; import { Icon, colors, colorsRaw, shadows, buttons } from 'netlify-cms-ui-default';
const EditorToggleButton = styled.button` const EditorToggleButton = styled.button`

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled, { css } from 'react-emotion'; import { css } from '@emotion/core';
import styled from '@emotion/styled';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import { Map } from 'immutable'; import { Map } from 'immutable';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { colors } from 'netlify-cms-ui-default'; import { colors } from 'netlify-cms-ui-default';
const EmptyMessageContainer = styled.div` const EmptyMessageContainer = styled.div`

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled, { css } from 'react-emotion'; import { css } from '@emotion/core';
import styled from '@emotion/styled';
import { FileUploadButton } from 'UI'; import { FileUploadButton } from 'UI';
import { buttons, shadows } from 'netlify-cms-ui-default'; import { buttons, shadows } from 'netlify-cms-ui-default';

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { colors, borders, lengths, shadows, effects } from 'netlify-cms-ui-default'; import { colors, borders, lengths, shadows, effects } from 'netlify-cms-ui-default';
const IMAGE_HEIGHT = 160; const IMAGE_HEIGHT = 160;

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import Waypoint from 'react-waypoint'; import Waypoint from 'react-waypoint';
import MediaLibraryCard from './MediaLibraryCard'; import MediaLibraryCard from './MediaLibraryCard';
import { Map } from 'immutable'; import { Map } from 'immutable';

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { Icon, shadows, colors, buttons } from 'netlify-cms-ui-default'; import { Icon, shadows, colors, buttons } from 'netlify-cms-ui-default';
const CloseButton = styled.button` const CloseButton = styled.button`

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import { Modal } from 'UI'; import { Modal } from 'UI';

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { Icon, lengths, colors } from 'netlify-cms-ui-default'; import { Icon, lengths, colors } from 'netlify-cms-ui-default';
const SearchContainer = styled.div` const SearchContainer = styled.div`

View File

@ -1,42 +1,42 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import styled, { css } from 'react-emotion'; import styled from '@emotion/styled';
import copyToClipboard from 'copy-text-to-clipboard'; import copyToClipboard from 'copy-text-to-clipboard';
import { localForage } from 'netlify-cms-lib-util'; import { localForage } from 'netlify-cms-lib-util';
import { buttons, colors } from 'netlify-cms-ui-default'; import { buttons, colors } from 'netlify-cms-ui-default';
const ISSUE_URL = 'https://github.com/netlify/netlify-cms/issues/new?template=bug_report.md'; const ISSUE_URL = 'https://github.com/netlify/netlify-cms/issues/new?template=bug_report.md';
const styles = { const ErrorBoundaryContainer = styled.div`
errorBoundary: css` padding: 40px;
padding: 40px;
h1 { h1 {
font-size: 28px; font-size: 28px;
} color: ${colors.text};
}
h2 { h2 {
font-size: 20px; font-size: 20px;
} }
strong { strong {
color: ${colors.textLead}; color: ${colors.textLead};
font-weight: 500; font-weight: 500;
} }
hr { hr {
width: 200px; width: 200px;
margin: 30px 0; margin: 30px 0;
border: 0; border: 0;
height: 1px; height: 1px;
background-color: ${colors.text}; background-color: ${colors.text};
} }
`,
errorText: css` a {
color: ${colors.errorText}; color: ${colors.text};
`, }
}; `;
const CopyButton = styled.button` const CopyButton = styled.button`
${buttons.button}; ${buttons.button};
@ -104,16 +104,11 @@ class ErrorBoundary extends React.Component {
return this.props.children; return this.props.children;
} }
return ( return (
<div className={styles.errorBoundary}> <ErrorBoundaryContainer>
<h1 className={styles.errorBoundaryText}>{t('ui.errorBoundary.title')}</h1> <h1>{t('ui.errorBoundary.title')}</h1>
<p> <p>
<span>{t('ui.errorBoundary.details')}</span> <span>{t('ui.errorBoundary.details')}</span>
<a <a href={ISSUE_URL} target="_blank" rel="noopener noreferrer">
href={ISSUE_URL}
target="_blank"
rel="noopener noreferrer"
className={styles.errorBoundaryText}
>
{t('ui.errorBoundary.reportIt')} {t('ui.errorBoundary.reportIt')}
</a> </a>
</p> </p>
@ -121,7 +116,7 @@ class ErrorBoundary extends React.Component {
<h2>{t('ui.errorBoundary.detailsHeading')}</h2> <h2>{t('ui.errorBoundary.detailsHeading')}</h2>
<p>{errorMessage}</p> <p>{errorMessage}</p>
{backup && showBackup && <RecoveredEntry entry={backup} t={t} />} {backup && showBackup && <RecoveredEntry entry={backup} t={t} />}
</div> </ErrorBoundaryContainer>
); );
} }
} }

View File

@ -1,17 +1,21 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { css, cx, injectGlobal } from 'react-emotion'; import { css, Global, ClassNames } from '@emotion/core';
import ReactModal from 'react-modal'; import ReactModal from 'react-modal';
import { transitions, shadows, lengths } from 'netlify-cms-ui-default'; import { transitions, shadows, lengths } from 'netlify-cms-ui-default';
injectGlobal` const ReactModalGlobalStyles = () => (
.ReactModal__Body--open { <Global
overflow: hidden; styles={css`
} .ReactModal__Body--open {
`; overflow: hidden;
}
`}
/>
);
const styles = { const styleStrings = {
modalBody: css` modalBody: `
${shadows.dropDeep}; ${shadows.dropDeep};
background-color: #fff; background-color: #fff;
border-radius: ${lengths.borderRadius}; border-radius: ${lengths.borderRadius};
@ -24,7 +28,7 @@ const styles = {
outline: none; outline: none;
} }
`, `,
overlay: css` overlay: `
z-index: 99999; z-index: 99999;
position: fixed; position: fixed;
top: 0; top: 0;
@ -38,11 +42,11 @@ const styles = {
background-color: rgba(0, 0, 0, 0); background-color: rgba(0, 0, 0, 0);
transition: background-color ${transitions.main}, opacity ${transitions.main}; transition: background-color ${transitions.main}, opacity ${transitions.main};
`, `,
overlayAfterOpen: css` overlayAfterOpen: `
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(0, 0, 0, 0.6);
opacity: 1; opacity: 1;
`, `,
overlayBeforeClose: css` overlayBeforeClose: `
background-color: rgba(0, 0, 0, 0); background-color: rgba(0, 0, 0, 0);
opacity: 0; opacity: 0;
`, `,
@ -63,23 +67,41 @@ export class Modal extends React.Component {
render() { render() {
const { isOpen, children, className, onClose } = this.props; const { isOpen, children, className, onClose } = this.props;
return ( return (
<ReactModal <>
isOpen={isOpen} <ReactModalGlobalStyles />
onRequestClose={onClose} <ClassNames>
closeTimeoutMS={300} {({ css, cx }) => (
className={{ <ReactModal
base: cx(styles.modalBody, className), isOpen={isOpen}
afterOpen: '', onRequestClose={onClose}
beforeClose: '', closeTimeoutMS={300}
}} className={{
overlayClassName={{ base: cx(
base: styles.overlay, css`
afterOpen: styles.overlayAfterOpen, ${styleStrings.modalBody};
beforeClose: styles.overlayBeforeClose, `,
}} className,
> ),
{children} afterOpen: '',
</ReactModal> beforeClose: '',
}}
overlayClassName={{
base: css`
${styleStrings.overlay};
`,
afterOpen: css`
${styleStrings.overlayAfterOpen};
`,
beforeClose: css`
${styleStrings.overlayBeforeClose};
`,
}}
>
{children}
</ReactModal>
)}
</ClassNames>
</>
); );
} }
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled, { css } from 'react-emotion'; import { css } from '@emotion/core';
import styled from '@emotion/styled';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import { Icon, Dropdown, DropdownItem, DropdownButton, colors } from 'netlify-cms-ui-default'; import { Icon, Dropdown, DropdownItem, DropdownButton, colors } from 'netlify-cms-ui-default';
import { stripProtocol } from 'Lib/urlHelper'; import { stripProtocol } from 'Lib/urlHelper';

View File

@ -1,18 +1,24 @@
/** @jsx jsx */
// eslint-disable-next-line no-unused-vars
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { css, injectGlobal, cx } from 'react-emotion'; import { jsx, css, Global } from '@emotion/core';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import reduxNotificationsStyles from 'redux-notifications/lib/styles.css'; import reduxNotificationsStyles from 'redux-notifications/lib/styles.css';
import { shadows, colors, lengths } from 'netlify-cms-ui-default'; import { shadows, colors, lengths } from 'netlify-cms-ui-default';
injectGlobal` const ReduxNotificationsGlobalStyles = () => (
${reduxNotificationsStyles}; <Global
styles={css`
${reduxNotificationsStyles};
.notif__container { .notif__container {
z-index: 10000; z-index: 10000;
white-space: pre-wrap; white-space: pre-wrap;
} }
`; `}
/>
);
const styles = { const styles = {
toast: css` toast: css`
@ -43,7 +49,8 @@ const styles = {
}; };
const Toast = ({ kind, message, t }) => ( const Toast = ({ kind, message, t }) => (
<div className={cx(styles.toast, styles[kind])}> <div css={[styles.toast, styles[kind]]}>
<ReduxNotificationsGlobalStyles />
{t(message.key, { details: message.details })} {t(message.key, { details: message.details })}
</div> </div>
); );

View File

@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { OrderedMap } from 'immutable'; import { OrderedMap } from 'immutable';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import { connect } from 'react-redux'; import { connect } from 'react-redux';

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled, { css } from 'react-emotion'; import { css } from '@emotion/core';
import styled from '@emotion/styled';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { components, colors, colorsRaw, transitions, buttons } from 'netlify-cms-ui-default'; import { components, colors, colorsRaw, transitions, buttons } from 'netlify-cms-ui-default';

View File

@ -1,7 +1,9 @@
/** @jsx jsx */
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled, { css, cx } from 'react-emotion'; import { jsx, css } from '@emotion/core';
import styled from '@emotion/styled';
import moment from 'moment'; import moment from 'moment';
import { translate } from 'react-polyglot'; import { translate } from 'react-polyglot';
import { colors, lengths } from 'netlify-cms-ui-default'; import { colors, lengths } from 'netlify-cms-ui-default';
@ -16,22 +18,16 @@ const WorkflowListContainer = styled.div`
`; `;
const styles = { const styles = {
column: css` columnPosition: idx =>
margin: 0 20px; (idx === 0 &&
transition: background-color 0.5s ease; css`
border: 2px dashed transparent; margin-left: 0;
border-radius: 4px; `) ||
position: relative; (idx === 2 &&
css`
&:first-child { margin-right: 0;
margin-left: 0; `) ||
} css`
&:last-child {
margin-right: 0;
}
&:not(:first-child):not(:last-child) {
&:before, &:before,
&:after { &:after {
content: ''; content: '';
@ -50,7 +46,14 @@ const styles = {
&:after { &:after {
right: -23px; right: -23px;
} }
} `,
column: css`
margin: 0 20px;
transition: background-color 0.5s ease;
border: 2px dashed transparent;
border-radius: 4px;
position: relative;
height: 100%;
`, `,
columnHovered: css` columnHovered: css`
border-color: ${colors.active}; border-color: ${colors.active};
@ -140,11 +143,12 @@ class WorkflowList extends React.Component {
this.props.handlePublish(collection, slug); this.props.handlePublish(collection, slug);
}; };
// eslint-disable-next-line react/display-name
renderColumns = (entries, column) => { renderColumns = (entries, column) => {
if (!entries) return null; if (!entries) return null;
if (!column) { if (!column) {
return entries.entrySeq().map(([currColumn, currEntries]) => ( return entries.entrySeq().map(([currColumn, currEntries], idx) => (
<DropTarget <DropTarget
namespace={DNDNamespace} namespace={DNDNamespace}
key={currColumn} key={currColumn}
@ -152,16 +156,24 @@ class WorkflowList extends React.Component {
> >
{(connect, { isHovered }) => {(connect, { isHovered }) =>
connect( connect(
<div className={cx(styles.column, { [styles.columnHovered]: isHovered })}> <div style={{ height: '100%' }}>
<ColumnHeader name={currColumn}> <div
{getColumnHeaderText(currColumn, this.props.t)} css={[
</ColumnHeader> styles.column,
<ColumnCount> styles.columnPosition(idx),
{this.props.t('workflow.workflowList.currentEntries', { isHovered && styles.columnHovered,
smart_count: currEntries.size, ]}
})} >
</ColumnCount> <ColumnHeader name={currColumn}>
{this.renderColumns(currEntries, currColumn)} {getColumnHeaderText(currColumn, this.props.t)}
</ColumnHeader>
<ColumnCount>
{this.props.t('workflow.workflowList.currentEntries', {
smart_count: currEntries.size,
})}
</ColumnCount>
{this.renderColumns(currEntries, currColumn)}
</div>
</div>, </div>,
) )
} }

View File

@ -84,7 +84,7 @@ describe('config', () => {
media_folder: 'baz', media_folder: 'baz',
collections: [], collections: [],
}); });
}).toThrowError("'collections' should NOT have less than 1 items"); }).toThrowError("'collections' should NOT have fewer than 1 items");
}); });
it('should throw if collections is an array with a single null element in config', () => { it('should throw if collections is an array with a single null element in config', () => {

View File

@ -1,4 +1,5 @@
import { Map } from 'immutable'; import { Map } from 'immutable';
import { oneLine } from 'common-tags';
import EditorComponent from 'ValueObjects/EditorComponent'; import EditorComponent from 'ValueObjects/EditorComponent';
/** /**
@ -59,10 +60,39 @@ export function getPreviewTemplate(name) {
* Editor Widgets * Editor Widgets
*/ */
export function registerWidget(name, control, preview) { export function registerWidget(name, control, preview) {
// A registered widget control can be reused by a new widget, allowing if (Array.isArray(name)) {
// multiple copies with different previews. name.forEach(widget => {
const newControl = typeof control === 'string' ? registry.widgets[control].control : control; if (typeof widget !== 'object') {
registry.widgets[name] = { control: newControl, preview }; console.error(`Cannot register widget: ${widget}`);
} else {
registerWidget(widget);
}
});
} else if (typeof name === 'string') {
// A registered widget control can be reused by a new widget, allowing
// multiple copies with different previews.
const newControl = typeof control === 'string' ? registry.widgets[control].control : control;
registry.widgets[name] = { control: newControl, preview };
} else if (typeof name === 'object') {
const {
name: widgetName,
controlComponent: control,
previewComponent: preview,
globalStyles,
} = name;
if (registry.widgets[widgetName]) {
console.error(oneLine`
Multiple widgets registered with name "${widgetName}". Only the last widget registered with
this name will be used.
`);
}
if (!control) {
throw Error(`Widget "${widgetName}" registered without \`controlComponent\`.`);
}
registry.widgets[widgetName] = { control, preview, globalStyles };
} else {
console.error('`registerWidget` failed, called with incorrect arguments.');
}
} }
export function getWidget(name) { export function getWidget(name) {
return registry.widgets[name]; return registry.widgets[name];

View File

@ -1,7 +1,7 @@
import { Map } from 'immutable'; import { Map } from 'immutable';
/* /*
* Reducer for some global UI state that we want to share between components * Reducer for some global UI state that we want to share between components
* */ * */
const globalUI = (state = Map({ isFetching: false }), action) => { const globalUI = (state = Map({ isFetching: false }), action) => {
// Generic, global loading indicator // Generic, global loading indicator
if (action.type.indexOf('REQUEST') > -1) { if (action.type.indexOf('REQUEST') > -1) {

View File

@ -20,8 +20,8 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^16.4.1" "react": "^16.4.1"

View File

@ -24,8 +24,8 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"immutable": "^3.7.6", "immutable": "^3.7.6",

View File

@ -1,5 +1,5 @@
import trim from 'lodash/trim'; import trim from 'lodash/trim';
import trimEnd from 'lodash/trim'; import trimEnd from 'lodash/trimEnd';
const NETLIFY_API = 'https://api.netlify.com'; const NETLIFY_API = 'https://api.netlify.com';
const AUTH_ENDPOINT = 'auth'; const AUTH_ENDPOINT = 'auth';

View File

@ -17,12 +17,12 @@
}, },
"dependencies": { "dependencies": {
"js-sha256": "^0.9.0", "js-sha256": "^0.9.0",
"localforage": "^1.4.2" "localforage": "^1.7.3"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"immutable": "^3.7.6", "immutable": "^3.7.6",

View File

@ -25,8 +25,8 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"netlify-cms-lib-util": "^2.0.4" "netlify-cms-lib-util": "^2.0.4"

View File

@ -23,11 +23,11 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"dependencies": { "dependencies": {
"uploadcare-widget": "^3.6.2", "uploadcare-widget": "^3.7.0",
"uploadcare-widget-tab-effects": "^1.3.0" "uploadcare-widget-tab-effects": "^1.4.0"
} }
} }

View File

@ -18,18 +18,18 @@
"dependencies": { "dependencies": {
"react-aria-menubutton": "^5.1.0", "react-aria-menubutton": "^5.1.0",
"react-toggled": "^1.1.2", "react-toggled": "^1.1.2",
"react-transition-group": "^2.2.1" "react-transition-group": "^2.6.0"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.9",
"lodash": "^4.13.1", "lodash": "^4.13.1",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "^16.4.1", "react": "^16.4.1"
"react-emotion": "^9.2.6"
} }
} }

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import Icon from './Icon'; import Icon from './Icon';
import { buttons, shadows } from './styles'; import { buttons, shadows } from './styles';

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled, { css } from 'react-emotion'; import { css } from '@emotion/core';
import styled from '@emotion/styled';
import { Wrapper, Button as DropdownButton, Menu, MenuItem } from 'react-aria-menubutton'; import { Wrapper, Button as DropdownButton, Menu, MenuItem } from 'react-aria-menubutton';
import { buttons, components } from './styles'; import { buttons, components } from './styles';
import Icon from './Icon'; import Icon from './Icon';

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import icons from './Icon/icons'; import icons from './Icon/icons';
const IconWrapper = styled.span` const IconWrapper = styled.span`

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import Icon from './Icon'; import Icon from './Icon';
import { colors, lengths, buttons } from './styles'; import { colors, lengths, buttons } from './styles';

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled, { css, keyframes } from 'react-emotion'; import styled from '@emotion/styled';
import { css, keyframes } from '@emotion/core';
import CSSTransition from 'react-transition-group/CSSTransition'; import CSSTransition from 'react-transition-group/CSSTransition';
import { colors } from './styles'; import { colors } from './styles';

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled, { css } from 'react-emotion'; import styled from '@emotion/styled';
import { css } from '@emotion/core';
import Icon from './Icon'; import Icon from './Icon';
import { colors, buttons } from './styles'; import { colors, buttons } from './styles';
import Dropdown, { StyledDropdownButton, DropdownItem } from './Dropdown'; import Dropdown, { StyledDropdownButton, DropdownItem } from './Dropdown';

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled, { css } from 'react-emotion'; import styled from '@emotion/styled';
import { css } from '@emotion/core';
import ReactToggled from 'react-toggled'; import ReactToggled from 'react-toggled';
import { colors, colorsRaw, shadows, transitions } from './styles'; import { colors, colorsRaw, shadows, transitions } from './styles';

View File

@ -1,4 +1,4 @@
import styled from 'react-emotion'; import styled from '@emotion/styled';
const WidgetPreviewContainer = styled.div` const WidgetPreviewContainer = styled.div`
margin: 15px 2px; margin: 15px 2px;

View File

@ -18,4 +18,5 @@ export {
transitions, transitions,
effects, effects,
reactSelectStyles, reactSelectStyles,
GlobalStyles,
} from './styles'; } from './styles';

View File

@ -1,18 +1,5 @@
import { css, injectGlobal } from 'react-emotion'; import React from 'react';
import { css, Global } from '@emotion/core';
export {
fonts,
colorsRaw,
colors,
lengths,
components,
buttons,
shadows,
borders,
transitions,
effects,
reactSelectStyles,
};
/** /**
* Font Stacks * Font Stacks
@ -102,6 +89,7 @@ const lengths = {
borderWidth: '2px', borderWidth: '2px',
topCardWidth: '682px', topCardWidth: '682px',
pageMargin: '28px 18px', pageMargin: '28px 18px',
objectWidgetTopBarContainerPadding: '0 14px 14px',
}; };
const borders = { const borders = {
@ -130,27 +118,25 @@ const shadows = {
`, `,
}; };
const gradients = {
checkerboard: `
linear-gradient(
45deg,
${colors.checkerboardDark} 25%,
transparent 25%,
transparent 75%,
${colors.checkerboardDark} 75%,
${colors.checkerboardDark}
)
`,
};
const effects = { const effects = {
checkerboard: css` checkerboard: css`
background-color: ${colors.checkerboardLight}; background-color: ${colors.checkerboardLight};
background-size: 16px 16px; background-size: 16px 16px;
background-position: 0 0, 8px 8px; background-position: 0 0, 8px 8px;
background-image: linear-gradient( background-image: ${gradients.checkerboard}, ${gradients.checkerboard};
45deg,
${colors.checkerboardDark} 25%,
transparent 25%,
transparent 75%,
${colors.checkerboardDark} 75%,
${colors.checkerboardDark}
),
linear-gradient(
45deg,
${colors.checkerboardDark} 25%,
transparent 25%,
transparent 75%,
${colors.checkerboardDark} 75%,
${colors.checkerboardDark}
);
`, `,
}; };
@ -305,7 +291,7 @@ const components = {
margin-top: 8px; margin-top: 8px;
`, `,
objectWidgetTopBarContainer: css` objectWidgetTopBarContainer: css`
padding: 0 14px 14px; padding: ${lengths.objectWidgetTopBarContainerPadding};
`, `,
dropdownList: css` dropdownList: css`
${shadows.dropDeep}; ${shadows.dropDeep};
@ -350,8 +336,8 @@ const reactSelectStyles = {
backgroundColor: state.isSelected backgroundColor: state.isSelected
? `${colors.active}` ? `${colors.active}`
: state.isFocused : state.isFocused
? `${colors.activeBackground}` ? `${colors.activeBackground}`
: 'transparent', : 'transparent',
paddingLeft: '22px', paddingLeft: '22px',
}), }),
menu: styles => ({ ...styles, right: 0, zIndex: 2 }), menu: styles => ({ ...styles, right: 0, zIndex: 2 }),
@ -381,73 +367,105 @@ const reactSelectStyles = {
}), }),
}; };
injectGlobal` const GlobalStyles = () => (
*, *:before, *:after { <Global
box-sizing: border-box; styles={css`
} *,
*:before,
*:after {
box-sizing: border-box;
}
:focus { :focus {
outline: -webkit-focus-ring-color auto ${lengths.borderRadius}; outline: -webkit-focus-ring-color auto ${lengths.borderRadius};
} }
/** /**
* Don't show outlines if the user is utilizing mouse rather than keyboard. * Don't show outlines if the user is utilizing mouse rather than keyboard.
*/ */
[data-whatintent="mouse"] *:focus { [data-whatintent='mouse'] *:focus {
outline: none; outline: none;
} }
input {
border: 0;
}
input { body {
border: 0; font-family: ${fonts.primary};
} font-weight: normal;
background-color: ${colors.background};
color: ${colors.text};
margin: 0;
}
body { ul,
font-family: ${fonts.primary}; ol {
font-weight: normal; padding-left: 0;
background-color: ${colors.background}; }
color: ${colors.text};
margin: 0;
}
ul, ol { h1,
padding-left: 0; h2,
} h3,
h4,
h5,
h6,
p {
font-family: ${fonts.primary};
color: ${colors.textLead};
font-size: 15px;
line-height: 1.5;
margin-top: 0;
}
h1, h2, h3, h4, h5, h6, p { h1,
font-family: ${fonts.primary}; h2,
color: ${colors.textLead}; h3,
font-size: 15px; h4,
line-height: 1.5; h5,
margin-top: 0; h6 {
} font-weight: 500;
}
h1, h2, h3, h4, h5, h6 { h1 {
font-weight: 500; font-size: 24px;
} letter-spacing: 0.4px;
color: ${colors.textLead};
}
h1 { a,
font-size: 24px; button {
letter-spacing: 0.4px; font-size: 14px;
color: ${colors.textLead}; font-weight: 500;
} }
a, a {
button { color: ${colors.text};
font-size: 14px; text-decoration: none;
font-weight: 500; }
}
a { img {
color: ${colors.text}; max-width: 100%;
text-decoration: none; }
}
img { textarea {
max-width: 100%; resize: none;
} }
`}
/>
);
textarea { export {
resize: none; fonts,
} colorsRaw,
`; colors,
lengths,
components,
buttons,
shadows,
borders,
transitions,
effects,
reactSelectStyles,
GlobalStyles,
};

View File

@ -21,11 +21,11 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",

View File

@ -1,12 +1,18 @@
/** @jsx jsx */
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from 'react-emotion'; import { jsx, css } from '@emotion/core';
import { Toggle, ToggleBackground, colors } from 'netlify-cms-ui-default'; import { Toggle, ToggleBackground, colors } from 'netlify-cms-ui-default';
const BooleanBackground = styled(ToggleBackground)` const BooleanBackground = ({ isActive, ...props }) => (
background-color: ${props => (props.isActive ? colors.active : colors.textFieldBorder)}; <ToggleBackground
`; css={css`
background-color: ${isActive ? colors.active : colors.textFieldBorder};
`}
{...props}
/>
);
export default class BooleanControl extends React.Component { export default class BooleanControl extends React.Component {
render() { render() {

View File

@ -21,21 +21,21 @@
"build": "cross-env NODE_ENV=production webpack" "build": "cross-env NODE_ENV=production webpack"
}, },
"dependencies": { "dependencies": {
"react-datetime": "^2.11.0" "react-datetime": "^2.16.3"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"css-loader": "^1.0.0", "css-loader": "^2.1.1",
"to-string-loader": "^1.1.5", "to-string-loader": "^1.1.5",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.9",
"moment": "^2.11.2", "moment": "^2.11.2",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "^16.4.1", "react": "^16.4.1"
"react-emotion": "^9.2.6"
} }
} }

View File

@ -1,14 +1,11 @@
/** @jsx jsx */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectGlobal } from 'react-emotion'; import { jsx, css } from '@emotion/core';
import reactDateTimeStyles from 'react-datetime/css/react-datetime.css';
import DateTime from 'react-datetime'; import DateTime from 'react-datetime';
import dateTimeStyles from 'react-datetime/css/react-datetime.css';
import moment from 'moment'; import moment from 'moment';
injectGlobal`
${dateTimeStyles}
`;
export default class DateControl extends React.Component { export default class DateControl extends React.Component {
static propTypes = { static propTypes = {
field: PropTypes.object.isRequired, field: PropTypes.object.isRequired,
@ -106,15 +103,21 @@ export default class DateControl extends React.Component {
const { forID, value, classNameWrapper, setActiveStyle } = this.props; const { forID, value, classNameWrapper, setActiveStyle } = this.props;
const { format, dateFormat, timeFormat } = this.formats; const { format, dateFormat, timeFormat } = this.formats;
return ( return (
<DateTime <div
dateFormat={dateFormat} css={css`
timeFormat={timeFormat} ${reactDateTimeStyles};
value={moment(value, format)} `}
onChange={this.handleChange} >
onFocus={setActiveStyle} <DateTime
onBlur={this.onBlur} dateFormat={dateFormat}
inputProps={{ className: classNameWrapper, id: forID }} timeFormat={timeFormat}
/> value={moment(value, format)}
onChange={this.handleChange}
onFocus={setActiveStyle}
onBlur={this.onBlur}
inputProps={{ className: classNameWrapper, id: forID }}
/>
</div>
); );
} }
} }

View File

@ -1,2 +1,11 @@
export DateControl from './DateControl'; import controlComponent from './DateControl';
export DatePreview from './DatePreview'; import previewComponent from './DatePreview';
const Widget = (opts = {}) => ({
name: 'date',
controlComponent,
previewComponent,
...opts,
});
export { Widget as default, controlComponent, previewComponent };

View File

@ -26,11 +26,11 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"react": "^16.4.1" "react": "^16.4.1"
}, },
"localExternals": [ "localExternals": [

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { DateControl } from 'netlify-cms-widget-date'; import { controlComponent as DateControl } from 'netlify-cms-widget-date';
export default class DateTimeControl extends React.Component { export default class DateTimeControl extends React.Component {
render() { render() {

View File

@ -1,2 +1,11 @@
export DateTimeControl from './DateTimeControl'; import controlComponent from './DateTimeControl';
export { DatePreview as DateTimePreview } from 'netlify-cms-widget-date'; import { previewComponent } from 'netlify-cms-widget-date';
const Widget = (opts = {}) => ({
name: 'datetime',
controlComponent,
previewComponent,
...opts,
});
export { Widget as default, controlComponent, previewComponent };

View File

@ -27,16 +27,16 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.9",
"immutable": "^3.7.6", "immutable": "^3.7.6",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "^16.4.1", "react": "^16.4.1",
"react-emotion": "^9.2.6",
"react-immutable-proptypes": "^2.1.0" "react-immutable-proptypes": "^2.1.0"
} }
} }

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { List } from 'immutable'; import { List } from 'immutable';
import { WidgetPreviewContainer } from 'netlify-cms-ui-default'; import { WidgetPreviewContainer } from 'netlify-cms-ui-default';

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { Map, List } from 'immutable'; import { Map, List } from 'immutable';
import { once } from 'lodash'; import { once } from 'lodash';
import uuid from 'uuid/v4'; import uuid from 'uuid/v4';
@ -33,12 +33,6 @@ const MultiImageWrapper = styled.div`
flex-wrap: wrap; flex-wrap: wrap;
`; `;
const FileInfo = styled.div`
button:not(:first-child) {
margin-top: 12px;
}
`;
const FileLink = styled.a` const FileLink = styled.a`
margin-bottom: 20px; margin-bottom: 20px;
font-weight: normal; font-weight: normal;
@ -51,6 +45,10 @@ const FileLink = styled.a`
} }
`; `;
const FileLinks = styled.div`
margin-bottom: 12px;
`;
const FileLinkList = styled.ul` const FileLinkList = styled.ul`
list-style-type: none; list-style-type: none;
`; `;
@ -63,6 +61,7 @@ const FileWidgetButton = styled.button`
const FileWidgetButtonRemove = styled.button` const FileWidgetButtonRemove = styled.button`
${buttons.button}; ${buttons.button};
${components.badgeDanger}; ${components.badgeDanger};
margin-top: 12px;
`; `;
function isMultiple(value) { function isMultiple(value) {
@ -192,14 +191,16 @@ export default function withFileControl({ forImage } = {}) {
if (isMultiple(value)) { if (isMultiple(value)) {
return ( return (
<FileLinkList> <FileLinks>
{value.map(val => ( <FileLinkList>
<li key={val}>{this.renderFileLink(val)}</li> {value.map(val => (
))} <li key={val}>{this.renderFileLink(val)}</li>
</FileLinkList> ))}
</FileLinkList>
</FileLinks>
); );
} }
return this.renderFileLink(value); return <FileLinks>{this.renderFileLink(value)}</FileLinks>;
}; };
renderImages = () => { renderImages = () => {
@ -225,7 +226,7 @@ export default function withFileControl({ forImage } = {}) {
renderSelection = subject => ( renderSelection = subject => (
<div> <div>
{forImage ? this.renderImages() : null} {forImage ? this.renderImages() : null}
<FileInfo> <div>
{forImage ? null : this.renderFileLinks()} {forImage ? null : this.renderFileLinks()}
<FileWidgetButton onClick={this.handleChange}> <FileWidgetButton onClick={this.handleChange}>
Choose different {subject} Choose different {subject}
@ -233,7 +234,7 @@ export default function withFileControl({ forImage } = {}) {
<FileWidgetButtonRemove onClick={this.handleRemove}> <FileWidgetButtonRemove onClick={this.handleRemove}>
Remove {subject} Remove {subject}
</FileWidgetButtonRemove> </FileWidgetButtonRemove>
</FileInfo> </div>
</div> </div>
); );

View File

@ -26,15 +26,16 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.9",
"immutable": "^3.7.6", "immutable": "^3.7.6",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "^16.4.1", "react": "^16.4.1"
"react-emotion": "^9.2.6"
}, },
"localExternals": [ "localExternals": [
"netlify-cms-widget-file" "netlify-cms-widget-file"

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { List } from 'immutable'; import { List } from 'immutable';
import { WidgetPreviewContainer } from 'netlify-cms-ui-default'; import { WidgetPreviewContainer } from 'netlify-cms-ui-default';

View File

@ -26,17 +26,17 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.9",
"immutable": "^3.7.6", "immutable": "^3.7.6",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "^16.4.1", "react": "^16.4.1",
"react-emotion": "^9.2.6",
"react-immutable-proptypes": "^2.1.0" "react-immutable-proptypes": "^2.1.0"
}, },
"localExternals": [ "localExternals": [

View File

@ -1,7 +1,9 @@
/** @jsx jsx */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled, { cx, css } from 'react-emotion'; import styled from '@emotion/styled';
import { jsx, css, ClassNames } from '@emotion/core';
import { List, Map } from 'immutable'; import { List, Map } from 'immutable';
import { partial } from 'lodash'; import { partial } from 'lodash';
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc'; import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
@ -12,13 +14,7 @@ import {
resolveFieldKeyType, resolveFieldKeyType,
getErrorMessageForTypedFieldAndValue, getErrorMessageForTypedFieldAndValue,
} from './typedListHelpers'; } from './typedListHelpers';
import { import { ListItemTopBar, ObjectWidgetTopBar, colors, lengths } from 'netlify-cms-ui-default';
ListItemTopBar,
ObjectWidgetTopBar,
colors,
lengths,
components,
} from 'netlify-cms-ui-default';
function valueToString(value) { function valueToString(value) {
return value ? value.join(',').replace(/,([^\s]|$)/g, ', $1') : ''; return value ? value.join(',').replace(/,([^\s]|$)/g, ', $1') : '';
@ -41,10 +37,16 @@ const NestedObjectLabel = styled.div`
border-radius: 0 0 ${lengths.borderRadius} ${lengths.borderRadius}; border-radius: 0 0 ${lengths.borderRadius} ${lengths.borderRadius};
`; `;
const styles = { const styleStrings = {
collapsedObjectControl: css` collapsedObjectControl: `
display: none; display: none;
`, `,
objectWidgetTopBarContainer: `
padding: ${lengths.objectWidgetTopBarContainerPadding};
`,
};
const styles = {
listControlItem: css` listControlItem: css`
margin-top: 18px; margin-top: 18px;
@ -275,6 +277,7 @@ export default class ListControl extends React.Component {
this.setState({ itemsCollapsed: updatedItemsCollapsed }); this.setState({ itemsCollapsed: updatedItemsCollapsed });
}; };
// eslint-disable-next-line react/display-name
renderItem = (item, index) => { renderItem = (item, index) => {
const { const {
classNameWrapper, classNameWrapper,
@ -300,7 +303,7 @@ export default class ListControl extends React.Component {
return ( return (
<SortableListItem <SortableListItem
className={cx(styles.listControlItem, { [styles.listControlItemCollapsed]: collapsed })} css={[styles.listControlItem, collapsed && styles.listControlItemCollapsed]}
index={index} index={index}
key={`item-${index}`} key={`item-${index}`}
> >
@ -311,21 +314,29 @@ export default class ListControl extends React.Component {
dragHandleHOC={SortableHandle} dragHandleHOC={SortableHandle}
/> />
<NestedObjectLabel collapsed={collapsed}>{this.objectLabel(item)}</NestedObjectLabel> <NestedObjectLabel collapsed={collapsed}>{this.objectLabel(item)}</NestedObjectLabel>
<ObjectControl <ClassNames>
classNameWrapper={cx(classNameWrapper, { [styles.collapsedObjectControl]: collapsed })} {({ css, cx }) => (
value={item} <ObjectControl
field={field} classNameWrapper={cx(classNameWrapper, {
onChangeObject={this.handleChangeFor(index)} [css`
editorControl={editorControl} ${styleStrings.collapsedObjectControl};
resolveWidget={resolveWidget} `]: collapsed,
metadata={metadata} })}
forList value={item}
onValidateObject={onValidateObject} field={field}
clearFieldErrors={clearFieldErrors} onChangeObject={this.handleChangeFor(index)}
fieldsErrors={fieldsErrors} editorControl={editorControl}
ref={this.processControlRef} resolveWidget={resolveWidget}
controlRef={controlRef} metadata={metadata}
/> forList
onValidateObject={onValidateObject}
clearFieldErrors={clearFieldErrors}
fieldsErrors={fieldsErrors}
ref={this.processControlRef}
controlRef={controlRef}
/>
)}
</ClassNames>
</SortableListItem> </SortableListItem>
); );
}; };
@ -335,7 +346,7 @@ export default class ListControl extends React.Component {
const errorMessage = getErrorMessageForTypedFieldAndValue(field, item); const errorMessage = getErrorMessageForTypedFieldAndValue(field, item);
return ( return (
<SortableListItem <SortableListItem
className={cx(styles.listControlItem, styles.listControlItemCollapsed)} css={[styles.listControlItem, styles.listControlItemCollapsed]}
index={index} index={index}
key={`item-${index}`} key={`item-${index}`}
> >
@ -360,25 +371,37 @@ export default class ListControl extends React.Component {
const listLabel = items.size === 1 ? labelSingular.toLowerCase() : label.toLowerCase(); const listLabel = items.size === 1 ? labelSingular.toLowerCase() : label.toLowerCase();
return ( return (
<div id={forID} className={cx(classNameWrapper, components.objectWidgetTopBarContainer)}> <ClassNames>
<ObjectWidgetTopBar {({ cx, css }) => (
allowAdd={field.get('allow_add', true)} <div
onAdd={this.handleAdd} id={forID}
types={field.get(TYPES_KEY, null)} className={cx(
onAddType={type => this.handleAddType(type, resolveFieldKeyType(field))} classNameWrapper,
heading={`${items.size} ${listLabel}`} css`
label={labelSingular.toLowerCase()} ${styleStrings.objectWidgetTopBarContainer}
onCollapseToggle={this.handleCollapseAllToggle} `,
collapsed={itemsCollapsed.every(val => val === true)} )}
/> >
<SortableList <ObjectWidgetTopBar
items={items} allowAdd={field.get('allow_add', true)}
renderItem={this.renderItem} onAdd={this.handleAdd}
onSortEnd={this.onSortEnd} types={field.get(TYPES_KEY, null)}
useDragHandle onAddType={type => this.handleAddType(type, resolveFieldKeyType(field))}
lockAxis="y" heading={`${items.size} ${listLabel}`}
/> label={labelSingular.toLowerCase()}
</div> onCollapseToggle={this.handleCollapseAllToggle}
collapsed={itemsCollapsed.every(val => val === true)}
/>
<SortableList
items={items}
renderItem={this.renderItem}
onSortEnd={this.onSortEnd}
useDragHandle
lockAxis="y"
/>
</div>
)}
</ClassNames>
); );
} }

View File

@ -22,13 +22,13 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"css-loader": "^2.1.0", "css-loader": "^2.1.1",
"to-string-loader": "^1.1.5", "to-string-loader": "^1.1.5",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
@ -36,6 +36,6 @@
"react-immutable-proptypes": "^2.1.0" "react-immutable-proptypes": "^2.1.0"
}, },
"dependencies": { "dependencies": {
"ol": "^5.3.0" "ol": "^5.3.1"
} }
} }

View File

@ -1,3 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ClassNames } from '@emotion/core';
import olStyles from 'ol/ol.css'; import olStyles from 'ol/ol.css';
import Map from 'ol/Map.js'; import Map from 'ol/Map.js';
import View from 'ol/View.js'; import View from 'ol/View.js';
@ -7,13 +10,6 @@ import TileLayer from 'ol/layer/Tile.js';
import VectorLayer from 'ol/layer/Vector.js'; import VectorLayer from 'ol/layer/Vector.js';
import OSMSource from 'ol/source/OSM.js'; import OSMSource from 'ol/source/OSM.js';
import VectorSource from 'ol/source/Vector.js'; import VectorSource from 'ol/source/Vector.js';
import PropTypes from 'prop-types';
import React from 'react';
import { injectGlobal } from 'react-emotion';
injectGlobal`
${olStyles}
`;
const formatOptions = { const formatOptions = {
dataProjection: 'EPSG:4326', dataProjection: 'EPSG:4326',
@ -70,7 +66,23 @@ export default function withMapControl({ getFormat, getMap } = {}) {
} }
render() { render() {
return <div ref={this.mapContainer}> </div>; return (
<ClassNames>
{({ cx, css }) => (
<div
className={cx(
this.props.classNameWrapper,
css`
${olStyles};
padding: 0;
overflow: hidden;
`,
)}
ref={this.mapContainer}
/>
)}
</ClassNames>
);
} }
}; };
} }

View File

@ -21,9 +21,9 @@
"build": "cross-env NODE_ENV=production webpack" "build": "cross-env NODE_ENV=production webpack"
}, },
"dependencies": { "dependencies": {
"is-hotkey": "^0.1.1", "is-hotkey": "^0.1.4",
"mdast-util-definitions": "^1.2.2", "mdast-util-definitions": "^1.2.3",
"mdast-util-to-string": "^1.0.4", "mdast-util-to-string": "^1.0.5",
"rehype-parse": "^3.1.0", "rehype-parse": "^3.1.0",
"rehype-remark": "^2.0.0", "rehype-remark": "^2.0.0",
"rehype-stringify": "^3.0.0", "rehype-stringify": "^3.0.0",
@ -42,18 +42,18 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.9",
"immutable": "^3.7.6", "immutable": "^3.7.6",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "^16.4.1", "react": "^16.4.1",
"react-dom": "^16.0.0", "react-dom": "^16.0.0",
"react-emotion": "^9.2.5",
"react-immutable-proptypes": "^2.1.0" "react-immutable-proptypes": "^2.1.0"
} }
} }

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled, { css, cx } from 'react-emotion'; import styled from '@emotion/styled';
import { ClassNames } from '@emotion/core';
import { Editor as Slate } from 'slate-react'; import { Editor as Slate } from 'slate-react';
import Plain from 'slate-plain-serializer'; import Plain from 'slate-plain-serializer';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
@ -9,8 +10,8 @@ import { lengths, fonts } from 'netlify-cms-ui-default';
import { editorStyleVars, EditorControlBar } from '../styles'; import { editorStyleVars, EditorControlBar } from '../styles';
import Toolbar from './Toolbar'; import Toolbar from './Toolbar';
const styles = { const styleStrings = {
slateRaw: css` slateRaw: `
position: relative; position: relative;
overflow: hidden; overflow: hidden;
overflow-x: auto; overflow-x: auto;
@ -83,12 +84,21 @@ export default class RawEditor extends React.Component {
rawMode rawMode
/> />
</EditorControlBar> </EditorControlBar>
<Slate <ClassNames>
className={cx(className, styles.slateRaw)} {({ css, cx }) => (
value={this.state.value} <Slate
onChange={this.handleChange} className={cx(
onPaste={this.handlePaste} className,
/> css`
${styleStrings.slateRaw}
`,
)}
value={this.state.value}
onChange={this.handleChange}
onPaste={this.handlePaste}
/>
)}
</ClassNames>
</RawEditorContainer> </RawEditorContainer>
); );
} }

View File

@ -2,7 +2,8 @@
import React from 'react'; import React from 'react';
import { Map } from 'immutable'; import { Map } from 'immutable';
import styled, { css } from 'react-emotion'; import styled from '@emotion/styled';
import { css } from '@emotion/core';
import { partial, capitalize } from 'lodash'; import { partial, capitalize } from 'lodash';
import { ListItemTopBar, components, colors, lengths } from 'netlify-cms-ui-default'; import { ListItemTopBar, components, colors, lengths } from 'netlify-cms-ui-default';
import { getEditorControl, getEditorComponents } from './index'; import { getEditorControl, getEditorComponents } from './index';

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled, { css } from 'react-emotion'; import styled from '@emotion/styled';
import { css } from '@emotion/core';
import { List } from 'immutable'; import { List } from 'immutable';
import { import {
Toggle, Toggle,
@ -24,6 +25,7 @@ const ToolbarContainer = styled.div`
padding: 11px 14px; padding: 11px 14px;
min-height: 58px; min-height: 58px;
transition: background-color ${transitions.main}, color ${transitions.main}; transition: background-color ${transitions.main}, color ${transitions.main};
color: ${colors.text};
`; `;
const ToolbarDropdownWrapper = styled.div` const ToolbarDropdownWrapper = styled.div`

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'react-emotion'; import styled from '@emotion/styled';
import { Icon, buttons } from 'netlify-cms-ui-default'; import { Icon, buttons } from 'netlify-cms-ui-default';
const StyledToolbarButton = styled.button` const StyledToolbarButton = styled.button`

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import styled, { cx } from 'react-emotion'; import styled from '@emotion/styled';
import { ClassNames } from '@emotion/core';
import { get, isEmpty, debounce, uniq } from 'lodash'; import { get, isEmpty, debounce, uniq } from 'lodash';
import { List } from 'immutable'; import { List } from 'immutable';
import { Value, Document, Block, Text } from 'slate'; import { Value, Document, Block, Text } from 'slate';
@ -259,19 +260,28 @@ export default class Editor extends React.Component {
buttons={field.get('buttons')} buttons={field.get('buttons')}
/> />
</EditorControlBar> </EditorControlBar>
<Slate <ClassNames>
className={cx(className, visualEditorStyles)} {({ css, cx }) => (
value={this.state.value} <Slate
renderNode={renderNode} className={cx(
renderMark={renderMark} className,
validateNode={validateNode} css`
plugins={plugins} ${visualEditorStyles}
onChange={this.handleChange} `,
onKeyDown={onKeyDown} )}
onPaste={this.handlePaste} value={this.state.value}
ref={this.processRef} renderNode={renderNode}
spellCheck renderMark={renderMark}
/> validateNode={validateNode}
plugins={plugins}
onChange={this.handleChange}
onKeyDown={onKeyDown}
onPaste={this.handlePaste}
ref={this.processRef}
spellCheck
/>
)}
</ClassNames>
</VisualEditorContainer> </VisualEditorContainer>
); );
} }

View File

@ -1,8 +1,7 @@
import { css } from 'react-emotion';
import { colors, lengths, fonts } from 'netlify-cms-ui-default'; import { colors, lengths, fonts } from 'netlify-cms-ui-default';
import { editorStyleVars } from '../styles'; import { editorStyleVars } from '../styles';
export default css` export default `
position: relative; position: relative;
overflow: hidden; overflow: hidden;
overflow-x: auto; overflow-x: auto;

View File

@ -1,4 +1,4 @@
import styled from 'react-emotion'; import styled from '@emotion/styled';
export const editorStyleVars = { export const editorStyleVars = {
stickyDistanceBottom: '100px', stickyDistanceBottom: '100px',

View File

@ -21,11 +21,10 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "^16.4.1" "react": "^16.4.1"

View File

@ -23,17 +23,17 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.9",
"immutable": "^3.7.6", "immutable": "^3.7.6",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "^16.4.1", "react": "^16.4.1",
"react-emotion": "^9.2.6",
"react-immutable-proptypes": "^2.1.0" "react-immutable-proptypes": "^2.1.0"
} }
} }

View File

@ -1,20 +1,23 @@
import React, { Component } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { css, cx } from 'react-emotion'; import { ClassNames } from '@emotion/core';
import { Map, List } from 'immutable'; import { Map, List } from 'immutable';
import { ObjectWidgetTopBar, components } from 'netlify-cms-ui-default'; import { ObjectWidgetTopBar, lengths } from 'netlify-cms-ui-default';
const styles = { const styleStrings = {
nestedObjectControl: css` nestedObjectControl: `
padding: 6px 14px 14px; padding: 6px 14px 14px;
border-top: 0; border-top: 0;
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
`, `,
objectWidgetTopBarContainer: `
padding: ${lengths.objectWidgetTopBarContainerPadding}
`,
}; };
export default class ObjectControl extends Component { export default class ObjectControl extends React.Component {
componentValidate = {}; componentValidate = {};
static propTypes = { static propTypes = {
@ -116,20 +119,32 @@ export default class ObjectControl extends Component {
if (multiFields || singleField) { if (multiFields || singleField) {
return ( return (
<div <ClassNames>
id={forID} {({ css, cx }) => (
className={cx(classNameWrapper, components.objectWidgetTopBarContainer, { <div
[styles.nestedObjectControl]: forList, id={forID}
})} className={cx(
> classNameWrapper,
{forList ? null : ( css`
<ObjectWidgetTopBar ${styleStrings.objectWidgetTopBarContainer}
collapsed={collapsed} `,
onCollapseToggle={this.handleCollapseToggle} {
/> [css`
${styleStrings.nestedObjectControl}
`]: forList,
},
)}
>
{forList ? null : (
<ObjectWidgetTopBar
collapsed={collapsed}
onCollapseToggle={this.handleCollapseToggle}
/>
)}
{collapsed ? null : this.renderFields(multiFields, singleField)}
</div>
)} )}
{collapsed ? null : this.renderFields(multiFields, singleField)} </ClassNames>
</div>
); );
} }

View File

@ -21,21 +21,21 @@
"build": "cross-env NODE_ENV=production webpack" "build": "cross-env NODE_ENV=production webpack"
}, },
"dependencies": { "dependencies": {
"react-select": "^2.3.0" "react-select": "^2.4.2"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"webpack": "^4.16.1", "webpack": "^4.29.6",
"webpack-cli": "^3.1.0" "webpack-cli": "^3.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"emotion": "^9.2.6", "@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.9",
"immutable": "^3.7.6", "immutable": "^3.7.6",
"lodash": "^4.17.10", "lodash": "^4.17.10",
"netlify-cms-ui-default": "^2.0.0", "netlify-cms-ui-default": "^2.0.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "^16.4.1", "react": "^16.4.1",
"react-emotion": "^9.2.5",
"uuid": "^3.1.0" "uuid": "^3.1.0"
} }
} }

View File

@ -1,207 +1,206 @@
import React from 'react'; import React from 'react';
import { fromJS, Map } from 'immutable'; import { fromJS, Map } from 'immutable';
import { last } from 'lodash'; import { last } from 'lodash';
import { render, fireEvent, wait } from 'react-testing-library'; import { render, fireEvent, wait } from 'react-testing-library';
import 'react-testing-library/cleanup-after-each'; import 'react-testing-library/cleanup-after-each';
import 'jest-dom/extend-expect'; import 'jest-dom/extend-expect';
import { RelationControl } from '../'; import { RelationControl } from '../';
const fieldConfig = { const fieldConfig = {
name: 'post', name: 'post',
collection: 'posts', collection: 'posts',
displayFields: ['title', 'slug'], displayFields: ['title', 'slug'],
searchFields: ['title', 'body'], searchFields: ['title', 'body'],
valueField: 'title', valueField: 'title',
}; };
const generateHits = length => { const generateHits = length => {
const hits = Array.from({ length }, (val, idx) => { const hits = Array.from({ length }, (val, idx) => {
const title = `Post # ${idx + 1}`; const title = `Post # ${idx + 1}`;
const slug = `post-number-${idx + 1}`; const slug = `post-number-${idx + 1}`;
return { collection: 'posts', data: { title, slug } }; return { collection: 'posts', data: { title, slug } };
}); });
return [ return [
...hits, ...hits,
{ {
collection: 'posts', collection: 'posts',
data: { title: 'YAML post', slug: 'post-yaml', body: 'Body yaml' }, data: { title: 'YAML post', slug: 'post-yaml', body: 'Body yaml' },
}, },
]; ];
}; };
class RelationController extends React.Component { class RelationController extends React.Component {
state = { state = {
value: this.props.value, value: this.props.value,
queryHits: Map(), queryHits: Map(),
}; };
handleOnChange = jest.fn(value => { handleOnChange = jest.fn(value => {
this.setState({ ...this.state, value }); this.setState({ ...this.state, value });
}); });
setQueryHits = jest.fn(hits => { setQueryHits = jest.fn(hits => {
const queryHits = Map().set('relation-field', hits); const queryHits = Map().set('relation-field', hits);
this.setState({ ...this.state, queryHits }); this.setState({ ...this.state, queryHits });
}); });
query = jest.fn((...args) => { query = jest.fn((...args) => {
const queryHits = generateHits(25); const queryHits = generateHits(25);
if (last(args) === 'YAML') { if (last(args) === 'YAML') {
return Promise.resolve({ payload: { response: { hits: [last(queryHits)] } } }); return Promise.resolve({ payload: { response: { hits: [last(queryHits)] } } });
} }
return Promise.resolve({ payload: { response: { hits: queryHits } } }); return Promise.resolve({ payload: { response: { hits: queryHits } } });
}); });
render() { render() {
return this.props.children({ return this.props.children({
value: this.state.value, value: this.state.value,
handleOnChange: this.handleOnChange, handleOnChange: this.handleOnChange,
query: this.query, query: this.query,
queryHits: this.state.queryHits, queryHits: this.state.queryHits,
setQueryHits: this.setQueryHits, setQueryHits: this.setQueryHits,
}); });
} }
} }
function setup({ field, value }) { function setup({ field, value }) {
let renderArgs; let renderArgs;
const setActiveSpy = jest.fn(); const setActiveSpy = jest.fn();
const setInactiveSpy = jest.fn(); const setInactiveSpy = jest.fn();
const helpers = render( const helpers = render(
<RelationController value={value}> <RelationController value={value}>
{({ handleOnChange, value, query, queryHits, setQueryHits }) => { {({ handleOnChange, value, query, queryHits, setQueryHits }) => {
renderArgs = { value, onChangeSpy: handleOnChange, setQueryHitsSpy: setQueryHits }; renderArgs = { value, onChangeSpy: handleOnChange, setQueryHitsSpy: setQueryHits };
return ( return (
<RelationControl <RelationControl
field={field} field={field}
value={value} value={value}
query={query} query={query}
queryHits={queryHits} queryHits={queryHits}
onChange={handleOnChange} onChange={handleOnChange}
forID="relation-field" forID="relation-field"
classNameWrapper="" classNameWrapper=""
setActiveStyle={setActiveSpy} setActiveStyle={setActiveSpy}
setInactiveStyle={setInactiveSpy} setInactiveStyle={setInactiveSpy}
/> />
); );
}} }}
</RelationController>, </RelationController>,
); );
const input = helpers.container.querySelector('input'); const input = helpers.container.querySelector('input');
return { return {
...helpers, ...helpers,
...renderArgs, ...renderArgs,
setActiveSpy, setActiveSpy,
setInactiveSpy, setInactiveSpy,
input, input,
}; };
} }
describe('Relation widget', () => { describe('Relation widget', () => {
it('should list the first 20 option hits on initial load', async () => { it('should list the first 20 option hits on initial load', async () => {
const field = fromJS(fieldConfig); const field = fromJS(fieldConfig);
const { getAllByRole, input } = setup({ field }); const { getAllByText, input } = setup({ field });
fireEvent.keyDown(input, { key: 'ArrowDown' });
await wait(() => {
fireEvent.keyDown(input, { key: 'ArrowDown' }); await wait(() => {
expect(getAllByRole('option')).toHaveLength(20); expect(getAllByText(/^Post # (\d{1,2}) post-number-\1$/)).toHaveLength(20);
}); });
}); });
it('should update option list based on search term', async () => { it('should update option list based on search term', async () => {
const field = fromJS(fieldConfig); const field = fromJS(fieldConfig);
const { getAllByRole, getByText, input } = setup({ field }); const { getAllByText, input } = setup({ field });
fireEvent.change(input, { target: { value: 'YAML' } });
await wait(() => {
fireEvent.change(input, { target: { value: 'YAML' } }); await wait(() => {
expect(getAllByRole('option')).toHaveLength(1); expect(getAllByText('YAML post post-yaml')).toHaveLength(1);
expect(getByText('YAML post post-yaml')).toBeInTheDocument(); });
}); });
});
it('should call onChange with correct selectedItem value and metadata', async () => {
it('should call onChange with correct selectedItem value and metadata', async () => { const field = fromJS(fieldConfig);
const field = fromJS(fieldConfig); const { getByText, input, onChangeSpy } = setup({ field });
const { getByText, input, onChangeSpy } = setup({ field }); const value = 'Post # 1';
const value = 'Post # 1'; const label = 'Post # 1 post-number-1';
const label = 'Post # 1 post-number-1'; const metadata = {
const metadata = { post: { posts: { 'Post # 1': { title: 'Post # 1', slug: 'post-number-1' } } },
post: { posts: { 'Post # 1': { title: 'Post # 1', slug: 'post-number-1' } } }, };
};
await wait(() => {
await wait(() => { fireEvent.keyDown(input, { key: 'ArrowDown' });
fireEvent.keyDown(input, { key: 'ArrowDown' }); fireEvent.click(getByText(label));
fireEvent.click(getByText(label)); expect(onChangeSpy).toHaveBeenCalledTimes(1);
expect(onChangeSpy).toHaveBeenCalledTimes(1); expect(onChangeSpy).toHaveBeenCalledWith(value, metadata);
expect(onChangeSpy).toHaveBeenCalledWith(value, metadata); });
}); });
});
it('should update metadata for initial preview', async () => {
it('should update metadata for initial preview', async () => { const field = fromJS(fieldConfig);
const field = fromJS(fieldConfig); const value = 'Post # 1';
const value = 'Post # 1'; const { getByText, onChangeSpy, setQueryHitsSpy } = setup({ field, value });
const { getByText, onChangeSpy, setQueryHitsSpy } = setup({ field, value }); const label = 'Post # 1 post-number-1';
const label = 'Post # 1 post-number-1'; const metadata = {
const metadata = { post: { posts: { 'Post # 1': { title: 'Post # 1', slug: 'post-number-1' } } },
post: { posts: { 'Post # 1': { title: 'Post # 1', slug: 'post-number-1' } } }, };
};
setQueryHitsSpy(generateHits(1));
setQueryHitsSpy(generateHits(1));
await wait(() => {
await wait(() => { expect(getByText(label)).toBeInTheDocument();
expect(getByText(label)).toBeInTheDocument(); expect(onChangeSpy).toHaveBeenCalledTimes(1);
expect(onChangeSpy).toHaveBeenCalledTimes(1); expect(onChangeSpy).toHaveBeenCalledWith(value, metadata);
expect(onChangeSpy).toHaveBeenCalledWith(value, metadata); });
}); });
});
describe('with multiple', () => {
describe('with multiple', () => { it('should call onChange with correct selectedItem value and metadata', async () => {
it('should call onChange with correct selectedItem value and metadata', async () => { const field = fromJS({ ...fieldConfig, multiple: true });
const field = fromJS({ ...fieldConfig, multiple: true }); const { getByText, input, onChangeSpy } = setup({ field });
const { getByText, input, onChangeSpy } = setup({ field }); const metadata1 = {
const metadata1 = { post: { posts: { 'Post # 1': { title: 'Post # 1', slug: 'post-number-1' } } },
post: { posts: { 'Post # 1': { title: 'Post # 1', slug: 'post-number-1' } } }, };
}; const metadata2 = {
const metadata2 = { post: { posts: { 'Post # 2': { title: 'Post # 2', slug: 'post-number-2' } } },
post: { posts: { 'Post # 2': { title: 'Post # 2', slug: 'post-number-2' } } }, };
};
await wait(() => {
await wait(() => { fireEvent.keyDown(input, { key: 'ArrowDown' });
fireEvent.keyDown(input, { key: 'ArrowDown' }); fireEvent.click(getByText('Post # 1 post-number-1'));
fireEvent.click(getByText('Post # 1 post-number-1')); fireEvent.keyDown(input, { key: 'ArrowDown' });
fireEvent.keyDown(input, { key: 'ArrowDown' }); fireEvent.click(getByText('Post # 2 post-number-2'));
fireEvent.click(getByText('Post # 2 post-number-2'));
expect(onChangeSpy).toHaveBeenCalledTimes(2);
expect(onChangeSpy).toHaveBeenCalledTimes(2); expect(onChangeSpy).toHaveBeenCalledWith(fromJS(['Post # 1']), metadata1);
expect(onChangeSpy).toHaveBeenCalledWith(fromJS(['Post # 1']), metadata1); expect(onChangeSpy).toHaveBeenCalledWith(fromJS(['Post # 1', 'Post # 2']), metadata2);
expect(onChangeSpy).toHaveBeenCalledWith(fromJS(['Post # 1', 'Post # 2']), metadata2); });
}); });
});
it('should update metadata for initial preview', async () => {
it('should update metadata for initial preview', async () => { const field = fromJS({ ...fieldConfig, multiple: true });
const field = fromJS({ ...fieldConfig, multiple: true }); const value = fromJS(['Post # 1', 'Post # 2']);
const value = fromJS(['Post # 1', 'Post # 2']); const { getByText, onChangeSpy, setQueryHitsSpy } = setup({ field, value });
const { getByText, onChangeSpy, setQueryHitsSpy } = setup({ field, value }); const metadata1 = {
const metadata1 = { post: { posts: { 'Post # 1': { title: 'Post # 1', slug: 'post-number-1' } } },
post: { posts: { 'Post # 1': { title: 'Post # 1', slug: 'post-number-1' } } }, };
}; const metadata2 = {
const metadata2 = { post: { posts: { 'Post # 2': { title: 'Post # 2', slug: 'post-number-2' } } },
post: { posts: { 'Post # 2': { title: 'Post # 2', slug: 'post-number-2' } } }, };
};
setQueryHitsSpy(generateHits(2));
setQueryHitsSpy(generateHits(2));
await wait(() => {
await wait(() => { expect(getByText('Post # 1 post-number-1')).toBeInTheDocument();
expect(getByText('Post # 1 post-number-1')).toBeInTheDocument(); expect(getByText('Post # 2 post-number-2')).toBeInTheDocument();
expect(getByText('Post # 2 post-number-2')).toBeInTheDocument();
expect(onChangeSpy).toHaveBeenCalledTimes(2);
expect(onChangeSpy).toHaveBeenCalledTimes(2); expect(onChangeSpy).toHaveBeenCalledWith(value, metadata1);
expect(onChangeSpy).toHaveBeenCalledWith(value, metadata1); expect(onChangeSpy).toHaveBeenCalledWith(value, metadata2);
expect(onChangeSpy).toHaveBeenCalledWith(value, metadata2); });
}); });
}); });
}); });
});

Some files were not shown because too many files have changed in this diff Show More