Signed-off-by: soupette <cyril@strapi.io>
This commit is contained in:
soupette 2021-09-22 09:10:40 +02:00
parent c8f74728c1
commit 94e456a9fb
7 changed files with 483 additions and 56 deletions

View File

@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { P } from '@strapi/parts/Text';
export const FieldError = ({ id, error, name }) => {
const { formatMessage } = useIntl();
const errorMessage = error ? formatMessage({ id: error, defaultMessage: error }) : '';
if (!error) {
return null;
}
return (
<P small id={`${id || name}-error`} textColor="danger600" data-strapi-field-error>
{errorMessage}
</P>
);
};
FieldError.defaultProps = {
id: undefined,
error: undefined,
};
FieldError.propTypes = {
error: PropTypes.string,
id: PropTypes.string,
name: PropTypes.string.isRequired,
};
export default FieldError;

View File

@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { P } from '@strapi/parts/Text';
export const Hint = ({ id, error, name, description }) => {
const { formatMessage } = useIntl();
const hint = description
? formatMessage(
{ id: description.id, defaultMessage: description.defaultMessage },
{ ...description.values }
)
: '';
if (!hint || error) {
return null;
}
return (
<P small id={`${id || name}-hint`} textColor="neutral600">
{hint}
</P>
);
};
Hint.defaultProps = {
id: undefined,
description: undefined,
error: undefined,
};
Hint.propTypes = {
description: PropTypes.shape({
id: PropTypes.string.isRequired,
defaultMessage: PropTypes.string.isRequired,
values: PropTypes.object,
}),
error: PropTypes.string,
id: PropTypes.string,
name: PropTypes.string.isRequired,
};
export default Hint;

View File

@ -0,0 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { useIntl } from 'react-intl';
import { Text } from '@strapi/parts/Text';
import { Box } from '@strapi/parts/Box';
import { Row } from '@strapi/parts/Row';
const LabelAction = styled(Box)`
svg path {
fill: ${({ theme }) => theme.colors.neutral500};
}
`;
const Label = ({ id, intlLabel, labelAction, name }) => {
const { formatMessage } = useIntl();
const label = intlLabel?.id
? formatMessage(
{ id: intlLabel.id, defaultMessage: intlLabel.defaultMessage },
{ ...intlLabel.values }
)
: name;
return (
<Row>
<Text textColor="neutral800" htmlFor={id || name} small bold as="label">
{label}
</Text>
{labelAction && <LabelAction paddingLeft={1}>{labelAction}</LabelAction>}
</Row>
);
};
Label.defaultProps = {
id: undefined,
intlLabel: undefined,
labelAction: undefined,
};
Label.propTypes = {
id: PropTypes.string,
intlLabel: PropTypes.shape({
id: PropTypes.string.isRequired,
defaultMessage: PropTypes.string.isRequired,
values: PropTypes.object,
}),
labelAction: PropTypes.element,
name: PropTypes.string.isRequired,
};
export default Label;

View File

