mirror of
https://github.com/strapi/strapi.git
synced 2025-11-03 11:25:17 +00:00
Add JSON
Signed-off-by: soupette <cyril@strapi.io>
This commit is contained in:
parent
c8f74728c1
commit
94e456a9fb
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -1,30 +1,314 @@
|
||||
import styled from 'styled-components';
|
||||
import { Box } from '@strapi/parts/Box';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
margin-bottom: 3px;
|
||||
line-height: 18px;
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'initial')};
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
/* eslint-disable */
|
||||
/* stylelint-disable */
|
||||
const EditorWrapper = styled.div`
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed !important' : 'auto')};
|
||||
/* BASICS */
|
||||
|
||||
.colored {
|
||||
background-color: yellow;
|
||||
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 };
|
||||
|
||||
@ -7,10 +7,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cm from 'codemirror';
|
||||
|
||||
import { trimStart } from 'lodash';
|
||||
import trimStart from 'lodash/trimStart';
|
||||
import { Stack } from '@strapi/parts/Stack';
|
||||
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 stringify = JSON.stringify;
|
||||
@ -60,7 +63,6 @@ class InputJSON extends React.Component {
|
||||
fontSize: '13px',
|
||||
});
|
||||
this.codeMirror.on('change', this.handleChange);
|
||||
this.codeMirror.on('blur', this.handleBlur);
|
||||
|
||||
this.setSize();
|
||||
this.setInitValue();
|
||||
@ -112,21 +114,6 @@ class InputJSON extends React.Component {
|
||||
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) => {
|
||||
if (change.origin === 'setValue') {
|
||||
return;
|
||||
@ -172,24 +159,61 @@ class InputJSON extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper disabled={this.props.disabled}>
|
||||
<textarea ref={this.editor} autoComplete="off" id={this.props.name} defaultValue="" />
|
||||
</Wrapper>
|
||||
<Stack size={1}>
|
||||
<Label
|
||||
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 = {
|
||||
description: null,
|
||||
disabled: false,
|
||||
onBlur: () => {},
|
||||
id: undefined,
|
||||
error: undefined,
|
||||
intlLabel: undefined,
|
||||
labelAction: undefined,
|
||||
onChange: () => {},
|
||||
value: null,
|
||||
};
|
||||
|
||||
InputJSON.propTypes = {
|
||||
description: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
values: PropTypes.object,
|
||||
}),
|
||||
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,
|
||||
onBlur: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.any,
|
||||
};
|
||||
|
||||
@ -10,7 +10,7 @@ import { useContentTypeLayout } from '../../hooks';
|
||||
import { getFieldName } from '../../utils';
|
||||
import Wysiwyg from '../Wysiwyg';
|
||||
import GenericInput from './GenericInput';
|
||||
// import InputJSONWithErrors from '../InputJSONWithErrors';
|
||||
import InputJSON from '../InputJSON';
|
||||
// import SelectWrapper from '../SelectWrapper';
|
||||
// import WysiwygWithErrors from '../WysiwygWithErrors';
|
||||
// import InputUID from '../InputUID';
|
||||
@ -31,7 +31,7 @@ function Inputs({
|
||||
keys,
|
||||
labelAction,
|
||||
metadatas,
|
||||
onBlur,
|
||||
|
||||
onChange,
|
||||
readableFields,
|
||||
shouldNotRunValidations,
|
||||
@ -207,19 +207,15 @@ function Inputs({
|
||||
// {...metadatas}
|
||||
autoComplete="new-password"
|
||||
intlLabel={{ id: label, defaultMessage: label }}
|
||||
// autoFocus={autoFocus}
|
||||
description={description ? { id: description, defaultMessage: description } : null}
|
||||
disabled={shouldDisableField}
|
||||
error={errorId}
|
||||
// inputDescription={description}
|
||||
labelAction={labelAction}
|
||||
contentTypeUID={currentContentTypeLayout.uid}
|
||||
customInputs={{
|
||||
// json: InputJSONWithErrors,
|
||||
// wysiwyg: WysiwygWithErrors,
|
||||
// uid: InputUID,
|
||||
// ...fields,
|
||||
json: () => <div>TODO json</div>,
|
||||
json: InputJSON,
|
||||
media: () => <div>TODO media</div>,
|
||||
uid: () => <div>TODO uid</div>,
|
||||
// wysiwyg: () => <div>TODO wysiwyg</div>,
|
||||
@ -228,7 +224,6 @@ function Inputs({
|
||||
multiple={fieldSchema.multiple || false}
|
||||
attribute={fieldSchema}
|
||||
name={keys}
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
options={options}
|
||||
placeholder={placeholder ? { id: placeholder, defaultMessage: placeholder } : null}
|
||||
@ -244,7 +239,6 @@ function Inputs({
|
||||
Inputs.defaultProps = {
|
||||
formErrors: {},
|
||||
labelAction: undefined,
|
||||
onBlur: null,
|
||||
queryInfos: {},
|
||||
value: null,
|
||||
};
|
||||
@ -257,7 +251,6 @@ Inputs.propTypes = {
|
||||
isCreatingEntry: PropTypes.bool.isRequired,
|
||||
labelAction: PropTypes.element,
|
||||
metadatas: PropTypes.object.isRequired,
|
||||
onBlur: PropTypes.func,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
readableFields: PropTypes.array.isRequired,
|
||||
shouldNotRunValidations: PropTypes.bool.isRequired,
|
||||
|
||||
@ -125,7 +125,7 @@ const Wysiwyg = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row cols="auto auto 1fr" gap={1}>
|
||||
<Row>
|
||||
<ButtonText>{label}</ButtonText>
|
||||
{labelAction && <LabelAction paddingLeft={1}>{labelAction}</LabelAction>}
|
||||
</Row>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user