add markdown editor

This commit is contained in:
Simon Larsen 2022-08-25 18:41:03 +01:00
parent f6a16038f5
commit 3d279d1327
No known key found for this signature in database
GPG Key ID: AB45983AA9C81CDE
4 changed files with 275 additions and 171 deletions

View File

@ -38,6 +38,7 @@ import Route from 'Common/Types/API/Route';
import Exception from 'Common/Types/Exception/Exception';
import HashedString from 'Common/Types/HashedString';
import Input from '../Input/Input';
import Markdown from '../Markdown.tsx/Markdown';
export const DefaultValidateFunction: Function = (
_values: FormValues<JSONObject>
@ -105,7 +106,7 @@ const BasicForm: Function = <T extends Object>(
if (props.showAsColumns && props.showAsColumns > 2) {
throw new BadDataException(
'showAsCOlumns should be <= 2. It is currently ' +
props.showAsColumns
props.showAsColumns
);
}
@ -153,7 +154,7 @@ const BasicForm: Function = <T extends Object>(
placeholder={field.placeholder || ''}
initialValue={
initialValues &&
(initialValues as any)[fieldName]
(initialValues as any)[fieldName]
? (initialValues as any)[fieldName]
: ''
}
@ -165,45 +166,45 @@ const BasicForm: Function = <T extends Object>(
{(field.fieldType === FormFieldSchemaType.Dropdown ||
field.fieldType ===
FormFieldSchemaType.MultiSelectDropdown) && (
<Field name={fieldName}>
{({ form }: any) => {
return (
<Dropdown
onChange={async (
value:
| DropdownValue
| Array<DropdownValue>
) => {
await form.setFieldValue(
fieldName,
value,
true
);
}}
onBlur={async () => {
await form.setFieldTouched(
fieldName,
true
);
}}
isMultiSelect={
field.fieldType ===
FormFieldSchemaType.MultiSelectDropdown
}
options={field.dropdownOptions || []}
placeholder={field.placeholder || ''}
initialValue={
initialValues &&
(initialValues as any)[fieldName]
? (initialValues as any)[fieldName]
: ''
}
/>
);
}}
</Field>
)}
FormFieldSchemaType.MultiSelectDropdown) && (
<Field name={fieldName}>
{({ form }: any) => {
return (
<Dropdown
onChange={async (
value:
| DropdownValue
| Array<DropdownValue>
) => {
await form.setFieldValue(
fieldName,
value,
true
);
}}
onBlur={async () => {
await form.setFieldTouched(
fieldName,
true
);
}}
isMultiSelect={
field.fieldType ===
FormFieldSchemaType.MultiSelectDropdown
}
options={field.dropdownOptions || []}
placeholder={field.placeholder || ''}
initialValue={
initialValues &&
(initialValues as any)[fieldName]
? (initialValues as any)[fieldName]
: ''
}
/>
);
}}
</Field>
)}
{field.fieldType === FormFieldSchemaType.LongText && (
<Field name={fieldName}>
@ -226,10 +227,45 @@ const BasicForm: Function = <T extends Object>(
}}
initialValue={
initialValues &&
(initialValues as any)[fieldName]
(initialValues as any)[fieldName]
? (initialValues as any)[
fieldName
]
fieldName
]
: ''
}
placeholder={field.placeholder || ''}
/>
</>
);
}}
</Field>
)}
{field.fieldType === FormFieldSchemaType.Markdown && (
<Field name={fieldName}>
{({ form }: any) => {
return (
<>
<Markdown
onChange={async (text: string) => {
await form.setFieldValue(
fieldName,
text,
true
);
}}
onBlur={async () => {
await form.setFieldTouched(
fieldName,
true
);
}}
initialValue={
initialValues &&
(initialValues as any)[fieldName]
? (initialValues as any)[
fieldName
]
: ''
}
placeholder={field.placeholder || ''}
@ -261,10 +297,10 @@ const BasicForm: Function = <T extends Object>(
}}
initialValue={
initialValues &&
(initialValues as any)[fieldName]
(initialValues as any)[fieldName]
? (initialValues as any)[
fieldName
]
fieldName
]
: false
}
/>
@ -287,38 +323,38 @@ const BasicForm: Function = <T extends Object>(
field.fieldType === FormFieldSchemaType.Date ||
field.fieldType === FormFieldSchemaType.Port ||
field.fieldType === FormFieldSchemaType.PositveNumber) && (
<Field
tabIndex={index + 1}
name={fieldName}
disabled={isDisabled || field.disabled}
>
{({ form }: FieldProps) => {
return (
<Input
className="form-control"
type={fieldType as 'text'}
onChange={(text: string) => {
form.setFieldValue(
fieldName,
text,
true
);
}}
onBlur={() => {
form.setFieldTouched(fieldName, true);
}}
initialValue={
initialValues &&
(initialValues as any)[fieldName]
? (initialValues as any)[fieldName]
: ''
}
placeholder={field.placeholder || ''}
/>
);
}}
</Field>
)}
<Field
tabIndex={index + 1}
name={fieldName}
disabled={isDisabled || field.disabled}
>
{({ form }: FieldProps) => {
return (
<Input
className="form-control"
type={fieldType as 'text'}
onChange={(text: string) => {
form.setFieldValue(
fieldName,
text,
true
);
}}
onBlur={() => {
form.setFieldTouched(fieldName, true);
}}
initialValue={
initialValues &&
(initialValues as any)[fieldName]
? (initialValues as any)[fieldName]
: ''
}
placeholder={field.placeholder || ''}
/>
);
}}
</Field>
)}
<ErrorMessage
className="mt-1 text-danger"
@ -340,17 +376,15 @@ const BasicForm: Function = <T extends Object>(
if (field.validation) {
if (field.validation.minLength) {
if (content.trim().length < field.validation?.minLength) {
return `${field.title || name} cannot be less than ${
field.validation.minLength
} characters.`;
return `${field.title || name} cannot be less than ${field.validation.minLength
} characters.`;
}
}
if (field.validation.maxLength) {
if (content.trim().length > field.validation?.maxLength) {
return `${field.title || name} cannot be more than ${
field.validation.maxLength
} characters.`;
return `${field.title || name} cannot be more than ${field.validation.maxLength
} characters.`;
}
}
@ -392,17 +426,15 @@ const BasicForm: Function = <T extends Object>(
if (field.validation.maxValue) {
if (content > field.validation?.maxValue) {
return `${field.title || name} should not be more than ${
field.validation?.maxValue
}.`;
return `${field.title || name} should not be more than ${field.validation?.maxValue
}.`;
}
}
if (field.validation.minValue) {
if (content < field.validation?.minValue) {
return `${field.title || name} should not be less than ${
field.validation?.minValue
}.`;
return `${field.title || name} should not be less than ${field.validation?.minValue
}.`;
}
}
}
@ -429,7 +461,7 @@ const BasicForm: Function = <T extends Object>(
field.validation?.toMatchField &&
entity[field.validation?.toMatchField] &&
(entity[field.validation?.toMatchField] as string).trim() !==
content.trim()
content.trim()
) {
return `${field.title} should match ${field.validation?.toMatchField}`;
}
@ -483,79 +515,79 @@ const BasicForm: Function = <T extends Object>(
values: FormValues<T>
) => void | object | Promise<FormikErrors<FormValues<T>>>) &
Function = (values: FormValues<T>): FormikErrors<FormValues<T>> => {
const errors: JSONObject = {};
const entries: JSONObject = { ...values } as JSONObject;
const errors: JSONObject = {};
const entries: JSONObject = { ...values } as JSONObject;
for (const field of props.fields) {
const name: string = field.overideFieldKey
? field.overideFieldKey
: (Object.keys(field.field)[0] as string);
for (const field of props.fields) {
const name: string = field.overideFieldKey
? field.overideFieldKey
: (Object.keys(field.field)[0] as string);
if (name in entries) {
const content: string | undefined = entries[name]?.toString();
if (name in entries) {
const content: string | undefined = entries[name]?.toString();
// Check Required fields.
const resultRequired: string | null = validateRequired(
content,
field
);
if (resultRequired) {
errors[name] = resultRequired;
// Check Required fields.
const resultRequired: string | null = validateRequired(
content,
field
);
if (resultRequired) {
errors[name] = resultRequired;
}
// Check for valid email data.
const resultValidateData: string | null = validateData(
content,
field
);
if (resultValidateData) {
errors[name] = resultValidateData;
}
const resultMatch: string | null = validateMatchField(
content,
field,
entries
);
if (resultMatch) {
errors[name] = resultMatch;
}
// check for length of content
const result: string | null = validateLength(content, field);
if (result) {
errors[name] = result;
}
// check for date
const resultDate: string | null = validateDate(content, field);
if (resultDate) {
errors[name] = resultDate;
}
// check for length of content
const resultMaxMinValue: string | null =
validateMaxValueAndMinValue(content, field);
if (resultMaxMinValue) {
errors[name] = resultMaxMinValue;
}
} else if (field.required) {
errors[name] = `${field.title || name} is required.`;
}
// Check for valid email data.
const resultValidateData: string | null = validateData(
content,
field
);
if (resultValidateData) {
errors[name] = resultValidateData;
}
const resultMatch: string | null = validateMatchField(
content,
field,
entries
);
if (resultMatch) {
errors[name] = resultMatch;
}
// check for length of content
const result: string | null = validateLength(content, field);
if (result) {
errors[name] = result;
}
// check for date
const resultDate: string | null = validateDate(content, field);
if (resultDate) {
errors[name] = resultDate;
}
// check for length of content
const resultMaxMinValue: string | null =
validateMaxValueAndMinValue(content, field);
if (resultMaxMinValue) {
errors[name] = resultMaxMinValue;
}
} else if (field.required) {
errors[name] = `${field.title || name} is required.`;
}
}
let customValidateResult: JSONObject = {};
let customValidateResult: JSONObject = {};
if (props.onValidate) {
customValidateResult = props.onValidate(values);
}
if (props.onValidate) {
customValidateResult = props.onValidate(values);
}
return { ...errors, ...customValidateResult } as FormikErrors<
FormValues<T>
>;
};
return { ...errors, ...customValidateResult } as FormikErrors<
FormValues<T>
>;
};
const formRef: any = useRef<any>(null);
@ -669,21 +701,19 @@ const BasicForm: Function = <T extends Object>(
<div className={`col-lg-12 flex`}>
<div
className={`col-lg-${
12 / (props.showAsColumns || 1)
} ${
(props.showAsColumns || 1) > 1
className={`col-lg-${12 / (props.showAsColumns || 1)
} ${(props.showAsColumns || 1) > 1
? 'pr-10'
: ''
}`}
}`}
>
{props.fields &&
props.fields.map(
(field: DataField<T>, i: number) => {
if (
i %
(props.showAsColumns ||
1) ===
(props.showAsColumns ||
1) ===
0
) {
return getFormField(
@ -698,13 +728,11 @@ const BasicForm: Function = <T extends Object>(
</div>
{(props.showAsColumns || 1) > 1 && (
<div
className={`col-lg-${
12 / (props.showAsColumns || 1)
} ${
(props.showAsColumns || 1) > 1
className={`col-lg-${12 / (props.showAsColumns || 1)
} ${(props.showAsColumns || 1) > 1
? 'pl-10'
: ''
}`}
}`}
>
{props.fields &&
props.fields.map(
@ -714,8 +742,8 @@ const BasicForm: Function = <T extends Object>(
) => {
if (
i %
(props.showAsColumns ||
1) !==
(props.showAsColumns ||
1) !==
0
) {
return getFormField(

View File

@ -17,6 +17,7 @@ enum FormFieldSchemaType {
Checkbox,
Port,
EncryptedText,
Markdown
}
export default FormFieldSchemaType;

View File

@ -0,0 +1,74 @@
import React, {
FunctionComponent,
ReactElement,
useEffect,
useState,
} from 'react';
import MDEditor from '@uiw/react-md-editor';
export interface ComponentProps {
initialValue?: undefined | string;
onClick?: undefined | (() => void);
placeholder?: undefined | string;
className?: undefined | string;
onChange?: undefined | ((value: string) => void);
value?: string | undefined;
onFocus?: (() => void) | undefined;
onBlur?: (() => void) | undefined;
}
const Markdown: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
const [value, setValue] = useState<string>('');
useEffect(() => {
if (props.initialValue) {
setValue(props.initialValue);
}
if (props.value) {
setValue(props.value);
}
}, []);
useEffect(() => {
setValue(props.value ? props.value : '');
}, [props.value]);
useEffect(() => {
if (props.initialValue) {
setValue(props.initialValue);
}
}, [props.initialValue]);
return (
<div
className={`flex ${props.className}`}
onClick={() => {
props.onClick && props.onClick();
props.onFocus && props.onFocus();
}}
>
<MDEditor
height={200}
value={value}
onBlur={() => {
if (props.onBlur) {
props.onBlur();
}
}}
placeholder={props.placeholder}
onChange={(text: string | undefined) => {
if (text) {
setValue(text);
if (props.onChange) {
props.onChange(text);
}
}
}} />
</div>
);
};
export default Markdown;

View File

@ -17,6 +17,7 @@ enum FieldType {
Actions = 'Actions',
Boolean = 'Boolean',
Entity = 'Entity',
Markdown = 'Markdown'
}
export default FieldType;