@ -1,30 +1,314 @@
import styled from 'styled-components'; import styled from 'styled-components';
import { Box } from '@strapi/parts/Box';
const Wrapper = styled.div` /* eslint-disable */
position: relative; /* stylelint-disable */
margin-bottom: 3px; const EditorWrapper = styled.div`
line-height: 18px; cursor: ${({ disabled }) => (disabled ? 'not-allowed !important' : 'auto')};
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'initial')}; /* BASICS */
.CodeMirror {
font-size: 13px !important;
}
> div {
border-radius: 3px;
> div:last-of-type {
min-height: 315px;
max-height: 635px;
font-weight: 500;
font-size: 1.3rem !important;
}
}
.colored { .colored {
background-color: yellow; background-color: yellow;
color: black !important; color: black !important;
} }
> div {
border-radius: 3px;
> div:last-of-type {
min-height: 253px;
max-height: 506px;
}
}
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-size: ${14 / 16}rem;
color: ${({ theme }) => theme.colors.neutral800};
direction: ltr;
}
/* PADDING */
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {
}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
white-space: nowrap;
}
.CodeMirror-guttermarker {
color: black;
}
.CodeMirror-guttermarker-subtle {
color: #999;
}
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
/* z-index: 1; */
}
.cm-fat-cursor-mark {
background-color: rgba(20, 255, 20, 0.5);
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {
}
.cm-tab {
display: inline-block;
text-decoration: inherit;
}
.CodeMirror-rulers {
position: absolute;
left: 0;
right: 0;
top: -50px;
bottom: 0;
overflow: hidden;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
top: 0;
bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 50px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -50px;
margin-right: -50px;
padding-bottom: 50px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 50px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar,
.CodeMirror-hscrollbar,
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
position: absolute;
/* z-index: 6; */
display: none;
outline: none;
}
.CodeMirror-vscrollbar {
right: 0;
top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0;
left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0;
bottom: 0;
}
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
/* Reset some styles that the rest of the page might have set */
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
-moz-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
/* z-index: 2; */
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
.CodeMirror pre.CodeMirror-line-like {
z-index: 2;
}
.CodeMirror-wrap pre.CodeMirror-line,
.CodeMirror-wrap pre.CodeMirror-line-like {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
/* z-index: 2; */
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-widget {
}
.CodeMirror-rtl pre {
direction: rtl;
}
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre {
position: static;
}
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
+ div {
z-index: 0 !important;
}
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected {
background: ${({ theme }) => theme.colors.neutral200};
/* z-index: -10; */
}
.CodeMirror-crosshair {
cursor: crosshair;
}
/* Used to force a border model for a node */
.cm-force-border {
padding-right: 0.1px;
}
/* See issue #2901 */
.cm-tab-wrap-hack:after {
content: '';
}
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext {
background: none;
}
.cm-s-solarized.CodeMirror {
box-shadow: none;
border-radius: ${({ theme }) => theme.borderRadius};
}
`; `;
export default Wrapper; const StyledBox = styled(Box)`
border-radius: ${({ theme }) => theme.borderRadius};
border: 1px solid ${({ theme, error }) => (error ? theme.colors.danger600 : 'transparent')};
`;
export { EditorWrapper, StyledBox };

View File

@ -7,10 +7,13 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cm from 'codemirror'; import cm from 'codemirror';
import trimStart from 'lodash/trimStart';
import { trimStart } from 'lodash'; import { Stack } from '@strapi/parts/Stack';
import jsonlint from './jsonlint'; import jsonlint from './jsonlint';
import Wrapper from './components'; import { EditorWrapper, StyledBox } from './components';
import Hint from './Hint';
import Label from './Label';
import FieldError from './FieldError';
const WAIT = 600; const WAIT = 600;
const stringify = JSON.stringify; const stringify = JSON.stringify;
@ -60,7 +63,6 @@ class InputJSON extends React.Component {
fontSize: '13px', fontSize: '13px',
}); });
this.codeMirror.on('change', this.handleChange); this.codeMirror.on('change', this.handleChange);
this.codeMirror.on('blur', this.handleBlur);
this.setSize(); this.setSize();
this.setInitValue(); this.setInitValue();
@ -112,21 +114,6 @@ class InputJSON extends React.Component {
this.setState({ markedText }); this.setState({ markedText });
}; };
handleBlur = ({ target }) => {
const { name, onBlur } = this.props;
if (target === undefined) {
// codemirror catches multiple events
onBlur({
target: {
name,
type: 'json',
value: this.getValue(),
},
});
}
};
handleChange = (doc, change) => { handleChange = (doc, change) => {
if (change.origin === 'setValue') { if (change.origin === 'setValue') {
return; return;
@ -172,24 +159,61 @@ class InputJSON extends React.Component {
} }
return ( return (
<Wrapper disabled={this.props.disabled}> <Stack size={1}>
<textarea ref={this.editor} autoComplete="off" id={this.props.name} defaultValue="" /> <Label
</Wrapper> intlLabel={this.props.intlLabel}
labelAction={this.props.labelAction}
name={this.props.name}
/>
<StyledBox error={this.props.error}>
<EditorWrapper disabled={this.props.disabled}>
<textarea
ref={this.editor}
autoComplete="off"
id={this.props.id || this.props.name}
defaultValue=""
/>
</EditorWrapper>
</StyledBox>
<Hint
description={this.props.description}
name={this.props.name}
id={this.props.id}
error={this.props.error}
/>
<FieldError id={this.props.id} name={this.props.name} error={this.props.error} />
</Stack>
); );
} }
} }
InputJSON.defaultProps = { InputJSON.defaultProps = {
description: null,
disabled: false, disabled: false,
onBlur: () => {}, id: undefined,
error: undefined,
intlLabel: undefined,
labelAction: undefined,
onChange: () => {}, onChange: () => {},
value: null, value: null,
}; };
InputJSON.propTypes = { InputJSON.propTypes = {
description: PropTypes.shape({
id: PropTypes.string.isRequired,
defaultMessage: PropTypes.string.isRequired,
values: PropTypes.object,
}),
disabled: PropTypes.bool, disabled: PropTypes.bool,
error: PropTypes.string,
id: PropTypes.string,
intlLabel: PropTypes.shape({
id: PropTypes.string.isRequired,
defaultMessage: PropTypes.string.isRequired,
values: PropTypes.object,
}),
labelAction: PropTypes.element,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
onBlur: PropTypes.func,
onChange: PropTypes.func, onChange: PropTypes.func,
value: PropTypes.any, value: PropTypes.any,
}; };

