mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-01 11:09:14 +00:00
* Fix #3202 UI Markdown text editor is broken * Move markdownwithpreview component to rich-text-editor folder * Remove unused component files * Fixed component import error on test files * Fix Failing test * Remove Markdownwith preview component. * Fix failing tests * Add support for readonly * Add unit test Co-authored-by: Vivek Ratnavel Subramanian <vivekratnavel90@gmail.com>
This commit is contained in:
parent
4dce0a061a
commit
d429f0b868
@ -23,6 +23,7 @@
|
||||
"@fortawesome/react-fontawesome": "^0.1.17",
|
||||
"@okta/okta-auth-js": "^6.1.0",
|
||||
"@okta/okta-react": "^6.4.2",
|
||||
"@toast-ui/react-editor": "^3.1.3",
|
||||
"autoprefixer": "^9.8.6",
|
||||
"axios": "^0.21.1",
|
||||
"babel-plugin-named-asset-import": "^0.3.6",
|
||||
|
||||
@ -5,7 +5,7 @@ import ColumnTestForm from './ColumnTestForm';
|
||||
|
||||
const mockFunction = jest.fn();
|
||||
|
||||
jest.mock('../../common/editor/MarkdownWithPreview', () => {
|
||||
jest.mock('../../common/rich-text-editor/RichTextEditor', () => {
|
||||
return jest.fn().mockReturnValue(<div>MarkdownWithPreview component</div>);
|
||||
});
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ import {
|
||||
import SVGIcons from '../../../utils/SvgUtils';
|
||||
import { getDataTypeString } from '../../../utils/TableUtils';
|
||||
import { Button } from '../../buttons/Button/Button';
|
||||
import MarkdownWithPreview from '../../common/editor/MarkdownWithPreview';
|
||||
import RichTextEditor from '../../common/rich-text-editor/RichTextEditor';
|
||||
|
||||
type Props = {
|
||||
data?: ColumnTest;
|
||||
@ -657,10 +657,10 @@ const ColumnTestForm = ({
|
||||
htmlFor="description">
|
||||
Description:
|
||||
</label>
|
||||
<MarkdownWithPreview
|
||||
<RichTextEditor
|
||||
data-testid="description"
|
||||
initialValue={description}
|
||||
ref={markdownRef}
|
||||
value={description}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import TableTestForm from './TableTestForm';
|
||||
|
||||
const mockFunction = jest.fn();
|
||||
|
||||
jest.mock('../../common/editor/MarkdownWithPreview', () => {
|
||||
jest.mock('../../common/rich-text-editor/RichTextEditor', () => {
|
||||
return jest.fn().mockReturnValue(<div>MarkdownWithPreview component</div>);
|
||||
});
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ import {
|
||||
requiredField,
|
||||
} from '../../../utils/CommonUtils';
|
||||
import { Button } from '../../buttons/Button/Button';
|
||||
import MarkdownWithPreview from '../../common/editor/MarkdownWithPreview';
|
||||
import RichTextEditor from '../../common/rich-text-editor/RichTextEditor';
|
||||
|
||||
type Props = {
|
||||
data?: TableTest;
|
||||
@ -319,10 +319,10 @@ const TableTestForm = ({
|
||||
htmlFor="description">
|
||||
Description:
|
||||
</label>
|
||||
<MarkdownWithPreview
|
||||
<RichTextEditor
|
||||
data-testid="description"
|
||||
initialValue={description}
|
||||
ref={markdownRef}
|
||||
value={description}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ import {
|
||||
requiredField,
|
||||
} from '../../utils/CommonUtils';
|
||||
import { Button } from '../buttons/Button/Button';
|
||||
import MarkdownWithPreview from '../common/editor/MarkdownWithPreview';
|
||||
import RichTextEditor from '../common/rich-text-editor/RichTextEditor';
|
||||
import PageLayout from '../containers/PageLayout';
|
||||
import Loader from '../Loader/Loader';
|
||||
import ReviewerModal from '../Modals/ReviewerModal/ReviewerModal.component';
|
||||
@ -212,11 +212,11 @@ const AddGlossary = ({
|
||||
htmlFor="description">
|
||||
Description:
|
||||
</label>
|
||||
<MarkdownWithPreview
|
||||
<RichTextEditor
|
||||
data-testid="description"
|
||||
initialValue={description}
|
||||
readonly={!allowAccess}
|
||||
ref={markdownRef}
|
||||
value={description}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
|
||||
@ -32,6 +32,10 @@ jest.mock('../common/rich-text-editor/RichTextEditorPreviewer', () => {
|
||||
return jest.fn().mockReturnValue(<p>RichTextEditorPreviewer</p>);
|
||||
});
|
||||
|
||||
jest.mock('../common/rich-text-editor/RichTextEditor', () => {
|
||||
return jest.fn().mockReturnValue(<p>RichTextEditor</p>);
|
||||
});
|
||||
|
||||
jest.mock('../../axiosAPIs/glossaryAPI', () => ({
|
||||
addGlossaries: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
@ -31,7 +31,7 @@ import {
|
||||
} from '../../utils/CommonUtils';
|
||||
import SVGIcons from '../../utils/SvgUtils';
|
||||
import { Button } from '../buttons/Button/Button';
|
||||
import MarkdownWithPreview from '../common/editor/MarkdownWithPreview';
|
||||
import RichTextEditor from '../common/rich-text-editor/RichTextEditor';
|
||||
import PageLayout from '../containers/PageLayout';
|
||||
import Loader from '../Loader/Loader';
|
||||
import RelatedTermsModal from '../Modals/RelatedTermsModal/RelatedTermsModal';
|
||||
@ -325,11 +325,11 @@ const AddGlossaryTerm = ({
|
||||
htmlFor="description">
|
||||
Description:
|
||||
</label>
|
||||
<MarkdownWithPreview
|
||||
<RichTextEditor
|
||||
data-testid="description"
|
||||
initialValue={description}
|
||||
readonly={!allowAccess}
|
||||
ref={markdownRef}
|
||||
value={description}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
|
||||
@ -40,6 +40,10 @@ jest.mock('../common/rich-text-editor/RichTextEditorPreviewer', () => {
|
||||
return jest.fn().mockReturnValue(<p>RichTextEditorPreviewer</p>);
|
||||
});
|
||||
|
||||
jest.mock('../common/rich-text-editor/RichTextEditor', () => {
|
||||
return jest.fn().mockReturnValue(<p>RichTextEditor</p>);
|
||||
});
|
||||
|
||||
const mockOnCancel = jest.fn();
|
||||
const mockOnSave = jest.fn();
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ import {
|
||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||
import { Button } from '../buttons/Button/Button';
|
||||
import CopyToClipboardButton from '../buttons/CopyToClipboardButton/CopyToClipboardButton';
|
||||
import MarkdownWithPreview from '../common/editor/MarkdownWithPreview';
|
||||
import RichTextEditor from '../common/rich-text-editor/RichTextEditor';
|
||||
import PageLayout from '../containers/PageLayout';
|
||||
import DropDown from '../dropdown/DropDown';
|
||||
import Loader from '../Loader/Loader';
|
||||
@ -502,11 +502,11 @@ const AddWebhook: FunctionComponent<AddWebhookProps> = ({
|
||||
htmlFor="description">
|
||||
Description:
|
||||
</label>
|
||||
<MarkdownWithPreview
|
||||
<RichTextEditor
|
||||
data-testid="description"
|
||||
initialValue={description}
|
||||
readonly={!allowAccess}
|
||||
ref={markdownRef}
|
||||
value={description}
|
||||
/>
|
||||
</Field>
|
||||
<Field>
|
||||
|
||||
@ -23,7 +23,7 @@ import { EntityReference as UserTeams } from '../../generated/entity/teams/user'
|
||||
import jsonData from '../../jsons/en';
|
||||
import { errorMsg, requiredField } from '../../utils/CommonUtils';
|
||||
import { Button } from '../buttons/Button/Button';
|
||||
import MarkdownWithPreview from '../common/editor/MarkdownWithPreview';
|
||||
import RichTextEditor from '../common/rich-text-editor/RichTextEditor';
|
||||
import PageLayout from '../containers/PageLayout';
|
||||
import DropDown from '../dropdown/DropDown';
|
||||
import { DropDownListItem } from '../dropdown/types';
|
||||
@ -246,7 +246,7 @@ const CreateUser = ({
|
||||
<label className="tw-block tw-form-label tw-mb-0" htmlFor="description">
|
||||
Description:
|
||||
</label>
|
||||
<MarkdownWithPreview ref={markdownRef} value={description} />
|
||||
<RichTextEditor initialValue={description} ref={markdownRef} />
|
||||
</Field>
|
||||
<Field>
|
||||
<label className="tw-block tw-form-label tw-mb-0">Teams:</label>
|
||||
|
||||
@ -33,7 +33,7 @@ jest.mock('../dropdown/DropDown', () => {
|
||||
return jest.fn().mockReturnValue(<p>Dropdown component</p>);
|
||||
});
|
||||
|
||||
jest.mock('../common/editor/MarkdownWithPreview', () => {
|
||||
jest.mock('../common/rich-text-editor/RichTextEditor', () => {
|
||||
return jest.fn().mockReturnValue(<p>MarkdownWithPreview component</p>);
|
||||
});
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ const mockOnCancel = jest.fn();
|
||||
|
||||
const mockServiceList: Array<ServiceDataObj> = [{ name: mockData.name }];
|
||||
|
||||
jest.mock('../../common/editor/MarkdownWithPreview', () => {
|
||||
jest.mock('../../common/rich-text-editor/RichTextEditor', () => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockReturnValue(<p data-testid="description">MarkdownWithPreview</p>);
|
||||
|
||||
@ -69,7 +69,7 @@ import {
|
||||
import SVGIcons, { Icons } from '../../../utils/SvgUtils';
|
||||
import { Button } from '../../buttons/Button/Button';
|
||||
import CronEditor from '../../common/CronEditor/CronEditor';
|
||||
import MarkdownWithPreview from '../../common/editor/MarkdownWithPreview';
|
||||
import RichTextEditor from '../../common/rich-text-editor/RichTextEditor';
|
||||
import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import IngestionStepper from '../../IngestionStepper/IngestionStepper.component';
|
||||
|
||||
@ -1681,10 +1681,10 @@ export const AddServiceModal: FunctionComponent<Props> = ({
|
||||
<label className="tw-block tw-form-label" htmlFor="description">
|
||||
Description:
|
||||
</label>
|
||||
<MarkdownWithPreview
|
||||
<RichTextEditor
|
||||
data-testid="description"
|
||||
initialValue={description}
|
||||
ref={markdownRef}
|
||||
value={description}
|
||||
/>
|
||||
</Field>
|
||||
</Fragment>
|
||||
|
||||
@ -19,7 +19,7 @@ const mockOnSave = jest.fn();
|
||||
const mockOnCancel = jest.fn();
|
||||
const mockValue = 'Test value';
|
||||
|
||||
jest.mock('../../common/editor/MarkdownWithPreview', () => {
|
||||
jest.mock('../../common/rich-text-editor/RichTextEditor', () => {
|
||||
return () => jest.fn().mockImplementation(() => mockValue);
|
||||
});
|
||||
|
||||
|
||||
@ -11,15 +11,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
faWindowMaximize,
|
||||
faWindowMinimize,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import classnames from 'classnames';
|
||||
import React, { FunctionComponent, useRef, useState } from 'react';
|
||||
import { Button } from '../../buttons/Button/Button';
|
||||
import MarkdownWithPreview from '../../common/editor/MarkdownWithPreview';
|
||||
import {
|
||||
faWindowMinimize,
|
||||
faWindowMaximize,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import RichTextEditor from '../../common/rich-text-editor/RichTextEditor';
|
||||
|
||||
type EditorContentRef = {
|
||||
getEditorContent: () => string;
|
||||
@ -98,11 +98,7 @@ export const ModalWithMarkdownEditor: FunctionComponent<Props> = ({
|
||||
)}
|
||||
</div>
|
||||
<div className="tw-modal-body tw-pt-0 tw-pb-1">
|
||||
<MarkdownWithPreview
|
||||
data-testid="markdown-with-preview"
|
||||
ref={markdownRef}
|
||||
value={value}
|
||||
/>
|
||||
<RichTextEditor initialValue={value} ref={markdownRef} />
|
||||
</div>
|
||||
<div className="tw-modal-footer">
|
||||
<Button
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.field-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
margin-left: 15px;
|
||||
border-left: 3px solid #d9ceee;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.field-child {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
.field-child::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 3px;
|
||||
background: #d9ceee;
|
||||
position: relative;
|
||||
top: 11px;
|
||||
}
|
||||
.field-child-icon {
|
||||
cursor: pointer;
|
||||
color: #7147e8;
|
||||
}
|
||||
.field-child-icon i {
|
||||
vertical-align: sub;
|
||||
margin-right: 2px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.field-label-name {
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.child-fields-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* margin-top: -11px; */
|
||||
}
|
||||
@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { CSSProperties, useCallback, useState } from 'react';
|
||||
import './SchemaTreeStructure.css';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPlusCircle, faMinusCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
type Props = {
|
||||
positions?: Array<number>;
|
||||
name: string;
|
||||
type: string;
|
||||
fields?: Array<Props>;
|
||||
isCollapsed?: boolean;
|
||||
};
|
||||
|
||||
export const getStyle = (type: string) => {
|
||||
const sharedStyles = {
|
||||
padding: '4px 8px',
|
||||
borderRadius: '5px',
|
||||
minWidth: '60px',
|
||||
textAlign: 'center',
|
||||
display: 'inline-block',
|
||||
};
|
||||
switch (type) {
|
||||
case 'double':
|
||||
return {
|
||||
backgroundColor: '#B02AAC33',
|
||||
color: '#B02AAC',
|
||||
...sharedStyles,
|
||||
};
|
||||
|
||||
case 'string':
|
||||
return {
|
||||
backgroundColor: '#51c41a33',
|
||||
color: '#51c41a',
|
||||
...sharedStyles,
|
||||
};
|
||||
|
||||
case 'int':
|
||||
return {
|
||||
backgroundColor: '#1890FF33',
|
||||
color: '#1890FF',
|
||||
...sharedStyles,
|
||||
};
|
||||
|
||||
default:
|
||||
return {
|
||||
backgroundColor: '#EEEAF8',
|
||||
...sharedStyles,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const SchemaTreeStructure = ({
|
||||
name,
|
||||
type,
|
||||
fields,
|
||||
isCollapsed = false,
|
||||
// to track position of element [L0,L1,L2,...Ln]
|
||||
positions = [],
|
||||
}: Props) => {
|
||||
const [showChildren, setShowChildren] = useState<boolean>(!isCollapsed);
|
||||
const flag = (fields ?? []).length > 0;
|
||||
|
||||
const showChildrenHandler = useCallback(() => {
|
||||
setShowChildren(!showChildren);
|
||||
}, [showChildren, setShowChildren]);
|
||||
|
||||
const getIcon = () => {
|
||||
return (
|
||||
flag &&
|
||||
(showChildren ? (
|
||||
<FontAwesomeIcon icon={faMinusCircle} />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faPlusCircle} />
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="field-wrapper"
|
||||
style={{ paddingLeft: flag ? '26px' : '0px' }}>
|
||||
<div
|
||||
className="field-child"
|
||||
style={{ marginLeft: flag ? '-26px' : '0px' }}>
|
||||
<p className="field-child-icon" onClick={showChildrenHandler}>
|
||||
{getIcon()}
|
||||
</p>
|
||||
<p className="field-label">
|
||||
<span style={getStyle(type) as CSSProperties}>{type}</span>
|
||||
<span className="field-label-name">{name}</span>
|
||||
</p>
|
||||
</div>
|
||||
{flag && showChildren && (
|
||||
<div className="child-fields-wrapper">
|
||||
{(fields ?? []).map((field, index) => (
|
||||
<SchemaTreeStructure
|
||||
isCollapsed
|
||||
key={index}
|
||||
positions={[...positions, index]}
|
||||
{...field}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SchemaTreeStructure;
|
||||
@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import RichTextEditor from '../rich-text-editor/RichTextEditor';
|
||||
import { editorRef } from '../rich-text-editor/RichTextEditor.interface';
|
||||
import RichTextEditorPreviewer from '../rich-text-editor/RichTextEditorPreviewer';
|
||||
|
||||
type EditorContentRef = {
|
||||
getEditorContent: (value: string) => string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
value: string;
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
const MarkdownWithPreview = forwardRef<editorRef, Props>(
|
||||
({ value, readonly }: Props, ref) => {
|
||||
const [activeTab, setActiveTab] = useState<number>(1);
|
||||
const [preview, setPreview] = useState<string>('');
|
||||
const [initValue, setInitValue] = useState<string>(value ?? '');
|
||||
|
||||
const editorRef = useRef<EditorContentRef>();
|
||||
const getTabClasses = (tab: number, activeTab: number) => {
|
||||
return (
|
||||
'tw-gh-tabs tw-cursor-pointer' + (activeTab === tab ? ' active' : '')
|
||||
);
|
||||
};
|
||||
|
||||
const updateInternalValue = () => {
|
||||
if (editorRef.current) {
|
||||
setInitValue(editorRef.current?.getEditorContent('markdown'));
|
||||
setPreview(editorRef.current?.getEditorContent('markdown'));
|
||||
}
|
||||
};
|
||||
|
||||
const getPreview = () => {
|
||||
if (preview.length < 1) {
|
||||
return 'Nothing to preview';
|
||||
}
|
||||
|
||||
return (
|
||||
<RichTextEditorPreviewer
|
||||
enableSeeMoreVariant={false}
|
||||
markdown={preview}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getEditorContent() {
|
||||
return activeTab === 2
|
||||
? initValue
|
||||
: editorRef.current?.getEditorContent('markdown');
|
||||
},
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
setInitValue(value ?? '');
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="tw-bg-transparent">
|
||||
<nav className="tw-flex tw-flex-row tw-gh-tabs-container tw-px-6">
|
||||
<p
|
||||
className={getTabClasses(1, activeTab)}
|
||||
data-testid="tab"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setActiveTab(1);
|
||||
}}>
|
||||
{'Write '}
|
||||
</p>
|
||||
<p
|
||||
className={getTabClasses(2, activeTab)}
|
||||
data-testid="tab"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setActiveTab(2);
|
||||
updateInternalValue();
|
||||
}}>
|
||||
{'View '}
|
||||
</p>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="tw-my-5 tw-bg-white">
|
||||
{activeTab === 1 && (
|
||||
<RichTextEditor
|
||||
format="markdown"
|
||||
initvalue={initValue}
|
||||
readonly={readonly}
|
||||
ref={editorRef}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 2 && (
|
||||
<div className="editor-wrapper tw-flex tw-flex-col tw-flex-1 tw-overflow-y-auto tw-p-3 tw-pl-6 tw-min-h-32 tw-border tw-border-main tw-rounded tw-max-h-none">
|
||||
{getPreview()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
MarkdownWithPreview.displayName = 'MarkdownWithPreview';
|
||||
|
||||
export default MarkdownWithPreview;
|
||||
File diff suppressed because one or more lines are too long
@ -11,7 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { HTMLAttributes, ReactNode } from 'react';
|
||||
|
||||
export type editorRef = ReactNode | HTMLElement | string;
|
||||
export enum Format {
|
||||
@ -35,3 +35,19 @@ export interface PreviewerProp {
|
||||
maxLen?: number;
|
||||
enableSeeMoreVariant?: boolean;
|
||||
}
|
||||
|
||||
export type PreviewStyle = 'tab' | 'vertical';
|
||||
|
||||
export type EditorType = 'markdown' | 'wysiwyg';
|
||||
|
||||
export interface RichTextEditorProp extends HTMLAttributes<HTMLDivElement> {
|
||||
initialValue: string;
|
||||
placeHolder?: string;
|
||||
previewStyle?: PreviewStyle;
|
||||
editorType?: EditorType;
|
||||
previewHighlight?: boolean;
|
||||
extendedAutolinks?: boolean;
|
||||
hideModeSwitch?: boolean;
|
||||
useCommandShortcut?: boolean;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { findByTestId, queryByTestId, render } from '@testing-library/react';
|
||||
import React, { Component } from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import RichTextEditor from './RichTextEditor';
|
||||
|
||||
jest.mock('@toast-ui/react-editor', () => {
|
||||
class Editor extends Component {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
getInstance() {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
getRootElement() {}
|
||||
|
||||
render() {
|
||||
return <p>Editor</p>;
|
||||
}
|
||||
}
|
||||
|
||||
class Viewer extends Component {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
getInstance() {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
getRootElement() {}
|
||||
|
||||
render() {
|
||||
return <p>Viewer</p>;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
Editor,
|
||||
Viewer,
|
||||
};
|
||||
});
|
||||
|
||||
const mockProp = {
|
||||
initialValue: '',
|
||||
readonly: false,
|
||||
};
|
||||
|
||||
describe('Test RichText Editor', () => {
|
||||
it('Should render rich text editor', async () => {
|
||||
const { container } = render(<RichTextEditor {...mockProp} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
const editor = await findByTestId(container, 'editor');
|
||||
|
||||
expect(editor).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should render viewer if readOnly is true', async () => {
|
||||
const { container } = render(<RichTextEditor {...mockProp} readonly />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
const editor = queryByTestId(container, 'editor');
|
||||
const viewer = await findByTestId(container, 'viewer');
|
||||
|
||||
expect(editor).not.toBeInTheDocument();
|
||||
expect(viewer).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -11,122 +11,87 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { convertFromRaw, convertToRaw, EditorState } from 'draft-js';
|
||||
import { draftjsToMd } from 'draftjs-md-converter';
|
||||
import { markdownToDraft } from 'markdown-draft-js';
|
||||
/* eslint-disable */
|
||||
|
||||
import { Editor, Viewer } from '@toast-ui/react-editor';
|
||||
import React, {
|
||||
createRef,
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Editor } from 'react-draft-wysiwyg';
|
||||
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
|
||||
import ListUl from '../../../assets/svg/list-ul.svg';
|
||||
import { EditorProp, editorRef, Format } from './RichTextEditor.interface';
|
||||
import { Bold, Info, Italic, Link } from './ToolBarOptions';
|
||||
import './RichTextEditor.css';
|
||||
import { editorRef, RichTextEditorProp } from './RichTextEditor.interface';
|
||||
|
||||
const getIntialContent = (format: string, content?: string) => {
|
||||
/*eslint-disable */
|
||||
|
||||
if (content) {
|
||||
switch (format) {
|
||||
case Format.MARKDOWN:
|
||||
const rawData = markdownToDraft(content, {
|
||||
remarkablePreset: 'commonmark',
|
||||
remarkableOptions: {
|
||||
html: false,
|
||||
disable: {
|
||||
inline: ['links', 'emphasis'],
|
||||
block: ['heading', 'code', 'list'],
|
||||
},
|
||||
enable: {
|
||||
block: 'table',
|
||||
core: ['abbr'],
|
||||
},
|
||||
},
|
||||
preserveNewlines: true,
|
||||
});
|
||||
|
||||
const state = convertFromRaw({ ...rawData });
|
||||
|
||||
return EditorState.createWithContent(state);
|
||||
|
||||
default:
|
||||
return EditorState.createEmpty();
|
||||
}
|
||||
} else {
|
||||
return EditorState.createEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
const RichTextEditor = forwardRef<editorRef, EditorProp>(
|
||||
const RichTextEditor = forwardRef<editorRef, RichTextEditorProp>(
|
||||
(
|
||||
{
|
||||
format = 'markdown',
|
||||
initvalue,
|
||||
readonly = false,
|
||||
customOptions,
|
||||
}: EditorProp,
|
||||
placeHolder = 'Write your description',
|
||||
previewStyle = 'tab',
|
||||
editorType = 'markdown',
|
||||
previewHighlight = false,
|
||||
useCommandShortcut = false,
|
||||
extendedAutolinks = true,
|
||||
hideModeSwitch = true,
|
||||
initialValue = '',
|
||||
readonly,
|
||||
}: RichTextEditorProp,
|
||||
ref
|
||||
) => {
|
||||
const [editorState, setEditorState] = useState(
|
||||
getIntialContent(format, initvalue)
|
||||
);
|
||||
const onEditorStateChange = (newState: typeof editorState) => {
|
||||
setEditorState(newState);
|
||||
const richTextEditorRef = createRef<Editor>();
|
||||
|
||||
const [editorValue, setEditorValue] = useState(initialValue);
|
||||
|
||||
const onChangeHandler = () => {
|
||||
const value = richTextEditorRef.current
|
||||
?.getInstance()
|
||||
.getMarkdown() as string;
|
||||
setEditorValue(value);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getEditorContent(_format: 'json' | 'markdown') {
|
||||
// use switch case for multiple format support
|
||||
return draftjsToMd(convertToRaw(editorState.getCurrentContent()));
|
||||
getEditorContent() {
|
||||
return editorValue;
|
||||
},
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
setEditorState(getIntialContent(format, initvalue));
|
||||
}, [initvalue, format]);
|
||||
setEditorValue(initialValue);
|
||||
}, [initialValue]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="tw-min-h-32 tw-border tw-border-main tw-rounded tw-overflow-y-auto"
|
||||
data-testid="enterDescription">
|
||||
<Editor
|
||||
editorClassName="tw-px-1 tw-min-h-32"
|
||||
editorState={editorState}
|
||||
readOnly={readonly}
|
||||
toolbar={{
|
||||
options: ['list'],
|
||||
list: {
|
||||
className: 'my-list tw-order-4',
|
||||
options: ['unordered'],
|
||||
unordered: {
|
||||
icon: ListUl,
|
||||
className: 'list-option ',
|
||||
},
|
||||
},
|
||||
}}
|
||||
toolbarClassName="tw-py-2 tw-border-0 tw-border-b tw-border-main"
|
||||
toolbarCustomButtons={
|
||||
customOptions ?? [
|
||||
<Bold key="bold" />,
|
||||
<Italic key="italic" />,
|
||||
<Link key="link" />,
|
||||
<Info key="info" />,
|
||||
]
|
||||
}
|
||||
toolbarHidden={readonly}
|
||||
wrapperClassName="editor-wrapper"
|
||||
onEditorStateChange={onEditorStateChange}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
<div className="tw-my-4">
|
||||
{readonly ? (
|
||||
<div
|
||||
className="tw-border tw-border-main tw-p-2 tw-rounded"
|
||||
data-testid="viewer">
|
||||
<Viewer
|
||||
extendedAutolinks={extendedAutolinks}
|
||||
initialValue={editorValue}
|
||||
ref={richTextEditorRef}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div data-testid="editor">
|
||||
<Editor
|
||||
extendedAutolinks={extendedAutolinks}
|
||||
hideModeSwitch={hideModeSwitch}
|
||||
initialEditType={editorType}
|
||||
initialValue={editorValue}
|
||||
placeholder={placeHolder}
|
||||
previewHighlight={previewHighlight}
|
||||
previewStyle={previewStyle}
|
||||
ref={richTextEditorRef}
|
||||
toolbarItems={[['bold', 'italic'], ['ul', 'ol'], ['link']]}
|
||||
useCommandShortcut={useCommandShortcut}
|
||||
onChange={onChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
RichTextEditor.displayName = 'RichTextEditor';
|
||||
|
||||
export default RichTextEditor;
|
||||
|
||||
@ -1,378 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { EditorState, Modifier, SelectionState } from 'draft-js';
|
||||
import {
|
||||
faLink,
|
||||
faListUl,
|
||||
faListOl,
|
||||
faItalic,
|
||||
faInfoCircle,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import PopOver from '../popover/PopOver';
|
||||
|
||||
const getSelectedText = (editorState: any) => {
|
||||
const selection = editorState.getSelection();
|
||||
const anchorKey = selection.getAnchorKey();
|
||||
const currentContent = editorState.getCurrentContent();
|
||||
const currentBlock = currentContent.getBlockForKey(anchorKey);
|
||||
|
||||
const start = selection.getStartOffset();
|
||||
const end = selection.getEndOffset();
|
||||
const selectedText = currentBlock.getText().slice(start, end);
|
||||
|
||||
return selectedText;
|
||||
};
|
||||
|
||||
const updateEditorSelection = (eState: any, offsetDiff: any) => {
|
||||
const selection = eState.getSelection();
|
||||
const newFocusOffset = selection.focusOffset + offsetDiff;
|
||||
|
||||
const newSelection = new SelectionState({
|
||||
anchorKey: selection.anchorKey,
|
||||
anchorOffset: newFocusOffset,
|
||||
focusKey: selection.focusKey,
|
||||
focusOffset: newFocusOffset,
|
||||
});
|
||||
const newEditorState = EditorState.forceSelection(eState, newSelection);
|
||||
|
||||
return EditorState.push(newEditorState, newEditorState.getCurrentContent());
|
||||
};
|
||||
|
||||
export class Bold extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
editorState: PropTypes.object,
|
||||
};
|
||||
|
||||
makeBold = () => {
|
||||
const { editorState, onChange } = this.props as any;
|
||||
const selectedText = getSelectedText(editorState);
|
||||
|
||||
const contentState = Modifier.replaceText(
|
||||
editorState.getCurrentContent(),
|
||||
editorState.getSelection(),
|
||||
`${
|
||||
selectedText.startsWith('**') && selectedText.endsWith('**')
|
||||
? selectedText.replaceAll('**', '')
|
||||
: `**${selectedText}**`
|
||||
}`,
|
||||
editorState.getCurrentInlineStyle()
|
||||
);
|
||||
|
||||
const eState = EditorState.push(
|
||||
editorState,
|
||||
contentState,
|
||||
'insert-characters'
|
||||
);
|
||||
|
||||
onChange(updateEditorSelection(eState, -2));
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className="rdw-option-wrapper tw-font-bold"
|
||||
data-testid="boldButton"
|
||||
onClick={this.makeBold}>
|
||||
<PopOver
|
||||
arrow={false}
|
||||
position="bottom"
|
||||
size="small"
|
||||
title="Add bold text"
|
||||
trigger="mouseenter">
|
||||
<p>B</p>
|
||||
</PopOver>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Link extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
editorState: PropTypes.object,
|
||||
};
|
||||
|
||||
makeLink = () => {
|
||||
const { editorState, onChange } = this.props as any;
|
||||
|
||||
const selectedText = getSelectedText(editorState);
|
||||
|
||||
const contentState = Modifier.replaceText(
|
||||
editorState.getCurrentContent(),
|
||||
editorState.getSelection(),
|
||||
`${
|
||||
selectedText.startsWith('[') && selectedText.endsWith(')')
|
||||
? selectedText.replace(/ *\([^)]*\) */g, '').replace(/[\])}[{(]/g, '')
|
||||
: `[${selectedText}](url)`
|
||||
}`,
|
||||
editorState.getCurrentInlineStyle()
|
||||
);
|
||||
const eState = EditorState.push(
|
||||
editorState,
|
||||
contentState,
|
||||
'insert-characters'
|
||||
);
|
||||
onChange(updateEditorSelection(eState, -6));
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className="rdw-option-wrapper "
|
||||
data-testid="linkButton"
|
||||
onClick={this.makeLink}>
|
||||
<PopOver
|
||||
arrow={false}
|
||||
position="bottom"
|
||||
size="small"
|
||||
title="Add link"
|
||||
trigger="mouseenter">
|
||||
<FontAwesomeIcon icon={faLink} />
|
||||
</PopOver>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Italic extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
editorState: PropTypes.object,
|
||||
};
|
||||
|
||||
makeItalic = () => {
|
||||
const { editorState, onChange } = this.props as any;
|
||||
const selectedText = getSelectedText(editorState);
|
||||
|
||||
const contentState = Modifier.replaceText(
|
||||
editorState.getCurrentContent(),
|
||||
editorState.getSelection(),
|
||||
`${
|
||||
selectedText.startsWith('_') && selectedText.endsWith('_')
|
||||
? selectedText.replaceAll('_', '')
|
||||
: `_${selectedText}_`
|
||||
}`,
|
||||
editorState.getCurrentInlineStyle()
|
||||
);
|
||||
const eState = EditorState.push(
|
||||
editorState,
|
||||
contentState,
|
||||
'insert-characters'
|
||||
);
|
||||
|
||||
onChange(updateEditorSelection(eState, -1));
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className="rdw-option-wrapper "
|
||||
data-testid="italicButton"
|
||||
onClick={this.makeItalic}>
|
||||
<PopOver
|
||||
arrow={false}
|
||||
position="bottom"
|
||||
size="small"
|
||||
title="Add italic text"
|
||||
trigger="mouseenter">
|
||||
<FontAwesomeIcon icon={faItalic} />
|
||||
</PopOver>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export class Heading extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
editorState: PropTypes.object,
|
||||
};
|
||||
|
||||
makeHeading = () => {
|
||||
const { editorState, onChange } = this.props as any;
|
||||
const selectedText = getSelectedText(editorState);
|
||||
|
||||
const contentState = Modifier.replaceText(
|
||||
editorState.getCurrentContent(),
|
||||
editorState.getSelection(),
|
||||
`${
|
||||
selectedText.startsWith('### ')
|
||||
? selectedText.replaceAll('### ', '')
|
||||
: `### ${selectedText}`
|
||||
}`,
|
||||
editorState.getCurrentInlineStyle()
|
||||
);
|
||||
const eState = EditorState.push(
|
||||
editorState,
|
||||
contentState,
|
||||
'insert-characters'
|
||||
);
|
||||
onChange(updateEditorSelection(eState, 0));
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="rdw-option-wrapper" onClick={this.makeHeading}>
|
||||
<PopOver
|
||||
position="bottom"
|
||||
size="small"
|
||||
title="Add header text"
|
||||
trigger="mouseenter">
|
||||
<p>H</p>
|
||||
</PopOver>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ULLIST extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
editorState: PropTypes.object,
|
||||
};
|
||||
|
||||
makeLIST = () => {
|
||||
const { editorState, onChange } = this.props as any;
|
||||
const selectedText = getSelectedText(editorState);
|
||||
const selection = editorState.getSelection();
|
||||
const text = selectedText.startsWith('- ')
|
||||
? selectedText.replaceAll('- ', '')
|
||||
: `${
|
||||
selection.anchorOffset > 0 && selectedText.length <= 0
|
||||
? `\n\n- ${selectedText}`
|
||||
: `- ${selectedText}`
|
||||
}`;
|
||||
const contentState = Modifier.replaceText(
|
||||
editorState.getCurrentContent(),
|
||||
editorState.getSelection(),
|
||||
text,
|
||||
editorState.getCurrentInlineStyle()
|
||||
);
|
||||
onChange(EditorState.push(editorState, contentState, 'insert-characters'));
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="rdw-option-wrapper " onClick={this.makeLIST}>
|
||||
<PopOver
|
||||
position="bottom"
|
||||
size="small"
|
||||
title="Add unordered list"
|
||||
trigger="mouseenter">
|
||||
<FontAwesomeIcon icon={faListUl} />
|
||||
</PopOver>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export class OLLIST extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
editorState: PropTypes.object,
|
||||
};
|
||||
|
||||
makeLIST = () => {
|
||||
const { editorState, onChange } = this.props as any;
|
||||
const selection = editorState.getSelection();
|
||||
const currentKey = selection.getStartKey();
|
||||
const currentBlock = editorState
|
||||
.getCurrentContent()
|
||||
.getBlockForKey(currentKey);
|
||||
const textArr = currentBlock.getText().split('\n') || [];
|
||||
const lastText = textArr[textArr.length - 1];
|
||||
const match = lastText.match(/(\d+)/)?.[0];
|
||||
let len = 0;
|
||||
for (const txt of textArr) {
|
||||
len += txt.length;
|
||||
if (len >= selection.focusOffset) {
|
||||
break;
|
||||
}
|
||||
len++;
|
||||
}
|
||||
|
||||
const newSelection = new SelectionState({
|
||||
anchorKey: selection.anchorKey,
|
||||
anchorOffset: textArr.join(',').length,
|
||||
focusKey: selection.focusKey,
|
||||
focusOffset: textArr.join(',').length,
|
||||
});
|
||||
|
||||
const contentState = Modifier.replaceText(
|
||||
editorState.getCurrentContent(),
|
||||
newSelection,
|
||||
`${
|
||||
selection.anchorOffset > 0
|
||||
? `\n${match ? parseInt(match) + 1 : 1}. `
|
||||
: `${match ? parseInt(match) + 1 : 1}. `
|
||||
}`,
|
||||
editorState.getCurrentInlineStyle()
|
||||
);
|
||||
|
||||
onChange(EditorState.push(editorState, contentState, 'insert-characters'));
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="rdw-option-wrapper " onClick={this.makeLIST}>
|
||||
<PopOver
|
||||
position="bottom"
|
||||
size="small"
|
||||
title="Add unordered list"
|
||||
trigger="mouseenter">
|
||||
<FontAwesomeIcon icon={faListOl} />
|
||||
</PopOver>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Info extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
editorState: PropTypes.object,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="rdw-option-wrapper tw-order-5 tw-font-bold tw-ml-auto">
|
||||
<PopOver
|
||||
arrow={false}
|
||||
html={
|
||||
<div className="tw-flex tw-pb-1 tw-text-white tw-text-left">
|
||||
<div>
|
||||
<p className="tw-pt-2">
|
||||
Using headings in markdown is not allowed.
|
||||
</p>
|
||||
<p className="tw-pt-2">
|
||||
Use{' '}
|
||||
<span className=" tw-py-0.5 tw-px-1 tw-ml-1 tw-border tw-rounded tw-text-xs">{`<br/>`}</span>{' '}
|
||||
tag to add empty lines.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
position="left"
|
||||
size="small"
|
||||
trigger="mouseenter">
|
||||
<FontAwesomeIcon icon={faInfoCircle} />
|
||||
</PopOver>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,7 @@ const mockInitialData = {
|
||||
name: '',
|
||||
};
|
||||
|
||||
jest.mock('../../components/common/editor/MarkdownWithPreview', () => {
|
||||
jest.mock('../../components/common/rich-text-editor/RichTextEditor', () => {
|
||||
return jest.fn().mockReturnValue(<div>MarkdownWithPreview component</div>);
|
||||
});
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import MarkdownWithPreview from '../../components/common/editor/MarkdownWithPreview';
|
||||
import RichTextEditor from '../../components/common/rich-text-editor/RichTextEditor';
|
||||
import { CreateTagCategory } from '../../generated/api/tags/createTagCategory';
|
||||
import { errorMsg } from '../../utils/CommonUtils';
|
||||
|
||||
@ -120,7 +120,10 @@ const Form: React.FC<FormProp> = forwardRef(
|
||||
</div>
|
||||
<div>
|
||||
<label className="tw-form-label">Description</label>
|
||||
<MarkdownWithPreview ref={markdownRef} value={data.description} />
|
||||
<RichTextEditor
|
||||
initialValue={data.description}
|
||||
ref={markdownRef}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,7 +5,7 @@ import Form from './Form';
|
||||
|
||||
const mockFunction = jest.fn();
|
||||
|
||||
jest.mock('../../components/common/editor/MarkdownWithPreview', () => {
|
||||
jest.mock('../../components/common/rich-text-editor/RichTextEditor', () => {
|
||||
return jest.fn().mockReturnValue(<div>MarkdownWithPreview component</div>);
|
||||
});
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import MarkdownWithPreview from '../../components/common/editor/MarkdownWithPreview';
|
||||
import RichTextEditor from '../../components/common/rich-text-editor/RichTextEditor';
|
||||
import { errorMsg } from '../../utils/CommonUtils';
|
||||
|
||||
type FormProp = {
|
||||
@ -114,7 +114,10 @@ const Form: React.FC<FormProp> = forwardRef(
|
||||
</div>
|
||||
<div>
|
||||
<label className="tw-form-label">Description</label>
|
||||
<MarkdownWithPreview ref={markdownRef} value={data.description} />
|
||||
<RichTextEditor
|
||||
initialValue={data.description}
|
||||
ref={markdownRef}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1794,6 +1794,27 @@
|
||||
traverse "^0.6.6"
|
||||
unified "^9.2.1"
|
||||
|
||||
"@toast-ui/editor@^3.1.3":
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@toast-ui/editor/-/editor-3.1.3.tgz#12f15dab1fc9c1db336683f51208f8ba8017abb9"
|
||||
integrity sha512-4W8nKIhct4bGOKNMkYY2nGzt2k+8LUWlINwGZvCbNgIo6WKlcOarsbWD0o8stOAleaq2TeG6ixIvYK/wTG0OxA==
|
||||
dependencies:
|
||||
dompurify "^2.3.3"
|
||||
prosemirror-commands "^1.1.9"
|
||||
prosemirror-history "^1.1.3"
|
||||
prosemirror-inputrules "^1.1.3"
|
||||
prosemirror-keymap "^1.1.4"
|
||||
prosemirror-model "^1.14.1"
|
||||
prosemirror-state "^1.3.4"
|
||||
prosemirror-view "^1.18.7"
|
||||
|
||||
"@toast-ui/react-editor@^3.1.3":
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@toast-ui/react-editor/-/react-editor-3.1.3.tgz#5d55ecf08df4c6a230c104f5e4dbab9212107941"
|
||||
integrity sha512-k5W53y/R3cZvSH3UfDgeT8L1k8MpRri4O9hcTeuXtnbkkCtPQjt0m696tKrZvSXRNeqa4mKT0m8uNbHJAqWD4g==
|
||||
dependencies:
|
||||
"@toast-ui/editor" "^3.1.3"
|
||||
|
||||
"@tootallnate/once@1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||
@ -5001,6 +5022,11 @@ dompurify@^2.0.7:
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.4.tgz#1cf5cf0105ccb4debdf6db162525bd41e6ddacc6"
|
||||
integrity sha512-6BVcgOAVFXjI0JTjEvZy901Rghm+7fDQOrNIcxB4+gdhj6Kwp6T9VBhBY/AbagKHJocRkDYGd6wvI+p4/10xtQ==
|
||||
|
||||
dompurify@^2.3.3:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.6.tgz#2e019d7d7617aacac07cbbe3d88ae3ad354cf875"
|
||||
integrity sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==
|
||||
|
||||
domutils@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
|
||||
@ -9930,6 +9956,11 @@ optionator@^0.8.1, optionator@^0.8.3:
|
||||
type-check "~0.3.2"
|
||||
word-wrap "~1.2.3"
|
||||
|
||||
orderedmap@^1.1.0:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-1.1.5.tgz#4174c90b61bd7c25294932edf789f3b5677744d0"
|
||||
integrity sha512-/fzlCGKRmfayGoI9UUXvJfc2nMZlJHW30QqEvwPvlg8tsX7jyiUSomYie6mYqx7Z9bOMGoag0H/q1PS/0PjYkg==
|
||||
|
||||
organize-imports-cli@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/organize-imports-cli/-/organize-imports-cli-0.7.0.tgz#a6ffcec2d31c5de60748efe8861ea8cecd727276"
|
||||
@ -10570,6 +10601,70 @@ prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.8.1:
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.13.1"
|
||||
|
||||
prosemirror-commands@^1.1.9:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.2.2.tgz#1bd167372ee20abf488aca9cece63c43fab182c9"
|
||||
integrity sha512-TX+KpWudMon06frryfpO/u7hsQv2hu8L4VSVbCpi3/7wXHBgl+35mV85qfa3RpT8xD2f3MdeoTqH0vy5JdbXPg==
|
||||
dependencies:
|
||||
prosemirror-model "^1.0.0"
|
||||
prosemirror-state "^1.0.0"
|
||||
prosemirror-transform "^1.0.0"
|
||||
|
||||
prosemirror-history@^1.1.3:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.2.0.tgz#04cc4df8d2f7b2a46651a2780de191ada6d465ea"
|
||||
integrity sha512-B9v9xtf4fYbKxQwIr+3wtTDNLDZcmMMmGiI3TAPShnUzvo+Rmv1GiUrsQChY1meetHl7rhML2cppF3FTs7f7UQ==
|
||||
dependencies:
|
||||
prosemirror-state "^1.2.2"
|
||||
prosemirror-transform "^1.0.0"
|
||||
rope-sequence "^1.3.0"
|
||||
|
||||
prosemirror-inputrules@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.1.3.tgz#93f9199ca02473259c30d7e352e4c14022d54638"
|
||||
integrity sha512-ZaHCLyBtvbyIHv0f5p6boQTIJjlD6o2NPZiEaZWT2DA+j591zS29QQEMT4lBqwcLW3qRSf7ZvoKNbf05YrsStw==
|
||||
dependencies:
|
||||
prosemirror-state "^1.0.0"
|
||||
prosemirror-transform "^1.0.0"
|
||||
|
||||
prosemirror-keymap@^1.1.4:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.1.5.tgz#b5984c7d30f5c75956c853126c54e9e624c0327b"
|
||||
integrity sha512-8SZgPH3K+GLsHL2wKuwBD9rxhsbnVBTwpHCO4VUO5GmqUQlxd/2GtBVWTsyLq4Dp3N9nGgPd3+lZFKUDuVp+Vw==
|
||||
dependencies:
|
||||
prosemirror-state "^1.0.0"
|
||||
w3c-keyname "^2.2.0"
|
||||
|
||||
prosemirror-model@^1.0.0, prosemirror-model@^1.14.1, prosemirror-model@^1.16.0:
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.16.1.tgz#fb388270bc9609b66298d6a7e15d0cc1d6c61253"
|
||||
integrity sha512-r1/w0HDU40TtkXp0DyKBnFPYwd8FSlUSJmGCGFv4DeynfeSlyQF2FD0RQbVEMOe6P3PpUSXM6LZBV7W/YNZ4mA==
|
||||
dependencies:
|
||||
orderedmap "^1.1.0"
|
||||
|
||||
prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.4:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.3.4.tgz#4c6b52628216e753fc901c6d2bfd84ce109e8952"
|
||||
integrity sha512-Xkkrpd1y/TQ6HKzN3agsQIGRcLckUMA9u3j207L04mt8ToRgpGeyhbVv0HI7omDORIBHjR29b7AwlATFFf2GLA==
|
||||
dependencies:
|
||||
prosemirror-model "^1.0.0"
|
||||
prosemirror-transform "^1.0.0"
|
||||
|
||||
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.4.2.tgz#35f56091bcab3359f1eb90e82ce9f20cc52105c1"
|
||||
integrity sha512-bcIsf3uRZhfab0xRfyyxOEh6eqSszq/hJbDbmUumFnbHBoWhB/uXbpz6vvUxfk0XiEvrZDJ+5pXRrNDc1Hu3vQ==
|
||||
dependencies:
|
||||
prosemirror-model "^1.0.0"
|
||||
|
||||
prosemirror-view@^1.18.7:
|
||||
version "1.23.11"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.23.11.tgz#297f6ef8d10e1ff78c505d9c57358e062810a80e"
|
||||
integrity sha512-iBqsyrQZz9NYcJ13JC7sPZ+4PdbBbUXhs1qzbxkDQ2tplcVROwxmAn3bnxpVFst/guv+XFI5KTHHbw5stvKt0g==
|
||||
dependencies:
|
||||
prosemirror-model "^1.16.0"
|
||||
prosemirror-state "^1.0.0"
|
||||
prosemirror-transform "^1.1.0"
|
||||
property-information@^6.0.0:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.1.1.tgz#5ca85510a3019726cb9afed4197b7b8ac5926a22"
|
||||
@ -11693,6 +11788,11 @@ rimraf@^3.0.0:
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
rope-sequence@^1.3.0:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.2.tgz#a19e02d72991ca71feb6b5f8a91154e48e3c098b"
|
||||
integrity sha512-ku6MFrwEVSVmXLvy3dYph3LAMNS0890K7fabn+0YIRQ2T96T9F4gkFf0vf0WW0JUraNWwGRtInEpH7yO4tbQZg==
|
||||
|
||||
rsvp@^4.8.4:
|
||||
version "4.8.5"
|
||||
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
||||
@ -13618,6 +13718,11 @@ w3c-hr-time@^1.0.1, w3c-hr-time@^1.0.2:
|
||||
dependencies:
|
||||
browser-process-hrtime "^1.0.0"
|
||||
|
||||
w3c-keyname@^2.2.0:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.4.tgz#4ade6916f6290224cdbd1db8ac49eab03d0eef6b"
|
||||
integrity sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw==
|
||||
|
||||
w3c-xmlserializer@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user