View File

@ -10,7 +10,7 @@ import { useContentTypeLayout } from '../../hooks';
import { getFieldName } from '../../utils'; import { getFieldName } from '../../utils';
import Wysiwyg from '../Wysiwyg'; import Wysiwyg from '../Wysiwyg';
import GenericInput from './GenericInput'; import GenericInput from './GenericInput';
// import InputJSONWithErrors from '../InputJSONWithErrors'; import InputJSON from '../InputJSON';
// import SelectWrapper from '../SelectWrapper'; // import SelectWrapper from '../SelectWrapper';
// import WysiwygWithErrors from '../WysiwygWithErrors'; // import WysiwygWithErrors from '../WysiwygWithErrors';
// import InputUID from '../InputUID'; // import InputUID from '../InputUID';
@ -31,7 +31,7 @@ function Inputs({
keys, keys,
labelAction, labelAction,
metadatas, metadatas,
onBlur,
onChange, onChange,
readableFields, readableFields,
shouldNotRunValidations, shouldNotRunValidations,
@ -207,19 +207,15 @@ function Inputs({
// {...metadatas} // {...metadatas}
autoComplete="new-password" autoComplete="new-password"
intlLabel={{ id: label, defaultMessage: label }} intlLabel={{ id: label, defaultMessage: label }}
// autoFocus={autoFocus}
description={description ? { id: description, defaultMessage: description } : null} description={description ? { id: description, defaultMessage: description } : null}
disabled={shouldDisableField} disabled={shouldDisableField}
error={errorId} error={errorId}
// inputDescription={description}
labelAction={labelAction} labelAction={labelAction}
contentTypeUID={currentContentTypeLayout.uid} contentTypeUID={currentContentTypeLayout.uid}
customInputs={{ customInputs={{
// json: InputJSONWithErrors,
// wysiwyg: WysiwygWithErrors,
// uid: InputUID, // uid: InputUID,
// ...fields, // ...fields,
json: () => <div>TODO json</div>, json: InputJSON,
media: () => <div>TODO media</div>, media: () => <div>TODO media</div>,
uid: () => <div>TODO uid</div>, uid: () => <div>TODO uid</div>,
// wysiwyg: () => <div>TODO wysiwyg</div>, // wysiwyg: () => <div>TODO wysiwyg</div>,
@ -228,7 +224,6 @@ function Inputs({
multiple={fieldSchema.multiple || false} multiple={fieldSchema.multiple || false}
attribute={fieldSchema} attribute={fieldSchema}
name={keys} name={keys}
onBlur={onBlur}
onChange={onChange} onChange={onChange}
options={options} options={options}
placeholder={placeholder ? { id: placeholder, defaultMessage: placeholder } : null} placeholder={placeholder ? { id: placeholder, defaultMessage: placeholder } : null}
@ -244,7 +239,6 @@ function Inputs({
Inputs.defaultProps = { Inputs.defaultProps = {
formErrors: {}, formErrors: {},
labelAction: undefined, labelAction: undefined,
onBlur: null,
queryInfos: {}, queryInfos: {},
value: null, value: null,
}; };
@ -257,7 +251,6 @@ Inputs.propTypes = {
isCreatingEntry: PropTypes.bool.isRequired, isCreatingEntry: PropTypes.bool.isRequired,
labelAction: PropTypes.element, labelAction: PropTypes.element,
metadatas: PropTypes.object.isRequired, metadatas: PropTypes.object.isRequired,
onBlur: PropTypes.func,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
readableFields: PropTypes.array.isRequired, readableFields: PropTypes.array.isRequired,
shouldNotRunValidations: PropTypes.bool.isRequired, shouldNotRunValidations: PropTypes.bool.isRequired,

View File

@ -125,7 +125,7 @@ const Wysiwyg = ({
return ( return (
<> <>
<Row cols="auto auto 1fr" gap={1}> <Row>
<ButtonText>{label}</ButtonText> <ButtonText>{label}</ButtonText>
{labelAction && <LabelAction paddingLeft={1}>{labelAction}</LabelAction>} {labelAction && <LabelAction paddingLeft={1}>{labelAction}</LabelAction>}
</Row> </Row>